diff --git a/app/build.gradle b/app/build.gradle index 802911d..e6a2f25 100755 --- a/app/build.gradle +++ b/app/build.gradle @@ -124,4 +124,7 @@ dependencies { // Work manager implementation 'androidx.work:work-runtime-ktx:2.8.0' + + // Navigation animation + implementation 'com.google.accompanist:accompanist-navigation-animation:0.29.1-alpha' } diff --git a/app/src/main/java/com/example/buspayment/MainScreen.kt b/app/src/main/java/com/example/buspayment/MainScreen.kt index acf47da..9c01192 100755 --- a/app/src/main/java/com/example/buspayment/MainScreen.kt +++ b/app/src/main/java/com/example/buspayment/MainScreen.kt @@ -1,33 +1,37 @@ +@file:OptIn(ExperimentalAnimationApi::class) + package com.example.buspayment import android.os.Bundle import androidx.activity.ComponentActivity import androidx.activity.compose.setContent +import androidx.compose.animation.ExperimentalAnimationApi import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Surface import androidx.compose.ui.Modifier -import androidx.navigation.NavHostController -import androidx.navigation.compose.rememberNavController import com.example.buspayment.funtions.NotificationService +import com.example.buspayment.funtions.ToastService import com.example.buspayment.navigations.SetupNavGraph import com.example.buspayment.realtimeDB.repository.DBRepository import com.example.buspayment.ui.theme.BusPaymentTheme +import com.google.accompanist.navigation.animation.rememberAnimatedNavController import dagger.hilt.android.AndroidEntryPoint @AndroidEntryPoint class MainActivity : ComponentActivity() { - private lateinit var navController: NavHostController override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) DBRepository.setContext(this) + ToastService.setContext(this) NotificationService(applicationContext).createNotificationChannel() setContent { BusPaymentTheme { + val navController = rememberAnimatedNavController() Surface( modifier = Modifier.fillMaxSize(), color = MaterialTheme.colorScheme.background @@ -35,7 +39,6 @@ class MainActivity : ComponentActivity() { Column( modifier = Modifier.fillMaxSize(), ) { - navController = rememberNavController() SetupNavGraph(navController) } } diff --git a/app/src/main/java/com/example/buspayment/funtions/ToastService.kt b/app/src/main/java/com/example/buspayment/funtions/ToastService.kt new file mode 100644 index 0000000..ded0a01 --- /dev/null +++ b/app/src/main/java/com/example/buspayment/funtions/ToastService.kt @@ -0,0 +1,19 @@ +package com.example.buspayment.funtions + +import android.annotation.SuppressLint +import android.content.Context +import android.widget.Toast + +class ToastService() { + companion object { + @SuppressLint("StaticFieldLeak") + private lateinit var context: Context + fun setContext(con: Context) { + context = con + } + } + + fun showToast(text: String) { + Toast.makeText(context, "", Toast.LENGTH_SHORT).show() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/example/buspayment/navigations/NavGraph.kt b/app/src/main/java/com/example/buspayment/navigations/NavGraph.kt index 077f7db..8943b92 100755 --- a/app/src/main/java/com/example/buspayment/navigations/NavGraph.kt +++ b/app/src/main/java/com/example/buspayment/navigations/NavGraph.kt @@ -1,9 +1,10 @@ +@file:OptIn(ExperimentalAnimationApi::class) + package com.example.buspayment.navigations +import androidx.compose.animation.ExperimentalAnimationApi import androidx.compose.runtime.Composable import androidx.navigation.NavHostController -import androidx.navigation.compose.NavHost -import androidx.navigation.compose.composable import com.example.buspayment.screens.admin.AddBusScreen import com.example.buspayment.screens.admin.AdminHomeScreen import com.example.buspayment.screens.admin.ManageAccountsScreen @@ -18,27 +19,14 @@ import com.example.buspayment.screens.user.RechargeScreen import com.example.buspayment.screens.user.ScanScreen import com.example.buspayment.screens.user.UserHistoryScreen import com.example.buspayment.screens.user.UserHomeScreen +import com.google.accompanist.navigation.animation.AnimatedNavHost +import com.google.accompanist.navigation.animation.composable @Composable fun SetupNavGraph(navController: NavHostController) { -// val context = LocalContext.current -// val mUserViewModel: UserViewModel = -// viewModel(factory = UserViewModel.UserViewModelFactory(context.applicationContext as Application)) -// var email = mUserViewModel.readUser.observeAsState(listOf()).value -// if (email.isNotEmpty()) { -// Credentials().setEmail(email[0].email.toString()) -// navController.navigate(Screens.Home.route) { -// popUpTo(0) -// } -// } - NavHost( + AnimatedNavHost( navController, startDestination = Screens.Splash.route -// startDestination = if ( -// Credentials().getEmail() -// .isNotEmpty() -// ) Screens.Home.route -// else Screens.Login.route ) { composable( route = Screens.Login.route diff --git a/app/src/main/java/com/example/buspayment/realtimeDB/repository/DBRepository.kt b/app/src/main/java/com/example/buspayment/realtimeDB/repository/DBRepository.kt index 53fda8b..d50cd9b 100644 --- a/app/src/main/java/com/example/buspayment/realtimeDB/repository/DBRepository.kt +++ b/app/src/main/java/com/example/buspayment/realtimeDB/repository/DBRepository.kt @@ -113,8 +113,8 @@ class DBRepository @Inject constructor( it.value } notificationService.showNotification( - "Payment ${payment[4]}", - "Your payment of ${payment[3]} taka from ${payment[1]} to ${payment[5]} has been ${payment[4]}", + "Payment ${payment[5]}", + "Your payment of ${payment[4]} taka from ${payment[2]} to ${payment[6]} has been ${payment[5]}", ) } @@ -308,15 +308,16 @@ class DBRepository @Inject constructor( } } - override fun updateBalance(pay: Double, userId: String): Flow> = + override fun updateBalance(pay: Double, from: String, to: String): Flow> = callbackFlow { - Log.d("entered", "entered") trySend(ResultState.Loading) val paymentTransaction = object : Transaction.Handler { override fun doTransaction(currentData: MutableData): Transaction.Result { - Log.i("firebase payment", "Updating balance for $userId") val currentValue = currentData.getValue(RealtimeUserResponse.UserResponse::class.java) - currentValue?.balance = currentValue?.balance?.plus(pay)!! + if (currentValue!!.userId == from) + currentValue.balance = currentValue.balance.plus(pay) + else + currentValue.balance = currentValue.balance.plus(-pay) currentData.value = currentValue return Transaction.success(currentData) } @@ -347,12 +348,18 @@ class DBRepository @Inject constructor( } } - db.child("userList").orderByChild("userId").equalTo(userId) + db.child("userList").orderByChild("userId").equalTo(from) .addListenerForSingleValueEvent(singleValueListener) + if (to.isNotEmpty()) + db.child("userList").orderByChild("userId").equalTo(to) + .addListenerForSingleValueEvent(singleValueListener) // db.child("userList").runTransaction(paymentTransaction) awaitClose { - db.child("userList").orderByChild("userId").equalTo(userId) + db.child("userList").orderByChild("userId").equalTo(from) .removeEventListener(singleValueListener) + if (to.isNotEmpty()) + db.child("userList").orderByChild("userId").equalTo(to) + .removeEventListener(singleValueListener) close() } } diff --git a/app/src/main/java/com/example/buspayment/realtimeDB/repository/Repository.kt b/app/src/main/java/com/example/buspayment/realtimeDB/repository/Repository.kt index 6e84b2e..96a37d2 100644 --- a/app/src/main/java/com/example/buspayment/realtimeDB/repository/Repository.kt +++ b/app/src/main/java/com/example/buspayment/realtimeDB/repository/Repository.kt @@ -15,7 +15,7 @@ interface Repository { fun getUser(email: String = ""): Flow>> fun getBus(): Flow>> fun getDistance(): Flow>> - fun updateBalance(pay: Double, userId: String): Flow> + fun updateBalance(pay: Double, from: String, to: String): Flow> fun submitPayment( payment: RealtimePaymentResponse.PaymentResponse, from: String, diff --git a/app/src/main/java/com/example/buspayment/realtimeDB/ui/RealtimeViewModel.kt b/app/src/main/java/com/example/buspayment/realtimeDB/ui/RealtimeViewModel.kt index 940aa08..9cbe546 100644 --- a/app/src/main/java/com/example/buspayment/realtimeDB/ui/RealtimeViewModel.kt +++ b/app/src/main/java/com/example/buspayment/realtimeDB/ui/RealtimeViewModel.kt @@ -196,7 +196,7 @@ class RealtimeViewModel @Inject constructor( fun updatePayment(email: String, payment: RealtimePaymentResponse) = repo.updatePayment(email, payment) - fun updateBalance(pay: Double, userId: String) = repo.updateBalance(pay, userId) + fun updateBalance(pay: Double, from: String, to: String) = repo.updateBalance(pay, from, to) } data class UserState( diff --git a/app/src/main/java/com/example/buspayment/screens/conductor/ConductorHomeScreen.kt b/app/src/main/java/com/example/buspayment/screens/conductor/ConductorHomeScreen.kt index 3ba7f4b..ce2fafc 100755 --- a/app/src/main/java/com/example/buspayment/screens/conductor/ConductorHomeScreen.kt +++ b/app/src/main/java/com/example/buspayment/screens/conductor/ConductorHomeScreen.kt @@ -1,30 +1,71 @@ +@file:OptIn(ExperimentalMaterial3Api::class) + package com.example.buspayment.screens.conductor +import android.app.Application +import androidx.compose.animation.animateContentSize +import androidx.compose.animation.core.LinearOutSlowInEasing +import androidx.compose.animation.core.tween import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.width import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.Person +import androidx.compose.material3.CircularProgressIndicator +import androidx.compose.material3.ElevatedAssistChip +import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme import androidx.compose.material3.OutlinedButton import androidx.compose.material3.Surface import androidx.compose.material3.Text import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.getValue +import androidx.compose.runtime.livedata.observeAsState +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp +import androidx.hilt.navigation.compose.hiltViewModel +import androidx.lifecycle.viewmodel.compose.viewModel import androidx.navigation.NavController +import com.example.buspayment.data.User +import com.example.buspayment.data.UserViewModel import com.example.buspayment.navigations.Screens +import com.example.buspayment.realtimeDB.ui.RealtimeViewModel import com.example.buspayment.ui.theme.LogoImage @Composable -fun ConductorHomeScreen(navController: NavController) { +fun ConductorHomeScreen( + navController: NavController, + viewModel: RealtimeViewModel = hiltViewModel() +) { + val context = LocalContext.current + val users = viewModel.userRes.value + var balance by remember { mutableStateOf(0.0) } + var user by remember { mutableStateOf(listOf()) } + val mUserViewModel: UserViewModel = + viewModel(factory = UserViewModel.UserViewModelFactory(context.applicationContext as Application)) + user = mUserViewModel.readUser.observeAsState(emptyList()).value + LaunchedEffect(key1 = user) { + if (user.isNotEmpty()) { + viewModel.getUser(user[0].email) + } + } + if (users.user.isNotEmpty()) { + balance = users.user[0].user?.balance!! + } Column { Column( Modifier @@ -35,8 +76,37 @@ fun ConductorHomeScreen(navController: NavController) { Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween, ) { - Row { - Text(text = "Conductor") + Row( + modifier = Modifier.animateContentSize( + animationSpec = tween( + durationMillis = 500, + easing = LinearOutSlowInEasing + ) + ) + ) { + ElevatedAssistChip(onClick = { navController.navigate(Screens.Recharge.route) }, label = { + Row( + modifier = Modifier.animateContentSize( + animationSpec = tween( + durationMillis = 500, + easing = LinearOutSlowInEasing + ), + ), + verticalAlignment = Alignment.CenterVertically + ) { + Text( + text = "Balance " + ) + if (users.isLoading) { + CircularProgressIndicator( + modifier = Modifier + .height(16.dp) + .width(16.dp) + ) + } else Text("$balance") + + } + }) } Row { Text( diff --git a/app/src/main/java/com/example/buspayment/screens/conductor/PaymentListScreen.kt b/app/src/main/java/com/example/buspayment/screens/conductor/PaymentListScreen.kt index 6262ecc..4ddbdef 100644 --- a/app/src/main/java/com/example/buspayment/screens/conductor/PaymentListScreen.kt +++ b/app/src/main/java/com/example/buspayment/screens/conductor/PaymentListScreen.kt @@ -1,7 +1,10 @@ +@file:OptIn(ExperimentalFoundationApi::class) + package com.example.buspayment.screens.conductor import android.app.Application import android.widget.Toast +import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.background import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Arrangement @@ -89,7 +92,7 @@ fun PaymentListScreen( "@" ) } - LazyColumn() { + LazyColumn(reverseLayout = true) { items( paymentList, key = { @@ -141,6 +144,7 @@ fun PaymentListRow( RealtimePaymentResponse( payment = RealtimePaymentResponse.PaymentResponse( fromUser = itemState.payment.fromUser, + code = itemState.payment.code, status = "Accepted", toUser = itemState.payment.toUser, from = itemState.payment.from, @@ -176,6 +180,7 @@ fun PaymentListRow( RealtimePaymentResponse( payment = RealtimePaymentResponse.PaymentResponse( fromUser = itemState.payment.fromUser, + code = itemState.payment.code, status = "Rejected", toUser = itemState.payment.toUser, from = itemState.payment.from, diff --git a/app/src/main/java/com/example/buspayment/screens/user/RechargeScreen.kt b/app/src/main/java/com/example/buspayment/screens/user/RechargeScreen.kt index 593756f..49ecb99 100644 --- a/app/src/main/java/com/example/buspayment/screens/user/RechargeScreen.kt +++ b/app/src/main/java/com/example/buspayment/screens/user/RechargeScreen.kt @@ -83,7 +83,7 @@ fun RechargeScreen( .clickable { scope.launch { viewModel - .updateBalance(50.0, user[0].email.substringBefore("@")) + .updateBalance(50.0, user[0].email.substringBefore("@"), "") .collect { response -> when (response) { is ResultState.Success -> { @@ -118,7 +118,7 @@ fun RechargeScreen( .clickable { scope.launch { viewModel - .updateBalance(100.0, user[0].email.substringBefore("@")) + .updateBalance(100.0, user[0].email.substringBefore("@"), "") .collect { response -> when (response) { is ResultState.Success -> { @@ -154,7 +154,7 @@ fun RechargeScreen( .clickable { scope.launch { viewModel - .updateBalance(500.0, user[0].email.substringBefore("@")) + .updateBalance(500.0, user[0].email.substringBefore("@"), "") .collect { response -> when (response) { is ResultState.Success -> { diff --git a/app/src/main/java/com/example/buspayment/screens/user/ScanScreen.kt b/app/src/main/java/com/example/buspayment/screens/user/ScanScreen.kt index 971bb47..ce880a1 100755 --- a/app/src/main/java/com/example/buspayment/screens/user/ScanScreen.kt +++ b/app/src/main/java/com/example/buspayment/screens/user/ScanScreen.kt @@ -5,7 +5,6 @@ package com.example.buspayment.screens.user import android.Manifest import android.app.Application import android.content.pm.PackageManager -import android.util.Log import android.util.Size import android.widget.Toast import androidx.activity.compose.rememberLauncherForActivityResult @@ -30,6 +29,7 @@ import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.ArrowBack import androidx.compose.material.icons.filled.KeyboardArrowDown import androidx.compose.material.icons.filled.KeyboardArrowUp +import androidx.compose.material.icons.filled.Person import androidx.compose.material3.Card import androidx.compose.material3.DropdownMenu import androidx.compose.material3.DropdownMenuItem @@ -38,6 +38,7 @@ import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme import androidx.compose.material3.OutlinedButton import androidx.compose.material3.OutlinedTextField +import androidx.compose.material3.Slider import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect @@ -66,6 +67,7 @@ import com.example.buspayment.data.User import com.example.buspayment.data.UserViewModel import com.example.buspayment.funtions.QrCodeAnalysis import com.example.buspayment.navigations.Screens +import com.example.buspayment.realtimeDB.responses.RealtimeBusResponse import com.example.buspayment.realtimeDB.responses.RealtimePaymentResponse import com.example.buspayment.realtimeDB.ui.RealtimeViewModel import com.example.buspayment.ui.theme.Typography @@ -74,6 +76,7 @@ import com.google.android.gms.location.LocationServices import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import kotlin.math.abs +import kotlin.math.roundToInt import kotlin.random.Random @Composable @@ -85,13 +88,15 @@ fun ScanScreen( val distance = viewModel.distRes.value val scope = rememberCoroutineScope() val buses = viewModel.busRes.value - var bus by remember { mutableStateOf("") } var user by remember { mutableStateOf(listOf()) } +// var bus by remember { mutableStateOf(listOf()) } +// var bus: RealtimeBusResponse.BusResponse? by remember + var bus by remember { mutableStateOf(RealtimeBusResponse.BusResponse()) } val mUserViewModel: UserViewModel = viewModel(factory = UserViewModel.UserViewModelFactory(context.applicationContext as Application)) user = mUserViewModel.readUser.observeAsState(listOf()).value var code by remember { mutableStateOf("") } - var busName by remember { mutableStateOf("") } + var person by remember { mutableStateOf(1f) } var isExpanded by remember { mutableStateOf(false) } var isToExpanded by remember { mutableStateOf(false) } val lifeCycleOwner = LocalLifecycleOwner.current @@ -99,9 +104,9 @@ fun ScanScreen( val toList = mutableListOf() var fromPrice by remember { mutableStateOf(0.0) } var toPrice by remember { mutableStateOf(0.0) } - var price = 0.0 + var price by remember { mutableStateOf(0.0) } if (fromPrice > 0 && toPrice > 0) { - price = abs((fromPrice - toPrice) * 2.5) + price = abs((fromPrice - toPrice) * 2.5) * (person.toInt()) } var selectedFrom by remember { mutableStateOf("") } val busList = mutableListOf() @@ -116,7 +121,7 @@ fun ScanScreen( LaunchedEffect(code) { buses.bus.map { item -> if (code == item.bus!!.id) { - busName = item.bus.name + bus = item.bus } } } @@ -133,7 +138,6 @@ fun ScanScreen( if (selectedTo == item.dist.name) { toPrice = item.dist.value } - Log.d("Check price", "$fromPrice $toPrice $fromPrice-$toPrice") } } var initialLocation = LocationServices.getFusedLocationProviderClient(context) @@ -168,7 +172,7 @@ fun ScanScreen( verticalArrangement = Arrangement.Center, modifier = Modifier.fillMaxSize() ) { - if (busName.isNotEmpty()) { + if (bus.name.isNotEmpty()) { Card( modifier = Modifier .width(300.dp) @@ -176,10 +180,10 @@ fun ScanScreen( ) { Column(modifier = Modifier.padding(20.dp, 10.dp)) { Text( - text = code, + text = bus.name, style = Typography.headlineLarge, ) - Text(text = "From 'Chandra' to 'Mirpur'") + Text(text = "From '${bus.startAddress}' to '${bus.endAddress}'") Text(text = "Rate per kilometer: 2.50 taka") Column(Modifier.padding(20.dp)) { Row { @@ -256,6 +260,20 @@ fun ScanScreen( } } + Row(modifier = Modifier.fillMaxWidth()) { + Text(text = "${person.roundToInt()}") + Slider( + value = person, + onValueChange = { + person = it + }, + steps = 2, + valueRange = 1f..4f, + thumb = { + Icon(imageVector = Icons.Filled.Person, contentDescription = "", tint = Color.Red) + } + ) + } } } Column( @@ -274,13 +292,13 @@ fun ScanScreen( fromUser = user[0].email.substringBefore("@"), toUser = code, paid = price, - bus = busName, + bus = bus.name, code = Random.nextInt(1000, 10000).toString() -// code = java.util.Random().nextInt(9999 - 1001) + 1000.toString(), ), from = user[0].email.substringBefore("@"), to = code ).collect { response -> when (response) { is ResultState.Success -> { + Toast.makeText(context, "Payment successful", Toast.LENGTH_SHORT).show() navController.navigate(Screens.UHome.route) } @@ -293,15 +311,13 @@ fun ScanScreen( } } scope.launch(Dispatchers.Main) { - viewModel.updateBalance(-price, user[0].email.substringBefore("@")) + viewModel.updateBalance(-price, user[0].email.substringBefore("@"), code) .collect { response -> when (response) { is ResultState.Success -> { - Toast.makeText(context, "Payment successful", Toast.LENGTH_SHORT).show() } is ResultState.Failure -> { - Toast.makeText(context, "Failed", Toast.LENGTH_SHORT).show() } is ResultState.Loading -> {} @@ -317,7 +333,9 @@ fun ScanScreen( } } else { if (hasCameraPermission) { - Column { + Column( + modifier = Modifier.fillMaxSize() + ) { Row( Modifier .fillMaxWidth() @@ -334,12 +352,7 @@ fun ScanScreen( ) } Text("Scan for Bus", style = MaterialTheme.typography.headlineSmall) - Row { - Text( - text = "", - ) - - } + Text("") } AndroidView( factory = { context -> @@ -362,7 +375,6 @@ fun ScanScreen( ContextCompat.getMainExecutor(context), QrCodeAnalysis { result -> code = result - bus = code // scope.launch(Dispatchers.Main) { // if (buses.bus.isNotEmpty()) { // bus = buses.bus.filter { item -> item.bus!!.id == code }[0].bus!!.name @@ -390,8 +402,18 @@ fun ScanScreen( ) .clip(RoundedCornerShape(40.dp)) ) + if (code.isNotEmpty() && bus.name.isEmpty()) + Row(modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.Center) { + Text( + text = "Invalid QR Code", + style = MaterialTheme.typography.headlineMedium, + color = Color.Red + ) + + } } } } } -} \ No newline at end of file +} + diff --git a/app/src/main/java/com/example/buspayment/screens/user/UserHistoryScreen.kt b/app/src/main/java/com/example/buspayment/screens/user/UserHistoryScreen.kt index a3f797d..cdb2ac7 100644 --- a/app/src/main/java/com/example/buspayment/screens/user/UserHistoryScreen.kt +++ b/app/src/main/java/com/example/buspayment/screens/user/UserHistoryScreen.kt @@ -81,7 +81,7 @@ fun UserHistoryScreen( } Column { if (history.payment.isNotEmpty()) { - LazyColumn() { + LazyColumn(reverseLayout = true) { items( history.payment, key = { it.key!! }