Related
I am building a video call app . I have implemented refering to agora official sample repsoitoryRefer.
In my implementation call s getting connected , but the video of remote user is rendered for 2 seconds and it gets stuck . Is any thing wrong in my implemengtations?
This is my whole composable screen code
#OptIn(ExperimentalAnimationApi::class)
#Composable
fun VideoCallScreenV2(
viewModel: CallViewModel,
navController: NavHostController,
onBack: () -> Unit = {}
) {
val context = LocalContext.current
val localSurfaceView: TextureView? by remember {
mutableStateOf(RtcEngine.CreateTextureView(context))
}
var remoteUserMap by remember {
mutableStateOf(mapOf<Int, TextureView?>())
}
var remoteUser = remember {
mutableStateOf<RemoteUser?>(null)
}
val permissionNotEnabled = remember {
mutableStateOf(!viewModel.hasPermissions)
}
var isInPictureInPicMode by remember {
mutableStateOf(false)
}
val screenState = remember {
viewModel.callScreenState
}
var networkQuality by remember {
mutableStateOf(-1)
}
PermissionHandler(visible = permissionNotEnabled,
permissions = viewModel.getRequiredPermissionList(),
permissionText = stringResource(
id = R.string.camera_audio_permission_text
),
permissionNotAvailable = {
context.showToast(context.getString(R.string.agora_video_perms_denied))
//If permissions are not given, video calls should not take place. Then navigate to chat screen
// navController.popBackStack(Routes.INBOX_V2, true)
}) {
permissionNotEnabled.value = false
viewModel.hasPermissions = true
}
val mEngine = remember(viewModel.callScreenState.rtcToken) {
initEngine(
context,
getRtcEngineEventHandler(
onUserJoined = { uid, elapsed ->
remoteUser.value = RemoteUser(uid)
},
onUserOffline = { uid, reason ->
remoteUser.value = null
context.findActivity()?.finish()
},
onNetworkQuality = { uid, txQuality, rxQuality ->
networkQuality = rxQuality
},
onLeaveChannel = {
Timber.e("hhp-CallScreen onLeaveChannel Success")
context.findActivity()?.finish()
}
),
viewModel.callScreenState.chatRoom?.channelName ?: "",
viewModel.userRole,
token = viewModel.callScreenState.rtcToken?.token ?: ""
)
}
if (viewModel.userRole == "Broadcaster") {
mEngine.setupLocalVideo(VideoCanvas(localSurfaceView, Constants.RENDER_MODE_FIT, 0))
}
var isRemoteFullScreen by remember {
mutableStateOf(true)
}
val remoteViewZindex by animateFloatAsState(targetValue = if (isRemoteFullScreen) 1.0f else 1.5f)
val localViewZindex by animateFloatAsState(targetValue = if (isRemoteFullScreen) 1.5f else 1.0f)
val remoteViewSize by animateFloatAsState(targetValue = if (isRemoteFullScreen) 1f else .25f)
val localViewSize by animateFloatAsState(targetValue = if (isRemoteFullScreen) .35f else 1f)
var showControls by remember {
mutableStateOf(true)
}
Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.BottomEnd) {
RemoteView(
remoteListInfo = remoteUserMap,
remoteUser = remoteUser.value,
mEngine = mEngine,
modifier = Modifier
.padding(
end = if (!isRemoteFullScreen) 24.dp else 0.dp,
bottom = if (!isRemoteFullScreen) 54.dp else 0.dp
)
.fillMaxSize(remoteViewSize)
.zIndex(remoteViewZindex)
//.background(green)
.clickable {
if (!isRemoteFullScreen)
isRemoteFullScreen = true
else if (isRemoteFullScreen)
showControls = !showControls
}
)
localSurfaceView?.let { local ->
AndroidView(
factory = {
local
},
update = { it ->
it.setOnClickListener {
if (isRemoteFullScreen)
isRemoteFullScreen = false
else if (!isRemoteFullScreen)
showControls = !showControls
}
},
modifier = Modifier
.fillMaxSize(localViewSize)
.zIndex(localViewZindex)
.padding(
end = if (isRemoteFullScreen) 24.dp else 0.dp,
bottom = if (isRemoteFullScreen) 54.dp else 0.dp
)
)
NetworkQualityIndicator(quality = networkQuality)
}
AnimatedVisibility(visible = showControls, modifier = Modifier.zIndex(3f)) {
UserControls(
isMuted = viewModel.muted,
isVideoDisabled = viewModel.videoDisabled,
onEndCallClicked = {
viewModel.apply {
whatIf(
given = isUsingByDoctor(),
whatIf = {
onEvent(CallEvents.DoctorEndedCall(mEngine))
}, whatIfNot = {
onEvent(CallEvents.PatientEndedCall(mEngine))
}
)
}
}, onToggleMute = {
viewModel.muted = !viewModel.muted
mEngine.muteLocalAudioStream(viewModel.muted)
}, onToggleVideo = {
})
}
}
}
fun initEngine(
current: Context,
eventHandler: IRtcEngineEventHandler,
channelName: String,
userRole: String,
token: String
): RtcEngine =
RtcEngine.create(current, BuildConfig.AGORA_APPID, eventHandler).apply {
enableVideo()
setChannelProfile(1)
if (userRole == "Broadcaster") {
setClientRole(1)
} else {
setClientRole(0)
}
setDefaultAudioRoutetoSpeakerphone(true)
setVideoEncoderConfiguration(
VideoEncoderConfiguration(
VideoEncoderConfiguration.VideoDimensions(640, 480),
VideoEncoderConfiguration.FRAME_RATE.FRAME_RATE_FPS_15,
0,
VideoEncoderConfiguration.ORIENTATION_MODE.ORIENTATION_MODE_FIXED_PORTRAIT
)
)
if (token.isNotEmpty() && channelName.isNotEmpty()){
joinChannel(token, channelName, "", 0)
Timber.e("hhp-CallScreen initEngine token $token channel name $channelName userRole $userRole")
}
}
#Composable
private fun RemoteView(
remoteListInfo: Map<Int, TextureView?>,
remoteUser: RemoteUser?,
mEngine: RtcEngine,
modifier: Modifier
) {
val context = LocalContext.current
Row(
modifier = modifier
) {
if (remoteUser != null) {
Timber.e("hhp-CallScreen Remote User $remoteUser")
val remoteTextureView = remoteUser.textureView ?: RtcEngine.CreateTextureView(context)
AndroidView(
factory = { remoteTextureView!! }
)
mEngine.setupRemoteVideo(
VideoCanvas(
remoteTextureView,
Constants.RENDER_MODE_HIDDEN,
remoteUser.uid
)
)
} else {
Timber.e("hhp-CallScreen Remote User Null")
}
}
}
#Composable
private fun UserControls(
isMuted: Boolean,
isVideoDisabled: Boolean,
onEndCallClicked: () -> Unit,
onToggleMute: (Boolean) -> Unit,
onToggleVideo: (Boolean) -> Unit
) {
val activity = (LocalContext.current as? Activity)
Row(
modifier = Modifier
.fillMaxWidth()
.padding(bottom = 50.dp),
Arrangement.SpaceEvenly,
Alignment.Bottom
) {
OutlinedButton(
onClick = {
onToggleMute(isMuted)
},
shape = CircleShape,
modifier = Modifier.size(50.dp),
contentPadding = PaddingValues(0.dp),
colors = ButtonDefaults.outlinedButtonColors(backgroundColor = if (isMuted) Color.Blue else Color.White)
) {
if (isMuted) {
Icon(
Icons.Rounded.MicOff,
contentDescription = "Tap to unmute mic",
tint = Color.White
)
} else {
Icon(Icons.Rounded.Mic, contentDescription = "Tap to mute mic", tint = Color.Blue)
}
}
OutlinedButton(
onClick = {
//mEngine.leaveChannel()
// activity?.finish()
onEndCallClicked()
},
shape = CircleShape,
modifier = Modifier.size(70.dp),
contentPadding = PaddingValues(0.dp),
colors = ButtonDefaults.outlinedButtonColors(backgroundColor = Color.Red)
) {
Icon(
Icons.Rounded.CallEnd,
contentDescription = "Tap to disconnect Call",
tint = Color.White
)
}
OutlinedButton(
onClick = {
//videoDisabled = !videoDisabled
//mEngine.muteLocalVideoStream(videoDisabled)
onToggleVideo(isVideoDisabled)
},
shape = CircleShape,
modifier = Modifier.size(50.dp),
contentPadding = PaddingValues(0.dp),
colors = ButtonDefaults.outlinedButtonColors(backgroundColor = if (isVideoDisabled) Color.Blue else Color.White)
) {
if (isVideoDisabled) {
Icon(
Icons.Rounded.VideocamOff,
contentDescription = "Tap to enable Video",
tint = Color.White
)
} else {
Icon(
Icons.Rounded.Videocam,
contentDescription = "Tap to disable Video",
tint = Color.Blue
)
}
}
}
}
#Composable
fun NetworkQualityIndicator(quality: Int) {
if (BuildConfig.DEBUG) {
when (updateNetworkStatus(quality)) {
CallQuality.Excellent -> QualityLabel(label = "Excellent", color = green)
CallQuality.Good -> QualityLabel(label = "Good", color = cream)
CallQuality.Poor -> QualityLabel(label = "Poor", color = lightRed)
CallQuality.Unknown -> QualityLabel(label = "Analysing", color = white)
}
}
}
#Composable
fun QualityLabel(
label: String,
color: Color
) {
Text(
text = "Call Quality $label", modifier = Modifier
.fillMaxWidth()
.zIndex(3f)
.background(
color
),
textAlign = TextAlign.Center
)
}
private fun updateNetworkStatus(quality: Int): CallQuality {
return if (quality in 1..2)
CallQuality.Excellent
else if (quality <= 4) CallQuality.Good
else if (quality <= 6) CallQuality.Poor
else CallQuality.Unknown
}
I build a really small practicing application, but I found some Questions.
package com.example.jetrecept
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.*
import androidx.compose.material.*
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.sharp.Add
import androidx.compose.material.icons.sharp.Delete
import androidx.compose.material.icons.sharp.Edit
import androidx.compose.runtime.Composable
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.compose.ui.window.Dialog
import com.example.jetrecept.ui.theme.JetReceptTheme
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
JetReceptTheme {
// A surface container using the 'background' color from the theme
Surface(
modifier = Modifier.fillMaxSize(),
color = MaterialTheme.colors.background
) {
MainContent()
}
}
}
}
}
#Preview(showBackground = true)
#Composable
fun MainContent() {
var recipeTitle by remember { mutableStateOf("") }
val ingredientList = remember { mutableStateListOf<Ingredient>() }
var amount by remember { mutableStateOf("") } // Change to Double
var unit by remember { mutableStateOf("") }
var notation by remember { mutableStateOf("") }
RecipeContent(
recipeTitle = recipeTitle,
onRecipeTitleChange = { recipeTitle = it },
ingredientList = ingredientList,
onIngredientListUpdate = { ingredient: Ingredient, i: Int -> ingredientList[i] = ingredient},
onIngredientListAdd = { ingredientList.add(it) },
onIngredientListRemoveItem = { ingredientList.removeAt(it)},
amount = amount,
onAmountChange = { amount = it},
unit = unit,
onUnitChange = { unit = it},
notation = notation,
onNotationChange = { notation = it}
)
RecipeHeader(
recipeTitle = recipeTitle
)
}
#Composable
fun RecipeHeader(
recipeTitle: String
) {
//JetReciptTheme {
Column {
//Text(text="recipetitle: $recipeTitle") // Just for testing, if hoisting works
}
//}
}
#Composable
fun RecipeContent(
recipeTitle: String,
onRecipeTitleChange: (String) -> Unit,
ingredientList: MutableList<Ingredient>,
onIngredientListAdd: (Ingredient) -> Unit,
onIngredientListUpdate: (Ingredient, Int) -> Unit,
onIngredientListRemoveItem: (Int) -> Unit,
amount: String, // change to Double,
onAmountChange: (String) -> Unit, //change to (Double) -> Unit,
unit: String,
onUnitChange: (String) -> Unit,
notation: String,
onNotationChange: (String) -> Unit
) {
Column(modifier = Modifier
.fillMaxSize()
.padding(21.dp)) {
// Headline
Text(text="Add new recipe",
modifier = Modifier
.fillMaxWidth()
.padding(34.dp),
textAlign = TextAlign.Center)
// Title of recipe - input
SectionHeadline(title = "Recipename")
TextField(
value = recipeTitle,
onValueChange = { onRecipeTitleChange(it) },
modifier = Modifier
.fillMaxWidth()
.padding(bottom = 34.dp),
// Keyboard Action fehlt noch
)
// Ingredient - section
SectionHeadline(title = "Ingredients")
// Writing headline over the Ingredient table
if (ingredientList.isNotEmpty()) {
IngredientDescription(
modifier = Modifier.padding(bottom=4.dp),
firstColumn = "Nr.",
secondColumn = "Amount",
thirdColumn = "Unit",
fourthColumn = "Notation"
)
Divider(modifier = Modifier
.height(10.dp)
.padding(bottom = 4.dp, top = 4.dp))
}
// Print all Ingredients to an Row
ingredientList.mapIndexed { index, ingredient -> IngredientRow(
id = index,
ingredient = ingredient,
onIngredientListUpdate = onIngredientListUpdate,
onIngredientListRemoveItem = onIngredientListRemoveItem,
amount = amount,
onAmountChange = onAmountChange,
unit = unit,
onUnitChange = onUnitChange,
notation = notation,
onNotationChange = onNotationChange
)
}
// Ingredient input row
Row {
TextField(
value = amount,
onValueChange = { onAmountChange(it)},
modifier = Modifier
.width(100.dp)
.padding(end = 8.dp),
label = { Text(text="amount") },
singleLine = true
)
TextField(
value = unit,
onValueChange = { onUnitChange(it)},
modifier = Modifier
.width(80.dp)
.padding(end = 8.dp),
label = { Text(text = "unit") },
singleLine = true
)
TextField(
value = notation,
onValueChange = { onNotationChange(it)},
modifier = Modifier.weight(1F),
label = { Text(text = "notation")},
singleLine = true
)
}
Spacer(modifier= Modifier.height(8.dp))
// Add - Button, adding Ingredients
IconButton(
buttonText = "add",
icon = Icons.Sharp.Add,
iconDescription = "add icon",
enabled = amount.isNotBlank() && unit.isNotBlank() && notation.isNotBlank(),
onClick = {
onIngredientListAdd(Ingredient(amount=amount, unit=unit, notation=notation))
onAmountChange(""); onUnitChange(""); onNotationChange("")
})
}
}
#Composable
fun IngredientRow(
id: Int = 1,
ingredient: Ingredient,
onIngredientListUpdate: (Ingredient, Int) -> Unit,
onIngredientListRemoveItem: (Int) -> Unit,
amount: String,
onAmountChange: (String) -> Unit,
unit: String,
onUnitChange: (String) -> Unit,
notation: String,
onNotationChange: (String) -> Unit
) {
var buttonVisibility by remember { mutableStateOf(false)}
var showEditDialog by remember { mutableStateOf(false)}
Column( modifier = Modifier.fillMaxWidth()) {
IngredientDescription(
modifier = Modifier.clickable { buttonVisibility = !buttonVisibility },
firstColumn = "$id",
secondColumn = ingredient.amount,
thirdColumn = ingredient.unit,
fourthColumn = ingredient.notation
)
if (buttonVisibility) {
Row(modifier = Modifier
.padding(bottom = 8.dp, top = 8.dp)
.fillMaxWidth(),
horizontalArrangement = Arrangement.SpaceEvenly
) {
IconButton(
buttonText = "edit",
icon = Icons.Sharp.Edit,
iconDescription = "edit icon",
onClick = {
onAmountChange( ingredient.amount)
onUnitChange( ingredient.unit)
onNotationChange( ingredient.notation)
showEditDialog = true}
)
IconButton(
buttonText = "delete",
icon = Icons.Sharp.Delete,
iconDescription = "delete icon",
onClick = { onIngredientListRemoveItem(id) }
)
}
}
Divider(modifier = Modifier
.height(10.dp)
.padding(bottom = 4.dp, top = 4.dp))
if (showEditDialog) {
AlertDialog(
onDismissRequest = {
onAmountChange(""); onUnitChange(""); onNotationChange("")
showEditDialog = false},
title = {Text(text="Edit ingredient")},
text = {
Column {
Text(text="Amount:")
TextField(value = amount, onValueChange = { onAmountChange(it) })
Text(text="Unit:")
TextField(value = unit, onValueChange = { onUnitChange(it) })
Text(text="Notation:")
TextField(value = notation, onValueChange = { onNotationChange(it) })
}
},
confirmButton = { onIngredientListUpdate(
Ingredient(amount= amount, unit= unit, notation= notation), id)
Button(onClick = {
showEditDialog = false
buttonVisibility = false
onAmountChange(""); onUnitChange(""); onNotationChange("")}) {
Text(text = "apply")
}
},
dismissButton = {
Button(onClick = {
showEditDialog = false
buttonVisibility = false
onAmountChange(""); onUnitChange(""); onNotationChange("")}) {
Text(text = "discard")
}
}
)
}
}
}
#Composable
fun SectionHeadline(title: String) {
Text(text = title)
Divider(modifier = Modifier.height(2.dp))
Spacer(modifier = Modifier.height(8.dp))
}
#Composable
fun IngredientDescription(
modifier: Modifier = Modifier,
firstColumn: String, secondColumn: String, thirdColumn: String, fourthColumn:String) {
Row( modifier = modifier
.fillMaxWidth()
.padding(bottom = 4.dp, top = 4.dp),
verticalAlignment = Alignment.CenterVertically
) {
Text(text = firstColumn, modifier = Modifier.width(30.dp), textAlign = TextAlign.Center)
Text(text = secondColumn, modifier = Modifier.width(60.dp), textAlign = TextAlign.Center)
Text(text = thirdColumn, modifier = Modifier.width(50.dp), textAlign = TextAlign.Center)
Text(text = fourthColumn, modifier = Modifier.weight(1F))
}
}
#Composable
fun IconButton(
buttonText: String,
icon: ImageVector,
enabled: Boolean = true,
iconDescription: String,
onClick: () -> Unit
) {
Button(onClick = onClick, enabled = enabled) {
Text(text=buttonText, modifier = Modifier.padding(end=5.dp))
Icon(imageVector = icon, contentDescription = iconDescription)
}
}
// data class Ingredient (var amount: Double, var unit: String, var notation: String)
data class Ingredient (var amount: String, var unit: String, var notation: String)
Is the way of hoisting correct? I mean, I take most of the states out of the Composables but some states inside are just for internal working. Should I also take this out of the Composable to make it complete stateless (buttonVisibility and showEditDialog)?
Are there any things I can do better?
val flowable01 = Flowable.fromArray(listOf(
Value(epoch = 1,string ="apple"),
Value(epoch = 2,string ="apple"),
Value(epoch = 3,string ="apple"),
Value(epoch = 4,string ="apple"),
Value(epoch = 5,string ="apple"),
Value(epoch = 6,string ="apple"),
Value(epoch = 7,string ="apple"),
Value(epoch = 8,string ="apple"),
Value(epoch = 9,string ="apple"),
Value(epoch = 10,string ="apple"),
))
val flowable02 = Flowable.fromArray(listOf(
Value(epoch = 2,string ="orange"),
Value(epoch = 3,string ="orange"),
Value(epoch = 5,string ="orange"),
Value(epoch = 8,string ="orange"),
Value(epoch = 10,string ="orange"),
))
flowable01.filter { it.epoch == flowable02.emission.epoch }
.map { Value(it.epoch, "juice") }
How to combine two flowable and filter out only same values from emissions and map it to another data class?
Maybe using combineLatest/zip
//Result:
Value(epoch = 2,string ="juice")
Value(epoch = 3,string ="juice")
Value(epoch = 5,string ="juice")
Value(epoch = 8,string ="juice")
Value(epoch = 10,string ="juice")
You can use flatMap:
flowable01.flatMap { first ->
flowable02.filter { second -> first.epoch == second.epoch }
.map { second -> Value(second.epoch, "Juice") }
}
If flowable02 is an external datasource, you may not want to re-query it each time so you can make it cached upfront:
flowable02cache = flowable02.cache()
flowable01.flatMap { first ->
flowable02cache.filter { second -> first.epoch == second.epoch }
.map { second -> Value(second.epoch, "Juice") }
}
If you want to exploit the ordering in each source, that's more complicated on the merging step:
Single.zip(flowable01.toList(), flowable02.toList()) { first, second ->
val result = ArrayList<Value>()
var i = 0;
var j = 0;
while (i < first.length && j < second.length) {
if (first[i].epoch == second[j].epoch) {
result.add(Value(first[i].epoch, "Juice"))
i++;
j++;
}
if (first[i].epoch < second[j].epoch) {
i++;
}
if (first[i].epoch > second[j].epoch) {
j++;
}
}
result;
}
I used collection view with 3 different sections where 1st section shows banner, 2nd section shows the sub categories and the 3rd shows the items/products, now what I want is when I scroll the whole view the 2nd section should work as a sticky header on scrolling.
I have done all the possible things that can be done, but nowhere near to solve the issue
Kindly help me out
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
switch section {
case 0: return 1
case 1: return subCategories.count
case 2: return selectedSubCategoryID == -1 ? products.count : productsSub.count
default: return 0
}
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
switch indexPath.section {
case 0:
let cell = collectionView.dequeueReusableCell(ofType: ShopBannerCell.self, for: indexPath)
cell.shopLogoImageView.loadImage(shop.logo)
cell.shopLogoImageView.layer.borderColor = UIColor.lightGray.cgColor
cell.shopLogoImageView.layer.borderWidth = 1
cell.shopBannerImageView.loadImage(details?.sliders?.first)
cell.images = details?.sliders ?? []
cell.shopNameLabel.text = shop.name
// cell.lcoationButton.setTitle(details?.address, for: .normal)
cell.showSideMenu = { [weak self] sender in
if let navVC = self?.navigationController as? NavigationController {
navVC.showSideMenu()
}
}
if currentLanguage == "en" {
cell.backArrowBtn.setImage(#imageLiteral(resourceName: "left_arrow"), for: .normal)
} else
{
cell.backArrowBtn.setImage(#imageLiteral(resourceName: "right-arrow"), for: .normal)
}
cell.backButton = { [weak self] sender in
self?.navigationController?.popViewController(animated: true)
}
cell.showSearch = { [weak self] sender in
NotificationCenter.default.post(name: .navigateToShopVC, object: self)
}
cell.showShopVC = { [weak self] sender in
self?.navigationController?.popToRootViewController(animated: true)
}
cell.showFilterVC = { [weak self] sender in
let productFilterVC = ProductFilterVC()
productFilterVC.modalPresentationStyle = .overFullScreen
productFilterVC.showShopVC = { [weak self] in
self?.navigationController?
.popToRootViewController(animated: true)
}
// variant_product
productFilterVC.applyFilters = { [weak self] filter in
if let priceRange = filter.priceRange, !priceRange.isEmpty, priceRange != 0...0 {
if self?.selectedSubCategoryID == -1 {
self?.products = self?.products.filter { priceRange.contains(Int($0.basePrice ?? 0))} ?? []
} else {
self?.productsSub = self?.productsSub.filter { priceRange.contains(Int($0.unitPrice ?? "0") ?? 0)} ?? []
}
}
if filter.sortOrder != -1 {
if self?.selectedSubCategoryID == -1 {
self?.products.sort(by: { item1, item2 -> Bool in
switch filter.sortOrder {
case 0: return item1.basePrice ?? 0 < item2.basePrice ?? 0
case 1: return item1.basePrice ?? 0 > item2.basePrice ?? 0
case 2: return item1.discount ?? 0 < item2.discount ?? 0
default: return true
}
})
} else {
self?.productsSub.sort(by: { item1, item2 -> Bool in
switch filter.sortOrder {
case 0: return Double(item1.unitPrice ?? "") ?? 0 < Double(item2.unitPrice ?? "") ?? 0
case 1: return Double(item1.unitPrice ?? "") ?? 0 > Double(item2.unitPrice ?? "") ?? 0
case 2: return Double(item1.discount ?? "") ?? 0 < Double(item2.discount ?? "") ?? 0
default: return true
}
})
}
}
self?.collectionView.reloadSections(IndexSet(integer: 2))
}
self?.present(productFilterVC, animated: true)
}
cell.gridButton.isSelected = self.selectedLayout == .grid
cell.listButton.isSelected = self.selectedLayout == .list
cell.listlayout = { [weak self] sender in
self?.selectedLayout = .list
self?.collectionView.collectionViewLayout.invalidateLayout()
self?.collectionView.reloadData()
}
cell.gridLayout = { [weak self] sender in
self?.selectedLayout = .grid
self?.collectionView.collectionViewLayout.invalidateLayout()
self?.collectionView.reloadData()
}
return cell
case 1:
let cell = collectionView.dequeueReusableCell(ofType: SubCategoryCell.self, for: indexPath)
let subCategory = subCategories[indexPath.row]
if subCategory.id == selectedSubCategoryID {
cell.button.isSelected = true
cell.button.backgroundColor = .accentColor
} else {
cell.button.isSelected = false
cell.button.backgroundColor = .clear
}
if currentLanguage == "en" {
cell.button.setTitle(subCategory.name?.capitalized, for: .normal)
} else
{
cell.button.setTitle(subCategory.ar_name?.capitalized, for: .normal)
}
cell.onPress = { [weak self] sender in
guard let self = self else { return }
self.selectedSubCategoryID = self.subCategories[indexPath.row].id ?? 1
}
return cell
case 2:
if selectedLayout == .grid {
let cell = collectionView.dequeueReusableCell(ofType: ProductCell.self, for: indexPath)
// cell.addToCartButton.setTitle("Add to Cart".localized(), for: .normal)
let image = selectedSubCategoryID == -1 ? products[indexPath.row].photos.first : productsSub[indexPath.row].photos?.first
cell.productImageView.loadImage(image as? String)
if currentLanguage == "en"{
cell.productNameLabel.text = selectedSubCategoryID == -1 ? products[indexPath.row].name : productsSub[indexPath.row].name
} else {
cell.productNameLabel.text = selectedSubCategoryID == -1 ? products[indexPath.row].arName : productsSub[indexPath.row].arName
}
cell.pricingLabel.text = "KD ".localized() + "\(products[indexPath.row].baseDiscountedPrice ?? "")"
if products[indexPath.row].basePrice == Double(products[indexPath.row].baseDiscountedPrice ?? ""){
cell.discountView.isHidden = true
cell.pricingLabel.textAlignment = .center
cell.pricingLabel.textColor = UIColor.black
}
else
{
cell.discountView.isHidden = false
cell.discountedLBL.text = "KD " + (String(products[indexPath.row].basePrice ?? 0)) + "0"
cell.pricingLabel.textColor = UIColor.red
}
// let price = selectedSubCategoryID == -1 ? "\(products[indexPath.row].basePrice ?? 0)" : productsSub[indexPath.row].unitPrice ?? ""
return cell
} else {
let cell = collectionView.dequeueReusableCell(ofType: ProductListCell.self, for: indexPath)
// cell.addToCartButton.setTitle("Add to Cart".localized(), for: .normal)
let image = selectedSubCategoryID == -1 ? products[indexPath.row].photos.first : productsSub[indexPath.row].photos?.first
cell.productImageView.loadImage(image as? String)
cell.productImageView.layer.borderColor = UIColor.lightGray.cgColor
cell.productImageView.layer.borderWidth = 1
cell.pricingLabel.text = "KD ".localized() + "\(products[indexPath.row].baseDiscountedPrice ?? "")"
if products[indexPath.row].basePrice == Double(products[indexPath.row].baseDiscountedPrice ?? ""){
cell.discountView.isHidden = true
cell.pricingLabel.textAlignment = .center
cell.pricingLabel.textColor = UIColor.black
}
else
{
cell.discountView.isHidden = false
cell.discountLbl.text = "KD " + (String(products[indexPath.row].basePrice ?? 0)) + "0"
cell.pricingLabel.textColor = UIColor.red
}
if currentLanguage == "en"{
cell.productNameLabel.text = selectedSubCategoryID == -1 ? products[indexPath.row].name : productsSub[indexPath.row].name
} else {
cell.productNameLabel.text = selectedSubCategoryID == -1 ? products[indexPath.row].arName : productsSub[indexPath.row].arName
}
// let price = selectedSubCategoryID == -1 ? "\(products[indexPath.row].basePrice ?? 0)" : productsSub[indexPath.row].unitPrice ?? ""
let productID = selectedSubCategoryID == -1 ? "\(products[indexPath.row].id ?? 0)" : "\(productsSub[indexPath.row].id ?? "")"
cell.onAddToCart = { [weak self] _ in
if UserRepository.shared.userName == "GUEST" {
self?.promptForGuestUser()
return
}
self?.addToCartAPI(product: "\(productID)")
}
return cell
}
default:
return collectionView.dequeueReusableCell(ofType: SubCategoryCell.self, for: indexPath)
}
}
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
collectionView.deselectItem(at: indexPath, animated: true)
switch indexPath.section {
case 2:
let product = products[indexPath.row]
let vc = ProductDetailsVC(id: "\(product.id ?? 0)",
subCategoryID: product.subcategoryID ?? "",
sellerID: product.sellerID ?? "")
navigationController?.pushViewController(vc, animated: true)
default: break
}
}
I am using firebase swift 3. I am new in firebase. So I build an application which user can sign up and post an event, other user can view events and click on attend button if they want to attend. Successfully I can implement all this but my problem is when user click on attend button it will save as user uid under peoplewhoattend child. now I want to view those people who will attend the event. I think I should link user uid to user profile but I can not do that. I have tried a lot and find some similar question but nothing work with me. Here is my code. Rhanks in advance for every positive comment and help
this is attend action button
#IBAction func btnAttend(_ sender: Any) {
self.btnattend.isEnabled = true
let dbRef = Database.database().reference()
let uid = Auth.auth().currentUser!.uid
let storageRef = Storage.storage().reference(forURL: "gs://event-f39cc.appspot.com")
let key = dbRef.child("posts").childByAutoId().key
let imageRef = storageRef.child("posts").child(uid).child("\(key).jpg")
let data = UIImageJPEGRepresentation(self.imgEvent.image!, 0.5)
dbRef.child("users").child("posts").child(self.EventId!).observeSingleEvent(of: .value, with: { (snapshot) in
let updateattends = ["peopleWhoattend/\(key)" : Auth.auth().currentUser!.uid , "UserEmail" : Auth.auth().currentUser?.email , "Username" : Auth.auth().currentUser?.displayName] as [String: Any]
dbRef.child("posts").child(self.EventId!).updateChildValues(updateattends, withCompletionBlock: {(error, reff) in
if error == nil{
dbRef.child("posts").child(self.EventId!).observeSingleEvent(of: .value, with: {(snap) in
if let properties = snap.value as? [String: AnyObject] {
if let Attend = properties["peopleWhoattend"] as? [String: AnyObject]{
let count = Attend.count
self.lblattend.text = "\(count)"
let update = ["Attend" : count]
dbRef.child("posts").child(self.EventId!).updateChildValues(update)
self.btnattend.isHidden = false
// self.unlikeBtn.isHidden = false
//self.unlikeBtn.isEnabled = true
}
}
})
}
})
})
dbRef.removeAllObservers()
}
my database on firebase in JASON format
<pre>
{
"posts" : {
"-KrbDH19ERe9Os5RrEin" : {
"Attend" : 10,
"Category" : "Film & Media",
"Event date End" : "15/08/2017 10:47 PM",
"Event date Start" : "15/08/2017 10:47 PM",
"EventId" : "-KrbDH19ERe9Os5RrEin",
"EventTitle" : "hhh",
"Fee" : "6",
"Location" : {
"latitude" : 23.61349209999999,
"longitude" : 58.5434909,
"place name" : "Ruwi St, Muscat, Oman"
},
"User Email" : "salwa#hotmail.com",
"author" : "abeer",
"commentsId" : "-KrbDH19ERe9Os5RrEio",
"description" : "Event Description : dsdss",
"likes" : 3,
"pathToImage" : "https://firebasestorage.googleapis.com/v0/b/event-f39cc.appspot.com/o/events%2FhjAPqucu2jh3sY6v9n9nRM5Fuz73%2F-KrbDH19ERe9Os5RrEin.jpg?alt=media&token=795f2555-f36d-49d7-9bdd-e521fcd14c16",
"peopleWhoLike" : {
"-KrdnWbWtQ7zmkVwN7MZ" : "hjAPqucu2jh3sY6v9n9nRM5Fuz73",
"-Krdnymu9a_zP81JFDzm" : "hjAPqucu2jh3sY6v9n9nRM5Fuz73",
"-KsZb-WL9dptWI7dHWDf" : "3IHHOBRaVyR3ldK4hGtcGoY7lox2"
},
"peopleWhoattend" : {
"-KrbDLO6ZIpIxt4lTax0" : "hjAPqucu2jh3sY6v9n9nRM5Fuz73",
"-KrbDMri-KrHDrvCAHS4" : "hjAPqucu2jh3sY6v9n9nRM5Fuz73",
"-KrbE3OppAyB_zI5XacN" : "hjAPqucu2jh3sY6v9n9nRM5Fuz73",
"-KrbE3bgAZgvLs9J2KJr" : "hjAPqucu2jh3sY6v9n9nRM5Fuz73",
"-KrbE3vSo1Z6EqOAO4SJ" : "hjAPqucu2jh3sY6v9n9nRM5Fuz73",
"-KrbE4H_kpwgsMRlRCt6" : "hjAPqucu2jh3sY6v9n9nRM5Fuz73",
"-KrbE4Xa2CP_KXC8dQbi" : "hjAPqucu2jh3sY6v9n9nRM5Fuz73",
"-KtL2Egv2slRRk8f_rF1" : "H5kCOhIQFMbxQlN5dzZYC8aptxR2",
"-KtL2TXGz5JkCd1vA3Z2" : "H5kCOhIQFMbxQlN5dzZYC8aptxR2",
"-KtL2TtWdIFn3I1zvMAD" : "H5kCOhIQFMbxQlN5dzZYC8aptxR2"
},
"userId" : "hjAPqucu2jh3sY6v9n9nRM5Fuz73",
"username" : "Salwa"
},
"-KrdlVlme6Jc6eVbNBTa" : {
"Attend" : 4,
"Category" : "Science & Technology",
"Event date End" : "20/08/2017 10:40 AM",
"Event date Start" : "16/08/2017 10:40 AM",
"EventId" : "-KrdlVlme6Jc6eVbNBTa",
"EventTitle" : "hhhh",
"Fee" : "6666",
"Location" : {
"latitude" : 23.6006718,
"longitude" : 58.55438340000001,
"place name" : "(Great Valley (Alkbir Wadi, Muscat, Oman"
},
"User Email" : "salwa#hotmail.com",
"author" : "abeer",
"commentsId" : "-KrdlVlme6Jc6eVbNBTb",
"description" : "Event Description : ttttt",
"likes" : 5,
"pathToImage" : "https://firebasestorage.googleapis.com/v0/b/event-f39cc.appspot.com/o/events%2FhjAPqucu2jh3sY6v9n9nRM5Fuz73%2F-KrdlVlme6Jc6eVbNBTa.jpg?alt=media&token=4471c063-5432-474e-82b3-030f44ce231f",
"peopleWhoLike" : {
"-Krdlhj9fflCIEguJlPZ" : "hjAPqucu2jh3sY6v9n9nRM5Fuz73",
"-KrdlhtXEdxuUMRfYJsR" : "hjAPqucu2jh3sY6v9n9nRM5Fuz73",
"-Krdlhz1914-GsfDsK21" : "hjAPqucu2jh3sY6v9n9nRM5Fuz73",
"-KsZDW38ka7dB5NtfrNm" : "Rufida",
"-KsnWCXJNFlZJT5qbh8J" : "FWTfyvXqsXXdI9sy3GihCx8o5a63"
},
"peopleWhoattend" : {
"-Krdlbv9aKWlYNGa1K_d" : "hjAPqucu2jh3sY6v9n9nRM5Fuz73",
"-Krdldu17azum7ZH5u-E" : "hjAPqucu2jh3sY6v9n9nRM5Fuz73",
"-KrdlgBey4dkBLY8FBBK" : "hjAPqucu2jh3sY6v9n9nRM5Fuz73",
"-KtL2V0WEYwRLCLpGvvW" : "H5kCOhIQFMbxQlN5dzZYC8aptxR2"
},
"user name" : "rufida#hotmail.com",
"userId" : "hjAPqucu2jh3sY6v9n9nRM5Fuz73",
"username" : "Salwa"
},
"users" : {
"3IHHOBRaVyR3ldK4hGtcGoY7lox2" : {
"name" : "Rufida",
"uid" : "3IHHOBRaVyR3ldK4hGtcGoY7lox2",
"urlToImage" : "https://firebasestorage.googleapis.com/v0/b/event-f39cc.appspot.com/o/users%2F3IHHOBRaVyR3ldK4hGtcGoY7lox2.jpg?alt=media&token=1c3ee6f2-f688-40b4-b1d6-69f90bf19bb1"
},
"FWTfyvXqsXXdI9sy3GihCx8o5a63" : {
"Email" : "Ola#hotmail.com",
"name" : "Ola",
"uid" : "FWTfyvXqsXXdI9sy3GihCx8o5a63",
"urlToImage" : "https://firebasestorage.googleapis.com/v0/b/event-f39cc.appspot.com/o/users%2FFWTfyvXqsXXdI9sy3GihCx8o5a63.jpg?alt=media&token=289d9b9c-65c1-4ac0-83e4-7f688925f927"
},
"H5kCOhIQFMbxQlN5dzZYC8aptxR2" : {
"Email" : "Salwa#hotmail.com",
"name" : "Salwa",
"uid" : "H5kCOhIQFMbxQlN5dzZYC8aptxR2",
"urlToImage" : "https://firebasestorage.googleapis.com/v0/b/event-f39cc.appspot.com/o/users%2FH5kCOhIQFMbxQlN5dzZYC8aptxR2.jpg?alt=media&token=8f676259-7240-43d2-8735-0896ec9ad090"
},
"hjAPqucu2jh3sY6v9n9nRM5Fuz73" : {
"name" : "abeer",
"uid" : "hjAPqucu2jh3sY6v9n9nRM5Fuz73",
"urlToImage" : "https://firebasestorage.googleapis.com/v0/b/event-f39cc.appspot.com/o/users%2FhjAPqucu2jh3sY6v9n9nRM5Fuz73.jpg?alt=media&token=2c343a4c-c6ad-4dcf-bd0d-10e9a7a635cb"
}
}
}
Attendess collection view to view people who attend
import UIKit
import Firebase
import FirebaseStorage
class AttendeesCollectionViewController: UIViewController , UICollectionViewDelegateFlowLayout, UICollectionViewDataSource {
var posts = [Events]()
#IBOutlet weak var CVAttendee: UICollectionView!
override func viewDidLoad() {
super.viewDidLoad()
self.navigationController?.navigationBar.barTintColor = UIColor(red: 0, green: 128.0/255.0, blue: 64.0/255.0, alpha: 1.0)
self.navigationController?.navigationBar.tintColor = UIColor.white
self.navigationController?.navigationBar.titleTextAttributes = [NSForegroundColorAttributeName: UIColor.white]
// Do any additional setup after loading the view.
fetchEvents()
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
let height: CGFloat = view.frame.size.height
let width: CGFloat = view.frame.size.width
return CGSize(width: CGFloat(width * 0.95), height: CGFloat(height * 0.75))
}
override func viewDidAppear(_ animated: Bool) {
self.tabBarController?.tabBar.isHidden = false
}
func numberOfSections(in collectionView: UICollectionView) -> Int {
return 1
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return self.posts.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "UserCell", for: indexPath) as! AttendeeCollectionViewCell
//Creating the cell
cell.lblFirstName.text = self.posts[indexPath.row].FirstName
cell.lblLastName.text = self.posts[indexPath.row].LastName
cell.lblNumberAttendee.text = self.posts[indexPath.row].Attend
if let imagePath = self.posts[indexPath.row].userPic{
cell.imgUser.downloadImage(from: imagePath)
}
else{
cell.imgUser.image = UIImage(named: "abstract-user-flat-4")
}
cell.EventId = self.posts[indexPath.row].EventId
cell.lblEmail.text = self.posts[indexPath.row].Email
cell.lblPhoneNum.text = self.posts[indexPath.row].PhoneNumber
return cell
}
func fetchEvents(){
let dbRef = Database.database().reference()
dbRef.child("Attendee").queryOrdered(byChild: "EventId").observeSingleEvent(of: .value, with: { (snapshot) in
let postData = snapshot.value as! [String: AnyObject]
for(_ ,value ) in postData{
let event = Events()
if
let FirstName = value["UserFirstName"] as? String,
let LastName = value["UserLastName"] as? String,
let Email = value["Email"] as? String,
let PhoneNumber = value["PhoneNumber"] as? String,
let attendingID = value["attendingID"] as? String, // let NumberAttend = value["Event date End"] as? String,
let pathToImage = value["pathToImage"] as? String,
let EventId = value["EventId"] as? String,
let userId = value["userId"] as? String {
event.FirstName = FirstName
event.LastName = LastName
event.Email = Email
event.PhoneNumber = PhoneNumber
event.pathToImage = pathToImage
// event.dateStart = NumberAttend
event.EventId = EventId
event.userId = userId
event.attendingID = attendingID
dbRef.child("users").queryOrderedByKey().observeSingleEvent(of: .value, with: { (snap) in
let users = snap.value as! [String: AnyObject]
for(_ ,userVal) in users {
if let uid = userVal["uid"] as? String{
if uid == userId{
if let img = userVal["urlToImage"] as? String{
event.userPic = img
}
}
}
}
})
dbRef.child("Attendee").observe(.value, with: { (snap) in
// post.commentCount = Int(arc4random_uniform(UInt32(snap.childrenCount)))
let comments = snap.value as! NSDictionary
for key in (comments.allKeys) {
if key as! String == attendingID{
let attendee = comments.value(forKey: attendingID) as! NSDictionary
if let attending = attendee.value(forKey: "Attendees"){
let attending = attending as? NSDictionary
// event.commentCount = messages?.count
}
else{
// event.commentCount = 0
}
self.CVAttendee.reloadData()
}
}
})
self.posts.append(event)
}
}
})
dbRef.removeAllObservers()
}
}
Events class
import UIKit
import Firebase
class Events: NSObject {
var author: String!
var Email: String!
var likes: Int!
var Attend : String!
var commentId: String!
var attendingID : String!
var pathToImage: String!
var userId: String!
var userPic: String!
var EventId: String!
var dateStart: String!
var dateEnd: String!
var desc: String!
var Category: String!
var Fee: String!
var EventTitle: String!
var commentCount: Int!
var SponsorPakage : String!
var locationAddress: NSDictionary!
var peopleWhoLike: [String] = [String]()
var peopleWhoattend: [String] = [String]()
var FirstName: String!
var LastName: String!
var PhoneNumber : String!
var go: goingStatus!
}
Firebase picture
post child firebase database shootscreen
user child firebase shootscreen