From 889e80ad9e2017f11e2aaf5a5f1c9e3dff09da8b Mon Sep 17 00:00:00 2001 From: Zyzf Date: Sun, 3 Dec 2023 18:03:39 +0400 Subject: [PATCH] started 4 lab --- .idea/deploymentTargetDropDown.xml | 17 +- .idea/gradle.xml | 5 +- .idea/kotlinc.xml | 2 +- .idea/migrations.xml | 10 + app/build.gradle.kts | 30 +- .../zyzf/coffeepreorder/CoffeeApplication.kt | 14 + .../com/zyzf/coffeepreorder/MainActivity.kt | 2 +- .../coffee/composeui/CoffeeList.kt | 390 ------------------ .../coffee/composeui/CoffeeView.kt | 72 ---- .../coffeepreorder/coffee/model/Coffee.kt | 27 -- .../zyzf/coffeepreorder/composeui/Login.kt | 152 ------- .../coffeepreorder/database/AppContainer.kt | 31 ++ .../coffeepreorder/database/AppDatabase.kt | 2 +- .../coffeepreorder/database/dao/CoffeeDao.kt | 7 +- .../coffeepreorder/database/dao/UserDao.kt | 11 +- .../coffeepreorder/database/model/Coffee.kt | 16 +- .../coffeepreorder/database/model/User.kt | 12 +- .../database/repository/CartRepository.kt | 12 + .../database/repository/CoffeeRepository.kt | 15 + .../repository/OfflineCartRepository.kt | 13 + .../repository/OfflineCoffeeRepository.kt | 16 + .../repository/OfflineUserRepository.kt | 17 + .../database/repository/UserRepository.kt | 16 + .../coffeepreorder/ui/AppViewModelProvider.kt | 23 ++ .../{composeui => ui/cart}/Cart.kt | 7 +- .../coffeepreorder/ui/coffee/CoffeeList.kt | 362 ++++++++++++++++ .../ui/coffee/CoffeeListViewModel.kt | 141 +++++++ .../com/zyzf/coffeepreorder/ui/login/Login.kt | 153 +++++++ .../coffeepreorder/ui/login/LoginViewModel.kt | 36 ++ .../navigation/MainNavbar.kt | 29 +- .../{composeui => ui}/navigation/Screen.kt | 4 +- .../{composeui => ui/order}/Order.kt | 4 +- .../{composeui => ui/profile}/Profile.kt | 10 +- .../{composeui => ui/register}/Register.kt | 6 +- build.gradle.kts | 6 +- gradle/wrapper/gradle-wrapper.properties | 2 +- 36 files changed, 942 insertions(+), 730 deletions(-) create mode 100644 .idea/migrations.xml create mode 100644 app/src/main/java/com/zyzf/coffeepreorder/CoffeeApplication.kt delete mode 100644 app/src/main/java/com/zyzf/coffeepreorder/coffee/composeui/CoffeeList.kt delete mode 100644 app/src/main/java/com/zyzf/coffeepreorder/coffee/composeui/CoffeeView.kt delete mode 100644 app/src/main/java/com/zyzf/coffeepreorder/coffee/model/Coffee.kt delete mode 100644 app/src/main/java/com/zyzf/coffeepreorder/composeui/Login.kt create mode 100644 app/src/main/java/com/zyzf/coffeepreorder/database/AppContainer.kt create mode 100644 app/src/main/java/com/zyzf/coffeepreorder/database/repository/CartRepository.kt create mode 100644 app/src/main/java/com/zyzf/coffeepreorder/database/repository/CoffeeRepository.kt create mode 100644 app/src/main/java/com/zyzf/coffeepreorder/database/repository/OfflineCartRepository.kt create mode 100644 app/src/main/java/com/zyzf/coffeepreorder/database/repository/OfflineCoffeeRepository.kt create mode 100644 app/src/main/java/com/zyzf/coffeepreorder/database/repository/OfflineUserRepository.kt create mode 100644 app/src/main/java/com/zyzf/coffeepreorder/database/repository/UserRepository.kt create mode 100644 app/src/main/java/com/zyzf/coffeepreorder/ui/AppViewModelProvider.kt rename app/src/main/java/com/zyzf/coffeepreorder/{composeui => ui/cart}/Cart.kt (97%) create mode 100644 app/src/main/java/com/zyzf/coffeepreorder/ui/coffee/CoffeeList.kt create mode 100644 app/src/main/java/com/zyzf/coffeepreorder/ui/coffee/CoffeeListViewModel.kt create mode 100644 app/src/main/java/com/zyzf/coffeepreorder/ui/login/Login.kt create mode 100644 app/src/main/java/com/zyzf/coffeepreorder/ui/login/LoginViewModel.kt rename app/src/main/java/com/zyzf/coffeepreorder/{composeui => ui}/navigation/MainNavbar.kt (83%) rename app/src/main/java/com/zyzf/coffeepreorder/{composeui => ui}/navigation/Screen.kt (91%) rename app/src/main/java/com/zyzf/coffeepreorder/{composeui => ui/order}/Order.kt (98%) rename app/src/main/java/com/zyzf/coffeepreorder/{composeui => ui/profile}/Profile.kt (98%) rename app/src/main/java/com/zyzf/coffeepreorder/{composeui => ui/register}/Register.kt (97%) diff --git a/.idea/deploymentTargetDropDown.xml b/.idea/deploymentTargetDropDown.xml index 931c4b7..0c0c338 100644 --- a/.idea/deploymentTargetDropDown.xml +++ b/.idea/deploymentTargetDropDown.xml @@ -1,17 +1,10 @@ - - - - - - - - - - - - + + + + + \ No newline at end of file diff --git a/.idea/gradle.xml b/.idea/gradle.xml index ae388c2..0897082 100644 --- a/.idea/gradle.xml +++ b/.idea/gradle.xml @@ -4,16 +4,15 @@ diff --git a/.idea/kotlinc.xml b/.idea/kotlinc.xml index f8467b4..e805548 100644 --- a/.idea/kotlinc.xml +++ b/.idea/kotlinc.xml @@ -1,6 +1,6 @@ - \ No newline at end of file diff --git a/.idea/migrations.xml b/.idea/migrations.xml new file mode 100644 index 0000000..f8051a6 --- /dev/null +++ b/.idea/migrations.xml @@ -0,0 +1,10 @@ + + + + + + \ No newline at end of file diff --git a/app/build.gradle.kts b/app/build.gradle.kts index f717f1e..6763406 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -41,7 +41,7 @@ android { compose = true } composeOptions { - kotlinCompilerExtensionVersion = "1.5.3" + kotlinCompilerExtensionVersion = "1.5.5" } packaging { resources { @@ -63,34 +63,32 @@ dependencies { implementation("androidx.activity:activity-compose:1.8.1") implementation(platform("androidx.compose:compose-bom:2023.10.01")) implementation("androidx.navigation:navigation-compose:2.7.5") - implementation("androidx.compose.ui:ui:1.6.0-beta01") - implementation("androidx.compose.ui:ui-graphics:1.6.0-beta01") - implementation("androidx.compose.ui:ui-tooling-preview:1.6.0-beta01") - implementation("androidx.compose.material3:material3:1.2.0-alpha11") + implementation("androidx.compose.ui:ui:1.6.0-beta02") + implementation("androidx.compose.ui:ui-graphics:1.6.0-beta02") + implementation("androidx.compose.ui:ui-tooling-preview:1.6.0-beta02") + implementation("androidx.compose.material3:material3:1.2.0-alpha12") // Retrofit implementation("com.jakewharton.retrofit:retrofit2-kotlinx-serialization-converter:1.0.0") implementation("com.squareup.retrofit2:retrofit:2.9.0") implementation("io.coil-kt:coil-compose:2.5.0") - implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.6.1") + implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.6.2") implementation("com.jcraft:jsch:0.1.55") // Room - implementation("androidx.room:room-runtime:2.6.0") - annotationProcessor("androidx.room:room-compiler:2.6.0") - ksp("androidx.room:room-compiler:2.6.0") - implementation("androidx.room:room-ktx:2.6.0") - implementation("androidx.room:room-paging:2.6.0") + implementation("androidx.room:room-runtime:2.6.1") + annotationProcessor("androidx.room:room-compiler:2.6.1") + ksp("androidx.room:room-compiler:2.6.1") + implementation("androidx.room:room-ktx:2.6.1") + implementation("androidx.room:room-paging:2.6.1") // Tests testImplementation("junit:junit:4.13.2") androidTestImplementation("androidx.test.ext:junit:1.1.5") androidTestImplementation("androidx.test.espresso:espresso-core:3.5.1") androidTestImplementation(platform("androidx.compose:compose-bom:2023.10.01")) - androidTestImplementation("androidx.compose.ui:ui-test-junit4:1.6.0-beta01") - debugImplementation("androidx.compose.ui:ui-tooling:1.6.0-beta01") - debugImplementation("androidx.compose.ui:ui-test-manifest:1.6.0-beta01") - - + androidTestImplementation("androidx.compose.ui:ui-test-junit4:1.6.0-beta02") + debugImplementation("androidx.compose.ui:ui-tooling:1.6.0-beta02") + debugImplementation("androidx.compose.ui:ui-test-manifest:1.6.0-beta02") } \ No newline at end of file diff --git a/app/src/main/java/com/zyzf/coffeepreorder/CoffeeApplication.kt b/app/src/main/java/com/zyzf/coffeepreorder/CoffeeApplication.kt new file mode 100644 index 0000000..657eeb0 --- /dev/null +++ b/app/src/main/java/com/zyzf/coffeepreorder/CoffeeApplication.kt @@ -0,0 +1,14 @@ +package com.zyzf.coffeepreorder + +import android.app.Application +import com.zyzf.coffeepreorder.database.AppContainer +import com.zyzf.coffeepreorder.database.AppDataContainer + +class CoffeeApplication : Application() { + lateinit var container: AppContainer + + override fun onCreate() { + super.onCreate() + container = AppDataContainer(this) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/zyzf/coffeepreorder/MainActivity.kt b/app/src/main/java/com/zyzf/coffeepreorder/MainActivity.kt index e92a8db..9b16b91 100644 --- a/app/src/main/java/com/zyzf/coffeepreorder/MainActivity.kt +++ b/app/src/main/java/com/zyzf/coffeepreorder/MainActivity.kt @@ -10,7 +10,7 @@ import androidx.compose.material3.Surface import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.tooling.preview.Preview -import com.zyzf.coffeepreorder.composeui.navigation.MainNavbar +import com.zyzf.coffeepreorder.ui.navigation.MainNavbar import com.zyzf.coffeepreorder.ui.theme.CoffeePreorderTheme class MainActivity : ComponentActivity() { diff --git a/app/src/main/java/com/zyzf/coffeepreorder/coffee/composeui/CoffeeList.kt b/app/src/main/java/com/zyzf/coffeepreorder/coffee/composeui/CoffeeList.kt deleted file mode 100644 index 19213ba..0000000 --- a/app/src/main/java/com/zyzf/coffeepreorder/coffee/composeui/CoffeeList.kt +++ /dev/null @@ -1,390 +0,0 @@ -package com.zyzf.coffeepreorder.coffee.composeui - -import android.annotation.SuppressLint -import android.content.res.Configuration -import android.graphics.Bitmap.CompressFormat -import android.graphics.BitmapFactory -import android.net.Uri -import android.os.StrictMode -import android.os.StrictMode.ThreadPolicy -import android.util.Log -import androidx.activity.compose.rememberLauncherForActivityResult -import androidx.activity.result.PickVisualMediaRequest -import androidx.activity.result.contract.ActivityResultContracts -import androidx.compose.foundation.ExperimentalFoundationApi -import androidx.compose.foundation.border -import androidx.compose.foundation.clickable -import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.Spacer -import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.heightIn -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.size -import androidx.compose.foundation.lazy.LazyColumn -import androidx.compose.foundation.lazy.items -import androidx.compose.foundation.shape.CircleShape -import androidx.compose.foundation.shape.RoundedCornerShape -import androidx.compose.foundation.text.KeyboardOptions -import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.filled.Add -import androidx.compose.material.icons.outlined.Create -import androidx.compose.material3.Button -import androidx.compose.material3.ButtonDefaults -import androidx.compose.material3.ExperimentalMaterial3Api -import androidx.compose.material3.FloatingActionButton -import androidx.compose.material3.Icon -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.ModalBottomSheet -import androidx.compose.material3.OutlinedIconButton -import androidx.compose.material3.OutlinedTextField -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.mutableDoubleStateOf -import androidx.compose.runtime.mutableStateListOf -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.draw.clip -import androidx.compose.ui.layout.ContentScale -import androidx.compose.ui.platform.LocalContext -import androidx.compose.ui.res.painterResource -import androidx.compose.ui.res.stringResource -import androidx.compose.ui.text.input.KeyboardType -import androidx.compose.ui.tooling.preview.Preview -import androidx.compose.ui.unit.dp -import androidx.compose.ui.unit.sp -import androidx.navigation.NavController -import coil.compose.AsyncImage -import coil.request.ImageRequest -import com.jcraft.jsch.Channel -import com.jcraft.jsch.ChannelSftp -import com.jcraft.jsch.JSch -import com.jcraft.jsch.JSchException -import com.jcraft.jsch.Session -import com.jcraft.jsch.SftpException -import com.zyzf.coffeepreorder.R -import com.zyzf.coffeepreorder.database.AppDatabase -import com.zyzf.coffeepreorder.database.model.Coffee -import com.zyzf.coffeepreorder.ui.theme.CoffeePreorderTheme -import kotlinx.coroutines.DelicateCoroutinesApi -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.GlobalScope -import kotlinx.coroutines.launch -import kotlinx.coroutines.withContext -import java.io.ByteArrayOutputStream -import java.io.File -import java.io.FileOutputStream -import java.util.Properties - - -@SuppressLint("UnrememberedMutableState") -@OptIn(ExperimentalMaterial3Api::class, ExperimentalFoundationApi::class, - DelicateCoroutinesApi::class -) -@Composable -fun CoffeeList(navController: NavController?) { - val openDialog = remember { mutableStateOf(false) } - val add = remember { mutableStateOf(false) } - val coffee = remember { mutableStateOf(Coffee("", 0.0, "", null, 0)) } - val context = LocalContext.current - val itemsList = remember { mutableStateListOf() } - - var imageUri: Any? by remember { mutableStateOf(R.drawable.img) } - - val photoPicker = rememberLauncherForActivityResult( - contract = ActivityResultContracts.PickVisualMedia() - ) { - if (it != null) { - Log.d("PhotoPicker", "Selected URI: $it") - imageUri = it - } else { - Log.d("PhotoPicker", "No media selected") - } - } - - LaunchedEffect(Unit) { - withContext(Dispatchers.IO) { - AppDatabase.getInstance(context).coffeeDao().getAll().collect { data -> - itemsList.clear() - itemsList.addAll(data) - } - } - } - LazyColumn( - horizontalAlignment = Alignment.CenterHorizontally) { - items(itemsList) { currentCoffee -> - Row(modifier = Modifier - .fillMaxWidth() - .heightIn(max = 140.dp) - .padding(bottom = 10.dp, top = 10.dp), - horizontalArrangement = Arrangement.SpaceAround) { - - AsyncImage( - model = ImageRequest.Builder(context = LocalContext.current).data("https://zyzf.space/s/zXgFRTmbR4KMxMH/download?path=&files=coffee_image_" + currentCoffee.uid +".png") - .crossfade(true).build(), - error = painterResource(R.drawable.ic_broken_image), - placeholder = painterResource(R.drawable.loading_img), - contentDescription = "Кофе", - contentScale = ContentScale.Crop, - modifier = Modifier - .size(100.dp) - .clip(RoundedCornerShape(50.dp)) - ) - - Column( - Modifier - .weight(2f) - .padding(start = 20.dp)) { - Text(text = currentCoffee.name, fontSize = 25.sp) - Text(text = String.format("%.2f", currentCoffee.cost), fontSize = 20.sp) - Text(text = currentCoffee.ingredients, fontSize = 15.sp) - Row( - Modifier - .fillMaxWidth() - .padding(top = 5.dp)) { - Button( - onClick = { - GlobalScope.launch (Dispatchers.Main) { - currentCoffee.uid?.let { - AppDatabase.getInstance(context).cartDao().get().uid?.let { it1 -> - AppDatabase.getInstance(context).cartDao().insertCoffee( - it1, - it, - 1 - ) - } - } - AppDatabase.getInstance(context).coffeeDao().getAll().collect { data -> - itemsList.clear() - itemsList.addAll(data) - } - } - }, - shape = CircleShape, - modifier = Modifier.fillMaxWidth(fraction = 0.75f) - ) { - Icon( - imageVector = Icons.Default.Add, - contentDescription = "Favorite", - modifier = Modifier.size(20.dp) - ) - Spacer(Modifier.size(ButtonDefaults.IconSpacing)) - Text(text = "В корзину") - } - OutlinedIconButton( - onClick = { - coffee.value = currentCoffee - add.value = false - openDialog.value = true - }, - Modifier - .padding(start = 10.dp) - .clip(CircleShape)) { - Icon( - imageVector = Icons.Outlined.Create, - contentDescription = "Favorite", - modifier = Modifier.size(20.dp) - ) - } - } - } - } - } - } - Box(modifier = Modifier.fillMaxSize()) { - FloatingActionButton(onClick = { coffee.value = Coffee("", 0.0, "", null, 0); add.value = true; openDialog.value = true }, - Modifier - .padding(all = 20.dp) - .align(alignment = Alignment.BottomEnd)) { - Icon( - imageVector = Icons.Filled.Add, - contentDescription = "Add", - modifier = Modifier.size(20.dp) - ) - } - } - if (openDialog.value) { - var name by remember { mutableStateOf(coffee.value.name) } - var cost by remember { mutableDoubleStateOf(coffee.value.cost) } - var ingredients by remember { mutableStateOf(coffee.value.ingredients) } - - ModalBottomSheet(onDismissRequest = { openDialog.value = false }) { - Column(modifier = Modifier - .fillMaxWidth() - .padding(all = 10.dp), - horizontalAlignment = Alignment.CenterHorizontally) { - OutlinedTextField(modifier = Modifier.fillMaxWidth(), - value = name, onValueChange = {name = it}, - label = { - Text(stringResource(id = R.string.coffee_name)) - }, - keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Text) - ) - OutlinedTextField(modifier = Modifier.fillMaxWidth(), - value = cost.toString(), onValueChange = {cost = it.toDouble()}, - label = { - Text(stringResource(id = R.string.coffee_cost)) - }, - keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Decimal) - ) - OutlinedTextField(modifier = Modifier.fillMaxWidth().padding(bottom = 10.dp), - value = ingredients, onValueChange = {ingredients = it}, - label = { - Text(stringResource(id = R.string.coffee_ingredients)) - }, - keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Text) - ) - AsyncImage( - modifier = Modifier - .size(100.dp).clip(RoundedCornerShape(50.dp)).border(2.dp, MaterialTheme.colorScheme.outline, shape = RoundedCornerShape(50.dp)) - .clickable { - photoPicker.launch( - PickVisualMediaRequest( - ActivityResultContracts.PickVisualMedia.ImageOnly - ) - ) - }.border(2.dp, MaterialTheme.colorScheme.outlineVariant, shape = RoundedCornerShape(50.dp)), - contentDescription = "Кофе", - error = painterResource(R.drawable.ic_broken_image), - placeholder = painterResource(R.drawable.loading_img), - contentScale = ContentScale.Crop, - model = ImageRequest.Builder(LocalContext.current) - .data(if (coffee.value.name != "") "https://zyzf.space/s/zXgFRTmbR4KMxMH/download?path=&files=coffee_image_" + coffee.value.uid +".png" else imageUri) - .crossfade(enable = true) - .build(), - ) - Row(modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.Center) { - if (add.value) { - Button(onClick = { - GlobalScope.launch (Dispatchers.Main) { - val newCoffee: Long = AppDatabase.getInstance(context).coffeeDao().insert( - name = name, cost = cost, ingredients = ingredients - ) - - val inputStream = context.contentResolver.openInputStream(imageUri as Uri) - val bitmap = BitmapFactory.decodeStream(inputStream) - - val f = File(context.cacheDir, "coffee_image_$newCoffee.png") - withContext(Dispatchers.IO) { - f.createNewFile() - val bos = ByteArrayOutputStream() - bitmap.compress(CompressFormat.PNG, 0 /*ignored for PNG*/, bos) - val bitmapdata = bos.toByteArray() - val fos = FileOutputStream(f) - fos.write(bitmapdata) - fos.flush() - fos.close() - } - - copyFileToSftp(f, "/mnt/nextcloud/data/Zyzf/files/Images") - - AppDatabase.getInstance(context).coffeeDao().getAll().collect { data -> - itemsList.clear() - itemsList.addAll(data) - } - openDialog.value = false - } - }, modifier = Modifier.padding(0.dp, 10.dp, 0.dp, 30.dp)) { - Text("Добавить") - } - } else { - Button(onClick = { - GlobalScope.launch (Dispatchers.Main) { - AppDatabase.getInstance(context).coffeeDao().update( - Coffee(name = name, cost = cost, ingredients = ingredients, cartId = coffee.value.cartId, count = coffee.value.count) - ) - AppDatabase.getInstance(context).coffeeDao().getAll().collect { data -> - itemsList.clear() - itemsList.addAll(data) - } - } - - }, modifier = Modifier.padding(0.dp, 10.dp, 0.dp, 30.dp)) { - Text("Изменить") - } - Spacer(modifier = Modifier.padding(all = 20.dp)) - Button(onClick = { - GlobalScope.launch (Dispatchers.Main) { - AppDatabase.getInstance(context).coffeeDao().delete( - coffee.value - ) - AppDatabase.getInstance(context).coffeeDao().getAll().collect { data -> - itemsList.clear() - itemsList.addAll(data) - } - openDialog.value = false - } - - }, modifier = Modifier.padding(0.dp, 10.dp, 0.dp, 30.dp)) { - Text("Удалить") - } - } - } - } - } - } -} - -const val REMOTE_HOST = "109.197.199.134" -const val USERNAME = "zyzf" -const val PASSWORD = "250303Zyzf-d-grad" -const val REMOTE_PORT = 2223 - -fun copyFileToSftp(srcFile: File, ftpPath: String): Boolean { - - var jschSession: Session? = null - - try { - val jsch = JSch() - jsch.setKnownHosts("/home/mkyong/.ssh/known_hosts") - jschSession = jsch.getSession(USERNAME, REMOTE_HOST, REMOTE_PORT) - jschSession.setPassword(PASSWORD) - - val config = Properties() - config["StrictHostKeyChecking"] = "no" - jschSession.setConfig(config) - - val policy = ThreadPolicy.Builder().permitAll().build() - StrictMode.setThreadPolicy(policy) - jschSession.connect(10000) - val sftp: Channel = jschSession.openChannel("sftp") - - sftp.connect(5000) - val channelSftp: ChannelSftp = sftp as ChannelSftp - - channelSftp.put(srcFile.absolutePath, ftpPath) - channelSftp.exit() - - } catch (e: JSchException) { - e.printStackTrace() - return false - } catch (e: SftpException) { - e.printStackTrace() - return false - } finally { - jschSession?.disconnect() - } - return true -} - -@Preview(name = "Light Mode", showBackground = true, uiMode = Configuration.UI_MODE_NIGHT_NO) -@Preview(name = "Dark Mode", showBackground = true, uiMode = Configuration.UI_MODE_NIGHT_YES) -@Composable -fun CoffeeListPreview() { - CoffeePreorderTheme { - Surface( - color = MaterialTheme.colorScheme.background - ) { - CoffeeList(navController = null) - } - } -} \ No newline at end of file diff --git a/app/src/main/java/com/zyzf/coffeepreorder/coffee/composeui/CoffeeView.kt b/app/src/main/java/com/zyzf/coffeepreorder/coffee/composeui/CoffeeView.kt deleted file mode 100644 index abec746..0000000 --- a/app/src/main/java/com/zyzf/coffeepreorder/coffee/composeui/CoffeeView.kt +++ /dev/null @@ -1,72 +0,0 @@ -package com.zyzf.coffeepreorder.coffee.composeui - -import android.content.res.Configuration -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.padding -import androidx.compose.material3.Button -import androidx.compose.material3.ExperimentalMaterial3Api -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.ModalBottomSheet -import androidx.compose.material3.OutlinedTextField -import androidx.compose.material3.Surface -import androidx.compose.material3.Text -import androidx.compose.runtime.Composable -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.res.stringResource -import androidx.compose.ui.tooling.preview.Preview -import androidx.compose.ui.unit.dp -import androidx.navigation.NavController -import com.zyzf.coffeepreorder.R -import com.zyzf.coffeepreorder.coffee.model.getCoffee -import com.zyzf.coffeepreorder.composeui.navigation.Screen -import com.zyzf.coffeepreorder.ui.theme.CoffeePreorderTheme - -@OptIn(ExperimentalMaterial3Api::class) -@Composable -fun CoffeeView(navController: NavController?, id: Int) { - val coffee = getCoffee()[id] - ModalBottomSheet(onDismissRequest = { navController?.navigate(Screen.CoffeeList.route) }) { - Column(modifier = Modifier.fillMaxWidth().padding(all = 10.dp), - horizontalAlignment = Alignment.CenterHorizontally) { - OutlinedTextField(modifier = Modifier.fillMaxWidth(), - value = coffee.name, onValueChange = {}, readOnly = true, - label = { - Text(stringResource(id = R.string.coffee_name)) - } - ) - OutlinedTextField(modifier = Modifier.fillMaxWidth(), - value = coffee.cost.toString(), onValueChange = {}, readOnly = true, - label = { - Text(stringResource(id = R.string.coffee_cost)) - } - ) - OutlinedTextField(modifier = Modifier.fillMaxWidth(), - value = coffee.ingredients, onValueChange = {}, readOnly = true, - label = { - Text(stringResource(id = R.string.coffee_ingredients)) - } - ) - Row(modifier = Modifier.fillMaxWidth()) { - Button(onClick = { navController?.navigate(Screen.CoffeeView.route) }) { - Text("Изменить") - } - } - } - } -} - -@Preview(name = "Light Mode", showBackground = true, uiMode = Configuration.UI_MODE_NIGHT_NO) -@Preview(name = "Dark Mode", showBackground = true, uiMode = Configuration.UI_MODE_NIGHT_YES) -@Composable -fun StudentViewPreview() { - CoffeePreorderTheme { - Surface( - color = MaterialTheme.colorScheme.background - ) { - CoffeeView(navController = null, id = 0) - } - } -} \ No newline at end of file diff --git a/app/src/main/java/com/zyzf/coffeepreorder/coffee/model/Coffee.kt b/app/src/main/java/com/zyzf/coffeepreorder/coffee/model/Coffee.kt deleted file mode 100644 index 76f258c..0000000 --- a/app/src/main/java/com/zyzf/coffeepreorder/coffee/model/Coffee.kt +++ /dev/null @@ -1,27 +0,0 @@ -package com.zyzf.coffeepreorder.coffee.model - -import com.zyzf.coffeepreorder.R -import java.io.Serializable - -data class Coffee( - val name: String, - val cost: Double, - val ingredients: String, - val image: Int -) : Serializable - -fun getCoffee(): List { - return listOf( - Coffee("Coffee1", 200.0, "Ing1", R.drawable.coffee_image), - Coffee("Coffee2", 300.0, "Ing2", R.drawable.coffee_image), - Coffee("Coffee3", 500.0, "Ing3", R.drawable.coffee_image), - Coffee("Coffee3", 500.0, "Ing3", R.drawable.coffee_image), - Coffee("Coffee3", 500.0, "Ing3", R.drawable.coffee_image), - Coffee("Coffee3", 500.0, "Ing3", R.drawable.coffee_image), - Coffee("Coffee3", 500.0, "Ing3", R.drawable.coffee_image), - Coffee("Coffee3", 500.0, "Ing3", R.drawable.coffee_image), - Coffee("Coffee3", 500.0, "Ing3", R.drawable.coffee_image), - Coffee("Coffee3", 500.0, "Ing3", R.drawable.coffee_image), - Coffee("Coffee3", 500.0, "Ing3", R.drawable.coffee_image) - ) -} diff --git a/app/src/main/java/com/zyzf/coffeepreorder/composeui/Login.kt b/app/src/main/java/com/zyzf/coffeepreorder/composeui/Login.kt deleted file mode 100644 index daf8f4f..0000000 --- a/app/src/main/java/com/zyzf/coffeepreorder/composeui/Login.kt +++ /dev/null @@ -1,152 +0,0 @@ -package com.zyzf.coffeepreorder.composeui - -import android.content.res.Configuration -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.Spacer -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.size -import androidx.compose.foundation.shape.CircleShape -import androidx.compose.foundation.text.KeyboardOptions -import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.filled.Add -import androidx.compose.material.icons.filled.Check -import androidx.compose.material3.Button -import androidx.compose.material3.ButtonDefaults -import androidx.compose.material3.Icon -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.OutlinedTextField -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.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.layout.ContentScale -import androidx.compose.ui.platform.LocalContext -import androidx.compose.ui.res.painterResource -import androidx.compose.ui.res.stringResource -import androidx.compose.ui.text.input.KeyboardType -import androidx.compose.ui.text.input.PasswordVisualTransformation -import androidx.compose.ui.tooling.preview.Preview -import androidx.compose.ui.unit.dp -import androidx.navigation.NavController -import coil.compose.AsyncImage -import coil.request.ImageRequest -import com.zyzf.coffeepreorder.R -import com.zyzf.coffeepreorder.composeui.navigation.Screen -import com.zyzf.coffeepreorder.database.AppDatabase -import com.zyzf.coffeepreorder.database.model.User -import com.zyzf.coffeepreorder.ui.theme.CoffeePreorderTheme -import kotlinx.coroutines.DelicateCoroutinesApi -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.GlobalScope -import kotlinx.coroutines.launch -import kotlinx.coroutines.withContext - -@OptIn(DelicateCoroutinesApi::class) -@Composable -fun Login(navController: NavController?) { - val context = LocalContext.current - var login by remember { mutableStateOf("") } - var password by remember { mutableStateOf("") } - LaunchedEffect(Unit) { - withContext(Dispatchers.IO) { - AppDatabase.getInstance(context).userDao().getAll() - } - } - Column(Modifier.padding(all = 10.dp), horizontalAlignment = Alignment.CenterHorizontally) { - AsyncImage( - model = ImageRequest.Builder(context = LocalContext.current).data("https://zyzf.space/s/YsHjPo3NDmoptSk/download/coffee_image.png") - .crossfade(true).build(), - error = painterResource(R.drawable.ic_broken_image), - placeholder = painterResource(R.drawable.loading_img), - contentDescription = "Кофе", - contentScale = ContentScale.Crop, - modifier = Modifier.size(150.dp) - ) - - Spacer(modifier = Modifier.padding(all = 20.dp)) - OutlinedTextField(modifier = Modifier.fillMaxWidth(), - value = login, onValueChange = {login = it}, - label = { - Text(stringResource(id = R.string.profile_login_label)) - }, - keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Text) - ) - OutlinedTextField(modifier = Modifier.fillMaxWidth(), - value = password, onValueChange = {password = it}, - label = { - Text(stringResource(id = R.string.profile_passw_label)) - }, - keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Password), - visualTransformation = PasswordVisualTransformation() - ) - Spacer(modifier = Modifier.padding(all = 20.dp)) - Button( - onClick = { - var user: User? - GlobalScope.launch (Dispatchers.Main) { - user = AppDatabase.getInstance(context).userDao().tryLogin(login, password) - if (user != null) { - AppDatabase.getInstance(context).userDao().logout() - AppDatabase.getInstance(context).userDao().setLogined(user!!.uid!!) - navController?.navigate(Screen.CoffeeList.route) - } else { - password = "" - login = "Неверный логин или пароль" - } - } - }, - shape = CircleShape, - modifier = Modifier.fillMaxWidth(fraction = 0.75f), - colors = ButtonDefaults.buttonColors( - containerColor = MaterialTheme.colorScheme.primaryContainer, - contentColor = MaterialTheme.colorScheme.primary - ) - ) { - Icon( - imageVector = Icons.Default.Check, - contentDescription = "Favorite", - modifier = Modifier.size(20.dp) - ) - Spacer(Modifier.size(ButtonDefaults.IconSpacing)) - Text(text = "Войти") - } - Button( - onClick = { navController?.navigate(Screen.Register.route) }, - shape = CircleShape, - modifier = Modifier.fillMaxWidth(fraction = 0.75f), - colors = ButtonDefaults.buttonColors( - containerColor = MaterialTheme.colorScheme.secondaryContainer, - contentColor = MaterialTheme.colorScheme.secondary - ) - ) { - // Inner content including an icon and a text label - Icon( - imageVector = Icons.Default.Add, - contentDescription = "Favorite", - modifier = Modifier.size(20.dp) - ) - Spacer(Modifier.size(ButtonDefaults.IconSpacing)) - Text(text = "Зарегистрироваться") - } - } -} - -@Preview(name = "Light Mode", showBackground = true, uiMode = Configuration.UI_MODE_NIGHT_NO) -@Preview(name = "Dark Mode", showBackground = true, uiMode = Configuration.UI_MODE_NIGHT_YES) -@Composable -fun LoginPreview() { - CoffeePreorderTheme { - Surface( - color = MaterialTheme.colorScheme.background - ) { - Login(null) - } - } -} \ No newline at end of file diff --git a/app/src/main/java/com/zyzf/coffeepreorder/database/AppContainer.kt b/app/src/main/java/com/zyzf/coffeepreorder/database/AppContainer.kt new file mode 100644 index 0000000..5e7cccc --- /dev/null +++ b/app/src/main/java/com/zyzf/coffeepreorder/database/AppContainer.kt @@ -0,0 +1,31 @@ +package com.zyzf.coffeepreorder.database + +import android.content.Context +import com.zyzf.coffeepreorder.database.repository.CartRepository +import com.zyzf.coffeepreorder.database.repository.CoffeeRepository +import com.zyzf.coffeepreorder.database.repository.OfflineCartRepository +import com.zyzf.coffeepreorder.database.repository.OfflineCoffeeRepository +import com.zyzf.coffeepreorder.database.repository.OfflineUserRepository +import com.zyzf.coffeepreorder.database.repository.UserRepository + +interface AppContainer { + val coffeeRepository: CoffeeRepository + val userRepository: UserRepository + val cartRepository: CartRepository +} + +class AppDataContainer(private val context: Context) : AppContainer { + override val coffeeRepository: CoffeeRepository by lazy { + OfflineCoffeeRepository(AppDatabase.getInstance(context).coffeeDao()) + } + override val userRepository: UserRepository by lazy { + OfflineUserRepository(AppDatabase.getInstance(context).userDao()) + } + override val cartRepository: CartRepository by lazy { + OfflineCartRepository(AppDatabase.getInstance(context).cartDao()) + } + + companion object { + const val TIMEOUT = 5000L + } +} \ No newline at end of file diff --git a/app/src/main/java/com/zyzf/coffeepreorder/database/AppDatabase.kt b/app/src/main/java/com/zyzf/coffeepreorder/database/AppDatabase.kt index 9ee6171..3331cec 100644 --- a/app/src/main/java/com/zyzf/coffeepreorder/database/AppDatabase.kt +++ b/app/src/main/java/com/zyzf/coffeepreorder/database/AppDatabase.kt @@ -32,7 +32,7 @@ abstract class AppDatabase : RoomDatabase() { INSTANCE?.let { database -> // Users val userDao = database.userDao() - val user1 = User("zyzf", "Ян К.", "+79911152503", "250303zyzf") + val user1 = User("zyzf", "Ян К.", "+79911152503", "250303zyzf", "admin") userDao.insert(user1) // Coffees val coffeeDao = database.coffeeDao() diff --git a/app/src/main/java/com/zyzf/coffeepreorder/database/dao/CoffeeDao.kt b/app/src/main/java/com/zyzf/coffeepreorder/database/dao/CoffeeDao.kt index 9003646..6b18fdd 100644 --- a/app/src/main/java/com/zyzf/coffeepreorder/database/dao/CoffeeDao.kt +++ b/app/src/main/java/com/zyzf/coffeepreorder/database/dao/CoffeeDao.kt @@ -3,7 +3,6 @@ package com.zyzf.coffeepreorder.database.dao import androidx.room.Dao import androidx.room.Delete import androidx.room.Query -import androidx.room.Update import com.zyzf.coffeepreorder.database.model.Coffee import com.zyzf.coffeepreorder.database.model.CoffeeWithCart import kotlinx.coroutines.flow.Flow @@ -20,13 +19,13 @@ interface CoffeeDao { fun getSumInCart(): Double @Query("select coffee.uid, name, cost, ingredients, cart_id, count, cart.uid as cart_uid from coffee left join cart on coffee.cart_id = cart.uid where coffee.uid = :uid") - suspend fun getByUid(uid: Int): CoffeeWithCart + suspend fun getByUid(uid: Int): CoffeeWithCart? @Query("insert into coffee (name, cost, ingredients, count) values (:name, :cost, :ingredients, 0)") suspend fun insert(name: String, cost: Double, ingredients: String): Long - @Update - suspend fun update(coffee: Coffee) + @Query("update coffee set name = :name, cost = :cost, ingredients = :ingredients, cart_id = :cartId, count = :count where uid = :uid") + suspend fun update(uid: Int, name: String, cost: Double, ingredients: String, cartId: Int?, count: Int): Int? @Delete suspend fun delete(coffee: Coffee) diff --git a/app/src/main/java/com/zyzf/coffeepreorder/database/dao/UserDao.kt b/app/src/main/java/com/zyzf/coffeepreorder/database/dao/UserDao.kt index 5f4853b..2661321 100644 --- a/app/src/main/java/com/zyzf/coffeepreorder/database/dao/UserDao.kt +++ b/app/src/main/java/com/zyzf/coffeepreorder/database/dao/UserDao.kt @@ -12,16 +12,17 @@ interface UserDao { fun getAll(): Flow> @Query("select * from user where login = :login and password = :password") - suspend fun tryLogin(login: String, password: String): User + suspend fun tryLogin(login: String, password: String): User? @Query("select * from user where uid = :uid") - suspend fun getByUid(uid: Int): User + suspend fun getByUid(uid: Int): User? - @Query("select user.uid, login, fio, phone, password from user join user_logined on user_logined.user_id = user.uid limit 1") - suspend fun getLogined(): User + @Query("select user.uid, login, fio, phone, password, role from user join user_logined on user_logined.user_id = user.uid limit 1") + suspend fun getLogined(): User? @Query("insert into user_logined (user_id) values (:userId)") suspend fun setLogined(userId: Int) + @Query("delete from user_logined") suspend fun logout() @@ -29,7 +30,7 @@ interface UserDao { suspend fun insert(user: User) @Query("update user set login = :login, fio = :fio, phone = :phone, password = :password where uid = :uid") - suspend fun update(uid: Int, login: String, fio: String, phone: String, password: String) : Int + suspend fun update(uid: Int, login: String, fio: String, phone: String, password: String) : Int? @Query("delete from user where uid = :uid") suspend fun delete(uid: Int) diff --git a/app/src/main/java/com/zyzf/coffeepreorder/database/model/Coffee.kt b/app/src/main/java/com/zyzf/coffeepreorder/database/model/Coffee.kt index 72b9029..d0516ed 100644 --- a/app/src/main/java/com/zyzf/coffeepreorder/database/model/Coffee.kt +++ b/app/src/main/java/com/zyzf/coffeepreorder/database/model/Coffee.kt @@ -38,12 +38,24 @@ data class Coffee( count: Int? ) : this(null, name, cost, ingredients, null, 0) + companion object { + fun getCoffee(index: Int = 0): Coffee { + return Coffee( + index, + "", + 0.0, + "", + null, + 0 + ) + } + } + override fun equals(other: Any?): Boolean { if (this === other) return true if (javaClass != other?.javaClass) return false other as Coffee - if (uid != other.uid) return false - return true + return uid == other.uid } override fun hashCode(): Int { diff --git a/app/src/main/java/com/zyzf/coffeepreorder/database/model/User.kt b/app/src/main/java/com/zyzf/coffeepreorder/database/model/User.kt index 0d57162..5903ce0 100644 --- a/app/src/main/java/com/zyzf/coffeepreorder/database/model/User.kt +++ b/app/src/main/java/com/zyzf/coffeepreorder/database/model/User.kt @@ -16,22 +16,24 @@ data class User( @ColumnInfo(name = "phone") var phone: String, @ColumnInfo(name = "password") - var password: String + var password: String, + @ColumnInfo(name = "role") + var role: String ) { @Ignore constructor( login: String, fio: String, phone: String, - password: String - ) : this(null, login, fio, phone, password) + password: String, + role: String + ) : this(null, login, fio, phone, password, role) override fun equals(other: Any?): Boolean { if (this === other) return true if (javaClass != other?.javaClass) return false other as User - if (uid != other.uid) return false - return true + return uid == other.uid } override fun hashCode(): Int { diff --git a/app/src/main/java/com/zyzf/coffeepreorder/database/repository/CartRepository.kt b/app/src/main/java/com/zyzf/coffeepreorder/database/repository/CartRepository.kt new file mode 100644 index 0000000..91e8a1c --- /dev/null +++ b/app/src/main/java/com/zyzf/coffeepreorder/database/repository/CartRepository.kt @@ -0,0 +1,12 @@ +package com.zyzf.coffeepreorder.database.repository + +import com.zyzf.coffeepreorder.database.model.Cart + +interface CartRepository { + suspend fun get(): Cart + suspend fun insert(cart: Cart) + suspend fun insertCoffee(cartId: Int, coffeeId: Int, count: Int) + suspend fun deleteCoffee(coffeeId: Int, count: Int) + suspend fun update(cart: Cart) + suspend fun deleteAll() +} \ No newline at end of file diff --git a/app/src/main/java/com/zyzf/coffeepreorder/database/repository/CoffeeRepository.kt b/app/src/main/java/com/zyzf/coffeepreorder/database/repository/CoffeeRepository.kt new file mode 100644 index 0000000..0d40a16 --- /dev/null +++ b/app/src/main/java/com/zyzf/coffeepreorder/database/repository/CoffeeRepository.kt @@ -0,0 +1,15 @@ +package com.zyzf.coffeepreorder.database.repository + +import com.zyzf.coffeepreorder.database.model.Coffee +import com.zyzf.coffeepreorder.database.model.CoffeeWithCart +import kotlinx.coroutines.flow.Flow + +interface CoffeeRepository { + fun getAll(): Flow> + fun getAllInCart(): Flow> + fun getSumInCart(): Double + suspend fun getByUid(uid: Int): CoffeeWithCart? + suspend fun insert(name: String, cost: Double, ingredients: String): Long + suspend fun update(uid: Int, name: String, cost: Double, ingredients: String, cartId: Int?, count: Int): Int? + suspend fun delete(coffee: Coffee) +} \ No newline at end of file diff --git a/app/src/main/java/com/zyzf/coffeepreorder/database/repository/OfflineCartRepository.kt b/app/src/main/java/com/zyzf/coffeepreorder/database/repository/OfflineCartRepository.kt new file mode 100644 index 0000000..394d81d --- /dev/null +++ b/app/src/main/java/com/zyzf/coffeepreorder/database/repository/OfflineCartRepository.kt @@ -0,0 +1,13 @@ +package com.zyzf.coffeepreorder.database.repository + +import com.zyzf.coffeepreorder.database.dao.CartDao +import com.zyzf.coffeepreorder.database.model.Cart + +class OfflineCartRepository(private val cartDao: CartDao) : CartRepository { + override suspend fun get(): Cart = cartDao.get() + override suspend fun insert(cart: Cart) = cartDao.insert(cart) + override suspend fun insertCoffee(cartId: Int, coffeeId: Int, count: Int) = cartDao.insertCoffee(cartId, coffeeId, count) + override suspend fun deleteCoffee(coffeeId: Int, count: Int) = cartDao.deleteCoffee(coffeeId, count) + override suspend fun update(cart: Cart) = cartDao.update(cart) + override suspend fun deleteAll() = cartDao.deleteAll() +} \ No newline at end of file diff --git a/app/src/main/java/com/zyzf/coffeepreorder/database/repository/OfflineCoffeeRepository.kt b/app/src/main/java/com/zyzf/coffeepreorder/database/repository/OfflineCoffeeRepository.kt new file mode 100644 index 0000000..9ca235c --- /dev/null +++ b/app/src/main/java/com/zyzf/coffeepreorder/database/repository/OfflineCoffeeRepository.kt @@ -0,0 +1,16 @@ +package com.zyzf.coffeepreorder.database.repository + +import com.zyzf.coffeepreorder.database.dao.CoffeeDao +import com.zyzf.coffeepreorder.database.model.Coffee +import com.zyzf.coffeepreorder.database.model.CoffeeWithCart +import kotlinx.coroutines.flow.Flow + +class OfflineCoffeeRepository(private val coffeeDao: CoffeeDao) : CoffeeRepository { + override fun getAll(): Flow> = coffeeDao.getAll() + override fun getAllInCart(): Flow> = coffeeDao.getAllInCart() + override fun getSumInCart(): Double = coffeeDao.getSumInCart() + override suspend fun getByUid(uid: Int): CoffeeWithCart? = coffeeDao.getByUid(uid) + override suspend fun insert(name: String, cost: Double, ingredients: String): Long = coffeeDao.insert(name, cost, ingredients) + override suspend fun update(uid: Int, name: String, cost: Double, ingredients: String, cartId: Int?, count: Int): Int? = coffeeDao.update(uid, name, cost, ingredients, cartId, count) + override suspend fun delete(coffee: Coffee) = coffeeDao.delete(coffee) +} \ No newline at end of file diff --git a/app/src/main/java/com/zyzf/coffeepreorder/database/repository/OfflineUserRepository.kt b/app/src/main/java/com/zyzf/coffeepreorder/database/repository/OfflineUserRepository.kt new file mode 100644 index 0000000..9031369 --- /dev/null +++ b/app/src/main/java/com/zyzf/coffeepreorder/database/repository/OfflineUserRepository.kt @@ -0,0 +1,17 @@ +package com.zyzf.coffeepreorder.database.repository + +import com.zyzf.coffeepreorder.database.dao.UserDao +import com.zyzf.coffeepreorder.database.model.User +import kotlinx.coroutines.flow.Flow + +class OfflineUserRepository(private val userDao: UserDao) : UserRepository { + override fun getAll(): Flow> = userDao.getAll() + override suspend fun tryLogin(login: String, password: String): User? = userDao.tryLogin(login, password) + override suspend fun getByUid(uid: Int): User? = userDao.getByUid(uid) + override suspend fun getLogined(): User? = userDao.getLogined() + override suspend fun setLogined(userId: Int) = userDao.setLogined(userId) + override suspend fun logout() = userDao.logout() + override suspend fun insert(user: User) = userDao.insert(user) + override suspend fun update(uid: Int, login: String, fio: String, phone: String, password: String) : Int? = userDao.update(uid, login, fio, phone, password) + override suspend fun delete(uid: Int) = userDao.delete(uid) +} \ No newline at end of file diff --git a/app/src/main/java/com/zyzf/coffeepreorder/database/repository/UserRepository.kt b/app/src/main/java/com/zyzf/coffeepreorder/database/repository/UserRepository.kt new file mode 100644 index 0000000..cfe8eec --- /dev/null +++ b/app/src/main/java/com/zyzf/coffeepreorder/database/repository/UserRepository.kt @@ -0,0 +1,16 @@ +package com.zyzf.coffeepreorder.database.repository + +import com.zyzf.coffeepreorder.database.model.User +import kotlinx.coroutines.flow.Flow + +interface UserRepository { + fun getAll(): Flow> + suspend fun tryLogin(login: String, password: String): User? + suspend fun getByUid(uid: Int): User? + suspend fun getLogined(): User? + suspend fun setLogined(userId: Int) + suspend fun logout() + suspend fun insert(user: User) + suspend fun update(uid: Int, login: String, fio: String, phone: String, password: String) : Int? + suspend fun delete(uid: Int) +} \ No newline at end of file diff --git a/app/src/main/java/com/zyzf/coffeepreorder/ui/AppViewModelProvider.kt b/app/src/main/java/com/zyzf/coffeepreorder/ui/AppViewModelProvider.kt new file mode 100644 index 0000000..68a9ee6 --- /dev/null +++ b/app/src/main/java/com/zyzf/coffeepreorder/ui/AppViewModelProvider.kt @@ -0,0 +1,23 @@ +package com.zyzf.coffeepreorder.ui + +import androidx.lifecycle.ViewModelProvider +import androidx.lifecycle.viewmodel.CreationExtras +import androidx.lifecycle.viewmodel.initializer +import androidx.lifecycle.viewmodel.viewModelFactory +import com.zyzf.coffeepreorder.CoffeeApplication +import com.zyzf.coffeepreorder.ui.coffee.CoffeeListViewModel +import com.zyzf.coffeepreorder.ui.login.LoginViewModel + +object AppViewModelProvider { + val Factory = viewModelFactory { + initializer { + CoffeeListViewModel(coffeeApplication().container.coffeeRepository, coffeeApplication().container.cartRepository) + } + initializer { + LoginViewModel(coffeeApplication().container.userRepository) + } + } +} + +fun CreationExtras.coffeeApplication(): CoffeeApplication = + (this[ViewModelProvider.AndroidViewModelFactory.APPLICATION_KEY] as CoffeeApplication) \ No newline at end of file diff --git a/app/src/main/java/com/zyzf/coffeepreorder/composeui/Cart.kt b/app/src/main/java/com/zyzf/coffeepreorder/ui/cart/Cart.kt similarity index 97% rename from app/src/main/java/com/zyzf/coffeepreorder/composeui/Cart.kt rename to app/src/main/java/com/zyzf/coffeepreorder/ui/cart/Cart.kt index f160344..d93f1d3 100644 --- a/app/src/main/java/com/zyzf/coffeepreorder/composeui/Cart.kt +++ b/app/src/main/java/com/zyzf/coffeepreorder/ui/cart/Cart.kt @@ -1,4 +1,4 @@ -package com.zyzf.coffeepreorder.composeui +package com.zyzf.coffeepreorder.ui.cart import android.content.res.Configuration import androidx.compose.foundation.layout.Arrangement @@ -21,7 +21,6 @@ import androidx.compose.material.icons.filled.Clear import androidx.compose.material3.AlertDialog import androidx.compose.material3.Button import androidx.compose.material3.ButtonDefaults -import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.ExtendedFloatingActionButton import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme @@ -48,9 +47,9 @@ import androidx.navigation.NavController import coil.compose.AsyncImage import coil.request.ImageRequest import com.zyzf.coffeepreorder.R -import com.zyzf.coffeepreorder.composeui.navigation.Screen import com.zyzf.coffeepreorder.database.AppDatabase import com.zyzf.coffeepreorder.database.model.Coffee +import com.zyzf.coffeepreorder.ui.navigation.Screen import com.zyzf.coffeepreorder.ui.theme.CoffeePreorderTheme import kotlinx.coroutines.DelicateCoroutinesApi import kotlinx.coroutines.Dispatchers @@ -58,7 +57,7 @@ import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.launch import kotlinx.coroutines.withContext -@OptIn(ExperimentalMaterial3Api::class, DelicateCoroutinesApi::class) +@OptIn(DelicateCoroutinesApi::class) @Composable fun Cart(navController: NavController?) { val openDialog = remember { mutableStateOf(false) } diff --git a/app/src/main/java/com/zyzf/coffeepreorder/ui/coffee/CoffeeList.kt b/app/src/main/java/com/zyzf/coffeepreorder/ui/coffee/CoffeeList.kt new file mode 100644 index 0000000..c41420c --- /dev/null +++ b/app/src/main/java/com/zyzf/coffeepreorder/ui/coffee/CoffeeList.kt @@ -0,0 +1,362 @@ +package com.zyzf.coffeepreorder.ui.coffee + +import android.content.Context +import android.content.res.Configuration +import android.net.Uri +import android.util.Log +import androidx.activity.compose.ManagedActivityResultLauncher +import androidx.activity.compose.rememberLauncherForActivityResult +import androidx.activity.result.PickVisualMediaRequest +import androidx.activity.result.contract.ActivityResultContracts +import androidx.compose.foundation.border +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.Spacer +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.heightIn +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.items +import androidx.compose.foundation.shape.CircleShape +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.foundation.text.KeyboardOptions +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Add +import androidx.compose.material.icons.outlined.Create +import androidx.compose.material3.Button +import androidx.compose.material3.ButtonDefaults +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.FloatingActionButton +import androidx.compose.material3.Icon +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.ModalBottomSheet +import androidx.compose.material3.OutlinedIconButton +import androidx.compose.material3.OutlinedTextField +import androidx.compose.material3.Scaffold +import androidx.compose.material3.SheetState +import androidx.compose.material3.Surface +import androidx.compose.material3.Text +import androidx.compose.material3.rememberModalBottomSheetState +import androidx.compose.runtime.Composable +import androidx.compose.runtime.MutableState +import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableDoubleStateOf +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.layout.ContentScale +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.input.KeyboardType +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import androidx.lifecycle.viewmodel.compose.viewModel +import coil.compose.AsyncImage +import coil.request.ImageRequest +import com.zyzf.coffeepreorder.R +import com.zyzf.coffeepreorder.database.model.Coffee +import com.zyzf.coffeepreorder.ui.AppViewModelProvider +import com.zyzf.coffeepreorder.ui.theme.CoffeePreorderTheme +import kotlinx.coroutines.launch + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun CoffeeList( + viewModel: CoffeeListViewModel = viewModel(factory = AppViewModelProvider.Factory) +) { + val coroutineScope = rememberCoroutineScope() + val coffeeListUiState = viewModel.coffeeListUiState.collectAsState() + val sheetState = rememberModalBottomSheetState() + val openDialog = remember { mutableStateOf(false) } + val photoPicker = rememberLauncherForActivityResult( + contract = ActivityResultContracts.PickVisualMedia() + ) { + if (it != null) { + Log.d("PhotoPicker", "Selected URI: $it") + viewModel.imageUri = it + } else { + Log.d("PhotoPicker", "No media selected") + } + } + Scaffold( + topBar = {}, + floatingActionButton = { + FloatingActionButton( + onClick = { + coroutineScope.launch { + viewModel.cleanCurrentCoffee() + } + }, + Modifier + .padding(all = 20.dp) + ) { + Icon( + imageVector = Icons.Filled.Add, + contentDescription = "Add", + modifier = Modifier.size(20.dp) + ) + } + } + ) { innerPadding -> + coffeeListUiState?.value?.coffeeList?.let { + CoffeeList( + modifier = Modifier + .padding(innerPadding) + .fillMaxSize(), + coffeeList = it, + onAddToCartClick = { coffeeUid: Int -> + coroutineScope.launch { + viewModel.addCoffeeToCart(coffeeUid = coffeeUid) + } + }, + onEditClick = { coffee: Coffee -> + coroutineScope.launch { + viewModel.editCoffee(coffee = coffee) + } + } + ) + } + AddEditModalBottomSheet( + coffee = viewModel.currentCoffee, + sheetState = sheetState, + openDialog = openDialog, + onAddClick = { coffee: Coffee, context: Context -> + coroutineScope.launch { + viewModel.createCoffee(coffee, context) + } + }, + onEditClick = { coffee: Coffee, context: Context -> + coroutineScope.launch { + viewModel.editCoffee(coffee, context) + } + }, + onDeleteClick = { coffee: Coffee -> + coroutineScope.launch { + viewModel.deleteCoffee(coffee) + } + }, + photoPicker = photoPicker, + imageUri = viewModel.imageUri + ) + } +} + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +private fun AddEditModalBottomSheet( + coffee: Coffee, + sheetState: SheetState, + openDialog: MutableState, + onAddClick: (coffee: Coffee, context: Context) -> Unit, + onEditClick: (coffee: Coffee, context: Context) -> Unit, + onDeleteClick: (coffee: Coffee) -> Unit, + photoPicker: ManagedActivityResultLauncher, + imageUri: Any?, + modifier: Modifier = Modifier +) { + var name by remember { mutableStateOf(coffee.name) } + var cost by remember { mutableDoubleStateOf(coffee.cost) } + var ingredients by remember { mutableStateOf(coffee.ingredients) } + val context = LocalContext.current + if (openDialog.value) { + ModalBottomSheet( + onDismissRequest = { openDialog.value = false }, + sheetState = sheetState + ) { + Column(modifier = Modifier + .fillMaxWidth() + .padding(all = 10.dp), + horizontalAlignment = Alignment.CenterHorizontally) { + OutlinedTextField(modifier = Modifier.fillMaxWidth(), + value = name, onValueChange = {name = it}, + label = { + Text(stringResource(id = R.string.coffee_name)) + }, + keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Text) + ) + OutlinedTextField(modifier = Modifier.fillMaxWidth(), + value = cost.toString(), onValueChange = {cost = it.toDouble()}, + label = { + Text(stringResource(id = R.string.coffee_cost)) + }, + keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Decimal) + ) + OutlinedTextField(modifier = Modifier + .fillMaxWidth() + .padding(bottom = 10.dp), + value = ingredients, onValueChange = {ingredients = it}, + label = { + Text(stringResource(id = R.string.coffee_ingredients)) + }, + keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Text) + ) + AsyncImage( + modifier = Modifier + .size(100.dp) + .clip(RoundedCornerShape(50.dp)) + .border( + 2.dp, + MaterialTheme.colorScheme.outline, + shape = RoundedCornerShape(50.dp) + ) + .clickable { + photoPicker.launch( + PickVisualMediaRequest( + ActivityResultContracts.PickVisualMedia.ImageOnly + ) + ) + } + .border( + 2.dp, + MaterialTheme.colorScheme.outlineVariant, + shape = RoundedCornerShape(50.dp) + ), + contentDescription = "Кофе", + error = painterResource(R.drawable.ic_broken_image), + placeholder = painterResource(R.drawable.loading_img), + contentScale = ContentScale.Crop, + model = ImageRequest.Builder(LocalContext.current) + .data(if (coffee.name != "") "https://zyzf.space/s/zXgFRTmbR4KMxMH/download?path=&files=coffee_image_" + coffee.uid +".png" else imageUri) + .crossfade(enable = true) + .build(), + ) + Row(modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.Center) { + if (coffee.uid == 0) { + Button(onClick = { + onAddClick(coffee, context) + openDialog.value = false + }, modifier = Modifier.padding(0.dp, 10.dp, 0.dp, 30.dp)) { + Text("Добавить") + } + } else { + Button(onClick = { + onEditClick(coffee, context) + openDialog.value = false + }, modifier = Modifier.padding(0.dp, 10.dp, 0.dp, 30.dp)) { + Text("Изменить") + } + Spacer(modifier = Modifier.padding(all = 20.dp)) + Button(onClick = { + onDeleteClick(coffee) + openDialog.value = false + }, modifier = Modifier.padding(0.dp, 10.dp, 0.dp, 30.dp)) { + Text("Удалить") + } + } + } + } + } + } +} + +@Composable +private fun CoffeeList( + modifier: Modifier = Modifier, + coffeeList: List, + onAddToCartClick: (coffeeUid: Int) -> Unit, + onEditClick: (coffee: Coffee) -> Unit +) { + LazyColumn( + horizontalAlignment = Alignment.CenterHorizontally) { + items(items = coffeeList, key = { it.uid!! }) { coffee -> + CoffeeListItem(coffee = coffee, onAddToCartClick = onAddToCartClick, onEditClick = onEditClick) + } + } +} + +@Composable +private fun CoffeeListItem( + coffee: Coffee, + modifier: Modifier = Modifier, + onAddToCartClick: (coffeeUid: Int) -> Unit, + onEditClick: (coffee: Coffee) -> Unit +) { + Row(modifier = Modifier + .fillMaxWidth() + .heightIn(max = 140.dp) + .padding(bottom = 10.dp, top = 10.dp), + horizontalArrangement = Arrangement.SpaceAround) { + + AsyncImage( + model = ImageRequest.Builder(context = LocalContext.current).data("https://zyzf.space/s/zXgFRTmbR4KMxMH/download?path=&files=coffee_image_" + coffee.uid +".png") + .crossfade(true).build(), + error = painterResource(R.drawable.ic_broken_image), + placeholder = painterResource(R.drawable.loading_img), + contentDescription = "Кофе", + contentScale = ContentScale.Crop, + modifier = Modifier + .size(100.dp) + .clip(RoundedCornerShape(50.dp)) + ) + + Column( + Modifier + .weight(2f) + .padding(start = 20.dp)) { + Text(text = coffee.name, fontSize = 25.sp) + Text(text = String.format("%.2f", coffee.cost), fontSize = 20.sp) + Text(text = coffee.ingredients, fontSize = 15.sp) + Row( + Modifier + .fillMaxWidth() + .padding(top = 5.dp)) { + Button( + onClick = { + coffee.uid?.let { onAddToCartClick(it) } + }, + shape = CircleShape, + modifier = Modifier.fillMaxWidth(fraction = 0.75f) + ) { + Icon( + imageVector = Icons.Default.Add, + contentDescription = "Favorite", + modifier = Modifier.size(20.dp) + ) + Spacer(Modifier.size(ButtonDefaults.IconSpacing)) + Text(text = "В корзину") + } + OutlinedIconButton( + onClick = { + onEditClick(coffee) + }, + Modifier + .padding(start = 10.dp) + .clip(CircleShape)) { + Icon( + imageVector = Icons.Outlined.Create, + contentDescription = "Favorite", + modifier = Modifier.size(20.dp) + ) + } + } + } + } +} + +@Preview(name = "Light Mode", showBackground = true, uiMode = Configuration.UI_MODE_NIGHT_NO) +@Preview(name = "Dark Mode", showBackground = true, uiMode = Configuration.UI_MODE_NIGHT_YES) +@Composable +fun CoffeeListPreview() { + CoffeePreorderTheme { + Surface( + color = MaterialTheme.colorScheme.background + ) { + CoffeeList( + coffeeList = (1..20).map { i -> Coffee.getCoffee(i) }, + onAddToCartClick = {}, + onEditClick = {} + ) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/zyzf/coffeepreorder/ui/coffee/CoffeeListViewModel.kt b/app/src/main/java/com/zyzf/coffeepreorder/ui/coffee/CoffeeListViewModel.kt new file mode 100644 index 0000000..ac880b5 --- /dev/null +++ b/app/src/main/java/com/zyzf/coffeepreorder/ui/coffee/CoffeeListViewModel.kt @@ -0,0 +1,141 @@ +package com.zyzf.coffeepreorder.ui.coffee + +import android.content.Context +import android.graphics.Bitmap +import android.graphics.BitmapFactory +import android.net.Uri +import android.os.StrictMode +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import com.jcraft.jsch.Channel +import com.jcraft.jsch.ChannelSftp +import com.jcraft.jsch.JSch +import com.jcraft.jsch.JSchException +import com.jcraft.jsch.Session +import com.jcraft.jsch.SftpException +import com.zyzf.coffeepreorder.R +import com.zyzf.coffeepreorder.database.AppDataContainer +import com.zyzf.coffeepreorder.database.model.Cart +import com.zyzf.coffeepreorder.database.model.Coffee +import com.zyzf.coffeepreorder.database.repository.CartRepository +import com.zyzf.coffeepreorder.database.repository.CoffeeRepository +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.flow.SharingStarted +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.stateIn +import kotlinx.coroutines.withContext +import java.io.ByteArrayOutputStream +import java.io.File +import java.io.FileOutputStream +import java.util.Properties + +class CoffeeListViewModel( + private val coffeeRepository: CoffeeRepository, + private val cartRepository: CartRepository +) : ViewModel() { + val coffeeListUiState: StateFlow = coffeeRepository.getAll().map { + CoffeeListUiState(it) + }.stateIn( + scope = viewModelScope, + started = SharingStarted.WhileSubscribed(stopTimeoutMillis = AppDataContainer.TIMEOUT), + initialValue = CoffeeListUiState() + ) + var currentCoffee: Coffee = Coffee.getCoffee() + var imageUri: Any? = R.drawable.img + + suspend fun addCoffeeToCart(coffeeUid: Int) { + val cart: Cart = cartRepository.get() + cart.uid?.let { cartRepository.insertCoffee(it, coffeeUid, 1) } + } + fun cleanCurrentCoffee() { + currentCoffee = Coffee.getCoffee() + } + fun editCoffee(coffee: Coffee) { + currentCoffee = coffee + } + suspend fun createCoffee(coffee: Coffee, context: Context) { + val newCoffee: Long = coffeeRepository.insert(coffee.name, coffee.cost, coffee.ingredients) + val inputStream = context.contentResolver.openInputStream(imageUri as Uri) + val bitmap = BitmapFactory.decodeStream(inputStream) + + val f = File(context.cacheDir, "coffee_image_$newCoffee.png") + withContext(Dispatchers.IO) { + f.createNewFile() + val bos = ByteArrayOutputStream() + bitmap.compress(Bitmap.CompressFormat.PNG, 0 /*ignored for PNG*/, bos) + val bitmapdata = bos.toByteArray() + val fos = FileOutputStream(f) + fos.write(bitmapdata) + fos.flush() + fos.close() + } + + copyFileToSftp(f, "/mnt/nextcloud/data/Zyzf/files/Images") + } + suspend fun editCoffee(coffee: Coffee, context: Context) { + val editedCoffee: Int = coffeeRepository.update(coffee.uid!!, coffee.name, coffee.cost, coffee.ingredients, coffee.cartId, coffee.count)!! + val inputStream = context.contentResolver.openInputStream(imageUri as Uri) + val bitmap = BitmapFactory.decodeStream(inputStream) + + val f = File(context.cacheDir, "coffee_image_$editedCoffee.png") + withContext(Dispatchers.IO) { + f.createNewFile() + val bos = ByteArrayOutputStream() + bitmap.compress(Bitmap.CompressFormat.PNG, 0 /*ignored for PNG*/, bos) + val bitmapdata = bos.toByteArray() + val fos = FileOutputStream(f) + fos.write(bitmapdata) + fos.flush() + fos.close() + } + copyFileToSftp(f, "/mnt/nextcloud/data/Zyzf/files/Images") + } + suspend fun deleteCoffee(coffee: Coffee) { + coffeeRepository.delete(coffee) + } +} + +data class CoffeeListUiState(val coffeeList: List = listOf()) + +const val REMOTE_HOST = "109.197.199.134" +const val USERNAME = "zyzf" +const val PASSWORD = "250303Zyzf-d-grad" +const val REMOTE_PORT = 2223 + +fun copyFileToSftp(srcFile: File, ftpPath: String): Boolean { + + var jschSession: Session? = null + + try { + val jsch = JSch() + jsch.setKnownHosts("/home/zyzf/.ssh/known_hosts") + jschSession = jsch.getSession(USERNAME, REMOTE_HOST, REMOTE_PORT) + jschSession.setPassword(PASSWORD) + + val config = Properties() + config["StrictHostKeyChecking"] = "no" + jschSession.setConfig(config) + + val policy = StrictMode.ThreadPolicy.Builder().permitAll().build() + StrictMode.setThreadPolicy(policy) + jschSession.connect(10000) + val sftp: Channel = jschSession.openChannel("sftp") + + sftp.connect(5000) + val channelSftp: ChannelSftp = sftp as ChannelSftp + + channelSftp.put(srcFile.absolutePath, ftpPath) + channelSftp.exit() + + } catch (e: JSchException) { + e.printStackTrace() + return false + } catch (e: SftpException) { + e.printStackTrace() + return false + } finally { + jschSession?.disconnect() + } + return true +} \ No newline at end of file diff --git a/app/src/main/java/com/zyzf/coffeepreorder/ui/login/Login.kt b/app/src/main/java/com/zyzf/coffeepreorder/ui/login/Login.kt new file mode 100644 index 0000000..d2ea14d --- /dev/null +++ b/app/src/main/java/com/zyzf/coffeepreorder/ui/login/Login.kt @@ -0,0 +1,153 @@ +package com.zyzf.coffeepreorder.ui.login + +import android.annotation.SuppressLint +import android.content.res.Configuration +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.shape.CircleShape +import androidx.compose.foundation.text.KeyboardOptions +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Add +import androidx.compose.material.icons.filled.Check +import androidx.compose.material3.Button +import androidx.compose.material3.ButtonDefaults +import androidx.compose.material3.Icon +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.OutlinedTextField +import androidx.compose.material3.Scaffold +import androidx.compose.material3.Surface +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.layout.ContentScale +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.input.KeyboardType +import androidx.compose.ui.text.input.PasswordVisualTransformation +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import androidx.lifecycle.viewmodel.compose.viewModel +import androidx.navigation.NavController +import coil.compose.AsyncImage +import coil.request.ImageRequest +import com.zyzf.coffeepreorder.R +import com.zyzf.coffeepreorder.ui.AppViewModelProvider +import com.zyzf.coffeepreorder.ui.navigation.Screen +import com.zyzf.coffeepreorder.ui.theme.CoffeePreorderTheme +import kotlinx.coroutines.launch + +@SuppressLint("UnusedMaterial3ScaffoldPaddingParameter") +@Composable +fun Login( + navController: NavController?, + viewModel: LoginViewModel = viewModel(factory = AppViewModelProvider.Factory) +) { + val coroutineScope = rememberCoroutineScope() + val userListUiState = viewModel.userListUiState.collectAsState() + var login by remember { mutableStateOf("") } + var password by remember { mutableStateOf("") } + Scaffold( + topBar = {}, + floatingActionButton = {} + ) { + Column(Modifier.padding(all = 10.dp), horizontalAlignment = Alignment.CenterHorizontally) { + AsyncImage( + model = ImageRequest.Builder(context = LocalContext.current) + .data("https://zyzf.space/s/YsHjPo3NDmoptSk/download/coffee_image.png") + .crossfade(true).build(), + error = painterResource(R.drawable.ic_broken_image), + placeholder = painterResource(R.drawable.loading_img), + contentDescription = "Кофе", + contentScale = ContentScale.Crop, + modifier = Modifier.size(150.dp) + ) + + Spacer(modifier = Modifier.padding(all = 20.dp)) + OutlinedTextField( + modifier = Modifier.fillMaxWidth(), + value = login, onValueChange = { login = it }, + label = { + Text(stringResource(id = R.string.profile_login_label)) + }, + keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Text) + ) + OutlinedTextField( + modifier = Modifier.fillMaxWidth(), + value = password, onValueChange = { password = it }, + label = { + Text(stringResource(id = R.string.profile_passw_label)) + }, + keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Password), + visualTransformation = PasswordVisualTransformation() + ) + Spacer(modifier = Modifier.padding(all = 20.dp)) + Button( + onClick = { + coroutineScope.launch { + if (viewModel.tryLogin(login, password)) { + navController?.navigate(Screen.CoffeeList.route) + } else { + password = "" + login = "Неверный логин или пароль" + } + } + }, + shape = CircleShape, + modifier = Modifier.fillMaxWidth(fraction = 0.75f), + colors = ButtonDefaults.buttonColors( + containerColor = MaterialTheme.colorScheme.primaryContainer, + contentColor = MaterialTheme.colorScheme.primary + ) + ) { + Icon( + imageVector = Icons.Default.Check, + contentDescription = "Favorite", + modifier = Modifier.size(20.dp) + ) + Spacer(Modifier.size(ButtonDefaults.IconSpacing)) + Text(text = "Войти") + } + Button( + onClick = { navController?.navigate(Screen.Register.route) }, + shape = CircleShape, + modifier = Modifier.fillMaxWidth(fraction = 0.75f), + colors = ButtonDefaults.buttonColors( + containerColor = MaterialTheme.colorScheme.secondaryContainer, + contentColor = MaterialTheme.colorScheme.secondary + ) + ) { + Icon( + imageVector = Icons.Default.Add, + contentDescription = "Favorite", + modifier = Modifier.size(20.dp) + ) + Spacer(Modifier.size(ButtonDefaults.IconSpacing)) + Text(text = "Зарегистрироваться") + } + } + } +} + +@Preview(name = "Light Mode", showBackground = true, uiMode = Configuration.UI_MODE_NIGHT_NO) +@Preview(name = "Dark Mode", showBackground = true, uiMode = Configuration.UI_MODE_NIGHT_YES) +@Composable +fun LoginPreview() { + CoffeePreorderTheme { + Surface( + color = MaterialTheme.colorScheme.background + ) { + Login(null) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/zyzf/coffeepreorder/ui/login/LoginViewModel.kt b/app/src/main/java/com/zyzf/coffeepreorder/ui/login/LoginViewModel.kt new file mode 100644 index 0000000..aeabaa1 --- /dev/null +++ b/app/src/main/java/com/zyzf/coffeepreorder/ui/login/LoginViewModel.kt @@ -0,0 +1,36 @@ +package com.zyzf.coffeepreorder.ui.login + +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import com.zyzf.coffeepreorder.database.AppDataContainer +import com.zyzf.coffeepreorder.database.model.User +import com.zyzf.coffeepreorder.database.repository.UserRepository +import kotlinx.coroutines.flow.SharingStarted +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.stateIn + +class LoginViewModel( + private val userRepository: UserRepository +) : ViewModel() { + val userListUiState: StateFlow = userRepository.getAll().map { + UserListUiState(it) + }.stateIn( + scope = viewModelScope, + started = SharingStarted.WhileSubscribed(stopTimeoutMillis = AppDataContainer.TIMEOUT), + initialValue = UserListUiState() + ) + var user: User? = null + suspend fun tryLogin(login: String, password: String): Boolean { + user = userRepository.tryLogin(login, password) + return if (user != null) { + userRepository.logout() + userRepository.setLogined(user!!.uid!!) + true + } else { + false + } + } +} + +data class UserListUiState(val userList: List = listOf()) \ No newline at end of file diff --git a/app/src/main/java/com/zyzf/coffeepreorder/composeui/navigation/MainNavbar.kt b/app/src/main/java/com/zyzf/coffeepreorder/ui/navigation/MainNavbar.kt similarity index 83% rename from app/src/main/java/com/zyzf/coffeepreorder/composeui/navigation/MainNavbar.kt rename to app/src/main/java/com/zyzf/coffeepreorder/ui/navigation/MainNavbar.kt index 44f8b5c..8143c12 100644 --- a/app/src/main/java/com/zyzf/coffeepreorder/composeui/navigation/MainNavbar.kt +++ b/app/src/main/java/com/zyzf/coffeepreorder/ui/navigation/MainNavbar.kt @@ -1,10 +1,10 @@ -package com.zyzf.coffeepreorder.composeui.navigation +package com.zyzf.coffeepreorder.ui.navigation import android.content.res.Configuration import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.padding import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.filled.ArrowBack +import androidx.compose.material.icons.automirrored.filled.ArrowBack import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.Icon import androidx.compose.material3.IconButton @@ -25,20 +25,17 @@ import androidx.navigation.NavDestination import androidx.navigation.NavDestination.Companion.hierarchy import androidx.navigation.NavGraph.Companion.findStartDestination import androidx.navigation.NavHostController -import androidx.navigation.NavType import androidx.navigation.compose.NavHost import androidx.navigation.compose.composable import androidx.navigation.compose.currentBackStackEntryAsState import androidx.navigation.compose.rememberNavController -import androidx.navigation.navArgument import com.zyzf.coffeepreorder.R -import com.zyzf.coffeepreorder.coffee.composeui.CoffeeList -import com.zyzf.coffeepreorder.coffee.composeui.CoffeeView -import com.zyzf.coffeepreorder.composeui.Cart -import com.zyzf.coffeepreorder.composeui.Login -import com.zyzf.coffeepreorder.composeui.Order -import com.zyzf.coffeepreorder.composeui.Profile -import com.zyzf.coffeepreorder.composeui.Register +import com.zyzf.coffeepreorder.ui.cart.Cart +import com.zyzf.coffeepreorder.ui.coffee.CoffeeList +import com.zyzf.coffeepreorder.ui.login.Login +import com.zyzf.coffeepreorder.ui.order.Order +import com.zyzf.coffeepreorder.ui.profile.Profile +import com.zyzf.coffeepreorder.ui.register.Register import com.zyzf.coffeepreorder.ui.theme.CoffeePreorderTheme @OptIn(ExperimentalMaterial3Api::class) @@ -62,7 +59,7 @@ fun Topbar( ) { IconButton(onClick = { navController.navigateUp() }) { Icon( - imageVector = Icons.Filled.ArrowBack, + imageVector = Icons.AutoMirrored.Filled.ArrowBack, contentDescription = null, tint = MaterialTheme.colorScheme.onPrimary ) @@ -111,16 +108,10 @@ fun Navhost( ) { composable(Screen.Login.route) { Login(navController) } composable(Screen.Register.route) { Register(navController) } - composable(Screen.CoffeeList.route) { CoffeeList(navController) } + composable(Screen.CoffeeList.route) { CoffeeList() } composable(Screen.Profile.route) { Profile(navController) } composable(Screen.Cart.route) { Cart(navController) } composable(Screen.Order.route) { Order(navController) } - composable( - Screen.CoffeeView.route, - arguments = listOf(navArgument("id") { type = NavType.IntType }) - ) { backStackEntry -> - backStackEntry.arguments?.let { CoffeeView(navController, it.getInt("id")) } - } } } diff --git a/app/src/main/java/com/zyzf/coffeepreorder/composeui/navigation/Screen.kt b/app/src/main/java/com/zyzf/coffeepreorder/ui/navigation/Screen.kt similarity index 91% rename from app/src/main/java/com/zyzf/coffeepreorder/composeui/navigation/Screen.kt rename to app/src/main/java/com/zyzf/coffeepreorder/ui/navigation/Screen.kt index 38523b9..40330e6 100644 --- a/app/src/main/java/com/zyzf/coffeepreorder/composeui/navigation/Screen.kt +++ b/app/src/main/java/com/zyzf/coffeepreorder/ui/navigation/Screen.kt @@ -1,4 +1,4 @@ -package com.zyzf.coffeepreorder.composeui.navigation +package com.zyzf.coffeepreorder.ui.navigation import androidx.annotation.StringRes import androidx.compose.material.icons.Icons @@ -46,7 +46,7 @@ enum class Screen( fun getItem(route: String): Screen? { val findRoute = route.split("/").first() - return values().find { value -> value.route.startsWith(findRoute) } + return entries.find { value -> value.route.startsWith(findRoute) } } } } \ No newline at end of file diff --git a/app/src/main/java/com/zyzf/coffeepreorder/composeui/Order.kt b/app/src/main/java/com/zyzf/coffeepreorder/ui/order/Order.kt similarity index 98% rename from app/src/main/java/com/zyzf/coffeepreorder/composeui/Order.kt rename to app/src/main/java/com/zyzf/coffeepreorder/ui/order/Order.kt index 7475cc6..e4493e5 100644 --- a/app/src/main/java/com/zyzf/coffeepreorder/composeui/Order.kt +++ b/app/src/main/java/com/zyzf/coffeepreorder/ui/order/Order.kt @@ -1,4 +1,4 @@ -package com.zyzf.coffeepreorder.composeui +package com.zyzf.coffeepreorder.ui.order import android.content.res.Configuration import androidx.compose.foundation.layout.Arrangement @@ -41,8 +41,8 @@ import androidx.compose.ui.unit.dp import androidx.compose.ui.window.Dialog import androidx.compose.ui.window.DialogProperties import androidx.navigation.NavController -import com.zyzf.coffeepreorder.composeui.navigation.Screen import com.zyzf.coffeepreorder.database.AppDatabase +import com.zyzf.coffeepreorder.ui.navigation.Screen import com.zyzf.coffeepreorder.ui.theme.CoffeePreorderTheme import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext diff --git a/app/src/main/java/com/zyzf/coffeepreorder/composeui/Profile.kt b/app/src/main/java/com/zyzf/coffeepreorder/ui/profile/Profile.kt similarity index 98% rename from app/src/main/java/com/zyzf/coffeepreorder/composeui/Profile.kt rename to app/src/main/java/com/zyzf/coffeepreorder/ui/profile/Profile.kt index 5c877b0..f801ea1 100644 --- a/app/src/main/java/com/zyzf/coffeepreorder/composeui/Profile.kt +++ b/app/src/main/java/com/zyzf/coffeepreorder/ui/profile/Profile.kt @@ -1,4 +1,4 @@ -package com.zyzf.coffeepreorder.composeui +package com.zyzf.coffeepreorder.ui.profile import android.content.res.Configuration import androidx.compose.foundation.BorderStroke @@ -41,9 +41,9 @@ import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import androidx.navigation.NavController import com.zyzf.coffeepreorder.R -import com.zyzf.coffeepreorder.composeui.navigation.Screen import com.zyzf.coffeepreorder.database.AppDatabase import com.zyzf.coffeepreorder.database.model.User +import com.zyzf.coffeepreorder.ui.navigation.Screen import com.zyzf.coffeepreorder.ui.theme.CoffeePreorderTheme import kotlinx.coroutines.DelicateCoroutinesApi import kotlinx.coroutines.Dispatchers @@ -58,7 +58,7 @@ fun Profile(navController: NavController?) { val openDialogExit = remember { mutableStateOf(false) } val openDialogDelete = remember { mutableStateOf(false) } val context = LocalContext.current - var user: User by remember { mutableStateOf(User("", "", "", "")) } + var user: User by remember { mutableStateOf(User("", "", "", "", "")) } var userLogin by remember { mutableStateOf("") } var userFIO by remember { mutableStateOf("") } var userPhone by remember { mutableStateOf("") } @@ -67,7 +67,7 @@ fun Profile(navController: NavController?) { val userNewPsswdConf = remember { mutableStateOf("") } LaunchedEffect(Unit) { withContext(Dispatchers.IO) { - user = AppDatabase.getInstance(context).userDao().getLogined() + user = AppDatabase.getInstance(context).userDao().getLogined()!! userLogin = user.login userFIO = user.fio userPhone = user.phone @@ -195,7 +195,7 @@ fun Profile(navController: NavController?) { GlobalScope.launch (Dispatchers.Main) { if (userOldPsswd.value == user.password && userNewPsswd.value == userNewPsswdConf.value) { AppDatabase.getInstance(context).userDao().update(user.uid!!, userLogin, userFIO, userPhone, userNewPsswd.value) - user = AppDatabase.getInstance(context).userDao().getLogined() + user = AppDatabase.getInstance(context).userDao().getLogined()!! } } openDialogEdit.value = false diff --git a/app/src/main/java/com/zyzf/coffeepreorder/composeui/Register.kt b/app/src/main/java/com/zyzf/coffeepreorder/ui/register/Register.kt similarity index 97% rename from app/src/main/java/com/zyzf/coffeepreorder/composeui/Register.kt rename to app/src/main/java/com/zyzf/coffeepreorder/ui/register/Register.kt index e6b2b4e..2adb778 100644 --- a/app/src/main/java/com/zyzf/coffeepreorder/composeui/Register.kt +++ b/app/src/main/java/com/zyzf/coffeepreorder/ui/register/Register.kt @@ -1,4 +1,4 @@ -package com.zyzf.coffeepreorder.composeui +package com.zyzf.coffeepreorder.ui.register import android.content.res.Configuration @@ -37,9 +37,9 @@ import androidx.navigation.NavController import coil.compose.AsyncImage import coil.request.ImageRequest import com.zyzf.coffeepreorder.R -import com.zyzf.coffeepreorder.composeui.navigation.Screen import com.zyzf.coffeepreorder.database.AppDatabase import com.zyzf.coffeepreorder.database.model.User +import com.zyzf.coffeepreorder.ui.navigation.Screen import com.zyzf.coffeepreorder.ui.theme.CoffeePreorderTheme import kotlinx.coroutines.DelicateCoroutinesApi import kotlinx.coroutines.Dispatchers @@ -106,7 +106,7 @@ fun Register(navController: NavController?) { var user: User? GlobalScope.launch (Dispatchers.Main) { if (password == confPassword) { - AppDatabase.getInstance(context).userDao().insert(User(login, fio, phone, password)) + AppDatabase.getInstance(context).userDao().insert(User(login, fio, phone, password, "user")) user = AppDatabase.getInstance(context).userDao().tryLogin(login, password) if (user != null) { AppDatabase.getInstance(context).userDao().logout() diff --git a/build.gradle.kts b/build.gradle.kts index 4f28a2a..99cbd5b 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,6 +1,6 @@ // Top-level build file where you can add configuration options common to all sub-projects/modules. plugins { - id("com.android.application") version "8.1.4" apply false - id("org.jetbrains.kotlin.android") version "1.9.10" apply false - id("com.google.devtools.ksp") version "1.9.10-1.0.13" apply false + id("com.android.application") version "8.2.0" apply false + id("org.jetbrains.kotlin.android") version "1.9.20" apply false + id("com.google.devtools.ksp") version "1.9.20-1.0.14" apply false } \ No newline at end of file diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index cb92000..aa81675 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ #Sun Oct 15 15:51:04 GMT+04:00 2023 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.0-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.2-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists