From 27893c3c29a86d4ae3c6f216bba24904abdf3874 Mon Sep 17 00:00:00 2001 From: Shreyas Date: Mon, 2 Aug 2021 16:42:43 +0530 Subject: [PATCH 01/27] Add dependency: accompanist swipe refresh --- noty-android/app/composeapp/build.gradle | 3 +++ noty-android/dependencies.gradle | 3 +++ 2 files changed, 6 insertions(+) diff --git a/noty-android/app/composeapp/build.gradle b/noty-android/app/composeapp/build.gradle index d7fc1dcd..4b0d8209 100644 --- a/noty-android/app/composeapp/build.gradle +++ b/noty-android/app/composeapp/build.gradle @@ -143,6 +143,9 @@ dependencies { // Compose Lifecycle implementation "androidx.compose.runtime:runtime-livedata:$composeVersion" + // Accompanist + implementation "com.google.accompanist:accompanist-swiperefresh:$accompanistVersion" + // Navigation implementation "androidx.navigation:navigation-compose:$composeNavVersion" diff --git a/noty-android/dependencies.gradle b/noty-android/dependencies.gradle index bd1dd3ba..396478cd 100644 --- a/noty-android/dependencies.gradle +++ b/noty-android/dependencies.gradle @@ -74,6 +74,9 @@ ext { lifecycleViewModelComposeVersion = "1.0.0-alpha07" + // Accompanist + accompanistVersion = "0.15.0" + // Testing jUnitVersion = "4.13.2" androidJUnitVersion = "1.1.2" From 8f64558babea77ea16f1ba7ff007f9f366920c99 Mon Sep 17 00:00:00 2001 From: Shreyas Date: Mon, 2 Aug 2021 16:43:00 +0530 Subject: [PATCH 02/27] Handle swipe refresh and UI mode in Notes screen --- .../noty/composeapp/ui/screens/NotesScreen.kt | 50 ++++++++++++------- 1 file changed, 32 insertions(+), 18 deletions(-) diff --git a/noty-android/app/composeapp/src/main/java/dev/shreyaspatil/noty/composeapp/ui/screens/NotesScreen.kt b/noty-android/app/composeapp/src/main/java/dev/shreyaspatil/noty/composeapp/ui/screens/NotesScreen.kt index 6f6050bd..99ecade7 100644 --- a/noty-android/app/composeapp/src/main/java/dev/shreyaspatil/noty/composeapp/ui/screens/NotesScreen.kt +++ b/noty-android/app/composeapp/src/main/java/dev/shreyaspatil/noty/composeapp/ui/screens/NotesScreen.kt @@ -16,6 +16,7 @@ package dev.shreyaspatil.noty.composeapp.ui.screens +import androidx.compose.foundation.isSystemInDarkTheme import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.material.FloatingActionButton import androidx.compose.material.Icon @@ -26,34 +27,36 @@ import androidx.compose.material.TopAppBar import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.Add import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.getValue import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color +import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalLifecycleOwner import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp import androidx.lifecycle.lifecycleScope import androidx.navigation.NavHostController +import com.google.accompanist.swiperefresh.SwipeRefresh +import com.google.accompanist.swiperefresh.rememberSwipeRefreshState import dev.shreyaspatil.noty.composeapp.component.NotesList import dev.shreyaspatil.noty.composeapp.component.action.AboutAction import dev.shreyaspatil.noty.composeapp.component.action.LogoutAction import dev.shreyaspatil.noty.composeapp.component.action.ThemeSwitchAction import dev.shreyaspatil.noty.composeapp.component.dialog.FailureDialog -import dev.shreyaspatil.noty.composeapp.component.dialog.LoaderDialog +import dev.shreyaspatil.noty.composeapp.ui.MainActivity import dev.shreyaspatil.noty.composeapp.ui.Screen -import dev.shreyaspatil.noty.core.model.Note import dev.shreyaspatil.noty.core.view.ViewState import dev.shreyaspatil.noty.view.viewmodel.NotesViewModel import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.InternalCoroutinesApi import kotlinx.coroutines.launch +@InternalCoroutinesApi @ExperimentalCoroutinesApi @Composable -fun NotesScreen( - toggleTheme: () -> Unit, - navController: NavHostController, - viewModel: NotesViewModel -) { +fun NotesScreen(navController: NavHostController, viewModel: NotesViewModel) { if (!viewModel.isUserLoggedIn()) { navigateToLogin(navController) return @@ -61,6 +64,13 @@ fun NotesScreen( val lifecycleScope = LocalLifecycleOwner.current.lifecycleScope + val currentActivity = LocalContext.current as MainActivity + val darkMode by currentActivity.preferenceManager.uiModeFlow.collectAsState(initial = isSystemInDarkTheme()) + + val switchTheme: () -> Unit = { + lifecycleScope.launch { currentActivity.preferenceManager.setDarkMode(!darkMode) } + } + Scaffold( topBar = { TopAppBar( @@ -76,7 +86,7 @@ fun NotesScreen( contentColor = MaterialTheme.colors.onPrimary, elevation = 0.dp, actions = { - ThemeSwitchAction(toggleTheme) + ThemeSwitchAction(switchTheme) AboutAction { navController.navigate( Screen.About.route @@ -94,20 +104,24 @@ fun NotesScreen( ) }, content = { - val notesState = viewModel.notes.collectAsState(initial = null).value + val notesState = viewModel.notes.collectAsState(initial = ViewState.loading()).value + val syncState = viewModel.syncState.collectAsState(initial = ViewState.loading()).value - val onNoteClicked: (Note) -> Unit = { - println("Note Clicked") - navController.navigate(Screen.NotesDetail.route(it.id)) - } + val isRefreshing = (notesState is ViewState.Loading) or (syncState is ViewState.Loading) - when (notesState) { - is ViewState.Loading, null -> LoaderDialog() - is ViewState.Success -> NotesList(notesState.data, onNoteClicked) - is ViewState.Failed -> FailureDialog(notesState.message) + SwipeRefresh( + state = rememberSwipeRefreshState(isRefreshing), + onRefresh = { viewModel.syncNotes() } + ) { + when (notesState) { + is ViewState.Success -> NotesList(notesState.data) { note -> + navController.navigate(Screen.NotesDetail.route(note.id)) + } + is ViewState.Failed -> FailureDialog(notesState.message) + } } - viewModel.syncNotes() + LaunchedEffect(true) { viewModel.syncNotes() } }, floatingActionButton = { FloatingActionButton( From 5356d0552731c08076b73ac793e79ac660040e8c Mon Sep 17 00:00:00 2001 From: Shreyas Date: Mon, 2 Aug 2021 16:43:18 +0530 Subject: [PATCH 03/27] Cleanup code --- .../noty/composeapp/navigation/NotyNavigation.kt | 4 ++-- .../noty/composeapp/ui/MainActivity.kt | 12 +++--------- .../noty/view/viewmodel/NotesViewModel.kt | 14 ++++++++++++-- 3 files changed, 17 insertions(+), 13 deletions(-) diff --git a/noty-android/app/composeapp/src/main/java/dev/shreyaspatil/noty/composeapp/navigation/NotyNavigation.kt b/noty-android/app/composeapp/src/main/java/dev/shreyaspatil/noty/composeapp/navigation/NotyNavigation.kt index 43aae190..458fd734 100644 --- a/noty-android/app/composeapp/src/main/java/dev/shreyaspatil/noty/composeapp/navigation/NotyNavigation.kt +++ b/noty-android/app/composeapp/src/main/java/dev/shreyaspatil/noty/composeapp/navigation/NotyNavigation.kt @@ -40,7 +40,7 @@ const val NOTY_NAV_HOST_ROUTE = "noty-main-route" @InternalCoroutinesApi @ExperimentalCoroutinesApi @Composable -fun NotyNavigation(toggleTheme: () -> Unit) { +fun NotyNavigation() { val navController = rememberNavController() NavHost(navController, startDestination = Screen.Notes.route, route = NOTY_NAV_HOST_ROUTE) { @@ -54,7 +54,7 @@ fun NotyNavigation(toggleTheme: () -> Unit) { AddNoteScreen(navController, hiltViewModel()) } composable(Screen.Notes.route) { - NotesScreen(toggleTheme, navController, hiltViewModel()) + NotesScreen(navController, hiltViewModel()) } composable( Screen.NotesDetail.route, diff --git a/noty-android/app/composeapp/src/main/java/dev/shreyaspatil/noty/composeapp/ui/MainActivity.kt b/noty-android/app/composeapp/src/main/java/dev/shreyaspatil/noty/composeapp/ui/MainActivity.kt index ae2e3169..a5c4d351 100644 --- a/noty-android/app/composeapp/src/main/java/dev/shreyaspatil/noty/composeapp/ui/MainActivity.kt +++ b/noty-android/app/composeapp/src/main/java/dev/shreyaspatil/noty/composeapp/ui/MainActivity.kt @@ -40,7 +40,6 @@ import dev.shreyaspatil.noty.view.viewmodel.NoteDetailViewModel import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.InternalCoroutinesApi import kotlinx.coroutines.flow.collect -import kotlinx.coroutines.launch import javax.inject.Inject @AndroidEntryPoint @@ -70,22 +69,17 @@ class MainActivity : AppCompatActivity() { private fun NotyMain() { val darkMode by preferenceManager.uiModeFlow.collectAsState(initial = isSystemInDarkTheme()) - val toggleTheme: () -> Unit = { - lifecycleScope.launch { preferenceManager.setDarkMode(!darkMode) } - } - NotyTheme(darkTheme = darkMode) { - // A surface container using the 'background' color from the theme Surface(color = MaterialTheme.colors.background) { - NotyNavigation(toggleTheme = toggleTheme) + NotyNavigation() } } } private fun observeUiTheme() { lifecycleScope.launchWhenStarted { - preferenceManager.uiModeFlow.collect { - val mode = when (it) { + preferenceManager.uiModeFlow.collect { isDarkMode -> + val mode = when (isDarkMode) { true -> AppCompatDelegate.MODE_NIGHT_YES false -> AppCompatDelegate.MODE_NIGHT_NO } diff --git a/noty-android/app/src/main/java/dev/shreyaspatil/noty/view/viewmodel/NotesViewModel.kt b/noty-android/app/src/main/java/dev/shreyaspatil/noty/view/viewmodel/NotesViewModel.kt index 9a3016d3..9b61d37b 100644 --- a/noty-android/app/src/main/java/dev/shreyaspatil/noty/view/viewmodel/NotesViewModel.kt +++ b/noty-android/app/src/main/java/dev/shreyaspatil/noty/view/viewmodel/NotesViewModel.kt @@ -30,8 +30,18 @@ import dev.shreyaspatil.noty.core.task.TaskState import dev.shreyaspatil.noty.core.view.ViewState import dev.shreyaspatil.noty.di.LocalRepository import dev.shreyaspatil.noty.utils.ext.shareWhileObserved -import kotlinx.coroutines.* -import kotlinx.coroutines.flow.* +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.Job +import kotlinx.coroutines.flow.MutableSharedFlow +import kotlinx.coroutines.flow.SharedFlow +import kotlinx.coroutines.flow.collect +import kotlinx.coroutines.flow.distinctUntilChanged +import kotlinx.coroutines.flow.first +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.onStart +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext import javax.inject.Inject @ExperimentalCoroutinesApi From db109f1ae4105c64ffef3c241f331d183f17967f Mon Sep 17 00:00:00 2001 From: Shreyas Date: Wed, 4 Aug 2021 10:19:52 +0530 Subject: [PATCH 04/27] Bump dagger version to 2.38.1 --- noty-android/dependencies.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/noty-android/dependencies.gradle b/noty-android/dependencies.gradle index 396478cd..1e34df05 100644 --- a/noty-android/dependencies.gradle +++ b/noty-android/dependencies.gradle @@ -47,7 +47,7 @@ ext { lottieComposeVersion = "4.0.0" // DI - daggerHiltVersion = "2.37" + daggerHiltVersion = "2.38.1" jetpackHiltVersion = "1.0.0" // Networking From 54a7c237cdc5d27638ce86695edbf280919bf26a Mon Sep 17 00:00:00 2001 From: Shreyas Date: Thu, 5 Aug 2021 17:25:53 +0530 Subject: [PATCH 05/27] Bump compose version to 1.0.1 and kotlin to 1.5.21 --- noty-android/dependencies.gradle | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/noty-android/dependencies.gradle b/noty-android/dependencies.gradle index 1e34df05..98857b24 100644 --- a/noty-android/dependencies.gradle +++ b/noty-android/dependencies.gradle @@ -19,7 +19,7 @@ ext { androidGradlePluginVersion = '7.0.0' // Kotlin - kotlinVersion = "1.5.10" + kotlinVersion = "1.5.21" ktlintVersion = "10.1.0" coroutinesVersion = "1.5.0" @@ -61,7 +61,7 @@ ext { javaxInjectVersion = "1" // Jetpack Compose - composeVersion = "1.0.0" + composeVersion = "1.0.1" // Constrain Layout (Compose) composeConstraintLayoutVersion = "1.0.0-beta01" From fcfae1809fb5a3474a9a3f0edb2024449d0a77e4 Mon Sep 17 00:00:00 2001 From: Shreyas Date: Thu, 5 Aug 2021 18:27:07 +0530 Subject: [PATCH 06/27] Remove preview from main activity --- .../dev/shreyaspatil/noty/composeapp/ui/MainActivity.kt | 7 ------- 1 file changed, 7 deletions(-) diff --git a/noty-android/app/composeapp/src/main/java/dev/shreyaspatil/noty/composeapp/ui/MainActivity.kt b/noty-android/app/composeapp/src/main/java/dev/shreyaspatil/noty/composeapp/ui/MainActivity.kt index a5c4d351..655c9ec5 100644 --- a/noty-android/app/composeapp/src/main/java/dev/shreyaspatil/noty/composeapp/ui/MainActivity.kt +++ b/noty-android/app/composeapp/src/main/java/dev/shreyaspatil/noty/composeapp/ui/MainActivity.kt @@ -26,7 +26,6 @@ import androidx.compose.material.Surface import androidx.compose.runtime.Composable import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue -import androidx.compose.ui.tooling.preview.Preview import androidx.lifecycle.lifecycleScope import dagger.hilt.EntryPoint import dagger.hilt.InstallIn @@ -88,9 +87,3 @@ class MainActivity : AppCompatActivity() { } } } - -@Preview(showBackground = true) -@Composable -fun DefaultPreview() { - NotyTheme {} -} From a9fb4abded0415599826ae97fcf77642b1a5b73e Mon Sep 17 00:00:00 2001 From: Shreyas Date: Thu, 5 Aug 2021 19:35:15 +0530 Subject: [PATCH 07/27] Add common text field component --- .../component/text/CommonTextField.kt | 54 +++++++++++++++++++ 1 file changed, 54 insertions(+) create mode 100644 noty-android/app/composeapp/src/main/java/dev/shreyaspatil/noty/composeapp/component/text/CommonTextField.kt diff --git a/noty-android/app/composeapp/src/main/java/dev/shreyaspatil/noty/composeapp/component/text/CommonTextField.kt b/noty-android/app/composeapp/src/main/java/dev/shreyaspatil/noty/composeapp/component/text/CommonTextField.kt new file mode 100644 index 00000000..cc5d7307 --- /dev/null +++ b/noty-android/app/composeapp/src/main/java/dev/shreyaspatil/noty/composeapp/component/text/CommonTextField.kt @@ -0,0 +1,54 @@ +/* + * Copyright 2020 Shreyas Patil + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package dev.shreyaspatil.noty.composeapp.component.text + +import androidx.compose.material.MaterialTheme +import androidx.compose.material.OutlinedTextField +import androidx.compose.material.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.text.TextStyle +import androidx.compose.ui.text.input.VisualTransformation +import androidx.compose.ui.unit.TextUnit +import androidx.compose.ui.unit.sp + +@Composable +fun NotyTextField( + value: String, + label: String, + onValueChange: (String) -> Unit, + modifier: Modifier = Modifier, + fontSize: TextUnit = 16.sp, + color: Color = MaterialTheme.colors.onPrimary, + leadingIcon: @Composable() (() -> Unit)? = null, + isError: Boolean = false, + visualTransformation: VisualTransformation = VisualTransformation.None +) { + OutlinedTextField( + value = value, + label = { Text(text = label) }, + modifier = modifier, + onValueChange = onValueChange, + leadingIcon = leadingIcon, + textStyle = TextStyle(color, fontSize = fontSize), + isError = isError, + visualTransformation = visualTransformation + ) +} + + From c1be766c0a3e7c073034f9c85fc176cdf63b8fd1 Mon Sep 17 00:00:00 2001 From: Shreyas Date: Thu, 5 Aug 2021 19:35:26 +0530 Subject: [PATCH 08/27] Add sealed class for value validation --- .../component/text/TextFieldValue.kt | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100644 noty-android/app/composeapp/src/main/java/dev/shreyaspatil/noty/composeapp/component/text/TextFieldValue.kt diff --git a/noty-android/app/composeapp/src/main/java/dev/shreyaspatil/noty/composeapp/component/text/TextFieldValue.kt b/noty-android/app/composeapp/src/main/java/dev/shreyaspatil/noty/composeapp/component/text/TextFieldValue.kt new file mode 100644 index 00000000..06345d26 --- /dev/null +++ b/noty-android/app/composeapp/src/main/java/dev/shreyaspatil/noty/composeapp/component/text/TextFieldValue.kt @@ -0,0 +1,22 @@ +/* + * Copyright 2020 Shreyas Patil + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package dev.shreyaspatil.noty.composeapp.component.text + +sealed class TextFieldValue(val data: T) { + class Valid(data: T) : TextFieldValue(data) + class Invalid(data: T) : TextFieldValue(data) +} From 0372838ddde55069a97818c5c5cbc3526903b5b4 Mon Sep 17 00:00:00 2001 From: Shreyas Date: Thu, 5 Aug 2021 19:35:41 +0530 Subject: [PATCH 09/27] Add text field components for username and password --- .../component/text/PasswordTextField.kt | 83 +++++++++++++++++++ .../component/text/UsernameTextField.kt | 53 ++++++++++++ 2 files changed, 136 insertions(+) create mode 100644 noty-android/app/composeapp/src/main/java/dev/shreyaspatil/noty/composeapp/component/text/PasswordTextField.kt create mode 100644 noty-android/app/composeapp/src/main/java/dev/shreyaspatil/noty/composeapp/component/text/UsernameTextField.kt diff --git a/noty-android/app/composeapp/src/main/java/dev/shreyaspatil/noty/composeapp/component/text/PasswordTextField.kt b/noty-android/app/composeapp/src/main/java/dev/shreyaspatil/noty/composeapp/component/text/PasswordTextField.kt new file mode 100644 index 00000000..a41b55a2 --- /dev/null +++ b/noty-android/app/composeapp/src/main/java/dev/shreyaspatil/noty/composeapp/component/text/PasswordTextField.kt @@ -0,0 +1,83 @@ +/* + * Copyright 2020 Shreyas Patil + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package dev.shreyaspatil.noty.composeapp.component.text + +import androidx.compose.material.Icon +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.outlined.Password +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Modifier +import androidx.compose.ui.text.input.PasswordVisualTransformation +import dev.shreyaspatil.noty.utils.validator.AuthValidator.isPasswordAndConfirmPasswordSame +import dev.shreyaspatil.noty.utils.validator.AuthValidator.isValidPassword + +@Composable +fun PasswordTextField( + modifier: Modifier = Modifier, + value: String = "", + onTextChange: (TextFieldValue) -> Unit, +) { + var startedTyping by remember { mutableStateOf(false) } + var isValid by remember { mutableStateOf(false) } + + NotyTextField( + value = value, + label = "Password", + onValueChange = { + isValid = isValidPassword(it) + onTextChange(if (isValid) TextFieldValue.Valid(it) else TextFieldValue.Invalid(it)) + if (!startedTyping) { + startedTyping = true + } + }, + modifier = modifier, + leadingIcon = { Icon(Icons.Outlined.Password, "Password") }, + visualTransformation = PasswordVisualTransformation(), + isError = !isValid && startedTyping + ) +} + +@Composable +fun ConfirmPasswordTextField( + modifier: Modifier = Modifier, + value: String = "", + expectedValue: String = "", + onTextChange: (TextFieldValue) -> Unit, +) { + var startedTyping by remember { mutableStateOf(false) } + var isValid by remember { mutableStateOf(false) } + + NotyTextField( + value = value, + label = "Confirm Password", + onValueChange = { + isValid = isValidPassword(it) && isPasswordAndConfirmPasswordSame(expectedValue, it) + onTextChange(if (isValid) TextFieldValue.Valid(it) else TextFieldValue.Invalid(it)) + if (!startedTyping) { + startedTyping = true + } + }, + modifier = modifier, + leadingIcon = { Icon(Icons.Outlined.Password, "Confirm Password") }, + visualTransformation = PasswordVisualTransformation(), + isError = !isValid && startedTyping + ) +} diff --git a/noty-android/app/composeapp/src/main/java/dev/shreyaspatil/noty/composeapp/component/text/UsernameTextField.kt b/noty-android/app/composeapp/src/main/java/dev/shreyaspatil/noty/composeapp/component/text/UsernameTextField.kt new file mode 100644 index 00000000..538e6901 --- /dev/null +++ b/noty-android/app/composeapp/src/main/java/dev/shreyaspatil/noty/composeapp/component/text/UsernameTextField.kt @@ -0,0 +1,53 @@ +/* + * Copyright 2020 Shreyas Patil + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package dev.shreyaspatil.noty.composeapp.component.text + +import androidx.compose.material.Icon +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.outlined.Person +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Modifier +import dev.shreyaspatil.noty.utils.validator.AuthValidator + +@Composable +fun UsernameTextField( + modifier: Modifier = Modifier, + value: String = "", + onTextChange: (TextFieldValue) -> Unit, +) { + var startedTyping by remember { mutableStateOf(false) } + var isValid by remember { mutableStateOf(false) } + + NotyTextField( + value = value, + label = "Username", + onValueChange = { + isValid = AuthValidator.isValidUsername(it) + onTextChange(if (isValid) TextFieldValue.Valid(it) else TextFieldValue.Invalid(it)) + if (!startedTyping) { + startedTyping = true + } + }, + modifier = modifier, + leadingIcon = { Icon(Icons.Outlined.Person, "User") }, + isError = !isValid && startedTyping + ) +} From 6f951634ccaadff1ef8d0cb3bb6a5a9ce5cb7ecd Mon Sep 17 00:00:00 2001 From: Shreyas Date: Thu, 5 Aug 2021 19:35:55 +0530 Subject: [PATCH 10/27] Reuse component in login and signup screen --- .../composeapp/ui/screens/SignUpScreen.kt | 93 +++++++------------ .../composeapp/view/screen/LoginScreen.kt | 50 ++++------ 2 files changed, 50 insertions(+), 93 deletions(-) diff --git a/noty-android/app/composeapp/src/main/java/dev/shreyaspatil/noty/composeapp/ui/screens/SignUpScreen.kt b/noty-android/app/composeapp/src/main/java/dev/shreyaspatil/noty/composeapp/ui/screens/SignUpScreen.kt index 7fd5b308..647575ef 100644 --- a/noty-android/app/composeapp/src/main/java/dev/shreyaspatil/noty/composeapp/ui/screens/SignUpScreen.kt +++ b/noty-android/app/composeapp/src/main/java/dev/shreyaspatil/noty/composeapp/ui/screens/SignUpScreen.kt @@ -24,13 +24,8 @@ import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.material.Button -import androidx.compose.material.Icon import androidx.compose.material.MaterialTheme import androidx.compose.material.Text -import androidx.compose.material.TextField -import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.outlined.Lock -import androidx.compose.material.icons.outlined.Person import androidx.compose.runtime.Composable import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue @@ -40,21 +35,20 @@ import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.text.SpanStyle -import androidx.compose.ui.text.TextStyle import androidx.compose.ui.text.buildAnnotatedString -import androidx.compose.ui.text.input.PasswordVisualTransformation -import androidx.compose.ui.text.input.TextFieldValue import androidx.compose.ui.unit.dp -import androidx.compose.ui.unit.sp import androidx.constraintlayout.compose.ConstraintLayout import androidx.navigation.NavHostController import dev.shreyaspatil.noty.composeapp.component.dialog.FailureDialog import dev.shreyaspatil.noty.composeapp.component.dialog.LoaderDialog +import dev.shreyaspatil.noty.composeapp.component.text.ConfirmPasswordTextField +import dev.shreyaspatil.noty.composeapp.component.text.PasswordTextField +import dev.shreyaspatil.noty.composeapp.component.text.TextFieldValue.Valid +import dev.shreyaspatil.noty.composeapp.component.text.UsernameTextField import dev.shreyaspatil.noty.composeapp.navigation.NOTY_NAV_HOST_ROUTE import dev.shreyaspatil.noty.composeapp.ui.Screen import dev.shreyaspatil.noty.composeapp.ui.theme.typography import dev.shreyaspatil.noty.core.view.ViewState -import dev.shreyaspatil.noty.utils.validator.AuthValidator import dev.shreyaspatil.noty.view.viewmodel.RegisterViewModel import kotlinx.coroutines.ExperimentalCoroutinesApi @@ -107,84 +101,65 @@ fun SignUpScreen( } ) - var username by remember { mutableStateOf(TextFieldValue()) } - val isValidUsername = AuthValidator.isValidUsername(username.text) + var username by remember { mutableStateOf("") } + var isValidUsername by remember { mutableStateOf(false) } - TextField( + UsernameTextField( modifier = Modifier .fillMaxWidth() .padding(16.dp, 0.dp, 16.dp, 0.dp) .constrainAs(usernameRef) { - top.linkTo(titleRef.bottom, margin = 50.dp) - }.background(MaterialTheme.colors.background), - label = { Text(text = "Username") }, - leadingIcon = { Icon(Icons.Outlined.Person, "Person") }, - textStyle = TextStyle( - color = MaterialTheme.colors.onPrimary, - fontSize = 16.sp - ), + top.linkTo(titleRef.bottom, margin = 30.dp) + } + .background(MaterialTheme.colors.background), value = username, - onValueChange = { - username = it - }, - isError = !isValidUsername + onTextChange = { + username = it.data + isValidUsername = it is Valid + } ) - var password by remember { mutableStateOf(TextFieldValue()) } - val isValidPassword = AuthValidator.isValidPassword(password.text) + var password by remember { mutableStateOf("") } + var isValidPassword by remember { mutableStateOf(false) } - TextField( + PasswordTextField( modifier = Modifier .fillMaxWidth() .padding(16.dp, 0.dp, 16.dp, 0.dp) .constrainAs(passwordRef) { top.linkTo(usernameRef.bottom, margin = 16.dp) - }.background(MaterialTheme.colors.background), - label = { Text(text = "Password") }, - leadingIcon = { Icon(Icons.Outlined.Lock, "Lock") }, - textStyle = TextStyle( - color = MaterialTheme.colors.onPrimary, - fontSize = 16.sp - ), - visualTransformation = PasswordVisualTransformation(), + } + .background(MaterialTheme.colors.background), value = password, - onValueChange = { - password = it - }, - isError = !isValidPassword + onTextChange = { + password = it.data + isValidPassword = it is Valid + } ) - var confirmPassword by remember { mutableStateOf(TextFieldValue()) } - val isValidConfirmPassword = AuthValidator.isPasswordAndConfirmPasswordSame( - password.text, - confirmPassword.text - ) + var confirmPassword by remember { mutableStateOf("") } + var isValidConfirmPassword by remember { mutableStateOf(false) } - TextField( + ConfirmPasswordTextField( modifier = Modifier .fillMaxWidth() .padding(16.dp, 0.dp, 16.dp, 0.dp) .constrainAs(confirmPasswordRef) { top.linkTo(passwordRef.bottom, margin = 16.dp) - }.background(MaterialTheme.colors.background), - label = { Text(text = "Confirm password") }, - leadingIcon = { Icon(Icons.Outlined.Lock, "Lock") }, - textStyle = TextStyle( - color = MaterialTheme.colors.onPrimary, - fontSize = 16.sp - ), - visualTransformation = PasswordVisualTransformation(), + } + .background(MaterialTheme.colors.background), value = confirmPassword, - onValueChange = { - confirmPassword = it - }, - isError = !isValidConfirmPassword + expectedValue = password, + onTextChange = { + confirmPassword = it.data + isValidConfirmPassword = it is Valid + } ) Button( onClick = { if (isValidUsername && isValidPassword && isValidConfirmPassword) { - viewModel.register(username.text, password.text) + viewModel.register(username, password) } }, modifier = Modifier diff --git a/noty-android/app/composeapp/src/main/java/dev/shreyaspatil/noty/composeapp/view/screen/LoginScreen.kt b/noty-android/app/composeapp/src/main/java/dev/shreyaspatil/noty/composeapp/view/screen/LoginScreen.kt index 0d9080ae..05a48669 100644 --- a/noty-android/app/composeapp/src/main/java/dev/shreyaspatil/noty/composeapp/view/screen/LoginScreen.kt +++ b/noty-android/app/composeapp/src/main/java/dev/shreyaspatil/noty/composeapp/view/screen/LoginScreen.kt @@ -26,13 +26,8 @@ import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.sizeIn import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.material.Button -import androidx.compose.material.Icon import androidx.compose.material.MaterialTheme import androidx.compose.material.Text -import androidx.compose.material.TextField -import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.outlined.Lock -import androidx.compose.material.icons.outlined.Person import androidx.compose.runtime.Composable import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue @@ -44,20 +39,19 @@ import androidx.compose.ui.graphics.Color import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.res.painterResource import androidx.compose.ui.text.SpanStyle -import androidx.compose.ui.text.TextStyle import androidx.compose.ui.text.buildAnnotatedString -import androidx.compose.ui.text.input.PasswordVisualTransformation import androidx.compose.ui.unit.dp -import androidx.compose.ui.unit.sp import androidx.constraintlayout.compose.ConstraintLayout import androidx.navigation.NavHostController import dev.shreyaspatil.noty.composeapp.R.drawable.noty_app_logo import dev.shreyaspatil.noty.composeapp.component.dialog.FailureDialog import dev.shreyaspatil.noty.composeapp.component.dialog.LoaderDialog +import dev.shreyaspatil.noty.composeapp.component.text.PasswordTextField +import dev.shreyaspatil.noty.composeapp.component.text.TextFieldValue +import dev.shreyaspatil.noty.composeapp.component.text.UsernameTextField import dev.shreyaspatil.noty.composeapp.ui.Screen import dev.shreyaspatil.noty.composeapp.ui.theme.typography import dev.shreyaspatil.noty.core.view.ViewState -import dev.shreyaspatil.noty.utils.validator.AuthValidator import dev.shreyaspatil.noty.view.viewmodel.LoginViewModel import kotlinx.coroutines.ExperimentalCoroutinesApi @@ -116,8 +110,9 @@ fun LoginScreen(navController: NavHostController, loginViewModel: LoginViewModel } ) var username by remember { mutableStateOf("") } - val isValidUsername = AuthValidator.isValidUsername(username) - TextField( + var isValidUsername by remember { mutableStateOf(false) } + + UsernameTextField( modifier = Modifier .fillMaxWidth() .padding(16.dp, 0.dp, 16.dp, 0.dp) @@ -125,23 +120,17 @@ fun LoginScreen(navController: NavHostController, loginViewModel: LoginViewModel top.linkTo(titleRef.bottom, margin = 30.dp) } .background(MaterialTheme.colors.background), - label = { Text(text = "Username") }, - leadingIcon = { Icon(Icons.Outlined.Person, "User") }, - textStyle = TextStyle( - color = MaterialTheme.colors.onPrimary, - fontSize = 16.sp - ), value = username, - onValueChange = { - username = it - }, - isError = !isValidUsername + onTextChange = { + username = it.data + isValidUsername = it is TextFieldValue.Valid + } ) var password by remember { mutableStateOf("") } - val isValidPassword = AuthValidator.isValidPassword(password) + var isValidPassword by remember { mutableStateOf(false) } - TextField( + PasswordTextField( modifier = Modifier .fillMaxWidth() .padding(16.dp, 0.dp, 16.dp, 0.dp) @@ -149,18 +138,11 @@ fun LoginScreen(navController: NavHostController, loginViewModel: LoginViewModel top.linkTo(usernameRef.bottom, margin = 16.dp) } .background(MaterialTheme.colors.background), - label = { Text(text = "Password") }, - leadingIcon = { Icon(Icons.Outlined.Lock, "Password") }, - textStyle = TextStyle( - color = MaterialTheme.colors.onPrimary, - fontSize = 16.sp - ), - visualTransformation = PasswordVisualTransformation(), value = password, - onValueChange = { - password = it - }, - isError = !isValidPassword + onTextChange = { + password = it.data + isValidPassword = it is TextFieldValue.Valid + } ) Button( From e04377e6d312484c45433896242cea97c92450bf Mon Sep 17 00:00:00 2001 From: Shreyas Date: Thu, 5 Aug 2021 19:38:03 +0530 Subject: [PATCH 11/27] Reformat with ktlint --- .../noty/composeapp/component/text/CommonTextField.kt | 2 -- .../shreyaspatil/noty/composeapp/ui/screens/NotesScreen.kt | 4 +++- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/noty-android/app/composeapp/src/main/java/dev/shreyaspatil/noty/composeapp/component/text/CommonTextField.kt b/noty-android/app/composeapp/src/main/java/dev/shreyaspatil/noty/composeapp/component/text/CommonTextField.kt index cc5d7307..35672fe8 100644 --- a/noty-android/app/composeapp/src/main/java/dev/shreyaspatil/noty/composeapp/component/text/CommonTextField.kt +++ b/noty-android/app/composeapp/src/main/java/dev/shreyaspatil/noty/composeapp/component/text/CommonTextField.kt @@ -50,5 +50,3 @@ fun NotyTextField( visualTransformation = visualTransformation ) } - - diff --git a/noty-android/app/composeapp/src/main/java/dev/shreyaspatil/noty/composeapp/ui/screens/NotesScreen.kt b/noty-android/app/composeapp/src/main/java/dev/shreyaspatil/noty/composeapp/ui/screens/NotesScreen.kt index 99ecade7..0e5db62e 100644 --- a/noty-android/app/composeapp/src/main/java/dev/shreyaspatil/noty/composeapp/ui/screens/NotesScreen.kt +++ b/noty-android/app/composeapp/src/main/java/dev/shreyaspatil/noty/composeapp/ui/screens/NotesScreen.kt @@ -65,7 +65,9 @@ fun NotesScreen(navController: NavHostController, viewModel: NotesViewModel) { val lifecycleScope = LocalLifecycleOwner.current.lifecycleScope val currentActivity = LocalContext.current as MainActivity - val darkMode by currentActivity.preferenceManager.uiModeFlow.collectAsState(initial = isSystemInDarkTheme()) + val darkMode by currentActivity.preferenceManager + .uiModeFlow + .collectAsState(isSystemInDarkTheme()) val switchTheme: () -> Unit = { lifecycleScope.launch { currentActivity.preferenceManager.setDarkMode(!darkMode) } From e2c1bbe581b907a068634766b7a3940abe74f418 Mon Sep 17 00:00:00 2001 From: Shreyas Date: Fri, 6 Aug 2021 15:31:45 +0530 Subject: [PATCH 12/27] Refactor: rename colors --- .../noty/composeapp/ui/theme/Color.kt | 17 +++++++++-------- .../noty/composeapp/ui/theme/Theme.kt | 14 +++++--------- 2 files changed, 14 insertions(+), 17 deletions(-) diff --git a/noty-android/app/composeapp/src/main/java/dev/shreyaspatil/noty/composeapp/ui/theme/Color.kt b/noty-android/app/composeapp/src/main/java/dev/shreyaspatil/noty/composeapp/ui/theme/Color.kt index 1762c93b..c3930b4c 100644 --- a/noty-android/app/composeapp/src/main/java/dev/shreyaspatil/noty/composeapp/ui/theme/Color.kt +++ b/noty-android/app/composeapp/src/main/java/dev/shreyaspatil/noty/composeapp/ui/theme/Color.kt @@ -16,19 +16,20 @@ package dev.shreyaspatil.noty.composeapp.ui.theme +import androidx.compose.foundation.isSystemInDarkTheme +import androidx.compose.runtime.Composable import androidx.compose.ui.graphics.Color -// primary color val primary = Color(0xFF7885FF) -// for bg -val bgDay = Color(0xfff3f7f9) -val bgNight = Color(0xff121212) +val backgroundDay = Color(0xfff3f7f9) +val backgroundNight = Color(0xff1A191E) -// for card colors -val day = Color(0xffffffff) -val night = Color(0xff1A191E) +val surfaceDay = Color(0xffffffff) +val surfaceNight = Color(0xFF38353F) -// for text colors val black = Color(0xff000000) val white = Color(0xffffffff) + +@Composable +fun getTextFieldHintColor(): Color = if (isSystemInDarkTheme()) Color.LightGray else Color.Gray diff --git a/noty-android/app/composeapp/src/main/java/dev/shreyaspatil/noty/composeapp/ui/theme/Theme.kt b/noty-android/app/composeapp/src/main/java/dev/shreyaspatil/noty/composeapp/ui/theme/Theme.kt index 8f9b10ee..1d1ef32c 100644 --- a/noty-android/app/composeapp/src/main/java/dev/shreyaspatil/noty/composeapp/ui/theme/Theme.kt +++ b/noty-android/app/composeapp/src/main/java/dev/shreyaspatil/noty/composeapp/ui/theme/Theme.kt @@ -25,8 +25,8 @@ import androidx.compose.runtime.Composable private val DarkColorPalette = darkColors( primary = primary, primaryVariant = primary, - background = bgNight, - surface = night, + background = backgroundNight, + surface = surfaceNight, onBackground = white, onPrimary = white ) @@ -34,19 +34,15 @@ private val DarkColorPalette = darkColors( private val LightColorPalette = lightColors( primary = primary, primaryVariant = primary, - background = bgDay, - surface = day, + background = backgroundDay, + surface = surfaceDay, onBackground = black, onPrimary = black ) @Composable fun NotyTheme(darkTheme: Boolean = isSystemInDarkTheme(), content: @Composable() () -> Unit) { - val colors = if (darkTheme) { - DarkColorPalette - } else { - LightColorPalette - } + val colors = if (darkTheme) DarkColorPalette else LightColorPalette MaterialTheme( colors = colors, From d64292ae11edafd702293a81b9ff16b8e8d9cba4 Mon Sep 17 00:00:00 2001 From: Shreyas Date: Fri, 6 Aug 2021 15:32:01 +0530 Subject: [PATCH 13/27] Add basic variant of Text field --- .../component/text/CommonTextField.kt | 39 +++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/noty-android/app/composeapp/src/main/java/dev/shreyaspatil/noty/composeapp/component/text/CommonTextField.kt b/noty-android/app/composeapp/src/main/java/dev/shreyaspatil/noty/composeapp/component/text/CommonTextField.kt index 35672fe8..6d646f74 100644 --- a/noty-android/app/composeapp/src/main/java/dev/shreyaspatil/noty/composeapp/component/text/CommonTextField.kt +++ b/noty-android/app/composeapp/src/main/java/dev/shreyaspatil/noty/composeapp/component/text/CommonTextField.kt @@ -16,16 +16,25 @@ package dev.shreyaspatil.noty.composeapp.component.text +import androidx.compose.animation.AnimatedVisibility +import androidx.compose.animation.ExperimentalAnimationApi +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.text.BasicTextField import androidx.compose.material.MaterialTheme import androidx.compose.material.OutlinedTextField import androidx.compose.material.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.SolidColor import androidx.compose.ui.text.TextStyle +import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.input.VisualTransformation import androidx.compose.ui.unit.TextUnit +import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp +import dev.shreyaspatil.noty.composeapp.ui.theme.getTextFieldHintColor @Composable fun NotyTextField( @@ -50,3 +59,33 @@ fun NotyTextField( visualTransformation = visualTransformation ) } + +@ExperimentalAnimationApi +@Composable +fun BasicNotyTextField( + modifier: Modifier = Modifier, + value: String = "", + label: String = "", + textStyle: TextStyle = TextStyle(fontSize = 16.sp, fontWeight = FontWeight.Normal), + onTextChange: (String) -> Unit, + maxLines: Int = Int.MAX_VALUE +) { + + Box(modifier = modifier.padding(4.dp)) { + AnimatedVisibility(visible = value.isBlank()) { + Text( + text = label, + color = getTextFieldHintColor(), + fontSize = textStyle.fontSize, + fontWeight = textStyle.fontWeight + ) + } + BasicTextField( + value = value, + onValueChange = onTextChange, + textStyle = textStyle.copy(color = MaterialTheme.colors.onPrimary), + maxLines = maxLines, + cursorBrush = SolidColor(MaterialTheme.colors.primary) + ) + } +} From 33c386494e6e36cedf6824668a0f2879ec82ff0c Mon Sep 17 00:00:00 2001 From: Shreyas Date: Fri, 6 Aug 2021 15:52:53 +0530 Subject: [PATCH 14/27] Add common note text fields --- .../component/text/NoteTextFields.kt | 60 +++++++++++++++++++ 1 file changed, 60 insertions(+) create mode 100644 noty-android/app/composeapp/src/main/java/dev/shreyaspatil/noty/composeapp/component/text/NoteTextFields.kt diff --git a/noty-android/app/composeapp/src/main/java/dev/shreyaspatil/noty/composeapp/component/text/NoteTextFields.kt b/noty-android/app/composeapp/src/main/java/dev/shreyaspatil/noty/composeapp/component/text/NoteTextFields.kt new file mode 100644 index 00000000..789b812c --- /dev/null +++ b/noty-android/app/composeapp/src/main/java/dev/shreyaspatil/noty/composeapp/component/text/NoteTextFields.kt @@ -0,0 +1,60 @@ +/* + * Copyright 2020 Shreyas Patil + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package dev.shreyaspatil.noty.composeapp.component.text + +import androidx.compose.animation.ExperimentalAnimationApi +import androidx.compose.material.MaterialTheme +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.text.TextStyle +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.unit.sp + +@ExperimentalAnimationApi +@Composable +fun NoteTitleField( + modifier: Modifier = Modifier, + value: String = "", + onTextChange: (String) -> Unit +) { + BasicNotyTextField( + modifier, + value = value, + label = "Title", + onTextChange = onTextChange, + textStyle = MaterialTheme.typography.h6, + maxLines = 2 + ) + +} + +@ExperimentalAnimationApi +@Composable +fun NoteField( + modifier: Modifier = Modifier, + value: String = "", + onTextChange: (String) -> Unit +) { + BasicNotyTextField( + modifier, + value = value, + label = "Write here", + onTextChange = onTextChange, + textStyle = TextStyle(fontSize = 18.sp, fontWeight = FontWeight.Light) + ) + +} From a75e9862ca145738e6ccb46c978375499ba91645 Mon Sep 17 00:00:00 2001 From: Shreyas Date: Fri, 6 Aug 2021 15:53:15 +0530 Subject: [PATCH 15/27] Use common note fields in note details screen --- .../ui/screens/NoteDetailsScreen.kt | 122 +++++++----------- 1 file changed, 50 insertions(+), 72 deletions(-) diff --git a/noty-android/app/composeapp/src/main/java/dev/shreyaspatil/noty/composeapp/ui/screens/NoteDetailsScreen.kt b/noty-android/app/composeapp/src/main/java/dev/shreyaspatil/noty/composeapp/ui/screens/NoteDetailsScreen.kt index e556910d..7b47300b 100644 --- a/noty-android/app/composeapp/src/main/java/dev/shreyaspatil/noty/composeapp/ui/screens/NoteDetailsScreen.kt +++ b/noty-android/app/composeapp/src/main/java/dev/shreyaspatil/noty/composeapp/ui/screens/NoteDetailsScreen.kt @@ -18,41 +18,44 @@ package dev.shreyaspatil.noty.composeapp.ui.screens import android.app.Activity import android.content.Intent +import androidx.compose.animation.ExperimentalAnimationApi import androidx.compose.foundation.background -import androidx.compose.foundation.layout.fillMaxHeight +import androidx.compose.foundation.gestures.Orientation +import androidx.compose.foundation.gestures.scrollable +import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.layout.wrapContentHeight +import androidx.compose.foundation.rememberScrollState import androidx.compose.material.ExtendedFloatingActionButton import androidx.compose.material.Icon import androidx.compose.material.IconButton import androidx.compose.material.MaterialTheme import androidx.compose.material.Scaffold import androidx.compose.material.Text -import androidx.compose.material.TextField import androidx.compose.material.TopAppBar import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.Done 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.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.painterResource -import androidx.compose.ui.text.TextStyle -import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp -import androidx.compose.ui.unit.sp import androidx.core.app.ShareCompat import androidx.navigation.NavHostController import dev.shreyaspatil.noty.composeapp.R import dev.shreyaspatil.noty.composeapp.component.action.DeleteAction import dev.shreyaspatil.noty.composeapp.component.action.ShareAction import dev.shreyaspatil.noty.composeapp.component.dialog.FailureDialog -import dev.shreyaspatil.noty.composeapp.component.dialog.LoaderDialog +import dev.shreyaspatil.noty.composeapp.component.text.NoteField +import dev.shreyaspatil.noty.composeapp.component.text.NoteTitleField import dev.shreyaspatil.noty.composeapp.utils.ShowToast import dev.shreyaspatil.noty.core.view.ViewState import dev.shreyaspatil.noty.utils.validator.NoteValidator @@ -60,6 +63,7 @@ import dev.shreyaspatil.noty.view.viewmodel.NoteDetailViewModel import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.InternalCoroutinesApi +@ExperimentalAnimationApi @InternalCoroutinesApi @ExperimentalCoroutinesApi @Composable @@ -74,11 +78,9 @@ fun NoteDetailsScreen( val note = viewModel.note.collectAsState(initial = null).value - if (note == null) { - LoaderDialog() - } else { - val titleText = remember { mutableStateOf(note.title) } - val noteText = remember { mutableStateOf(note.note) } + if (note != null) { + var titleText by remember { mutableStateOf(note.title) } + var noteText by remember { mutableStateOf(note.note) } Scaffold( topBar = { @@ -93,10 +95,8 @@ fun NoteDetailsScreen( }, navigationIcon = { IconButton( - modifier = Modifier.padding(12.dp, 0.dp, 0.dp, 0.dp), - onClick = { - navController.navigateUp() - } + modifier = Modifier.padding(4.dp, 0.dp, 0.dp, 0.dp), + onClick = { navController.navigateUp() } ) { Icon( painterResource(R.drawable.ic_back), @@ -110,59 +110,39 @@ fun NoteDetailsScreen( elevation = 0.dp, actions = { DeleteAction(onClick = { viewModel.deleteNote() }) - ShareAction( - onClick = { - shareNote( - activity, - titleText.value, - noteText.value - ) - } - ) + ShareAction(onClick = { shareNote(activity, titleText, noteText) }) } ) }, content = { - LazyColumn { - item { - TextField( - modifier = Modifier - .fillMaxWidth() - .padding(16.dp, 0.dp, 16.dp, 0.dp) - .background(MaterialTheme.colors.background), - label = { Text(text = "Title") }, - textStyle = TextStyle( - color = MaterialTheme.colors.onPrimary, - fontWeight = FontWeight.Bold, - fontSize = 24.sp - ), - value = titleText.value, - onValueChange = { titleText.value = it } - ) - } - item { - TextField( - modifier = Modifier - .fillMaxWidth() - .fillMaxHeight() - .padding(16.dp, 0.dp, 16.dp, 0.dp) - .background(MaterialTheme.colors.background), - label = { Text(text = "Write something...") }, - textStyle = TextStyle( - color = MaterialTheme.colors.onPrimary, - fontSize = 16.sp - ), - value = noteText.value, - onValueChange = { noteText.value = it } - ) - } + Column( + Modifier.scrollable( + rememberScrollState(), + orientation = Orientation.Vertical + ) + ) { + NoteTitleField( + modifier = Modifier + .fillMaxWidth() + .padding(16.dp, 0.dp, 16.dp, 0.dp) + .background(MaterialTheme.colors.background), + value = titleText, + onTextChange = { titleText = it } + ) + + NoteField( + modifier = Modifier + .fillMaxWidth() + .wrapContentHeight() + .padding(16.dp, 8.dp, 16.dp, 0.dp) + .background(MaterialTheme.colors.background), + value = noteText, + onTextChange = { noteText = it } + ) } }, floatingActionButton = { - val noteTitle = titleText.value - val noteContent = noteText.value - - if (NoteValidator.isValidNote(noteTitle, noteContent)) { + if (NoteValidator.isValidNote(titleText, noteText)) { ExtendedFloatingActionButton( text = { Text("Save", color = Color.White) }, icon = { @@ -172,7 +152,7 @@ fun NoteDetailsScreen( tint = Color.White ) }, - onClick = { viewModel.updateNote(noteTitle, noteContent) }, + onClick = { viewModel.updateNote(titleText.trim(), noteText.trim()) }, backgroundColor = MaterialTheme.colors.primary ) } else { @@ -181,17 +161,15 @@ fun NoteDetailsScreen( } ) - when (val state = updateState.value) { - is ViewState.Loading -> LoaderDialog() - is ViewState.Success -> navController.navigateUp() - is ViewState.Failed -> FailureDialog(state.message) + val registerOnStateChanged: @Composable (ViewState?) -> Unit = { state -> + when (state) { + is ViewState.Success -> navController.navigateUp() + is ViewState.Failed -> FailureDialog(state.message) + } } - when (val state = deleteState.value) { - is ViewState.Loading -> LoaderDialog() - is ViewState.Success -> navController.navigateUp() - is ViewState.Failed -> FailureDialog(state.message) - } + registerOnStateChanged(updateState.value) + registerOnStateChanged(deleteState.value) } } From 2f0cd0fd06d5b2c3b565e2248293494406b31d48 Mon Sep 17 00:00:00 2001 From: Shreyas Date: Fri, 6 Aug 2021 15:53:27 +0530 Subject: [PATCH 16/27] Use common note fields in add note screen --- .../composeapp/ui/screens/AddNotesScreen.kt | 75 +++++++++---------- 1 file changed, 35 insertions(+), 40 deletions(-) diff --git a/noty-android/app/composeapp/src/main/java/dev/shreyaspatil/noty/composeapp/ui/screens/AddNotesScreen.kt b/noty-android/app/composeapp/src/main/java/dev/shreyaspatil/noty/composeapp/ui/screens/AddNotesScreen.kt index ce4d36c9..4a0eed3e 100644 --- a/noty-android/app/composeapp/src/main/java/dev/shreyaspatil/noty/composeapp/ui/screens/AddNotesScreen.kt +++ b/noty-android/app/composeapp/src/main/java/dev/shreyaspatil/noty/composeapp/ui/screens/AddNotesScreen.kt @@ -16,18 +16,21 @@ package dev.shreyaspatil.noty.composeapp.ui.screens +import androidx.compose.animation.ExperimentalAnimationApi import androidx.compose.foundation.background +import androidx.compose.foundation.gestures.Orientation +import androidx.compose.foundation.gestures.scrollable +import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.fillMaxHeight import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.rememberScrollState import androidx.compose.material.ExtendedFloatingActionButton import androidx.compose.material.Icon import androidx.compose.material.IconButton import androidx.compose.material.MaterialTheme import androidx.compose.material.Scaffold import androidx.compose.material.Text -import androidx.compose.material.TextField import androidx.compose.material.TopAppBar import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.Done @@ -38,20 +41,20 @@ import androidx.compose.runtime.remember import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.res.painterResource -import androidx.compose.ui.text.TextStyle -import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp -import androidx.compose.ui.unit.sp import androidx.navigation.NavHostController import dev.shreyaspatil.noty.composeapp.R import dev.shreyaspatil.noty.composeapp.component.dialog.FailureDialog import dev.shreyaspatil.noty.composeapp.component.dialog.LoaderDialog +import dev.shreyaspatil.noty.composeapp.component.text.NoteField +import dev.shreyaspatil.noty.composeapp.component.text.NoteTitleField import dev.shreyaspatil.noty.core.view.ViewState import dev.shreyaspatil.noty.utils.validator.NoteValidator import dev.shreyaspatil.noty.view.viewmodel.AddNoteViewModel import kotlinx.coroutines.ExperimentalCoroutinesApi +@ExperimentalAnimationApi @ExperimentalCoroutinesApi @Composable fun AddNoteScreen( @@ -74,7 +77,7 @@ fun AddNoteScreen( TopAppBar( title = { Text( - text = "Noty", + text = "Add Note", textAlign = TextAlign.Start, color = MaterialTheme.colors.onPrimary, modifier = Modifier.fillMaxWidth() @@ -82,7 +85,7 @@ fun AddNoteScreen( }, navigationIcon = { IconButton( - modifier = Modifier.padding(12.dp, 0.dp, 0.dp, 0.dp), + modifier = Modifier.padding(4.dp, 0.dp, 0.dp, 0.dp), onClick = { navController.navigateUp() } @@ -100,39 +103,31 @@ fun AddNoteScreen( ) }, content = { - LazyColumn { - item { - TextField( - modifier = Modifier - .fillMaxWidth() - .padding(16.dp, 0.dp, 16.dp, 0.dp) - .background(MaterialTheme.colors.background), - label = { Text(text = "Title") }, - textStyle = TextStyle( - color = MaterialTheme.colors.onPrimary, - fontWeight = FontWeight.Bold, - fontSize = 24.sp - ), - value = titleText.value, - onValueChange = { titleText.value = it } - ) - } - item { - TextField( - modifier = Modifier - .fillMaxWidth() - .fillMaxHeight() - .padding(16.dp, 0.dp, 16.dp, 0.dp) - .background(MaterialTheme.colors.background), - label = { Text(text = "Write something...") }, - textStyle = TextStyle( - color = MaterialTheme.colors.onPrimary, - fontSize = 16.sp - ), - value = noteText.value, - onValueChange = { noteText.value = it } - ) - } + Column( + Modifier.scrollable( + rememberScrollState(), + orientation = Orientation.Vertical + ) + ) { + + NoteTitleField( + modifier = Modifier + .fillMaxWidth() + .padding(16.dp, 0.dp, 16.dp, 0.dp) + .background(MaterialTheme.colors.background), + value = titleText.value, + onTextChange = { titleText.value = it }, + ) + + NoteField( + modifier = Modifier + .fillMaxWidth() + .fillMaxHeight() + .padding(16.dp, 8.dp, 16.dp, 0.dp) + .background(MaterialTheme.colors.background), + value = noteText.value, + onTextChange = { noteText.value = it } + ) } }, floatingActionButton = { From 6ccfd20f82fa472fdd40fc584c0c0e282ac5b808 Mon Sep 17 00:00:00 2001 From: Shreyas Date: Fri, 6 Aug 2021 16:12:52 +0530 Subject: [PATCH 17/27] Add annotation @ExperimentalAnimationApi across usage --- .../noty/composeapp/component/text/NoteTextFields.kt | 2 -- .../shreyaspatil/noty/composeapp/navigation/NotyNavigation.kt | 2 ++ .../java/dev/shreyaspatil/noty/composeapp/ui/MainActivity.kt | 2 ++ .../dev/shreyaspatil/noty/composeapp/ui/screens/NotesScreen.kt | 2 ++ 4 files changed, 6 insertions(+), 2 deletions(-) diff --git a/noty-android/app/composeapp/src/main/java/dev/shreyaspatil/noty/composeapp/component/text/NoteTextFields.kt b/noty-android/app/composeapp/src/main/java/dev/shreyaspatil/noty/composeapp/component/text/NoteTextFields.kt index 789b812c..f825663c 100644 --- a/noty-android/app/composeapp/src/main/java/dev/shreyaspatil/noty/composeapp/component/text/NoteTextFields.kt +++ b/noty-android/app/composeapp/src/main/java/dev/shreyaspatil/noty/composeapp/component/text/NoteTextFields.kt @@ -39,7 +39,6 @@ fun NoteTitleField( textStyle = MaterialTheme.typography.h6, maxLines = 2 ) - } @ExperimentalAnimationApi @@ -56,5 +55,4 @@ fun NoteField( onTextChange = onTextChange, textStyle = TextStyle(fontSize = 18.sp, fontWeight = FontWeight.Light) ) - } diff --git a/noty-android/app/composeapp/src/main/java/dev/shreyaspatil/noty/composeapp/navigation/NotyNavigation.kt b/noty-android/app/composeapp/src/main/java/dev/shreyaspatil/noty/composeapp/navigation/NotyNavigation.kt index 458fd734..8397e830 100644 --- a/noty-android/app/composeapp/src/main/java/dev/shreyaspatil/noty/composeapp/navigation/NotyNavigation.kt +++ b/noty-android/app/composeapp/src/main/java/dev/shreyaspatil/noty/composeapp/navigation/NotyNavigation.kt @@ -16,6 +16,7 @@ package dev.shreyaspatil.noty.composeapp.navigation +import androidx.compose.animation.ExperimentalAnimationApi import androidx.compose.runtime.Composable import androidx.hilt.navigation.compose.hiltViewModel import androidx.navigation.NavType @@ -37,6 +38,7 @@ import kotlinx.coroutines.InternalCoroutinesApi const val NOTY_NAV_HOST_ROUTE = "noty-main-route" +@ExperimentalAnimationApi @InternalCoroutinesApi @ExperimentalCoroutinesApi @Composable diff --git a/noty-android/app/composeapp/src/main/java/dev/shreyaspatil/noty/composeapp/ui/MainActivity.kt b/noty-android/app/composeapp/src/main/java/dev/shreyaspatil/noty/composeapp/ui/MainActivity.kt index 655c9ec5..f5c86fe1 100644 --- a/noty-android/app/composeapp/src/main/java/dev/shreyaspatil/noty/composeapp/ui/MainActivity.kt +++ b/noty-android/app/composeapp/src/main/java/dev/shreyaspatil/noty/composeapp/ui/MainActivity.kt @@ -20,6 +20,7 @@ import android.os.Bundle import androidx.activity.compose.setContent import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatDelegate +import androidx.compose.animation.ExperimentalAnimationApi import androidx.compose.foundation.isSystemInDarkTheme import androidx.compose.material.MaterialTheme import androidx.compose.material.Surface @@ -41,6 +42,7 @@ import kotlinx.coroutines.InternalCoroutinesApi import kotlinx.coroutines.flow.collect import javax.inject.Inject +@ExperimentalAnimationApi @AndroidEntryPoint @ExperimentalCoroutinesApi @InternalCoroutinesApi diff --git a/noty-android/app/composeapp/src/main/java/dev/shreyaspatil/noty/composeapp/ui/screens/NotesScreen.kt b/noty-android/app/composeapp/src/main/java/dev/shreyaspatil/noty/composeapp/ui/screens/NotesScreen.kt index 0e5db62e..a47f1aee 100644 --- a/noty-android/app/composeapp/src/main/java/dev/shreyaspatil/noty/composeapp/ui/screens/NotesScreen.kt +++ b/noty-android/app/composeapp/src/main/java/dev/shreyaspatil/noty/composeapp/ui/screens/NotesScreen.kt @@ -16,6 +16,7 @@ package dev.shreyaspatil.noty.composeapp.ui.screens +import androidx.compose.animation.ExperimentalAnimationApi import androidx.compose.foundation.isSystemInDarkTheme import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.material.FloatingActionButton @@ -53,6 +54,7 @@ import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.InternalCoroutinesApi import kotlinx.coroutines.launch +@ExperimentalAnimationApi @InternalCoroutinesApi @ExperimentalCoroutinesApi @Composable From 85336b4e2d0fc81fa94025529f3ecb5c542b511e Mon Sep 17 00:00:00 2001 From: Shreyas Date: Fri, 6 Aug 2021 16:57:00 +0530 Subject: [PATCH 18/27] Avoid syncing multiple time after every re-composition --- .../noty/composeapp/ui/screens/NotesScreen.kt | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/noty-android/app/composeapp/src/main/java/dev/shreyaspatil/noty/composeapp/ui/screens/NotesScreen.kt b/noty-android/app/composeapp/src/main/java/dev/shreyaspatil/noty/composeapp/ui/screens/NotesScreen.kt index a47f1aee..a220ca55 100644 --- a/noty-android/app/composeapp/src/main/java/dev/shreyaspatil/noty/composeapp/ui/screens/NotesScreen.kt +++ b/noty-android/app/composeapp/src/main/java/dev/shreyaspatil/noty/composeapp/ui/screens/NotesScreen.kt @@ -31,6 +31,9 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.saveable.rememberSaveable +import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.platform.LocalContext @@ -108,9 +111,15 @@ fun NotesScreen(navController: NavHostController, viewModel: NotesViewModel) { ) }, content = { + var isSynced by rememberSaveable { mutableStateOf(false) } + val notesState = viewModel.notes.collectAsState(initial = ViewState.loading()).value val syncState = viewModel.syncState.collectAsState(initial = ViewState.loading()).value + // Check whether it's already synced in the past composition + // Or also check whether current state is also successful or not + isSynced = isSynced || syncState is ViewState.Success + val isRefreshing = (notesState is ViewState.Loading) or (syncState is ViewState.Loading) SwipeRefresh( @@ -125,7 +134,11 @@ fun NotesScreen(navController: NavHostController, viewModel: NotesViewModel) { } } - LaunchedEffect(true) { viewModel.syncNotes() } + LaunchedEffect(true) { + if (!isSynced) { + viewModel.syncNotes() + } + } }, floatingActionButton = { FloatingActionButton( From 8232f4109966a3c157d77678ccb87b033230a527 Mon Sep 17 00:00:00 2001 From: Shreyas Date: Fri, 6 Aug 2021 17:01:01 +0530 Subject: [PATCH 19/27] Add details of Accompanist in docs --- docs/pages/noty-android/getting-started.md | 2 ++ noty-android/README.md | 1 + 2 files changed, 3 insertions(+) diff --git a/docs/pages/noty-android/getting-started.md b/docs/pages/noty-android/getting-started.md index 7efecad1..cc3d072e 100644 --- a/docs/pages/noty-android/getting-started.md +++ b/docs/pages/noty-android/getting-started.md @@ -86,4 +86,6 @@ For single source of data. Implements `local` and `remote` modules. - [Jetpack Compose UI Toolkit](https://developer.android.com/jetpack/compose) - Modern UI development toolkit. +- [Accompanist](https://google.github.io/accompanist/) - Accompanist is a group of libraries that aim to supplement Jetpack Compose with features that are commonly required by developers but not yet available. + - [LeakCanary](https://square.github.io/leakcanary/) - Memory leak detection library for Android diff --git a/noty-android/README.md b/noty-android/README.md index dea77579..0724e30f 100644 --- a/noty-android/README.md +++ b/noty-android/README.md @@ -81,6 +81,7 @@ Design of this awesome application is implemented by [Sanju S](https://github.co - [Moshi Converter](https://github.com/square/retrofit/tree/master/retrofit-converters/moshi) - A Converter which uses Moshi for serialization to and from JSON. - [Material Components for Android](https://github.com/material-components/material-components-android) - Modular and customizable Material Design UI components for Android. - [Jetpack Compose UI Toolkit](https://developer.android.com/jetpack/compose) - Modern UI development toolkit. +- [Accompanist](https://google.github.io/accompanist/) - Accompanist is a group of libraries that aim to supplement Jetpack Compose with features that are commonly required by developers but not yet available. - [LeakCanary](https://square.github.io/leakcanary/) - Memory leak detection library for Android ## Modules From 14814257a0311ba09b2fafc23a58b013214fc3f2 Mon Sep 17 00:00:00 2001 From: Shreyas Date: Fri, 6 Aug 2021 17:09:50 +0530 Subject: [PATCH 20/27] Bump datastore version to 1.0.0 --- noty-android/dependencies.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/noty-android/dependencies.gradle b/noty-android/dependencies.gradle index 98857b24..4ac9627d 100644 --- a/noty-android/dependencies.gradle +++ b/noty-android/dependencies.gradle @@ -34,7 +34,7 @@ ext { navigationVersion = "2.3.5" securityCryptoVersion = '1.1.0-alpha03' roomVersion = "2.3.0" - dataStoreVersion = '1.0.0-rc02' + dataStoreVersion = '1.0.0' legacySupportVersion = "1.0.0" // Design From 04c89d96b3bd2069475b93baee50ca98304ced05 Mon Sep 17 00:00:00 2001 From: Shreyas Date: Fri, 6 Aug 2021 17:14:38 +0530 Subject: [PATCH 21/27] Keep file name and key token private in session manager --- .../java/dev/shreyaspatil/noty/session/SessionManagerImpl.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/noty-android/app/src/main/java/dev/shreyaspatil/noty/session/SessionManagerImpl.kt b/noty-android/app/src/main/java/dev/shreyaspatil/noty/session/SessionManagerImpl.kt index 2356a8f5..3f5cb945 100644 --- a/noty-android/app/src/main/java/dev/shreyaspatil/noty/session/SessionManagerImpl.kt +++ b/noty-android/app/src/main/java/dev/shreyaspatil/noty/session/SessionManagerImpl.kt @@ -42,7 +42,7 @@ class SessionManagerImpl(context: Context) : SessionManager { override fun getToken(): String? = sharedPreferences.getString(KEY_TOKEN, null) companion object { - const val FILE_NAME = "auth_shared_pref" - const val KEY_TOKEN = "auth_token" + private const val FILE_NAME = "auth_shared_pref" + private const val KEY_TOKEN = "auth_token" } } From 424c4673e3b3abcbcb6a1641f7a3afc43535fd60 Mon Sep 17 00:00:00 2001 From: Shreyas Date: Fri, 6 Aug 2021 17:27:14 +0530 Subject: [PATCH 22/27] Annotate utility classes with ExperimentalAnimationApi usage --- .../noty/composeapp/utils/AssistedViewModelUtils.kt | 3 +++ 1 file changed, 3 insertions(+) diff --git a/noty-android/app/composeapp/src/main/java/dev/shreyaspatil/noty/composeapp/utils/AssistedViewModelUtils.kt b/noty-android/app/composeapp/src/main/java/dev/shreyaspatil/noty/composeapp/utils/AssistedViewModelUtils.kt index 83bb062f..125b8961 100644 --- a/noty-android/app/composeapp/src/main/java/dev/shreyaspatil/noty/composeapp/utils/AssistedViewModelUtils.kt +++ b/noty-android/app/composeapp/src/main/java/dev/shreyaspatil/noty/composeapp/utils/AssistedViewModelUtils.kt @@ -17,6 +17,7 @@ package dev.shreyaspatil.noty.composeapp.utils import android.app.Activity +import androidx.compose.animation.ExperimentalAnimationApi import androidx.compose.runtime.Composable import androidx.compose.ui.platform.LocalContext import androidx.lifecycle.ViewModel @@ -29,6 +30,7 @@ import dev.shreyaspatil.noty.composeapp.ui.MainActivity import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.InternalCoroutinesApi +@ExperimentalAnimationApi @ExperimentalCoroutinesApi @InternalCoroutinesApi @Composable @@ -42,6 +44,7 @@ inline fun assistedViewModel( return viewModel(viewModelStoreOwner, factory = factory) } +@ExperimentalAnimationApi @ExperimentalCoroutinesApi @InternalCoroutinesApi @Composable From f89cd70085697e56b9ab60234ff98b1c486764af Mon Sep 17 00:00:00 2001 From: Shreyas Date: Fri, 6 Aug 2021 17:27:25 +0530 Subject: [PATCH 23/27] Use viewmodel for changing UI mode --- .../noty/composeapp/ui/screens/NotesScreen.kt | 20 +++++-------------- 1 file changed, 5 insertions(+), 15 deletions(-) diff --git a/noty-android/app/composeapp/src/main/java/dev/shreyaspatil/noty/composeapp/ui/screens/NotesScreen.kt b/noty-android/app/composeapp/src/main/java/dev/shreyaspatil/noty/composeapp/ui/screens/NotesScreen.kt index a220ca55..4d8b7a8f 100644 --- a/noty-android/app/composeapp/src/main/java/dev/shreyaspatil/noty/composeapp/ui/screens/NotesScreen.kt +++ b/noty-android/app/composeapp/src/main/java/dev/shreyaspatil/noty/composeapp/ui/screens/NotesScreen.kt @@ -32,15 +32,13 @@ import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color -import androidx.compose.ui.platform.LocalContext -import androidx.compose.ui.platform.LocalLifecycleOwner import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp -import androidx.lifecycle.lifecycleScope import androidx.navigation.NavHostController import com.google.accompanist.swiperefresh.SwipeRefresh import com.google.accompanist.swiperefresh.rememberSwipeRefreshState @@ -49,7 +47,6 @@ import dev.shreyaspatil.noty.composeapp.component.action.AboutAction import dev.shreyaspatil.noty.composeapp.component.action.LogoutAction import dev.shreyaspatil.noty.composeapp.component.action.ThemeSwitchAction import dev.shreyaspatil.noty.composeapp.component.dialog.FailureDialog -import dev.shreyaspatil.noty.composeapp.ui.MainActivity import dev.shreyaspatil.noty.composeapp.ui.Screen import dev.shreyaspatil.noty.core.view.ViewState import dev.shreyaspatil.noty.view.viewmodel.NotesViewModel @@ -67,16 +64,9 @@ fun NotesScreen(navController: NavHostController, viewModel: NotesViewModel) { return } - val lifecycleScope = LocalLifecycleOwner.current.lifecycleScope + val scope = rememberCoroutineScope() - val currentActivity = LocalContext.current as MainActivity - val darkMode by currentActivity.preferenceManager - .uiModeFlow - .collectAsState(isSystemInDarkTheme()) - - val switchTheme: () -> Unit = { - lifecycleScope.launch { currentActivity.preferenceManager.setDarkMode(!darkMode) } - } + val isInDarkMode = isSystemInDarkTheme() Scaffold( topBar = { @@ -93,7 +83,7 @@ fun NotesScreen(navController: NavHostController, viewModel: NotesViewModel) { contentColor = MaterialTheme.colors.onPrimary, elevation = 0.dp, actions = { - ThemeSwitchAction(switchTheme) + ThemeSwitchAction { viewModel.setDarkMode(!isInDarkMode) } AboutAction { navController.navigate( Screen.About.route @@ -101,7 +91,7 @@ fun NotesScreen(navController: NavHostController, viewModel: NotesViewModel) { } LogoutAction( onLogout = { - lifecycleScope.launch { + scope.launch { viewModel.clearUserSession() navigateToLogin(navController) } From 30cef591f4120927174064ed145d7a4cb20e4bdd Mon Sep 17 00:00:00 2001 From: Shreyas Date: Fri, 6 Aug 2021 17:37:51 +0530 Subject: [PATCH 24/27] Add docs for NotyTaskManager.kt --- .../noty/core/task/NotyTaskManager.kt | 33 +++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/noty-android/core/src/main/java/dev/shreyaspatil/noty/core/task/NotyTaskManager.kt b/noty-android/core/src/main/java/dev/shreyaspatil/noty/core/task/NotyTaskManager.kt index 07b9b2c0..1fc0e95e 100644 --- a/noty-android/core/src/main/java/dev/shreyaspatil/noty/core/task/NotyTaskManager.kt +++ b/noty-android/core/src/main/java/dev/shreyaspatil/noty/core/task/NotyTaskManager.kt @@ -23,10 +23,43 @@ import javax.inject.Singleton @Singleton interface NotyTaskManager { + /** + * Schedules a task for syncing notes. + * + * @return Unique work ID + */ fun syncNotes(): UUID + + /** + * Schedules a [NotyTask] task + * + * @return Unique work ID + */ fun scheduleTask(notyTask: NotyTask): UUID + + /** + * Retrieves the state of a task + * + * @param taskId Unique work ID + * @return Nullable (in case task not exists) task state + */ fun getTaskState(taskId: UUID): TaskState? + + /** + * Returns Flowable task state of a specific task + * + * @param taskId Unique work ID + * @return Flow of task state + */ fun observeTask(taskId: UUID): Flow + + /** + * Aborts/Stops all scheduled (ongoing) tasks + */ fun abortAllTasks() + + /** + * Generates task ID from note ID + */ fun getTaskIdFromNoteId(noteId: String) = noteId } From e96cc2486c2e5b9d1ec523f816eb766b1bc16932 Mon Sep 17 00:00:00 2001 From: Shreyas Date: Fri, 6 Aug 2021 17:38:18 +0530 Subject: [PATCH 25/27] Add docs for PreferenceManager.kt --- .../noty/core/preference/PreferenceManager.kt | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/noty-android/core/src/main/java/dev/shreyaspatil/noty/core/preference/PreferenceManager.kt b/noty-android/core/src/main/java/dev/shreyaspatil/noty/core/preference/PreferenceManager.kt index 238a0b19..ede21a71 100644 --- a/noty-android/core/src/main/java/dev/shreyaspatil/noty/core/preference/PreferenceManager.kt +++ b/noty-android/core/src/main/java/dev/shreyaspatil/noty/core/preference/PreferenceManager.kt @@ -16,8 +16,8 @@ package dev.shreyaspatil.noty.core.preference -import javax.inject.Singleton import kotlinx.coroutines.flow.Flow +import javax.inject.Singleton /** * Preference Manager for the application. @@ -25,6 +25,12 @@ import kotlinx.coroutines.flow.Flow */ @Singleton interface PreferenceManager { + /** + * Flow of the UI mode preference + * + * true - Dark mode enabled + * false - Dark mode disabled (i.e. Light mode) + */ val uiModeFlow: Flow /** From 7ce1cb362e17b643723e15ea329c34dfaa32f759 Mon Sep 17 00:00:00 2001 From: Shreyas Date: Fri, 6 Aug 2021 17:38:42 +0530 Subject: [PATCH 26/27] Rename ViewState -> UIDataState --- .../composeapp/ui/screens/AddNotesScreen.kt | 8 ++++---- .../ui/screens/NoteDetailsScreen.kt | 8 ++++---- .../noty/composeapp/ui/screens/NotesScreen.kt | 14 ++++++------- .../composeapp/ui/screens/SignUpScreen.kt | 8 ++++---- .../composeapp/view/screen/LoginScreen.kt | 8 ++++---- .../simpleapp/view/add/AddNoteFragment.kt | 8 ++++---- .../view/detail/NoteDetailFragment.kt | 14 ++++++------- .../simpleapp/view/login/LoginFragment.kt | 8 ++++---- .../simpleapp/view/notes/NotesFragment.kt | 18 ++++++++--------- .../view/register/RegisterFragment.kt | 8 ++++---- .../noty/view/viewmodel/AddNoteViewModel.kt | 10 +++++----- .../noty/view/viewmodel/LoginViewModel.kt | 12 +++++------ .../view/viewmodel/NoteDetailViewModel.kt | 18 ++++++++--------- .../noty/view/viewmodel/NotesViewModel.kt | 20 +++++++++---------- .../noty/view/viewmodel/RegisterViewModel.kt | 12 +++++------ .../{view/ViewState.kt => ui/UIDataState.kt} | 10 +++++----- 16 files changed, 92 insertions(+), 92 deletions(-) rename noty-android/core/src/main/java/dev/shreyaspatil/noty/core/{view/ViewState.kt => ui/UIDataState.kt} (78%) diff --git a/noty-android/app/composeapp/src/main/java/dev/shreyaspatil/noty/composeapp/ui/screens/AddNotesScreen.kt b/noty-android/app/composeapp/src/main/java/dev/shreyaspatil/noty/composeapp/ui/screens/AddNotesScreen.kt index 4a0eed3e..bc2a42f8 100644 --- a/noty-android/app/composeapp/src/main/java/dev/shreyaspatil/noty/composeapp/ui/screens/AddNotesScreen.kt +++ b/noty-android/app/composeapp/src/main/java/dev/shreyaspatil/noty/composeapp/ui/screens/AddNotesScreen.kt @@ -49,7 +49,7 @@ import dev.shreyaspatil.noty.composeapp.component.dialog.FailureDialog import dev.shreyaspatil.noty.composeapp.component.dialog.LoaderDialog import dev.shreyaspatil.noty.composeapp.component.text.NoteField import dev.shreyaspatil.noty.composeapp.component.text.NoteTitleField -import dev.shreyaspatil.noty.core.view.ViewState +import dev.shreyaspatil.noty.core.ui.UIDataState import dev.shreyaspatil.noty.utils.validator.NoteValidator import dev.shreyaspatil.noty.view.viewmodel.AddNoteViewModel import kotlinx.coroutines.ExperimentalCoroutinesApi @@ -67,9 +67,9 @@ fun AddNoteScreen( val addNoteState = viewModel.addNoteState.collectAsState(initial = null).value when (addNoteState) { - is ViewState.Loading -> LoaderDialog() - is ViewState.Success -> navController.navigateUp() - is ViewState.Failed -> FailureDialog(addNoteState.message) + is UIDataState.Loading -> LoaderDialog() + is UIDataState.Success -> navController.navigateUp() + is UIDataState.Failed -> FailureDialog(addNoteState.message) } Scaffold( diff --git a/noty-android/app/composeapp/src/main/java/dev/shreyaspatil/noty/composeapp/ui/screens/NoteDetailsScreen.kt b/noty-android/app/composeapp/src/main/java/dev/shreyaspatil/noty/composeapp/ui/screens/NoteDetailsScreen.kt index 7b47300b..977ac21a 100644 --- a/noty-android/app/composeapp/src/main/java/dev/shreyaspatil/noty/composeapp/ui/screens/NoteDetailsScreen.kt +++ b/noty-android/app/composeapp/src/main/java/dev/shreyaspatil/noty/composeapp/ui/screens/NoteDetailsScreen.kt @@ -57,7 +57,7 @@ import dev.shreyaspatil.noty.composeapp.component.dialog.FailureDialog import dev.shreyaspatil.noty.composeapp.component.text.NoteField import dev.shreyaspatil.noty.composeapp.component.text.NoteTitleField import dev.shreyaspatil.noty.composeapp.utils.ShowToast -import dev.shreyaspatil.noty.core.view.ViewState +import dev.shreyaspatil.noty.core.ui.UIDataState import dev.shreyaspatil.noty.utils.validator.NoteValidator import dev.shreyaspatil.noty.view.viewmodel.NoteDetailViewModel import kotlinx.coroutines.ExperimentalCoroutinesApi @@ -161,10 +161,10 @@ fun NoteDetailsScreen( } ) - val registerOnStateChanged: @Composable (ViewState?) -> Unit = { state -> + val registerOnStateChanged: @Composable (UIDataState?) -> Unit = { state -> when (state) { - is ViewState.Success -> navController.navigateUp() - is ViewState.Failed -> FailureDialog(state.message) + is UIDataState.Success -> navController.navigateUp() + is UIDataState.Failed -> FailureDialog(state.message) } } diff --git a/noty-android/app/composeapp/src/main/java/dev/shreyaspatil/noty/composeapp/ui/screens/NotesScreen.kt b/noty-android/app/composeapp/src/main/java/dev/shreyaspatil/noty/composeapp/ui/screens/NotesScreen.kt index 4d8b7a8f..f33a40f4 100644 --- a/noty-android/app/composeapp/src/main/java/dev/shreyaspatil/noty/composeapp/ui/screens/NotesScreen.kt +++ b/noty-android/app/composeapp/src/main/java/dev/shreyaspatil/noty/composeapp/ui/screens/NotesScreen.kt @@ -48,7 +48,7 @@ import dev.shreyaspatil.noty.composeapp.component.action.LogoutAction import dev.shreyaspatil.noty.composeapp.component.action.ThemeSwitchAction import dev.shreyaspatil.noty.composeapp.component.dialog.FailureDialog import dev.shreyaspatil.noty.composeapp.ui.Screen -import dev.shreyaspatil.noty.core.view.ViewState +import dev.shreyaspatil.noty.core.ui.UIDataState import dev.shreyaspatil.noty.view.viewmodel.NotesViewModel import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.InternalCoroutinesApi @@ -103,24 +103,24 @@ fun NotesScreen(navController: NavHostController, viewModel: NotesViewModel) { content = { var isSynced by rememberSaveable { mutableStateOf(false) } - val notesState = viewModel.notes.collectAsState(initial = ViewState.loading()).value - val syncState = viewModel.syncState.collectAsState(initial = ViewState.loading()).value + val notesState = viewModel.notes.collectAsState(initial = UIDataState.loading()).value + val syncState = viewModel.syncState.collectAsState(initial = UIDataState.loading()).value // Check whether it's already synced in the past composition // Or also check whether current state is also successful or not - isSynced = isSynced || syncState is ViewState.Success + isSynced = isSynced || syncState is UIDataState.Success - val isRefreshing = (notesState is ViewState.Loading) or (syncState is ViewState.Loading) + val isRefreshing = (notesState is UIDataState.Loading) or (syncState is UIDataState.Loading) SwipeRefresh( state = rememberSwipeRefreshState(isRefreshing), onRefresh = { viewModel.syncNotes() } ) { when (notesState) { - is ViewState.Success -> NotesList(notesState.data) { note -> + is UIDataState.Success -> NotesList(notesState.data) { note -> navController.navigate(Screen.NotesDetail.route(note.id)) } - is ViewState.Failed -> FailureDialog(notesState.message) + is UIDataState.Failed -> FailureDialog(notesState.message) } } diff --git a/noty-android/app/composeapp/src/main/java/dev/shreyaspatil/noty/composeapp/ui/screens/SignUpScreen.kt b/noty-android/app/composeapp/src/main/java/dev/shreyaspatil/noty/composeapp/ui/screens/SignUpScreen.kt index 647575ef..b1313107 100644 --- a/noty-android/app/composeapp/src/main/java/dev/shreyaspatil/noty/composeapp/ui/screens/SignUpScreen.kt +++ b/noty-android/app/composeapp/src/main/java/dev/shreyaspatil/noty/composeapp/ui/screens/SignUpScreen.kt @@ -48,7 +48,7 @@ import dev.shreyaspatil.noty.composeapp.component.text.UsernameTextField import dev.shreyaspatil.noty.composeapp.navigation.NOTY_NAV_HOST_ROUTE import dev.shreyaspatil.noty.composeapp.ui.Screen import dev.shreyaspatil.noty.composeapp.ui.theme.typography -import dev.shreyaspatil.noty.core.view.ViewState +import dev.shreyaspatil.noty.core.ui.UIDataState import dev.shreyaspatil.noty.view.viewmodel.RegisterViewModel import kotlinx.coroutines.ExperimentalCoroutinesApi @@ -62,8 +62,8 @@ fun SignUpScreen( val viewState = viewModel.authFlow.collectAsState(initial = null).value when (viewState) { - is ViewState.Loading -> LoaderDialog() - is ViewState.Success -> { + is UIDataState.Loading -> LoaderDialog() + is UIDataState.Success -> { navController.navigate( route = Screen.Notes.route, builder = { @@ -72,7 +72,7 @@ fun SignUpScreen( } ) } - is ViewState.Failed -> FailureDialog(viewState.message) + is UIDataState.Failed -> FailureDialog(viewState.message) } LazyColumn { diff --git a/noty-android/app/composeapp/src/main/java/dev/shreyaspatil/noty/composeapp/view/screen/LoginScreen.kt b/noty-android/app/composeapp/src/main/java/dev/shreyaspatil/noty/composeapp/view/screen/LoginScreen.kt index 05a48669..6f0ae993 100644 --- a/noty-android/app/composeapp/src/main/java/dev/shreyaspatil/noty/composeapp/view/screen/LoginScreen.kt +++ b/noty-android/app/composeapp/src/main/java/dev/shreyaspatil/noty/composeapp/view/screen/LoginScreen.kt @@ -51,7 +51,7 @@ import dev.shreyaspatil.noty.composeapp.component.text.TextFieldValue import dev.shreyaspatil.noty.composeapp.component.text.UsernameTextField import dev.shreyaspatil.noty.composeapp.ui.Screen import dev.shreyaspatil.noty.composeapp.ui.theme.typography -import dev.shreyaspatil.noty.core.view.ViewState +import dev.shreyaspatil.noty.core.ui.UIDataState import dev.shreyaspatil.noty.view.viewmodel.LoginViewModel import kotlinx.coroutines.ExperimentalCoroutinesApi @@ -62,9 +62,9 @@ fun LoginScreen(navController: NavHostController, loginViewModel: LoginViewModel val viewState = loginViewModel.authFlow.collectAsState(initial = null).value when (viewState) { - is ViewState.Loading -> LoaderDialog() - is ViewState.Failed -> FailureDialog(viewState.message) - is ViewState.Success -> { + is UIDataState.Loading -> LoaderDialog() + is UIDataState.Failed -> FailureDialog(viewState.message) + is UIDataState.Success -> { navController.navigate(Screen.Notes.route) { launchSingleTop = true popUpTo(Screen.Login.route) { inclusive = true } diff --git a/noty-android/app/simpleapp/src/main/java/dev/shreyaspatil/noty/simpleapp/view/add/AddNoteFragment.kt b/noty-android/app/simpleapp/src/main/java/dev/shreyaspatil/noty/simpleapp/view/add/AddNoteFragment.kt index a244dab4..26011f53 100644 --- a/noty-android/app/simpleapp/src/main/java/dev/shreyaspatil/noty/simpleapp/view/add/AddNoteFragment.kt +++ b/noty-android/app/simpleapp/src/main/java/dev/shreyaspatil/noty/simpleapp/view/add/AddNoteFragment.kt @@ -24,7 +24,7 @@ import androidx.core.widget.addTextChangedListener import androidx.lifecycle.asLiveData import androidx.navigation.fragment.findNavController import dagger.hilt.android.AndroidEntryPoint -import dev.shreyaspatil.noty.core.view.ViewState +import dev.shreyaspatil.noty.core.ui.UIDataState import dev.shreyaspatil.noty.simpleapp.databinding.AddNoteFragmentBinding import dev.shreyaspatil.noty.simpleapp.view.base.BaseFragment import dev.shreyaspatil.noty.simpleapp.view.hiltNotyMainNavGraphViewModels @@ -81,14 +81,14 @@ class AddNoteFragment : BaseFragment() private fun observeAddNoteResult() { viewModel.addNoteState.asLiveData().observe(viewLifecycleOwner) { viewState -> when (viewState) { - is ViewState.Loading -> showProgressDialog() + is UIDataState.Loading -> showProgressDialog() - is ViewState.Success -> { + is UIDataState.Success -> { hideProgressDialog() findNavController().navigateUp() } - is ViewState.Failed -> { + is UIDataState.Failed -> { hideProgressDialog() showErrorDialog("Failed to add a note", viewState.message) } diff --git a/noty-android/app/simpleapp/src/main/java/dev/shreyaspatil/noty/simpleapp/view/detail/NoteDetailFragment.kt b/noty-android/app/simpleapp/src/main/java/dev/shreyaspatil/noty/simpleapp/view/detail/NoteDetailFragment.kt index 38092391..53a5fb01 100644 --- a/noty-android/app/simpleapp/src/main/java/dev/shreyaspatil/noty/simpleapp/view/detail/NoteDetailFragment.kt +++ b/noty-android/app/simpleapp/src/main/java/dev/shreyaspatil/noty/simpleapp/view/detail/NoteDetailFragment.kt @@ -32,7 +32,7 @@ import androidx.lifecycle.lifecycleScope import androidx.navigation.fragment.findNavController import androidx.navigation.fragment.navArgs import dagger.hilt.android.AndroidEntryPoint -import dev.shreyaspatil.noty.core.view.ViewState +import dev.shreyaspatil.noty.core.ui.UIDataState import dev.shreyaspatil.noty.simpleapp.R import dev.shreyaspatil.noty.simpleapp.databinding.NoteDetailFragmentBinding import dev.shreyaspatil.noty.simpleapp.view.base.BaseFragment @@ -110,12 +110,12 @@ class NoteDetailFragment : BaseFragment when (viewState) { - is ViewState.Loading -> showProgressDialog() - is ViewState.Success -> { + is UIDataState.Loading -> showProgressDialog() + is UIDataState.Success -> { hideProgressDialog() findNavController().navigateUp() } - is ViewState.Failed -> { + is UIDataState.Failed -> { hideProgressDialog() toast("Error: ${viewState.message}") } @@ -126,12 +126,12 @@ class NoteDetailFragment : BaseFragment when (viewState) { - is ViewState.Loading -> showProgressDialog() - is ViewState.Success -> { + is UIDataState.Loading -> showProgressDialog() + is UIDataState.Success -> { hideProgressDialog() findNavController().navigateUp() } - is ViewState.Failed -> hideProgressDialog() + is UIDataState.Failed -> hideProgressDialog() } } } diff --git a/noty-android/app/simpleapp/src/main/java/dev/shreyaspatil/noty/simpleapp/view/login/LoginFragment.kt b/noty-android/app/simpleapp/src/main/java/dev/shreyaspatil/noty/simpleapp/view/login/LoginFragment.kt index 172686e8..3e6caa59 100644 --- a/noty-android/app/simpleapp/src/main/java/dev/shreyaspatil/noty/simpleapp/view/login/LoginFragment.kt +++ b/noty-android/app/simpleapp/src/main/java/dev/shreyaspatil/noty/simpleapp/view/login/LoginFragment.kt @@ -23,7 +23,7 @@ import android.view.ViewGroup import androidx.lifecycle.asLiveData import androidx.navigation.fragment.findNavController import dagger.hilt.android.AndroidEntryPoint -import dev.shreyaspatil.noty.core.view.ViewState +import dev.shreyaspatil.noty.core.ui.UIDataState import dev.shreyaspatil.noty.simpleapp.R import dev.shreyaspatil.noty.simpleapp.databinding.LoginFragmentBinding import dev.shreyaspatil.noty.simpleapp.view.base.BaseFragment @@ -47,12 +47,12 @@ class LoginFragment : BaseFragment() { private fun initData() { viewModel.authFlow.asLiveData().observe(viewLifecycleOwner) { viewState -> when (viewState) { - is ViewState.Loading -> showProgressDialog() - is ViewState.Success -> { + is UIDataState.Loading -> showProgressDialog() + is UIDataState.Success -> { hideProgressDialog() onAuthSuccess() } - is ViewState.Failed -> { + is UIDataState.Failed -> { hideProgressDialog() showErrorDialog( title = getString(R.string.dialog_title_login_failed), diff --git a/noty-android/app/simpleapp/src/main/java/dev/shreyaspatil/noty/simpleapp/view/notes/NotesFragment.kt b/noty-android/app/simpleapp/src/main/java/dev/shreyaspatil/noty/simpleapp/view/notes/NotesFragment.kt index 5a2d3b89..21d1f39e 100644 --- a/noty-android/app/simpleapp/src/main/java/dev/shreyaspatil/noty/simpleapp/view/notes/NotesFragment.kt +++ b/noty-android/app/simpleapp/src/main/java/dev/shreyaspatil/noty/simpleapp/view/notes/NotesFragment.kt @@ -27,7 +27,7 @@ import androidx.lifecycle.lifecycleScope import androidx.navigation.fragment.findNavController import dagger.hilt.android.AndroidEntryPoint import dev.shreyaspatil.noty.core.model.Note -import dev.shreyaspatil.noty.core.view.ViewState +import dev.shreyaspatil.noty.core.ui.UIDataState import dev.shreyaspatil.noty.simpleapp.R import dev.shreyaspatil.noty.simpleapp.databinding.NotesFragmentBinding import dev.shreyaspatil.noty.simpleapp.view.base.BaseFragment @@ -93,7 +93,7 @@ class NotesFragment : BaseFragment() { viewLifecycleOwner.lifecycleScope.launchWhenStarted { viewModel.notes.first().let { notesState -> when { - notesState is ViewState.Success -> notesListAdapter.submitList(notesState.data) + notesState is UIDataState.Success -> notesListAdapter.submitList(notesState.data) notesListAdapter.itemCount == 0 -> syncNotes() } } @@ -109,12 +109,12 @@ class NotesFragment : BaseFragment() { private fun observeNotes() { viewModel.notes.asLiveData().observe(viewLifecycleOwner) { when (it) { - is ViewState.Loading -> binding.swipeRefreshNotes.isRefreshing = true - is ViewState.Success -> onNotesLoaded(it.data).also { + is UIDataState.Loading -> binding.swipeRefreshNotes.isRefreshing = true + is UIDataState.Success -> onNotesLoaded(it.data).also { binding.swipeRefreshNotes.isRefreshing = false } - is ViewState.Failed -> { + is UIDataState.Failed -> { binding.swipeRefreshNotes.isRefreshing = false toast("Error: ${it.message}") } @@ -125,9 +125,9 @@ class NotesFragment : BaseFragment() { private fun observeSync() { viewModel.syncState.asLiveData().observe(viewLifecycleOwner) { when (it) { - is ViewState.Loading -> binding.swipeRefreshNotes.isRefreshing = true - is ViewState.Success -> binding.swipeRefreshNotes.isRefreshing = false - is ViewState.Failed -> { + is UIDataState.Loading -> binding.swipeRefreshNotes.isRefreshing = true + is UIDataState.Success -> binding.swipeRefreshNotes.isRefreshing = false + is UIDataState.Failed -> { binding.swipeRefreshNotes.isRefreshing = false toast("Sync Error: ${it.message}") } @@ -239,7 +239,7 @@ class NotesFragment : BaseFragment() { } private suspend fun shouldSyncNotes() = viewModel.notes.first() - .let { state -> state is ViewState.Failed || notesListAdapter.itemCount == 0 } + .let { state -> state is UIDataState.Failed || notesListAdapter.itemCount == 0 } override fun getViewBinding( inflater: LayoutInflater, diff --git a/noty-android/app/simpleapp/src/main/java/dev/shreyaspatil/noty/simpleapp/view/register/RegisterFragment.kt b/noty-android/app/simpleapp/src/main/java/dev/shreyaspatil/noty/simpleapp/view/register/RegisterFragment.kt index f811188e..6facc20a 100644 --- a/noty-android/app/simpleapp/src/main/java/dev/shreyaspatil/noty/simpleapp/view/register/RegisterFragment.kt +++ b/noty-android/app/simpleapp/src/main/java/dev/shreyaspatil/noty/simpleapp/view/register/RegisterFragment.kt @@ -23,7 +23,7 @@ import android.view.ViewGroup import androidx.lifecycle.asLiveData import androidx.navigation.fragment.findNavController import dagger.hilt.android.AndroidEntryPoint -import dev.shreyaspatil.noty.core.view.ViewState +import dev.shreyaspatil.noty.core.ui.UIDataState import dev.shreyaspatil.noty.simpleapp.R import dev.shreyaspatil.noty.simpleapp.databinding.RegisterFragmentBinding import dev.shreyaspatil.noty.simpleapp.view.base.BaseFragment @@ -47,12 +47,12 @@ class RegisterFragment : BaseFragment when (viewState) { - is ViewState.Loading -> showProgressDialog() - is ViewState.Success -> { + is UIDataState.Loading -> showProgressDialog() + is UIDataState.Success -> { hideProgressDialog() onAuthSuccess() } - is ViewState.Failed -> { + is UIDataState.Failed -> { hideProgressDialog() showErrorDialog( title = getString(R.string.dialog_title_signup_failed), diff --git a/noty-android/app/src/main/java/dev/shreyaspatil/noty/view/viewmodel/AddNoteViewModel.kt b/noty-android/app/src/main/java/dev/shreyaspatil/noty/view/viewmodel/AddNoteViewModel.kt index 24a47f6d..5aaf4be9 100644 --- a/noty-android/app/src/main/java/dev/shreyaspatil/noty/view/viewmodel/AddNoteViewModel.kt +++ b/noty-android/app/src/main/java/dev/shreyaspatil/noty/view/viewmodel/AddNoteViewModel.kt @@ -23,7 +23,7 @@ import dev.shreyaspatil.noty.core.model.NotyTask import dev.shreyaspatil.noty.core.repository.NotyNoteRepository import dev.shreyaspatil.noty.core.repository.ResponseResult import dev.shreyaspatil.noty.core.task.NotyTaskManager -import dev.shreyaspatil.noty.core.view.ViewState +import dev.shreyaspatil.noty.core.ui.UIDataState import dev.shreyaspatil.noty.di.LocalRepository import dev.shreyaspatil.noty.utils.ext.shareWhileObserved import kotlinx.coroutines.ExperimentalCoroutinesApi @@ -41,21 +41,21 @@ class AddNoteViewModel @Inject constructor( var job: Job? = null - private val _addNoteState = MutableSharedFlow>() + private val _addNoteState = MutableSharedFlow>() val addNoteState = _addNoteState.shareWhileObserved(viewModelScope) fun addNote(title: String, note: String) { job?.cancel() job = viewModelScope.launch { - _addNoteState.emit(ViewState.loading()) + _addNoteState.emit(UIDataState.loading()) val state = when (val result = noteRepository.addNote(title, note)) { is ResponseResult.Success -> { val noteId = result.data scheduleNoteCreate(noteId) - ViewState.success(noteId) + UIDataState.success(noteId) } - is ResponseResult.Error -> ViewState.failed(result.message) + is ResponseResult.Error -> UIDataState.failed(result.message) } _addNoteState.emit(state) diff --git a/noty-android/app/src/main/java/dev/shreyaspatil/noty/view/viewmodel/LoginViewModel.kt b/noty-android/app/src/main/java/dev/shreyaspatil/noty/view/viewmodel/LoginViewModel.kt index 66026ab4..fb0bf71a 100644 --- a/noty-android/app/src/main/java/dev/shreyaspatil/noty/view/viewmodel/LoginViewModel.kt +++ b/noty-android/app/src/main/java/dev/shreyaspatil/noty/view/viewmodel/LoginViewModel.kt @@ -22,7 +22,7 @@ import dagger.hilt.android.lifecycle.HiltViewModel import dev.shreyaspatil.noty.core.repository.NotyUserRepository import dev.shreyaspatil.noty.core.repository.ResponseResult import dev.shreyaspatil.noty.core.session.SessionManager -import dev.shreyaspatil.noty.core.view.ViewState +import dev.shreyaspatil.noty.core.ui.UIDataState import dev.shreyaspatil.noty.utils.ext.shareWhileObserved import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.MutableSharedFlow @@ -37,15 +37,15 @@ class LoginViewModel @Inject constructor( private val sessionManager: SessionManager ) : ViewModel() { - private val _authFlow = MutableSharedFlow>() - val authFlow: SharedFlow> = _authFlow.shareWhileObserved(viewModelScope) + private val _authFlow = MutableSharedFlow>() + val authFlow: SharedFlow> = _authFlow.shareWhileObserved(viewModelScope) fun login( username: String, password: String ) { viewModelScope.launch { - _authFlow.emit(ViewState.loading()) + _authFlow.emit(UIDataState.loading()) val responseState = notyUserRepository.getUserByUsernameAndPassword(username, password) @@ -53,10 +53,10 @@ class LoginViewModel @Inject constructor( is ResponseResult.Success -> { val authCredential = responseState.data saveToken(authCredential.token) - ViewState.success("Authentication Successful") + UIDataState.success("Authentication Successful") } - is ResponseResult.Error -> ViewState.failed(responseState.message) + is ResponseResult.Error -> UIDataState.failed(responseState.message) } _authFlow.emit(viewState) diff --git a/noty-android/app/src/main/java/dev/shreyaspatil/noty/view/viewmodel/NoteDetailViewModel.kt b/noty-android/app/src/main/java/dev/shreyaspatil/noty/view/viewmodel/NoteDetailViewModel.kt index 197dbf93..85972e8e 100644 --- a/noty-android/app/src/main/java/dev/shreyaspatil/noty/view/viewmodel/NoteDetailViewModel.kt +++ b/noty-android/app/src/main/java/dev/shreyaspatil/noty/view/viewmodel/NoteDetailViewModel.kt @@ -31,7 +31,7 @@ import dev.shreyaspatil.noty.core.model.NotyTask import dev.shreyaspatil.noty.core.repository.NotyNoteRepository import dev.shreyaspatil.noty.core.repository.ResponseResult import dev.shreyaspatil.noty.core.task.NotyTaskManager -import dev.shreyaspatil.noty.core.view.ViewState +import dev.shreyaspatil.noty.core.ui.UIDataState import dev.shreyaspatil.noty.di.LocalRepository import dev.shreyaspatil.noty.utils.ext.shareWhileObserved import kotlinx.coroutines.ExperimentalCoroutinesApi @@ -60,16 +60,16 @@ class NoteDetailViewModel @AssistedInject constructor( private val _note = MutableSharedFlow() val note: SharedFlow = _note.shareWhileObserved(viewModelScope) - private val _updateNoteState = MutableSharedFlow>() + private val _updateNoteState = MutableSharedFlow>() val updateNoteState = _updateNoteState.shareWhileObserved(viewModelScope) - private val _deleteNoteState = MutableSharedFlow>() + private val _deleteNoteState = MutableSharedFlow>() val deleteNoteState = _deleteNoteState.shareWhileObserved(viewModelScope) fun updateNote(title: String, note: String) { job?.cancel() job = viewModelScope.launch { - _updateNoteState.emit(ViewState.loading()) + _updateNoteState.emit(UIDataState.loading()) val viewState = when (val result = noteRepository.updateNote(noteId, title, note)) { is ResponseResult.Success -> { @@ -81,9 +81,9 @@ class NoteDetailViewModel @AssistedInject constructor( scheduleNoteUpdate(noteId) } - ViewState.success(Unit) + UIDataState.success(Unit) } - is ResponseResult.Error -> ViewState.failed(result.message) + is ResponseResult.Error -> UIDataState.failed(result.message) } _updateNoteState.emit(viewState) @@ -93,7 +93,7 @@ class NoteDetailViewModel @AssistedInject constructor( fun deleteNote() { job?.cancel() job = viewModelScope.launch { - _updateNoteState.emit(ViewState.loading()) + _updateNoteState.emit(UIDataState.loading()) val viewState = when (val result = noteRepository.deleteNote(noteId)) { is ResponseResult.Success -> { @@ -102,9 +102,9 @@ class NoteDetailViewModel @AssistedInject constructor( if (!NotyNoteRepository.isTemporaryNote(noteId)) { scheduleNoteDelete(noteId) } - ViewState.success(Unit) + UIDataState.success(Unit) } - is ResponseResult.Error -> ViewState.failed(result.message) + is ResponseResult.Error -> UIDataState.failed(result.message) } _deleteNoteState.emit(viewState) } diff --git a/noty-android/app/src/main/java/dev/shreyaspatil/noty/view/viewmodel/NotesViewModel.kt b/noty-android/app/src/main/java/dev/shreyaspatil/noty/view/viewmodel/NotesViewModel.kt index 9b61d37b..8ab8f16d 100644 --- a/noty-android/app/src/main/java/dev/shreyaspatil/noty/view/viewmodel/NotesViewModel.kt +++ b/noty-android/app/src/main/java/dev/shreyaspatil/noty/view/viewmodel/NotesViewModel.kt @@ -27,7 +27,7 @@ import dev.shreyaspatil.noty.core.repository.ResponseResult import dev.shreyaspatil.noty.core.session.SessionManager import dev.shreyaspatil.noty.core.task.NotyTaskManager import dev.shreyaspatil.noty.core.task.TaskState -import dev.shreyaspatil.noty.core.view.ViewState +import dev.shreyaspatil.noty.core.ui.UIDataState import dev.shreyaspatil.noty.di.LocalRepository import dev.shreyaspatil.noty.utils.ext.shareWhileObserved import kotlinx.coroutines.Dispatchers @@ -55,17 +55,17 @@ class NotesViewModel @Inject constructor( private var syncJob: Job? = null - private val _syncState = MutableSharedFlow>() - val syncState: SharedFlow> = _syncState.shareWhileObserved(viewModelScope) + private val _syncState = MutableSharedFlow>() + val syncState: SharedFlow> = _syncState.shareWhileObserved(viewModelScope) - val notes: SharedFlow>> = notyNoteRepository.getAllNotes() + val notes: SharedFlow>> = notyNoteRepository.getAllNotes() .distinctUntilChanged() .map { result -> when (result) { - is ResponseResult.Success -> ViewState.success(result.data) - is ResponseResult.Error -> ViewState.failed(result.message) + is ResponseResult.Success -> UIDataState.success(result.data) + is ResponseResult.Error -> UIDataState.failed(result.message) } - }.onStart { emit(ViewState.loading()) } + }.onStart { emit(UIDataState.loading()) } .shareWhileObserved(viewModelScope) fun syncNotes() { @@ -76,9 +76,9 @@ class NotesViewModel @Inject constructor( try { notyTaskManager.observeTask(taskId).collect { taskState -> val viewState = when (taskState) { - TaskState.SCHEDULED -> ViewState.loading() - TaskState.COMPLETED, TaskState.CANCELLED -> ViewState.success(Unit) - TaskState.FAILED -> ViewState.failed("Failed") + TaskState.SCHEDULED -> UIDataState.loading() + TaskState.COMPLETED, TaskState.CANCELLED -> UIDataState.success(Unit) + TaskState.FAILED -> UIDataState.failed("Failed") } _syncState.emit(viewState) diff --git a/noty-android/app/src/main/java/dev/shreyaspatil/noty/view/viewmodel/RegisterViewModel.kt b/noty-android/app/src/main/java/dev/shreyaspatil/noty/view/viewmodel/RegisterViewModel.kt index effb7f14..7a9d92ad 100644 --- a/noty-android/app/src/main/java/dev/shreyaspatil/noty/view/viewmodel/RegisterViewModel.kt +++ b/noty-android/app/src/main/java/dev/shreyaspatil/noty/view/viewmodel/RegisterViewModel.kt @@ -22,7 +22,7 @@ import dagger.hilt.android.lifecycle.HiltViewModel import dev.shreyaspatil.noty.core.repository.NotyUserRepository import dev.shreyaspatil.noty.core.repository.ResponseResult import dev.shreyaspatil.noty.core.session.SessionManager -import dev.shreyaspatil.noty.core.view.ViewState +import dev.shreyaspatil.noty.core.ui.UIDataState import dev.shreyaspatil.noty.utils.ext.shareWhileObserved import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.MutableSharedFlow @@ -37,15 +37,15 @@ class RegisterViewModel @Inject constructor( private val sessionManager: SessionManager ) : ViewModel() { - private val _authFlow = MutableSharedFlow>() - val authFlow: SharedFlow> = _authFlow.shareWhileObserved(viewModelScope) + private val _authFlow = MutableSharedFlow>() + val authFlow: SharedFlow> = _authFlow.shareWhileObserved(viewModelScope) fun register( username: String, password: String ) { viewModelScope.launch { - _authFlow.emit(ViewState.loading()) + _authFlow.emit(UIDataState.loading()) val responseState = notyUserRepository.addUser(username, password) @@ -53,10 +53,10 @@ class RegisterViewModel @Inject constructor( is ResponseResult.Success -> { val authCredential = responseState.data saveToken(authCredential.token) - ViewState.success("Registration Successful") + UIDataState.success("Registration Successful") } - is ResponseResult.Error -> ViewState.failed(responseState.message) + is ResponseResult.Error -> UIDataState.failed(responseState.message) } _authFlow.emit(viewState) diff --git a/noty-android/core/src/main/java/dev/shreyaspatil/noty/core/view/ViewState.kt b/noty-android/core/src/main/java/dev/shreyaspatil/noty/core/ui/UIDataState.kt similarity index 78% rename from noty-android/core/src/main/java/dev/shreyaspatil/noty/core/view/ViewState.kt rename to noty-android/core/src/main/java/dev/shreyaspatil/noty/core/ui/UIDataState.kt index 09857318..f16fe01e 100644 --- a/noty-android/core/src/main/java/dev/shreyaspatil/noty/core/view/ViewState.kt +++ b/noty-android/core/src/main/java/dev/shreyaspatil/noty/core/ui/UIDataState.kt @@ -14,15 +14,15 @@ * limitations under the License. */ -package dev.shreyaspatil.noty.core.view +package dev.shreyaspatil.noty.core.ui /** * State for managing UI operations. */ -sealed class ViewState { - class Loading : ViewState() - class Success(val data: T) : ViewState() - class Failed(val message: String) : ViewState() +sealed class UIDataState { + class Loading : UIDataState() + class Success(val data: T) : UIDataState() + class Failed(val message: String) : UIDataState() companion object { fun loading() = Loading() From deb3c7934c1974f358176f48aa2f41a751588e32 Mon Sep 17 00:00:00 2001 From: Shreyas Date: Fri, 6 Aug 2021 18:14:21 +0530 Subject: [PATCH 27/27] Reformat source with Ktlint --- .../noty/composeapp/ui/screens/NotesScreen.kt | 12 ++++++------ .../noty/simpleapp/view/notes/NotesFragment.kt | 14 ++++++++++---- 2 files changed, 16 insertions(+), 10 deletions(-) diff --git a/noty-android/app/composeapp/src/main/java/dev/shreyaspatil/noty/composeapp/ui/screens/NotesScreen.kt b/noty-android/app/composeapp/src/main/java/dev/shreyaspatil/noty/composeapp/ui/screens/NotesScreen.kt index f33a40f4..c4772257 100644 --- a/noty-android/app/composeapp/src/main/java/dev/shreyaspatil/noty/composeapp/ui/screens/NotesScreen.kt +++ b/noty-android/app/composeapp/src/main/java/dev/shreyaspatil/noty/composeapp/ui/screens/NotesScreen.kt @@ -103,24 +103,24 @@ fun NotesScreen(navController: NavHostController, viewModel: NotesViewModel) { content = { var isSynced by rememberSaveable { mutableStateOf(false) } - val notesState = viewModel.notes.collectAsState(initial = UIDataState.loading()).value - val syncState = viewModel.syncState.collectAsState(initial = UIDataState.loading()).value + val notes = viewModel.notes.collectAsState(UIDataState.loading()).value + val syncState = viewModel.syncState.collectAsState(UIDataState.loading()).value // Check whether it's already synced in the past composition // Or also check whether current state is also successful or not isSynced = isSynced || syncState is UIDataState.Success - val isRefreshing = (notesState is UIDataState.Loading) or (syncState is UIDataState.Loading) + val isRefreshing = (notes is UIDataState.Loading) or (syncState is UIDataState.Loading) SwipeRefresh( state = rememberSwipeRefreshState(isRefreshing), onRefresh = { viewModel.syncNotes() } ) { - when (notesState) { - is UIDataState.Success -> NotesList(notesState.data) { note -> + when (notes) { + is UIDataState.Success -> NotesList(notes.data) { note -> navController.navigate(Screen.NotesDetail.route(note.id)) } - is UIDataState.Failed -> FailureDialog(notesState.message) + is UIDataState.Failed -> FailureDialog(notes.message) } } diff --git a/noty-android/app/simpleapp/src/main/java/dev/shreyaspatil/noty/simpleapp/view/notes/NotesFragment.kt b/noty-android/app/simpleapp/src/main/java/dev/shreyaspatil/noty/simpleapp/view/notes/NotesFragment.kt index 21d1f39e..1ebc48a1 100644 --- a/noty-android/app/simpleapp/src/main/java/dev/shreyaspatil/noty/simpleapp/view/notes/NotesFragment.kt +++ b/noty-android/app/simpleapp/src/main/java/dev/shreyaspatil/noty/simpleapp/view/notes/NotesFragment.kt @@ -19,7 +19,12 @@ package dev.shreyaspatil.noty.simpleapp.view.notes import android.animation.Animator import android.animation.AnimatorListenerAdapter import android.os.Bundle -import android.view.* +import android.view.LayoutInflater +import android.view.Menu +import android.view.MenuInflater +import android.view.MenuItem +import android.view.View +import android.view.ViewGroup import androidx.core.content.ContextCompat import androidx.core.content.res.ResourcesCompat import androidx.lifecycle.asLiveData @@ -33,11 +38,12 @@ import dev.shreyaspatil.noty.simpleapp.databinding.NotesFragmentBinding import dev.shreyaspatil.noty.simpleapp.view.base.BaseFragment import dev.shreyaspatil.noty.simpleapp.view.hiltNotyMainNavGraphViewModels import dev.shreyaspatil.noty.simpleapp.view.notes.adapter.NotesListAdapter -import dev.shreyaspatil.noty.utils.* +import dev.shreyaspatil.noty.utils.ConnectionState import dev.shreyaspatil.noty.utils.ext.hide import dev.shreyaspatil.noty.utils.ext.setDrawableLeft import dev.shreyaspatil.noty.utils.ext.shareWhileObserved import dev.shreyaspatil.noty.utils.ext.show +import dev.shreyaspatil.noty.utils.observeConnectivityAsFlow import dev.shreyaspatil.noty.view.viewmodel.NotesViewModel import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.Flow @@ -91,9 +97,9 @@ class NotesFragment : BaseFragment() { private fun loadNotes() { viewLifecycleOwner.lifecycleScope.launchWhenStarted { - viewModel.notes.first().let { notesState -> + viewModel.notes.first().let { notes -> when { - notesState is UIDataState.Success -> notesListAdapter.submitList(notesState.data) + notes is UIDataState.Success -> notesListAdapter.submitList(notes.data) notesListAdapter.itemCount == 0 -> syncNotes() } }