From 4bc9bd70ad46b2cf851b2e322840e9b32c4ee9f1 Mon Sep 17 00:00:00 2001 From: jiyu13 Date: Wed, 23 Jul 2025 12:42:00 -0400 Subject: [PATCH 001/261] - maps the JSON field shared_code to the Kotlin property sharedCode - maps the JSON field created_at - maps the JSON field item_count --- .../java/com/example/listifyjetapp/model/ListModel.kt | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/app/src/main/java/com/example/listifyjetapp/model/ListModel.kt b/app/src/main/java/com/example/listifyjetapp/model/ListModel.kt index 09016c6..b58a842 100644 --- a/app/src/main/java/com/example/listifyjetapp/model/ListModel.kt +++ b/app/src/main/java/com/example/listifyjetapp/model/ListModel.kt @@ -1,11 +1,14 @@ package com.example.listifyjetapp.model -import java.time.LocalDateTime +import com.google.gson.annotations.SerializedName data class ListModel( val id: Int, val name: String, val share: Boolean, - val shareCode: String, // can be UUID or String - val createdAt: LocalDateTime + //val sharedCode: String, // can be UUID or String + //val createdAt: LocalDateTime + @SerializedName("shared_code") val sharedCode: String, + @SerializedName("created_at") val createdAt: String, + @SerializedName("item_count") val itemCount: Int, ) From 6988d2048958f69194546b3fade59f7de9bffc4e Mon Sep 17 00:00:00 2001 From: jiyu13 Date: Wed, 23 Jul 2025 12:44:25 -0400 Subject: [PATCH 002/261] - add colours --- app/src/main/java/com/example/listifyjetapp/ui/theme/Color.kt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/src/main/java/com/example/listifyjetapp/ui/theme/Color.kt b/app/src/main/java/com/example/listifyjetapp/ui/theme/Color.kt index 6b65d4a..d525909 100644 --- a/app/src/main/java/com/example/listifyjetapp/ui/theme/Color.kt +++ b/app/src/main/java/com/example/listifyjetapp/ui/theme/Color.kt @@ -12,6 +12,8 @@ val Pink40 = Color(0xFF7D5260) object ListifyColor { val SplashYellow = Color(0xFFFFCA3A) + val TextBlack = Color(0xff000000) val TextDark = Color(0xff3e4e50) val TextGrey = Color(0xff858585) + val IconGreen = Color(0xff0cc25f) } From 3c32bebbfd303accd95350aadb4d333160b5d750 Mon Sep 17 00:00:00 2001 From: jiyu13 Date: Wed, 23 Jul 2025 12:48:51 -0400 Subject: [PATCH 003/261] - Create app top bar composable - check if top bar is in ListsScreen - check if goBackIcon is null --- .../listifyjetapp/widgets/ListifyTopBar.kt | 59 +++++++++++++++++++ 1 file changed, 59 insertions(+) create mode 100644 app/src/main/java/com/example/listifyjetapp/widgets/ListifyTopBar.kt diff --git a/app/src/main/java/com/example/listifyjetapp/widgets/ListifyTopBar.kt b/app/src/main/java/com/example/listifyjetapp/widgets/ListifyTopBar.kt new file mode 100644 index 0000000..fae85b5 --- /dev/null +++ b/app/src/main/java/com/example/listifyjetapp/widgets/ListifyTopBar.kt @@ -0,0 +1,59 @@ +package com.example.listifyjetapp.widgets + +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.size +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Add +import androidx.compose.material3.CenterAlignedTopAppBar +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton +import androidx.compose.material3.Text +import androidx.compose.material3.TopAppBarDefaults.topAppBarColors +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.vector.ImageVector +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp + +@OptIn(ExperimentalMaterial3Api::class) +@Preview +@Composable +fun ListifyTopBar( + title: String = "Screen Tittle", + isListsScreen: Boolean = true, + goBackIcon: ImageVector? = null, + onGoBackButtonClicked: () -> Unit = {} +) { + + CenterAlignedTopAppBar( + //modifier = Modifier.shadow(elevation = 5.dp), + colors = topAppBarColors(containerColor = Color.Transparent), + title = { + Text(text = title, fontWeight = FontWeight.ExtraBold, fontSize = 24.sp) + }, + actions = { + if (isListsScreen) { + IconButton(onClick = { }) { + Icon( + modifier = Modifier.size(24.dp), + imageVector = Icons.Default.Add, + contentDescription = "Add list icon" + ) + } + } + }, + navigationIcon = { + if (goBackIcon != null) { + Icon( + imageVector = goBackIcon, + contentDescription = "GO back icon", + modifier = Modifier.size(24.dp).clickable { onGoBackButtonClicked.invoke() } + ) + } + } + ) +} \ No newline at end of file From d227eab730984d1cd5bfe286ebe0be493e05252f Mon Sep 17 00:00:00 2001 From: jiyu13 Date: Wed, 23 Jul 2025 12:53:35 -0400 Subject: [PATCH 004/261] - Create search bar composable --- .../listifyjetapp/widgets/ListifySearchBar.kt | 61 +++++++++++++++++++ 1 file changed, 61 insertions(+) create mode 100644 app/src/main/java/com/example/listifyjetapp/widgets/ListifySearchBar.kt diff --git a/app/src/main/java/com/example/listifyjetapp/widgets/ListifySearchBar.kt b/app/src/main/java/com/example/listifyjetapp/widgets/ListifySearchBar.kt new file mode 100644 index 0000000..8443b5f --- /dev/null +++ b/app/src/main/java/com/example/listifyjetapp/widgets/ListifySearchBar.kt @@ -0,0 +1,61 @@ +package com.example.listifyjetapp.widgets + + +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.foundation.text.KeyboardActions +import androidx.compose.foundation.text.KeyboardOptions +import androidx.compose.foundation.text.input.InputTransformation.Companion.keyboardOptions +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Search +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.Icon +import androidx.compose.material3.OutlinedTextField +import androidx.compose.material3.Text +import androidx.compose.material3.TextField +import androidx.compose.material3.TextFieldDefaults +import androidx.compose.runtime.Composable +import androidx.compose.runtime.MutableState +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.text.input.ImeAction +import androidx.compose.ui.text.input.KeyboardType +import androidx.compose.ui.unit.dp +import com.example.listifyjetapp.ui.theme.ListifyColor + + +@Composable +fun ListifySearchBar( + searchTextValue: MutableState, + onValueChange: (String) -> Unit, + imeAction: ImeAction = ImeAction.Next, // what happen when you press "Next" + keyboardAction: KeyboardActions = KeyboardActions.Default // what to do when an action is triggered +) { + TextField( + modifier = Modifier.fillMaxWidth(), + shape = RoundedCornerShape(12.dp), + colors = TextFieldDefaults.colors( + unfocusedIndicatorColor = Color.Transparent, + focusedIndicatorColor = Color.Transparent, + disabledIndicatorColor = Color.Transparent, + + unfocusedLabelColor = ListifyColor.TextDark.copy(0.1f), + focusedContainerColor = ListifyColor.TextDark.copy(0.1f), + cursorColor = ListifyColor.TextDark, + ), + singleLine = true, + maxLines = 1, + leadingIcon = { + Icon( + imageVector = Icons.Default.Search, + contentDescription = "Search icon", + ) + }, + + value = searchTextValue.value, + onValueChange = onValueChange, + placeholder = { Text(text="Search") }, + keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Text), // Sets the keyboard to normal text input. + keyboardActions = keyboardAction, + ) +} \ No newline at end of file From 1449a219f8d0eea0e825e98ae1d02d6b706a2095 Mon Sep 17 00:00:00 2001 From: jiyu13 Date: Wed, 23 Jul 2025 12:59:52 -0400 Subject: [PATCH 005/261] - add isLoading state - Sets isLoading = true while waiting for the result. - resets isLoading to false --- .../screens/lists/ListsViewModel.kt | 20 ++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/app/src/main/java/com/example/listifyjetapp/screens/lists/ListsViewModel.kt b/app/src/main/java/com/example/listifyjetapp/screens/lists/ListsViewModel.kt index c96eee0..893ed77 100644 --- a/app/src/main/java/com/example/listifyjetapp/screens/lists/ListsViewModel.kt +++ b/app/src/main/java/com/example/listifyjetapp/screens/lists/ListsViewModel.kt @@ -1,29 +1,35 @@ package com.example.listifyjetapp.screens.lists import androidx.compose.runtime.mutableStateListOf +import androidx.compose.runtime.mutableStateOf import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import com.example.listifyjetapp.data.ListifyResult import com.example.listifyjetapp.model.ListModel import com.example.listifyjetapp.repository.ListsRepository import dagger.hilt.android.lifecycle.HiltViewModel +import kotlinx.coroutines.delay import kotlinx.coroutines.launch import javax.inject.Inject @HiltViewModel class ListsViewModel @Inject constructor(private val repository: ListsRepository): ViewModel() { val lists = mutableStateListOf() + var isLoading = mutableStateOf(false) fun getUserLists(userId: Int) { viewModelScope.launch { - val result = repository.getUserLists(userId = userId) - when(result) { - is ListifyResult.Success -> { - lists.clear() - lists.addAll(result.data) + isLoading.value = true + val result = repository.getUserLists(userId = userId) + when (result) { + is ListifyResult.Success -> { + lists.clear() + lists.addAll(result.data) + } + + is ListifyResult.Failure -> Unit } - is ListifyResult.Failure -> Unit - } + isLoading.value = false } } } \ No newline at end of file From fe499a36c0d49140d8469452956b103773c2a069 Mon Sep 17 00:00:00 2001 From: jiyu13 Date: Wed, 23 Jul 2025 14:31:44 -0400 Subject: [PATCH 006/261] - add top bar + search bar - track isLoading + load lists if isLoading is true --- .../screens/lists/ListifyListsScreen.kt | 139 +++++++++++++++++- 1 file changed, 131 insertions(+), 8 deletions(-) diff --git a/app/src/main/java/com/example/listifyjetapp/screens/lists/ListifyListsScreen.kt b/app/src/main/java/com/example/listifyjetapp/screens/lists/ListifyListsScreen.kt index d313af2..9f4414f 100644 --- a/app/src/main/java/com/example/listifyjetapp/screens/lists/ListifyListsScreen.kt +++ b/app/src/main/java/com/example/listifyjetapp/screens/lists/ListifyListsScreen.kt @@ -1,17 +1,42 @@ package com.example.listifyjetapp.screens.lists +import android.util.Log +import androidx.compose.foundation.background +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.items +import androidx.compose.foundation.text.KeyboardActions +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.automirrored.filled.KeyboardArrowRight +import androidx.compose.material.icons.filled.Share +import androidx.compose.material3.CircularProgressIndicator +import androidx.compose.material3.HorizontalDivider +import androidx.compose.material3.Icon import androidx.compose.material3.Scaffold import androidx.compose.material3.Surface import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.saveable.rememberSaveable +import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.platform.LocalSoftwareKeyboardController +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp import androidx.hilt.navigation.compose.hiltViewModel +import com.example.listifyjetapp.ui.theme.ListifyColor +import com.example.listifyjetapp.widgets.ListifySearchBar +import com.example.listifyjetapp.widgets.ListifyTopBar @Composable fun ListifyListsScreen( @@ -19,22 +44,120 @@ fun ListifyListsScreen( ) { LaunchedEffect(Unit) { viewModel.getUserLists(4) } - Scaffold(modifier = Modifier.fillMaxSize()) { innerPadding -> + Scaffold( + modifier = Modifier.fillMaxSize(), + topBar = { ListifyTopBar( + title = "Lists", + isListsScreen = true, + ) } + ) { innerPadding -> Surface( modifier = Modifier.fillMaxSize().padding(innerPadding) ) { + Column( + modifier = Modifier.padding(horizontal = 16.dp), + verticalArrangement = Arrangement.Top, + horizontalAlignment = Alignment.CenterHorizontally + ) { + //val lists =viewModel.lists + //Log.d("Lists", lists.toString()) + val searchTextState = rememberSaveable { mutableStateOf("") } + val keyboardController = LocalSoftwareKeyboardController.current - if (viewModel.lists.isEmpty()) { - Text(text = "You don't have any lists yet.") - } else { - LazyColumn { - items(viewModel.lists) {list -> - Text(text = list.name) + ListifySearchBar( + searchTextValue = searchTextState, + onValueChange = {searchTextState.value = it}, + keyboardAction = KeyboardActions{ + // Trigger search logic or hide keyboard + searchTextState.value.trim() // perform the search + searchTextState.value = "" // clear text field when click Done, Next, etc + keyboardController?.hide() // hide keyboard + } + ) + //Text(text = "You don't have any lists yet.") + + if (viewModel.isLoading.value) { + Box( + modifier = Modifier.fillMaxSize(), + contentAlignment = Alignment.Center + ) { + CircularProgressIndicator() + } + } else if (viewModel.lists.isEmpty()) { + Text(text = "You don't have any lists yet.") + } else { + LazyColumn(modifier = Modifier.padding( + vertical = 16.dp, + horizontal = 8.dp + )){ + + items(viewModel.lists) {list -> + Log.d("list", list.itemCount.toString()) + Row( + modifier = Modifier + .clickable { } + .padding(vertical = 16.dp) + .fillMaxWidth() + .background(Color.Transparent) + ) { + Row( + modifier = Modifier.fillMaxWidth(), + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.SpaceBetween + ) { + // List info + Column { + Text( + text = list.name, + color = ListifyColor.TextBlack, + fontSize = 20.sp, + //fontWeight = FontWeight.SemiBold + ) + + if (list.share) { + Row { + Icon( + modifier = Modifier.size(16.dp), + tint = ListifyColor.IconGreen, + imageVector = Icons.Default.Share, + contentDescription = "Shared with" + + ) + } + } + + Text( + text = list.createdAt, + color = ListifyColor.TextGrey, + fontSize = 16.sp + ) + } + // List setting menu + + Row() { + Text( + text = list.itemCount.toString(), + color = ListifyColor.TextGrey, + fontSize = 16.sp + ) + + Icon( + imageVector = Icons.AutoMirrored.Filled.KeyboardArrowRight, + contentDescription = "Forward icon" + ) + + } + + } + + } + HorizontalDivider() + + } } } } } } - } \ No newline at end of file From d2f7e6fdfb37fe3fb255f3c2bbc8190f1958deac Mon Sep 17 00:00:00 2001 From: jiyu13 Date: Wed, 23 Jul 2025 14:45:23 -0400 Subject: [PATCH 007/261] - ignore file --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index aa724b7..86f1fbc 100644 --- a/.gitignore +++ b/.gitignore @@ -13,3 +13,4 @@ .externalNativeBuild .cxx local.properties +/app/src/main/java/com/example/listifyjetapp/utils/constants/Constants.kt \ No newline at end of file From c606fb756de6ff013cc4bec01a949a8241a14959 Mon Sep 17 00:00:00 2001 From: jiyu13 Date: Wed, 23 Jul 2025 21:59:19 -0400 Subject: [PATCH 008/261] - create User model - create SharedUsers model --- .../com/example/listifyjetapp/model/User.kt | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 app/src/main/java/com/example/listifyjetapp/model/User.kt diff --git a/app/src/main/java/com/example/listifyjetapp/model/User.kt b/app/src/main/java/com/example/listifyjetapp/model/User.kt new file mode 100644 index 0000000..706255e --- /dev/null +++ b/app/src/main/java/com/example/listifyjetapp/model/User.kt @@ -0,0 +1,17 @@ +package com.example.listifyjetapp.model + +import com.google.gson.annotations.SerializedName + +data class User( + val id: Int, + val username: String, + val email: String, + val password: String, + @SerializedName("created_at") val createdAt: String +) + +data class SharedUsers( + @SerializedName("user_id") val userId: Int, + val username: String, +) + From df6e1576baa5f42e3ceaf7d6b8b3879a06728564 Mon Sep 17 00:00:00 2001 From: jiyu13 Date: Wed, 23 Jul 2025 22:01:41 -0400 Subject: [PATCH 009/261] - add sharedWidth field --- app/src/main/java/com/example/listifyjetapp/model/ListModel.kt | 1 + 1 file changed, 1 insertion(+) diff --git a/app/src/main/java/com/example/listifyjetapp/model/ListModel.kt b/app/src/main/java/com/example/listifyjetapp/model/ListModel.kt index b58a842..6d8079e 100644 --- a/app/src/main/java/com/example/listifyjetapp/model/ListModel.kt +++ b/app/src/main/java/com/example/listifyjetapp/model/ListModel.kt @@ -11,4 +11,5 @@ data class ListModel( @SerializedName("shared_code") val sharedCode: String, @SerializedName("created_at") val createdAt: String, @SerializedName("item_count") val itemCount: Int, + @SerializedName("shared_with") val sharedWith: List ) From c77faa7057f5bb35e603591008f2c12cdc3a0286 Mon Sep 17 00:00:00 2001 From: jiyu13 Date: Wed, 23 Jul 2025 22:06:36 -0400 Subject: [PATCH 010/261] - create composable to show info of each list --- .../listifyjetapp/screens/lists/ListRow.kt | 95 +++++++++++++++++++ 1 file changed, 95 insertions(+) create mode 100644 app/src/main/java/com/example/listifyjetapp/screens/lists/ListRow.kt diff --git a/app/src/main/java/com/example/listifyjetapp/screens/lists/ListRow.kt b/app/src/main/java/com/example/listifyjetapp/screens/lists/ListRow.kt new file mode 100644 index 0000000..c10a9d4 --- /dev/null +++ b/app/src/main/java/com/example/listifyjetapp/screens/lists/ListRow.kt @@ -0,0 +1,95 @@ +package com.example.listifyjetapp.screens.lists + +import androidx.compose.foundation.background +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.automirrored.filled.KeyboardArrowRight +import androidx.compose.material.icons.filled.Share +import androidx.compose.material3.HorizontalDivider +import androidx.compose.material3.Icon +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.text.style.TextOverflow +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import com.example.listifyjetapp.model.ListModel +import com.example.listifyjetapp.ui.theme.ListifyColor + +@Composable +fun ListItemComposable( + list: ListModel, + //viewModel: User +) { + Row( + modifier = Modifier + .clickable { } + .padding(vertical = 16.dp) + .fillMaxWidth() + .background(Color.Transparent) + ) { + Row( + modifier = Modifier.fillMaxWidth(), + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.SpaceBetween + ) { + // List info + Column { + Text( + text = list.name, + color = ListifyColor.TextBlack, + fontSize = 20.sp, + //fontWeight = FontWeight.SemiBold + ) + + if (list.share) { + Row { + Icon( + modifier = Modifier.size(16.dp), + tint = ListifyColor.IconGreen, + imageVector = Icons.Default.Share, + contentDescription = "Shared with" + ) + Text( + text = list.sharedWith.take(8).joinToString(", ") { it.username }, + maxLines = 1, + overflow = TextOverflow.Ellipsis + ) + } + } + + Text( + text = list.createdAt, + color = ListifyColor.TextGrey, + fontSize = 16.sp + ) + } + // List setting menu + + Row() { + Text( + text = list.itemCount.toString(), + color = ListifyColor.TextGrey, + fontSize = 16.sp + ) + + Icon( + imageVector = Icons.AutoMirrored.Filled.KeyboardArrowRight, + contentDescription = "Forward icon" + ) + + } + + } + + } + HorizontalDivider() +} \ No newline at end of file From b1347d490aa3b3e0fbf0c40fa87a42f152741f76 Mon Sep 17 00:00:00 2001 From: jiyu13 Date: Wed, 23 Jul 2025 22:08:01 -0400 Subject: [PATCH 011/261] - rename composable --- .../java/com/example/listifyjetapp/screens/lists/ListRow.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/java/com/example/listifyjetapp/screens/lists/ListRow.kt b/app/src/main/java/com/example/listifyjetapp/screens/lists/ListRow.kt index c10a9d4..46a6875 100644 --- a/app/src/main/java/com/example/listifyjetapp/screens/lists/ListRow.kt +++ b/app/src/main/java/com/example/listifyjetapp/screens/lists/ListRow.kt @@ -25,7 +25,7 @@ import com.example.listifyjetapp.model.ListModel import com.example.listifyjetapp.ui.theme.ListifyColor @Composable -fun ListItemComposable( +fun ListRow( list: ListModel, //viewModel: User ) { From f9def51e42a9ff2fae20712296d1a3c0a34a8819 Mon Sep 17 00:00:00 2001 From: jiyu13 Date: Wed, 23 Jul 2025 22:08:10 -0400 Subject: [PATCH 012/261] - render ListRow --- .../screens/lists/ListifyListsScreen.kt | 73 +------------------ 1 file changed, 1 insertion(+), 72 deletions(-) diff --git a/app/src/main/java/com/example/listifyjetapp/screens/lists/ListifyListsScreen.kt b/app/src/main/java/com/example/listifyjetapp/screens/lists/ListifyListsScreen.kt index 9f4414f..8b6ae67 100644 --- a/app/src/main/java/com/example/listifyjetapp/screens/lists/ListifyListsScreen.kt +++ b/app/src/main/java/com/example/listifyjetapp/screens/lists/ListifyListsScreen.kt @@ -1,25 +1,15 @@ package com.example.listifyjetapp.screens.lists import android.util.Log -import androidx.compose.foundation.background -import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.size import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.items import androidx.compose.foundation.text.KeyboardActions -import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.automirrored.filled.KeyboardArrowRight -import androidx.compose.material.icons.filled.Share import androidx.compose.material3.CircularProgressIndicator -import androidx.compose.material3.HorizontalDivider -import androidx.compose.material3.Icon import androidx.compose.material3.Scaffold import androidx.compose.material3.Surface import androidx.compose.material3.Text @@ -29,12 +19,9 @@ import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.Color import androidx.compose.ui.platform.LocalSoftwareKeyboardController import androidx.compose.ui.unit.dp -import androidx.compose.ui.unit.sp import androidx.hilt.navigation.compose.hiltViewModel -import com.example.listifyjetapp.ui.theme.ListifyColor import com.example.listifyjetapp.widgets.ListifySearchBar import com.example.listifyjetapp.widgets.ListifyTopBar @@ -93,66 +80,8 @@ fun ListifyListsScreen( )){ items(viewModel.lists) {list -> - Log.d("list", list.itemCount.toString()) - Row( - modifier = Modifier - .clickable { } - .padding(vertical = 16.dp) - .fillMaxWidth() - .background(Color.Transparent) - ) { - Row( - modifier = Modifier.fillMaxWidth(), - verticalAlignment = Alignment.CenterVertically, - horizontalArrangement = Arrangement.SpaceBetween - ) { - // List info - Column { - Text( - text = list.name, - color = ListifyColor.TextBlack, - fontSize = 20.sp, - //fontWeight = FontWeight.SemiBold - ) - if (list.share) { - Row { - Icon( - modifier = Modifier.size(16.dp), - tint = ListifyColor.IconGreen, - imageVector = Icons.Default.Share, - contentDescription = "Shared with" - - ) - } - } - - Text( - text = list.createdAt, - color = ListifyColor.TextGrey, - fontSize = 16.sp - ) - } - // List setting menu - - Row() { - Text( - text = list.itemCount.toString(), - color = ListifyColor.TextGrey, - fontSize = 16.sp - ) - - Icon( - imageVector = Icons.AutoMirrored.Filled.KeyboardArrowRight, - contentDescription = "Forward icon" - ) - - } - - } - - } - HorizontalDivider() + ListRow(list) } } From 8ff17e2796454698293f0c6a8554852dcf9b748b Mon Sep 17 00:00:00 2001 From: jiyu13 Date: Wed, 23 Jul 2025 22:23:30 -0400 Subject: [PATCH 013/261] - add padding to list info column - center aligned list share row - style list share text --- .../listifyjetapp/screens/lists/ListRow.kt | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/app/src/main/java/com/example/listifyjetapp/screens/lists/ListRow.kt b/app/src/main/java/com/example/listifyjetapp/screens/lists/ListRow.kt index 46a6875..a479b26 100644 --- a/app/src/main/java/com/example/listifyjetapp/screens/lists/ListRow.kt +++ b/app/src/main/java/com/example/listifyjetapp/screens/lists/ListRow.kt @@ -42,7 +42,10 @@ fun ListRow( horizontalArrangement = Arrangement.SpaceBetween ) { // List info - Column { + Column( + modifier = Modifier.padding(horizontal = 8.dp), + + ) { Text( text = list.name, color = ListifyColor.TextBlack, @@ -51,7 +54,9 @@ fun ListRow( ) if (list.share) { - Row { + Row( + verticalAlignment = Alignment.CenterVertically + ) { Icon( modifier = Modifier.size(16.dp), tint = ListifyColor.IconGreen, @@ -59,9 +64,11 @@ fun ListRow( contentDescription = "Shared with" ) Text( - text = list.sharedWith.take(8).joinToString(", ") { it.username }, + text = list.sharedWith.take(3).joinToString(", ") { it.username }, maxLines = 1, - overflow = TextOverflow.Ellipsis + overflow = TextOverflow.Ellipsis, + color = ListifyColor.TextGrey, + fontSize = 16.sp ) } } From d183880a31fd615f3a3d2517c35089641af07768 Mon Sep 17 00:00:00 2001 From: jiyu13 Date: Thu, 24 Jul 2025 15:31:17 -0400 Subject: [PATCH 014/261] - check if input is empty - show original lists if input is empty - if not show lists filtered by input --- .../example/listifyjetapp/utils/filterListItems.kt | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 app/src/main/java/com/example/listifyjetapp/utils/filterListItems.kt diff --git a/app/src/main/java/com/example/listifyjetapp/utils/filterListItems.kt b/app/src/main/java/com/example/listifyjetapp/utils/filterListItems.kt new file mode 100644 index 0000000..e1c4f94 --- /dev/null +++ b/app/src/main/java/com/example/listifyjetapp/utils/filterListItems.kt @@ -0,0 +1,14 @@ +package com.example.listifyjetapp.utils + +import android.util.Log +import com.example.listifyjetapp.model.ListModel + +fun filterListItems(input: String, listItems: List): List { + + return if (input.isEmpty()) { + listItems + } else { + listItems.filter { it.name.contains(input, ignoreCase = true) } + } + +} From 51f611b02b6e65715e1527c1f3b29e970c815956 Mon Sep 17 00:00:00 2001 From: jiyu13 Date: Thu, 24 Jul 2025 15:32:03 -0400 Subject: [PATCH 015/261] - filter lists by input --- .../screens/lists/ListifyListsScreen.kt | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/app/src/main/java/com/example/listifyjetapp/screens/lists/ListifyListsScreen.kt b/app/src/main/java/com/example/listifyjetapp/screens/lists/ListifyListsScreen.kt index 8b6ae67..1deda8c 100644 --- a/app/src/main/java/com/example/listifyjetapp/screens/lists/ListifyListsScreen.kt +++ b/app/src/main/java/com/example/listifyjetapp/screens/lists/ListifyListsScreen.kt @@ -16,12 +16,14 @@ import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalSoftwareKeyboardController import androidx.compose.ui.unit.dp import androidx.hilt.navigation.compose.hiltViewModel +import com.example.listifyjetapp.utils.filterListItems import com.example.listifyjetapp.widgets.ListifySearchBar import com.example.listifyjetapp.widgets.ListifyTopBar @@ -47,9 +49,8 @@ fun ListifyListsScreen( verticalArrangement = Arrangement.Top, horizontalAlignment = Alignment.CenterHorizontally ) { - //val lists =viewModel.lists - //Log.d("Lists", lists.toString()) - val searchTextState = rememberSaveable { mutableStateOf("") } + + val searchTextState = remember { mutableStateOf("") } val keyboardController = LocalSoftwareKeyboardController.current ListifySearchBar( @@ -78,11 +79,11 @@ fun ListifyListsScreen( vertical = 16.dp, horizontal = 8.dp )){ + // Filter lists by search input + val results = filterListItems(searchTextState.value, viewModel.lists) - items(viewModel.lists) {list -> - + items(results) {list -> ListRow(list) - } } } From 6c5ee1e17581a25da8a4dd87a27243e4dcd389fa Mon Sep 17 00:00:00 2001 From: jiyu13 Date: Thu, 24 Jul 2025 15:32:59 -0400 Subject: [PATCH 016/261] - comment out reseting search text - keep search input when done is clicked --- .../example/listifyjetapp/screens/lists/ListifyListsScreen.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/java/com/example/listifyjetapp/screens/lists/ListifyListsScreen.kt b/app/src/main/java/com/example/listifyjetapp/screens/lists/ListifyListsScreen.kt index 1deda8c..f551cbd 100644 --- a/app/src/main/java/com/example/listifyjetapp/screens/lists/ListifyListsScreen.kt +++ b/app/src/main/java/com/example/listifyjetapp/screens/lists/ListifyListsScreen.kt @@ -59,7 +59,7 @@ fun ListifyListsScreen( keyboardAction = KeyboardActions{ // Trigger search logic or hide keyboard searchTextState.value.trim() // perform the search - searchTextState.value = "" // clear text field when click Done, Next, etc + //searchTextState.value = "" // clear text field when click Done, Next, etc keyboardController?.hide() // hide keyboard } ) From f631965d2737dac5c966becd81aa0dbbf5b49bc2 Mon Sep 17 00:00:00 2001 From: jiyu13 Date: Thu, 24 Jul 2025 19:42:10 -0400 Subject: [PATCH 017/261] - create dialog --- .../listifyjetapp/components/FormDialog.kt | 47 +++++++++++++++++++ 1 file changed, 47 insertions(+) create mode 100644 app/src/main/java/com/example/listifyjetapp/components/FormDialog.kt diff --git a/app/src/main/java/com/example/listifyjetapp/components/FormDialog.kt b/app/src/main/java/com/example/listifyjetapp/components/FormDialog.kt new file mode 100644 index 0000000..9a40f38 --- /dev/null +++ b/app/src/main/java/com/example/listifyjetapp/components/FormDialog.kt @@ -0,0 +1,47 @@ +package com.example.listifyjetapp.components + +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.Card +import androidx.compose.material3.CardDefaults +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.unit.dp +import androidx.compose.ui.window.Dialog +import androidx.compose.ui.window.DialogProperties + +@Composable +fun FormDialog( + onDismiss: () -> Unit = {}, + onConfirm: () -> Unit = {} +) { + Dialog ( + onDismissRequest = onDismiss, + properties = DialogProperties(usePlatformDefaultWidth = false) + ) { + Card( + elevation = CardDefaults.cardElevation(defaultElevation = 6.dp), + shape = RoundedCornerShape(15.dp), + modifier = Modifier.fillMaxWidth(0.95f) + ) { + Column( + modifier = Modifier.fillMaxWidth().padding(15.dp) + //verticalArrangement = Arrangement.SpaceBy(25.dp) + ) { + Text( + text = "Add new list", + textAlign = TextAlign.Center + ) + + Column { + Row { } + } + } + } + } +} \ No newline at end of file From f5a09b3b493ca70b5d924dbf59075816ef839d10 Mon Sep 17 00:00:00 2001 From: jiyu13 Date: Thu, 24 Jul 2025 19:43:02 -0400 Subject: [PATCH 018/261] - pass in onAddButtonClick to handle showing dialog --- .../listifyjetapp/widgets/ListifyTopBar.kt | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/app/src/main/java/com/example/listifyjetapp/widgets/ListifyTopBar.kt b/app/src/main/java/com/example/listifyjetapp/widgets/ListifyTopBar.kt index fae85b5..adbac57 100644 --- a/app/src/main/java/com/example/listifyjetapp/widgets/ListifyTopBar.kt +++ b/app/src/main/java/com/example/listifyjetapp/widgets/ListifyTopBar.kt @@ -26,7 +26,8 @@ fun ListifyTopBar( title: String = "Screen Tittle", isListsScreen: Boolean = true, goBackIcon: ImageVector? = null, - onGoBackButtonClicked: () -> Unit = {} + onGoBackButtonClicked: () -> Unit = {}, + onAddButtonClick: () -> Unit = {} ) { CenterAlignedTopAppBar( @@ -35,17 +36,17 @@ fun ListifyTopBar( title = { Text(text = title, fontWeight = FontWeight.ExtraBold, fontSize = 24.sp) }, + actions = { - if (isListsScreen) { - IconButton(onClick = { }) { - Icon( - modifier = Modifier.size(24.dp), - imageVector = Icons.Default.Add, - contentDescription = "Add list icon" - ) - } + IconButton(onClick = { onAddButtonClick() }) { + Icon( + modifier = Modifier.size(24.dp), + imageVector = Icons.Default.Add, + contentDescription = "Add list icon" + ) } }, + navigationIcon = { if (goBackIcon != null) { Icon( From 78d51d51f7376d5d305e41eb0da7f6a10cc62c4b Mon Sep 17 00:00:00 2001 From: jiyu13 Date: Thu, 24 Jul 2025 19:43:42 -0400 Subject: [PATCH 019/261] - show FormDialog when isDialogShown is true --- .../screens/lists/ListifyListsScreen.kt | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/com/example/listifyjetapp/screens/lists/ListifyListsScreen.kt b/app/src/main/java/com/example/listifyjetapp/screens/lists/ListifyListsScreen.kt index f551cbd..b5e7c12 100644 --- a/app/src/main/java/com/example/listifyjetapp/screens/lists/ListifyListsScreen.kt +++ b/app/src/main/java/com/example/listifyjetapp/screens/lists/ListifyListsScreen.kt @@ -17,12 +17,12 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember -import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalSoftwareKeyboardController import androidx.compose.ui.unit.dp import androidx.hilt.navigation.compose.hiltViewModel +import com.example.listifyjetapp.components.FormDialog import com.example.listifyjetapp.utils.filterListItems import com.example.listifyjetapp.widgets.ListifySearchBar import com.example.listifyjetapp.widgets.ListifyTopBar @@ -33,11 +33,14 @@ fun ListifyListsScreen( ) { LaunchedEffect(Unit) { viewModel.getUserLists(4) } + val isDialogShown = remember { mutableStateOf(false) } + Scaffold( modifier = Modifier.fillMaxSize(), topBar = { ListifyTopBar( title = "Lists", isListsScreen = true, + onAddButtonClick = { isDialogShown.value = true } ) } ) { innerPadding -> @@ -63,7 +66,6 @@ fun ListifyListsScreen( keyboardController?.hide() // hide keyboard } ) - //Text(text = "You don't have any lists yet.") if (viewModel.isLoading.value) { Box( @@ -88,6 +90,13 @@ fun ListifyListsScreen( } } } + + if (isDialogShown.value) { + FormDialog( + onDismiss = { isDialogShown.value = false }, + onConfirm = {} + ) + } } } } \ No newline at end of file From b11e00e022af7b79f0bc49e3aa6ac04ae65b26ea Mon Sep 17 00:00:00 2001 From: jiyu13 Date: Thu, 24 Jul 2025 21:37:40 -0400 Subject: [PATCH 020/261] - create add list screen composable - can navigate back --- .../screens/forms/ListifyNewListScreen.kt | 63 +++++++++++++++++++ 1 file changed, 63 insertions(+) create mode 100644 app/src/main/java/com/example/listifyjetapp/screens/forms/ListifyNewListScreen.kt diff --git a/app/src/main/java/com/example/listifyjetapp/screens/forms/ListifyNewListScreen.kt b/app/src/main/java/com/example/listifyjetapp/screens/forms/ListifyNewListScreen.kt new file mode 100644 index 0000000..a051c5a --- /dev/null +++ b/app/src/main/java/com/example/listifyjetapp/screens/forms/ListifyNewListScreen.kt @@ -0,0 +1,63 @@ +package com.example.listifyjetapp.screens.forms + +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.automirrored.filled.ArrowBack +import androidx.compose.material.icons.filled.ArrowBack + +import androidx.compose.material3.Scaffold +import androidx.compose.material3.Surface +import androidx.compose.runtime.Composable +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.ui.Modifier +import androidx.compose.ui.unit.dp +import androidx.navigation.NavController +import com.example.listifyjetapp.widgets.FormInputField +import com.example.listifyjetapp.widgets.ListifyTopBar + +@Composable +fun ListifyNewListScreen( + navController: NavController, + onDismiss: () -> Unit = {}, + onConfirm: () -> Unit = {} +) { + + val formTextState = remember { mutableStateOf("") } + + Scaffold( + modifier = Modifier.fillMaxSize(), + topBar = { ListifyTopBar( + title = "New List", + isListsScreen = false, + //goBackIcon = Icons.AutoMirrored.Filled.ArrowBack, + onGoBackButtonClicked = {navController.popBackStack()}, + leftText = "Cancel", + rightText = "Save", + onAddButtonClick = { onConfirm() } + ) } + ) { innerPadding -> + + Surface( + modifier = Modifier.fillMaxSize().padding(innerPadding) + ) { + + Column( + modifier = Modifier.fillMaxWidth().padding(16.dp), + verticalArrangement = Arrangement.spacedBy(25.dp) + ) { + + Column() { + FormInputField( + textState=formTextState, + onValueChange={ formTextState.value = it } + ) + } + } + } + } +} \ No newline at end of file From cf6430a4c3ead500fbc49a604160b40d5de51999 Mon Sep 17 00:00:00 2001 From: jiyu13 Date: Thu, 24 Jul 2025 21:39:04 -0400 Subject: [PATCH 021/261] - add let text param - add rightText, right Icon param - check if new params --- .../listifyjetapp/widgets/ListifyTopBar.kt | 37 ++++++++++++++++--- 1 file changed, 32 insertions(+), 5 deletions(-) diff --git a/app/src/main/java/com/example/listifyjetapp/widgets/ListifyTopBar.kt b/app/src/main/java/com/example/listifyjetapp/widgets/ListifyTopBar.kt index adbac57..c7da4f4 100644 --- a/app/src/main/java/com/example/listifyjetapp/widgets/ListifyTopBar.kt +++ b/app/src/main/java/com/example/listifyjetapp/widgets/ListifyTopBar.kt @@ -1,6 +1,7 @@ package com.example.listifyjetapp.widgets import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.Add @@ -18,6 +19,7 @@ import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp +import com.example.listifyjetapp.ui.theme.ListifyColor @OptIn(ExperimentalMaterial3Api::class) @Preview @@ -26,6 +28,9 @@ fun ListifyTopBar( title: String = "Screen Tittle", isListsScreen: Boolean = true, goBackIcon: ImageVector? = null, + leftText: String? = "", + rightIcon: ImageVector? = null, + rightText: String? = "", onGoBackButtonClicked: () -> Unit = {}, onAddButtonClick: () -> Unit = {} ) { @@ -38,11 +43,22 @@ fun ListifyTopBar( }, actions = { - IconButton(onClick = { onAddButtonClick() }) { - Icon( - modifier = Modifier.size(24.dp), - imageVector = Icons.Default.Add, - contentDescription = "Add list icon" + if (rightIcon != null) { + IconButton(onClick = { onAddButtonClick() }) { + Icon( + modifier = Modifier.size(24.dp), + imageVector = rightIcon, //Icons.Default.Add, + contentDescription = "Add icon" + ) + } + } + + if (rightText.toString().isNotEmpty() && rightText != null) { + Text( + text = rightText, + modifier = Modifier.padding(horizontal = 16.dp), + fontSize = 20.sp, + color = ListifyColor.SplashYellow ) } }, @@ -55,6 +71,17 @@ fun ListifyTopBar( modifier = Modifier.size(24.dp).clickable { onGoBackButtonClicked.invoke() } ) } + if (leftText.toString().isNotEmpty() && leftText != null) { + Text( + text = leftText, + modifier = Modifier + .padding(horizontal = 16.dp) + .clickable { onGoBackButtonClicked.invoke() }, + fontSize = 20.sp, + color = ListifyColor.SplashYellow + ) + } + } ) } \ No newline at end of file From ebaf7cde716ff1d39bfa311bb257aa74f19f6406 Mon Sep 17 00:00:00 2001 From: jiyu13 Date: Thu, 24 Jul 2025 21:39:33 -0400 Subject: [PATCH 022/261] - add route for ListifyNewListScreen --- .../example/listifyjetapp/navigation/ListifyNavigation.kt | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/com/example/listifyjetapp/navigation/ListifyNavigation.kt b/app/src/main/java/com/example/listifyjetapp/navigation/ListifyNavigation.kt index 93eea4a..d052c78 100644 --- a/app/src/main/java/com/example/listifyjetapp/navigation/ListifyNavigation.kt +++ b/app/src/main/java/com/example/listifyjetapp/navigation/ListifyNavigation.kt @@ -4,6 +4,7 @@ import androidx.compose.runtime.Composable import androidx.navigation.compose.NavHost import androidx.navigation.compose.composable import androidx.navigation.compose.rememberNavController +import com.example.listifyjetapp.screens.forms.ListifyNewListScreen import com.example.listifyjetapp.screens.lists.ListifyListsScreen import com.example.listifyjetapp.screens.profile.ListifyProfileScreen import com.example.listifyjetapp.screens.splash.ListifySplashScreen @@ -28,7 +29,12 @@ fun ListifyNavigation() { // TODO: Define a navigation route for ListsScreen composable(ListifyScreens.ListsScreen.route) { - ListifyListsScreen() + ListifyListsScreen(navController = navController) + } + + // TODO: Define a navigation route for NewListScreen + composable(ListifyScreens.NewListScreen.route) { + ListifyNewListScreen(navController = navController) } // TODO: Define a navigation route for DetailScreen From cad53ace8b18dea7e38418e2fff8e0aba67073f6 Mon Sep 17 00:00:00 2001 From: jiyu13 Date: Thu, 24 Jul 2025 21:42:49 -0400 Subject: [PATCH 023/261] - create input field for adding new list --- .../listifyjetapp/widgets/FormInputField.kt | 46 +++++++++++++++++++ 1 file changed, 46 insertions(+) create mode 100644 app/src/main/java/com/example/listifyjetapp/widgets/FormInputField.kt diff --git a/app/src/main/java/com/example/listifyjetapp/widgets/FormInputField.kt b/app/src/main/java/com/example/listifyjetapp/widgets/FormInputField.kt new file mode 100644 index 0000000..d74a32d --- /dev/null +++ b/app/src/main/java/com/example/listifyjetapp/widgets/FormInputField.kt @@ -0,0 +1,46 @@ +package com.example.listifyjetapp.widgets + +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.foundation.text.KeyboardOptions +import androidx.compose.material3.Text +import androidx.compose.material3.TextField +import androidx.compose.material3.TextFieldDefaults +import androidx.compose.runtime.Composable +import androidx.compose.runtime.MutableState +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.text.input.KeyboardType +import androidx.compose.ui.unit.dp +import com.example.listifyjetapp.ui.theme.ListifyColor + +@Composable +fun FormInputField( + textState: MutableState, + onValueChange: (String) -> Unit, +) { + + TextField( + modifier = Modifier.fillMaxWidth(), + + shape = RoundedCornerShape(12.dp), + colors = TextFieldDefaults.colors( + unfocusedIndicatorColor = Color.White, + focusedIndicatorColor = Color.White, + disabledIndicatorColor = Color.Transparent, + + unfocusedLabelColor = ListifyColor.TextDark, + unfocusedContainerColor = ListifyColor.TextDark.copy(0.1f), + focusedContainerColor = ListifyColor.TextDark.copy(0.1f), + cursorColor = ListifyColor.TextDark, + ), + singleLine = true, + maxLines = 1, + + value = textState.value, + onValueChange = onValueChange, + placeholder = { Text(text="e.g., grocery list") }, + keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Text), // Sets the keyboard to normal text input. + ) +} \ No newline at end of file From 8710f2e9ea592b5d7040eabe9388aa3b4ee4a40b Mon Sep 17 00:00:00 2001 From: jiyu13 Date: Thu, 24 Jul 2025 21:45:35 -0400 Subject: [PATCH 024/261] - rename function --- .../listifyjetapp/screens/forms/ListifyNewListScreen.kt | 2 +- .../java/com/example/listifyjetapp/widgets/ListifyTopBar.kt | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/app/src/main/java/com/example/listifyjetapp/screens/forms/ListifyNewListScreen.kt b/app/src/main/java/com/example/listifyjetapp/screens/forms/ListifyNewListScreen.kt index a051c5a..0e8695e 100644 --- a/app/src/main/java/com/example/listifyjetapp/screens/forms/ListifyNewListScreen.kt +++ b/app/src/main/java/com/example/listifyjetapp/screens/forms/ListifyNewListScreen.kt @@ -38,7 +38,7 @@ fun ListifyNewListScreen( onGoBackButtonClicked = {navController.popBackStack()}, leftText = "Cancel", rightText = "Save", - onAddButtonClick = { onConfirm() } + onRightButtonClick = { onConfirm() } ) } ) { innerPadding -> diff --git a/app/src/main/java/com/example/listifyjetapp/widgets/ListifyTopBar.kt b/app/src/main/java/com/example/listifyjetapp/widgets/ListifyTopBar.kt index c7da4f4..9bb9f47 100644 --- a/app/src/main/java/com/example/listifyjetapp/widgets/ListifyTopBar.kt +++ b/app/src/main/java/com/example/listifyjetapp/widgets/ListifyTopBar.kt @@ -32,7 +32,7 @@ fun ListifyTopBar( rightIcon: ImageVector? = null, rightText: String? = "", onGoBackButtonClicked: () -> Unit = {}, - onAddButtonClick: () -> Unit = {} + onRightButtonClick: () -> Unit = {} ) { CenterAlignedTopAppBar( @@ -44,7 +44,7 @@ fun ListifyTopBar( actions = { if (rightIcon != null) { - IconButton(onClick = { onAddButtonClick() }) { + IconButton(onClick = { onRightButtonClick() }) { Icon( modifier = Modifier.size(24.dp), imageVector = rightIcon, //Icons.Default.Add, From 0527bd1b9e2f1f0730592939691e4e119386d7a9 Mon Sep 17 00:00:00 2001 From: jiyu13 Date: Thu, 24 Jul 2025 21:46:10 -0400 Subject: [PATCH 025/261] - add NewListScreen --- .../com/example/listifyjetapp/navigation/ListifyScreens.kt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/com/example/listifyjetapp/navigation/ListifyScreens.kt b/app/src/main/java/com/example/listifyjetapp/navigation/ListifyScreens.kt index 362fe94..e8130db 100644 --- a/app/src/main/java/com/example/listifyjetapp/navigation/ListifyScreens.kt +++ b/app/src/main/java/com/example/listifyjetapp/navigation/ListifyScreens.kt @@ -6,7 +6,8 @@ enum class ListifyScreens(val route: String) { DetailScreen("detail"), ProfileScreen("profile"), LoginScreen("login"), - SignupScreen("signup") + SignupScreen("signup"), + NewListScreen("new-list") } // navController.navigate(ListifyScreens.LoginScreen.route) \ No newline at end of file From d0c9b94f7bed70d5f113c0e378fcc4821f46e23f Mon Sep 17 00:00:00 2001 From: jiyu13 Date: Thu, 24 Jul 2025 21:46:47 -0400 Subject: [PATCH 026/261] - remove isDialogShown state + if statement - add params for top bar --- .../screens/lists/ListifyListsScreen.kt | 20 +++++++++---------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/app/src/main/java/com/example/listifyjetapp/screens/lists/ListifyListsScreen.kt b/app/src/main/java/com/example/listifyjetapp/screens/lists/ListifyListsScreen.kt index b5e7c12..9ece679 100644 --- a/app/src/main/java/com/example/listifyjetapp/screens/lists/ListifyListsScreen.kt +++ b/app/src/main/java/com/example/listifyjetapp/screens/lists/ListifyListsScreen.kt @@ -1,6 +1,5 @@ package com.example.listifyjetapp.screens.lists -import android.util.Log import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column @@ -9,6 +8,8 @@ import androidx.compose.foundation.layout.padding import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.items import androidx.compose.foundation.text.KeyboardActions +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Add import androidx.compose.material3.CircularProgressIndicator import androidx.compose.material3.Scaffold import androidx.compose.material3.Surface @@ -22,25 +23,29 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalSoftwareKeyboardController import androidx.compose.ui.unit.dp import androidx.hilt.navigation.compose.hiltViewModel -import com.example.listifyjetapp.components.FormDialog +import androidx.navigation.NavController +import com.example.listifyjetapp.navigation.ListifyScreens import com.example.listifyjetapp.utils.filterListItems import com.example.listifyjetapp.widgets.ListifySearchBar import com.example.listifyjetapp.widgets.ListifyTopBar @Composable fun ListifyListsScreen( + navController: NavController, viewModel: ListsViewModel = hiltViewModel() ) { LaunchedEffect(Unit) { viewModel.getUserLists(4) } - val isDialogShown = remember { mutableStateOf(false) } Scaffold( modifier = Modifier.fillMaxSize(), topBar = { ListifyTopBar( title = "Lists", isListsScreen = true, - onAddButtonClick = { isDialogShown.value = true } + rightIcon = Icons.Default.Add, + onRightButtonClick = { + navController.navigate(ListifyScreens.NewListScreen.route) + } ) } ) { innerPadding -> @@ -62,7 +67,6 @@ fun ListifyListsScreen( keyboardAction = KeyboardActions{ // Trigger search logic or hide keyboard searchTextState.value.trim() // perform the search - //searchTextState.value = "" // clear text field when click Done, Next, etc keyboardController?.hide() // hide keyboard } ) @@ -91,12 +95,6 @@ fun ListifyListsScreen( } } - if (isDialogShown.value) { - FormDialog( - onDismiss = { isDialogShown.value = false }, - onConfirm = {} - ) - } } } } \ No newline at end of file From 192e5f44b67e564810b5eb86a9cd9a544310a487 Mon Sep 17 00:00:00 2001 From: jiyu13 Date: Wed, 30 Jul 2025 13:37:51 -0400 Subject: [PATCH 027/261] - add BaseList + listName models --- .../com/example/listifyjetapp/model/ListModel.kt | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/com/example/listifyjetapp/model/ListModel.kt b/app/src/main/java/com/example/listifyjetapp/model/ListModel.kt index 6d8079e..d539814 100644 --- a/app/src/main/java/com/example/listifyjetapp/model/ListModel.kt +++ b/app/src/main/java/com/example/listifyjetapp/model/ListModel.kt @@ -2,7 +2,7 @@ package com.example.listifyjetapp.model import com.google.gson.annotations.SerializedName -data class ListModel( +data class BaseList( val id: Int, val name: String, val share: Boolean, @@ -10,6 +10,18 @@ data class ListModel( //val createdAt: LocalDateTime @SerializedName("shared_code") val sharedCode: String, @SerializedName("created_at") val createdAt: String, +) + +data class ListName ( + val name: String +) + +data class ListModel( + val id: Int, + val name: String, + val share: Boolean, + @SerializedName("shared_code") val sharedCode: String, + @SerializedName("created_at") val createdAt: String, @SerializedName("item_count") val itemCount: Int, @SerializedName("shared_with") val sharedWith: List ) From 66580565fa1d9161741e472a47d156188dbe0b72 Mon Sep 17 00:00:00 2001 From: jiyu13 Date: Wed, 30 Jul 2025 13:39:14 -0400 Subject: [PATCH 028/261] - add onRightButtonClick() --- .../java/com/example/listifyjetapp/widgets/ListifyTopBar.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/java/com/example/listifyjetapp/widgets/ListifyTopBar.kt b/app/src/main/java/com/example/listifyjetapp/widgets/ListifyTopBar.kt index 9bb9f47..4d88598 100644 --- a/app/src/main/java/com/example/listifyjetapp/widgets/ListifyTopBar.kt +++ b/app/src/main/java/com/example/listifyjetapp/widgets/ListifyTopBar.kt @@ -56,7 +56,7 @@ fun ListifyTopBar( if (rightText.toString().isNotEmpty() && rightText != null) { Text( text = rightText, - modifier = Modifier.padding(horizontal = 16.dp), + modifier = Modifier.padding(horizontal = 16.dp).clickable { onRightButtonClick() }, fontSize = 20.sp, color = ListifyColor.SplashYellow ) From aa168e263f4f27d760352f709202d533991dfe25 Mon Sep 17 00:00:00 2001 From: jiyu13 Date: Wed, 30 Jul 2025 15:25:51 -0400 Subject: [PATCH 029/261] - add post request insertList() api - require userId + request as params - return a ListModel object --- .../java/com/example/listifyjetapp/network/ListsAPI.kt | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/com/example/listifyjetapp/network/ListsAPI.kt b/app/src/main/java/com/example/listifyjetapp/network/ListsAPI.kt index 65b078d..97ab95f 100644 --- a/app/src/main/java/com/example/listifyjetapp/network/ListsAPI.kt +++ b/app/src/main/java/com/example/listifyjetapp/network/ListsAPI.kt @@ -2,7 +2,9 @@ package com.example.listifyjetapp.network import androidx.room.Update import com.example.listifyjetapp.model.ListModel +import com.example.listifyjetapp.model.ListName import com.example.listifyjetapp.model.User +import retrofit2.http.Body import retrofit2.http.DELETE import retrofit2.http.GET import retrofit2.http.PATCH @@ -59,7 +61,10 @@ interface ListifyAPI { // Post new list @POST("lists/{user_id}") - suspend fun createList(@Path("user_id") userId: Int) {} + suspend fun insertList( + @Path("user_id") userId: Int, + @Body request: ListName + ): ListModel // Get items by list id @GET("lists/{list_id}") From 35a307ca30e0c9cad16569993534fd5b1ebb1280 Mon Sep 17 00:00:00 2001 From: jiyu13 Date: Wed, 30 Jul 2025 15:27:16 -0400 Subject: [PATCH 030/261] - commit out broken import - rename file --- .../com/example/listifyjetapp/navigation/ListifyNavigation.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/com/example/listifyjetapp/navigation/ListifyNavigation.kt b/app/src/main/java/com/example/listifyjetapp/navigation/ListifyNavigation.kt index d052c78..3254950 100644 --- a/app/src/main/java/com/example/listifyjetapp/navigation/ListifyNavigation.kt +++ b/app/src/main/java/com/example/listifyjetapp/navigation/ListifyNavigation.kt @@ -4,9 +4,9 @@ import androidx.compose.runtime.Composable import androidx.navigation.compose.NavHost import androidx.navigation.compose.composable import androidx.navigation.compose.rememberNavController -import com.example.listifyjetapp.screens.forms.ListifyNewListScreen +import com.example.listifyjetapp.screens.newList.ListifyNewListScreen import com.example.listifyjetapp.screens.lists.ListifyListsScreen -import com.example.listifyjetapp.screens.profile.ListifyProfileScreen +//import com.example.listifyjetapp.screens.profile.ListifyProfileScreen import com.example.listifyjetapp.screens.splash.ListifySplashScreen From dadbbe835c7c3da1f3219a08cc4edbe1a49cd04d Mon Sep 17 00:00:00 2001 From: jiyu13 Date: Wed, 30 Jul 2025 17:11:14 -0400 Subject: [PATCH 031/261] - create insertListByUser api call - call insertList with userId + new list data - return a formatted ListModel response --- .../repository/ListsRepository.kt | 20 ++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/com/example/listifyjetapp/repository/ListsRepository.kt b/app/src/main/java/com/example/listifyjetapp/repository/ListsRepository.kt index e32626f..ce2cd61 100644 --- a/app/src/main/java/com/example/listifyjetapp/repository/ListsRepository.kt +++ b/app/src/main/java/com/example/listifyjetapp/repository/ListsRepository.kt @@ -1,8 +1,8 @@ package com.example.listifyjetapp.repository -import android.util.Log import com.example.listifyjetapp.data.ListifyResult import com.example.listifyjetapp.model.ListModel +import com.example.listifyjetapp.model.ListName import com.example.listifyjetapp.network.ListifyAPI import javax.inject.Inject @@ -18,4 +18,22 @@ class ListsRepository@Inject constructor(private val api: ListifyAPI){ return ListifyResult.Failure(e.message ?: "Error fetching lists") } } + + suspend fun insertListByUser(userId: Int, newListData: ListName): ListifyResult { + try { + val response = api.insertList(userId, newListData) + val formatedResponse = ListModel ( + id = response.id, + name = response.name, + share = response.share, + sharedCode = response.sharedCode, + createdAt = response.createdAt, + itemCount = 0, + sharedWith= emptyList() + ) + return ListifyResult.Success(data = formatedResponse) + } catch (e:Exception) { + return ListifyResult.Failure(e.message ?: "Error creating new list") + } + } } \ No newline at end of file From 7c2420f8836a4e4cb9869c8322d404ccf467e60b Mon Sep 17 00:00:00 2001 From: jiyu13 Date: Wed, 30 Jul 2025 21:35:19 -0400 Subject: [PATCH 032/261] - implement list creation with state management - create insertListByUser suspense function to handle loading state + API response + navigation - add navigationComplete() to reset navigation state - implement StateFlow-based navigation stat --- .../screens/lists/ListsViewModel.kt | 25 ++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/com/example/listifyjetapp/screens/lists/ListsViewModel.kt b/app/src/main/java/com/example/listifyjetapp/screens/lists/ListsViewModel.kt index 893ed77..07086c4 100644 --- a/app/src/main/java/com/example/listifyjetapp/screens/lists/ListsViewModel.kt +++ b/app/src/main/java/com/example/listifyjetapp/screens/lists/ListsViewModel.kt @@ -4,11 +4,14 @@ import androidx.compose.runtime.mutableStateListOf import androidx.compose.runtime.mutableStateOf import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope +import androidx.navigation.NavController import com.example.listifyjetapp.data.ListifyResult import com.example.listifyjetapp.model.ListModel +import com.example.listifyjetapp.model.ListName import com.example.listifyjetapp.repository.ListsRepository import dagger.hilt.android.lifecycle.HiltViewModel -import kotlinx.coroutines.delay +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.launch import javax.inject.Inject @@ -17,6 +20,9 @@ class ListsViewModel @Inject constructor(private val repository: ListsRepository val lists = mutableStateListOf() var isLoading = mutableStateOf(false) + private val _navigateBack = MutableStateFlow(false) + val navigateBack = _navigateBack.asStateFlow() + fun getUserLists(userId: Int) { viewModelScope.launch { isLoading.value = true @@ -32,4 +38,21 @@ class ListsViewModel @Inject constructor(private val repository: ListsRepository isLoading.value = false } } + + fun insertListByUser(userId: Int, newListName: ListName) + = viewModelScope.launch { + isLoading.value = true + val result = repository.insertListByUser(userId, newListName) + when (result) { + is ListifyResult.Success -> { + _navigateBack.value = true + } + is ListifyResult.Failure -> Unit + } + isLoading.value = false + } + + fun navigationComplete() { + _navigateBack.value = false + } } \ No newline at end of file From 1b945797087049152941fb1228a7e7bf11fe7499 Mon Sep 17 00:00:00 2001 From: jiyu13 Date: Wed, 30 Jul 2025 21:37:32 -0400 Subject: [PATCH 033/261] - add LaunchedEffect to observe navigation events - implement automatic back navigation on success - connect form submission via onSaveClick() --- .../ListifyNewListScreen.kt | 39 ++++++++++++++----- 1 file changed, 30 insertions(+), 9 deletions(-) rename app/src/main/java/com/example/listifyjetapp/screens/{forms => newList}/ListifyNewListScreen.kt (60%) diff --git a/app/src/main/java/com/example/listifyjetapp/screens/forms/ListifyNewListScreen.kt b/app/src/main/java/com/example/listifyjetapp/screens/newList/ListifyNewListScreen.kt similarity index 60% rename from app/src/main/java/com/example/listifyjetapp/screens/forms/ListifyNewListScreen.kt rename to app/src/main/java/com/example/listifyjetapp/screens/newList/ListifyNewListScreen.kt index 0e8695e..73be285 100644 --- a/app/src/main/java/com/example/listifyjetapp/screens/forms/ListifyNewListScreen.kt +++ b/app/src/main/java/com/example/listifyjetapp/screens/newList/ListifyNewListScreen.kt @@ -1,34 +1,48 @@ -package com.example.listifyjetapp.screens.forms +package com.example.listifyjetapp.screens.newList import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding -import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.automirrored.filled.ArrowBack -import androidx.compose.material.icons.filled.ArrowBack import androidx.compose.material3.Scaffold import androidx.compose.material3.Surface import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.ui.Modifier import androidx.compose.ui.unit.dp +import androidx.hilt.navigation.compose.hiltViewModel import androidx.navigation.NavController +import com.example.listifyjetapp.model.ListName +import com.example.listifyjetapp.screens.lists.ListsViewModel import com.example.listifyjetapp.widgets.FormInputField import com.example.listifyjetapp.widgets.ListifyTopBar @Composable fun ListifyNewListScreen( navController: NavController, - onDismiss: () -> Unit = {}, - onConfirm: () -> Unit = {} + viewModel: ListsViewModel = hiltViewModel() ) { val formTextState = remember { mutableStateOf("") } + LaunchedEffect(viewModel.navigateBack) { + viewModel.navigateBack.collect { navigateBack -> + if (navigateBack) { + navController.popBackStack() + viewModel.navigationComplete() + } + } + } + + fun onSaveClick() { + val listName = ListName(name = formTextState.value) + viewModel.insertListByUser(4, listName) + } + Scaffold( modifier = Modifier.fillMaxSize(), topBar = { ListifyTopBar( @@ -38,16 +52,23 @@ fun ListifyNewListScreen( onGoBackButtonClicked = {navController.popBackStack()}, leftText = "Cancel", rightText = "Save", - onRightButtonClick = { onConfirm() } + onRightButtonClick = { + // TODO: save new list + onSaveClick() + } ) } ) { innerPadding -> Surface( - modifier = Modifier.fillMaxSize().padding(innerPadding) + modifier = Modifier + .fillMaxSize() + .padding(innerPadding) ) { Column( - modifier = Modifier.fillMaxWidth().padding(16.dp), + modifier = Modifier + .fillMaxWidth() + .padding(16.dp), verticalArrangement = Arrangement.spacedBy(25.dp) ) { From 0c14a4f16da2f734101e814d5af7bfdaf505e6f1 Mon Sep 17 00:00:00 2001 From: jiyu13 Date: Thu, 31 Jul 2025 12:04:04 -0400 Subject: [PATCH 034/261] delete file --- .../listifyjetapp/components/FormDialog.kt | 47 ------------------- 1 file changed, 47 deletions(-) delete mode 100644 app/src/main/java/com/example/listifyjetapp/components/FormDialog.kt diff --git a/app/src/main/java/com/example/listifyjetapp/components/FormDialog.kt b/app/src/main/java/com/example/listifyjetapp/components/FormDialog.kt deleted file mode 100644 index 9a40f38..0000000 --- a/app/src/main/java/com/example/listifyjetapp/components/FormDialog.kt +++ /dev/null @@ -1,47 +0,0 @@ -package com.example.listifyjetapp.components - -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.shape.RoundedCornerShape -import androidx.compose.material3.Card -import androidx.compose.material3.CardDefaults -import androidx.compose.material3.Text -import androidx.compose.runtime.Composable -import androidx.compose.ui.Modifier -import androidx.compose.ui.text.style.TextAlign -import androidx.compose.ui.unit.dp -import androidx.compose.ui.window.Dialog -import androidx.compose.ui.window.DialogProperties - -@Composable -fun FormDialog( - onDismiss: () -> Unit = {}, - onConfirm: () -> Unit = {} -) { - Dialog ( - onDismissRequest = onDismiss, - properties = DialogProperties(usePlatformDefaultWidth = false) - ) { - Card( - elevation = CardDefaults.cardElevation(defaultElevation = 6.dp), - shape = RoundedCornerShape(15.dp), - modifier = Modifier.fillMaxWidth(0.95f) - ) { - Column( - modifier = Modifier.fillMaxWidth().padding(15.dp) - //verticalArrangement = Arrangement.SpaceBy(25.dp) - ) { - Text( - text = "Add new list", - textAlign = TextAlign.Center - ) - - Column { - Row { } - } - } - } - } -} \ No newline at end of file From 06230551d274c49830330c874373a1345d87b80f Mon Sep 17 00:00:00 2001 From: jiyu13 Date: Thu, 31 Jul 2025 12:04:08 -0400 Subject: [PATCH 035/261] delete file --- .../listifyjetapp/ui/screens/newList/NewListViewModel.kt | 9 --------- 1 file changed, 9 deletions(-) delete mode 100644 app/src/main/java/com/example/listifyjetapp/ui/screens/newList/NewListViewModel.kt diff --git a/app/src/main/java/com/example/listifyjetapp/ui/screens/newList/NewListViewModel.kt b/app/src/main/java/com/example/listifyjetapp/ui/screens/newList/NewListViewModel.kt deleted file mode 100644 index 86b3cb6..0000000 --- a/app/src/main/java/com/example/listifyjetapp/ui/screens/newList/NewListViewModel.kt +++ /dev/null @@ -1,9 +0,0 @@ -package com.example.listifyjetapp.ui.screens.newList - -import com.example.listifyjetapp.repository.ListsRepository -import dagger.hilt.android.lifecycle.HiltViewModel -import javax.inject.Inject - -//@HiltViewModel -//class NewListViewModel @Inject constructor(private val repository: ListsRepository){ -//} \ No newline at end of file From db1bd9fed3e0f3a04e921c836de3191e76fb89bc Mon Sep 17 00:00:00 2001 From: jiyu13 Date: Thu, 31 Jul 2025 12:05:28 -0400 Subject: [PATCH 036/261] - add sign in button - add login button - add state to control when button show up - comment out navigate to list screen when splash is done --- .../ui/screens/splash/ListifySplashScreen.kt | 79 ++++++++++++++++++- 1 file changed, 78 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/com/example/listifyjetapp/ui/screens/splash/ListifySplashScreen.kt b/app/src/main/java/com/example/listifyjetapp/ui/screens/splash/ListifySplashScreen.kt index 928bbf2..097efc9 100644 --- a/app/src/main/java/com/example/listifyjetapp/ui/screens/splash/ListifySplashScreen.kt +++ b/app/src/main/java/com/example/listifyjetapp/ui/screens/splash/ListifySplashScreen.kt @@ -3,19 +3,38 @@ package com.example.listifyjetapp.ui.screens.splash import android.view.animation.OvershootInterpolator import androidx.compose.animation.core.Animatable import androidx.compose.animation.core.tween +import androidx.compose.foundation.BorderStroke import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.automirrored.filled.ExitToApp +import androidx.compose.material.icons.filled.Email +import androidx.compose.material.icons.filled.ExitToApp +import androidx.compose.material.icons.filled.MailOutline +import androidx.compose.material3.Button +import androidx.compose.material3.ButtonColors +import androidx.compose.material3.ButtonDefaults +import androidx.compose.material3.Icon +import androidx.compose.material3.OutlinedButton import androidx.compose.material3.Surface import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.scale +import androidx.compose.ui.graphics.Color import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import androidx.navigation.NavHostController import com.example.listifyjetapp.R @@ -28,6 +47,7 @@ import kotlinx.coroutines.delay fun ListifySplashScreen(navController: NavHostController) { // TODO: Create an Animated object that holds a Float value starting at 0f val scale = remember { Animatable(initialValue = 0f) } + val isShowButtons = remember { mutableStateOf(false) } LaunchedEffect(key1 = true, block = { // key1 = true ensure it runs once only scale.animateTo( @@ -41,7 +61,11 @@ fun ListifySplashScreen(navController: NavHostController) { // when the animation is over, delay 2s before going to next screen delay(2000L) // TODO: Navigate to MainScreen - navController.navigate(ListifyScreens.ListsScreen.route) + //navController.navigate(ListifyScreens.ListsScreen.route) + + // TODO: Show login + SignIn button at the bottom + isShowButtons.value = true + }) Surface( @@ -65,5 +89,58 @@ fun ListifySplashScreen(navController: NavHostController) { color = ListifyColor.TextGrey ) } + + if (isShowButtons.value) { + Column( + modifier = Modifier.fillMaxSize().padding(16.dp), + verticalArrangement = Arrangement.Bottom, + horizontalAlignment = Alignment.CenterHorizontally + ) { + OutlinedButton( + modifier = Modifier.fillMaxWidth().padding(vertical = 6.dp), + shape = RoundedCornerShape(10.dp), + border = BorderStroke(2.dp, color = Color.White), + + onClick = { } + ) { + Icon( + imageVector = Icons.Default.MailOutline, + modifier = Modifier.size(20.dp), + contentDescription = "Sign In Email", + ) + Text( + text = "SIGN UP WITH EMAIL", + modifier = Modifier.padding(8.dp), + fontWeight = FontWeight.Bold, + fontSize = 18.sp, + + ) + } + + Button( + modifier = Modifier.fillMaxWidth().padding(vertical = 6.dp), + shape = RoundedCornerShape(10.dp), + colors = ButtonDefaults.buttonColors( + containerColor = Color.White, + contentColor = ListifyColor.TextDark + ), + onClick = { } + ) { + Icon( + imageVector = Icons.AutoMirrored.Filled.ExitToApp, + modifier = Modifier.size(20.dp), + contentDescription = "Log In" + ) + + Text( + text = "LOG IN ", + fontWeight = FontWeight.ExtraBold, + fontSize = 20.sp, + modifier = Modifier.padding(8.dp) + ) + } + } + + } } } From 396e8241ab3298ac720526b66ac4065bf1fcf295 Mon Sep 17 00:00:00 2001 From: jiyu13 Date: Thu, 31 Jul 2025 12:16:47 -0400 Subject: [PATCH 037/261] - move button columns to a separate file --- .../components/authButtons/authButtons.kt | 83 +++++++++++++++++++ .../ui/screens/splash/ListifySplashScreen.kt | 71 +--------------- 2 files changed, 85 insertions(+), 69 deletions(-) create mode 100644 app/src/main/java/com/example/listifyjetapp/components/authButtons/authButtons.kt diff --git a/app/src/main/java/com/example/listifyjetapp/components/authButtons/authButtons.kt b/app/src/main/java/com/example/listifyjetapp/components/authButtons/authButtons.kt new file mode 100644 index 0000000..d88b7fb --- /dev/null +++ b/app/src/main/java/com/example/listifyjetapp/components/authButtons/authButtons.kt @@ -0,0 +1,83 @@ +package com.example.listifyjetapp.components.authButtons + +import androidx.compose.foundation.BorderStroke +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.automirrored.filled.ExitToApp +import androidx.compose.material.icons.filled.MailOutline +import androidx.compose.material3.Button +import androidx.compose.material3.ButtonDefaults +import androidx.compose.material3.Icon +import androidx.compose.material3.OutlinedButton +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import androidx.navigation.NavController +import com.example.listifyjetapp.ui.navigation.ListifyScreens +import com.example.listifyjetapp.ui.theme.ListifyColor + +@Composable +fun authButtons(navController: NavController) { + Column( + modifier = Modifier.fillMaxSize().padding(16.dp), + verticalArrangement = Arrangement.Bottom, + horizontalAlignment = Alignment.CenterHorizontally + ) { + OutlinedButton( + modifier = Modifier.fillMaxWidth().padding(vertical = 6.dp), + shape = RoundedCornerShape(10.dp), + border = BorderStroke(2.dp, color = Color.White), + + onClick = { } + ) { + Icon( + imageVector = Icons.Default.MailOutline, + modifier = Modifier.size(20.dp), + contentDescription = "Sign In Email", + ) + Text( + text = "SIGN UP WITH EMAIL", + modifier = Modifier.padding(8.dp), + fontWeight = FontWeight.Bold, + fontSize = 18.sp, + + ) + } + + Button( + modifier = Modifier.fillMaxWidth().padding(vertical = 6.dp), + shape = RoundedCornerShape(10.dp), + colors = ButtonDefaults.buttonColors( + containerColor = Color.White, + contentColor = ListifyColor.TextDark + ), + onClick = { + navController.navigate(ListifyScreens.LoginScreen.route) + } + ) { + Icon( + imageVector = Icons.AutoMirrored.Filled.ExitToApp, + modifier = Modifier.size(20.dp), + contentDescription = "Log In" + ) + + Text( + text = "LOG IN ", + fontWeight = FontWeight.ExtraBold, + fontSize = 18.sp, + modifier = Modifier.padding(8.dp) + ) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/example/listifyjetapp/ui/screens/splash/ListifySplashScreen.kt b/app/src/main/java/com/example/listifyjetapp/ui/screens/splash/ListifySplashScreen.kt index 097efc9..d18bf6d 100644 --- a/app/src/main/java/com/example/listifyjetapp/ui/screens/splash/ListifySplashScreen.kt +++ b/app/src/main/java/com/example/listifyjetapp/ui/screens/splash/ListifySplashScreen.kt @@ -3,25 +3,9 @@ package com.example.listifyjetapp.ui.screens.splash import android.view.animation.OvershootInterpolator import androidx.compose.animation.core.Animatable import androidx.compose.animation.core.tween -import androidx.compose.foundation.BorderStroke import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.height -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.size -import androidx.compose.foundation.shape.RoundedCornerShape -import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.automirrored.filled.ExitToApp -import androidx.compose.material.icons.filled.Email -import androidx.compose.material.icons.filled.ExitToApp -import androidx.compose.material.icons.filled.MailOutline -import androidx.compose.material3.Button -import androidx.compose.material3.ButtonColors -import androidx.compose.material3.ButtonDefaults -import androidx.compose.material3.Icon -import androidx.compose.material3.OutlinedButton import androidx.compose.material3.Surface import androidx.compose.material3.Text import androidx.compose.runtime.Composable @@ -31,14 +15,12 @@ import androidx.compose.runtime.remember import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.scale -import androidx.compose.ui.graphics.Color import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.font.FontWeight -import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import androidx.navigation.NavHostController import com.example.listifyjetapp.R -import com.example.listifyjetapp.ui.navigation.ListifyScreens +import com.example.listifyjetapp.components.authButtons.authButtons import com.example.listifyjetapp.ui.theme.ListifyColor import com.example.listifyjetapp.ui.theme.barriecitoFont import kotlinx.coroutines.delay @@ -91,56 +73,7 @@ fun ListifySplashScreen(navController: NavHostController) { } if (isShowButtons.value) { - Column( - modifier = Modifier.fillMaxSize().padding(16.dp), - verticalArrangement = Arrangement.Bottom, - horizontalAlignment = Alignment.CenterHorizontally - ) { - OutlinedButton( - modifier = Modifier.fillMaxWidth().padding(vertical = 6.dp), - shape = RoundedCornerShape(10.dp), - border = BorderStroke(2.dp, color = Color.White), - - onClick = { } - ) { - Icon( - imageVector = Icons.Default.MailOutline, - modifier = Modifier.size(20.dp), - contentDescription = "Sign In Email", - ) - Text( - text = "SIGN UP WITH EMAIL", - modifier = Modifier.padding(8.dp), - fontWeight = FontWeight.Bold, - fontSize = 18.sp, - - ) - } - - Button( - modifier = Modifier.fillMaxWidth().padding(vertical = 6.dp), - shape = RoundedCornerShape(10.dp), - colors = ButtonDefaults.buttonColors( - containerColor = Color.White, - contentColor = ListifyColor.TextDark - ), - onClick = { } - ) { - Icon( - imageVector = Icons.AutoMirrored.Filled.ExitToApp, - modifier = Modifier.size(20.dp), - contentDescription = "Log In" - ) - - Text( - text = "LOG IN ", - fontWeight = FontWeight.ExtraBold, - fontSize = 20.sp, - modifier = Modifier.padding(8.dp) - ) - } - } - + authButtons(navController) } } } From d58d485ca4398638db611a8e705641e14547eb38 Mon Sep 17 00:00:00 2001 From: jiyu13 Date: Thu, 31 Jul 2025 12:17:06 -0400 Subject: [PATCH 038/261] - uncomment ListifyLoginScreen --- .../example/listifyjetapp/ui/navigation/ListifyNavigation.kt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/com/example/listifyjetapp/ui/navigation/ListifyNavigation.kt b/app/src/main/java/com/example/listifyjetapp/ui/navigation/ListifyNavigation.kt index 8a11bf7..69541f3 100644 --- a/app/src/main/java/com/example/listifyjetapp/ui/navigation/ListifyNavigation.kt +++ b/app/src/main/java/com/example/listifyjetapp/ui/navigation/ListifyNavigation.kt @@ -4,6 +4,7 @@ import androidx.compose.runtime.Composable import androidx.navigation.compose.NavHost import androidx.navigation.compose.composable import androidx.navigation.compose.rememberNavController +import com.example.listifyjetapp.ui.screens.auth.ListifyLoginScreen import com.example.listifyjetapp.ui.screens.lists.ListifyListsScreen import com.example.listifyjetapp.ui.screens.newList.ListifyNewListScreen import com.example.listifyjetapp.ui.screens.splash.ListifySplashScreen @@ -47,7 +48,7 @@ fun ListifyNavigation() { // TODO: Define a navigation route for LoginScreen composable(ListifyScreens.LoginScreen.route) { - //ListifyLoginScreen(navController = navController) + ListifyLoginScreen(navController = navController) } // TODO: Define a navigation route for SignupScreen From 30d5ab2531323fc1fd0fce5e5279a928bcb8e193 Mon Sep 17 00:00:00 2001 From: jiyu13 Date: Thu, 31 Jul 2025 15:31:36 -0400 Subject: [PATCH 039/261] - rename file --- .../components/authButtons/{authButtons.kt => AuthButtons.kt} | 2 +- .../listifyjetapp/ui/screens/splash/ListifySplashScreen.kt | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) rename app/src/main/java/com/example/listifyjetapp/components/authButtons/{authButtons.kt => AuthButtons.kt} (98%) diff --git a/app/src/main/java/com/example/listifyjetapp/components/authButtons/authButtons.kt b/app/src/main/java/com/example/listifyjetapp/components/authButtons/AuthButtons.kt similarity index 98% rename from app/src/main/java/com/example/listifyjetapp/components/authButtons/authButtons.kt rename to app/src/main/java/com/example/listifyjetapp/components/authButtons/AuthButtons.kt index d88b7fb..4fa1c0f 100644 --- a/app/src/main/java/com/example/listifyjetapp/components/authButtons/authButtons.kt +++ b/app/src/main/java/com/example/listifyjetapp/components/authButtons/AuthButtons.kt @@ -28,7 +28,7 @@ import com.example.listifyjetapp.ui.navigation.ListifyScreens import com.example.listifyjetapp.ui.theme.ListifyColor @Composable -fun authButtons(navController: NavController) { +fun AuthButtons(navController: NavController) { Column( modifier = Modifier.fillMaxSize().padding(16.dp), verticalArrangement = Arrangement.Bottom, diff --git a/app/src/main/java/com/example/listifyjetapp/ui/screens/splash/ListifySplashScreen.kt b/app/src/main/java/com/example/listifyjetapp/ui/screens/splash/ListifySplashScreen.kt index d18bf6d..57da18c 100644 --- a/app/src/main/java/com/example/listifyjetapp/ui/screens/splash/ListifySplashScreen.kt +++ b/app/src/main/java/com/example/listifyjetapp/ui/screens/splash/ListifySplashScreen.kt @@ -20,7 +20,7 @@ import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.sp import androidx.navigation.NavHostController import com.example.listifyjetapp.R -import com.example.listifyjetapp.components.authButtons.authButtons +import com.example.listifyjetapp.components.authButtons.AuthButtons import com.example.listifyjetapp.ui.theme.ListifyColor import com.example.listifyjetapp.ui.theme.barriecitoFont import kotlinx.coroutines.delay @@ -73,7 +73,7 @@ fun ListifySplashScreen(navController: NavHostController) { } if (isShowButtons.value) { - authButtons(navController) + AuthButtons(navController) } } } From 948cce89835a158cb43bc3e73f2f02c70fe74b28 Mon Sep 17 00:00:00 2001 From: jiyu13 Date: Thu, 31 Jul 2025 17:02:11 -0400 Subject: [PATCH 040/261] - add material icons implementation --- app/build.gradle.kts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 1689416..e420fab 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -105,4 +105,7 @@ dependencies { // google font implementation(libs.androidx.ui.text.google.fonts) + // material icons + implementation (libs.androidx.material.icons.extended) + } \ No newline at end of file From 03cd2409f094d435d37ada1329bd55d17db7b5e9 Mon Sep 17 00:00:00 2001 From: jiyu13 Date: Thu, 31 Jul 2025 17:02:21 -0400 Subject: [PATCH 041/261] - add material icons implementation --- gradle/libs.versions.toml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 96b3b79..f511f22 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -11,6 +11,7 @@ composeBom = "2024.09.00" coilCompose = "1.4.0" converterGson = "2.9.0" +materialIconsExtended = "1.7.8" roomKtx = "2.7.2" roomRuntime = "2.7.2" hiltCompiler = "2.56.2" @@ -27,6 +28,7 @@ okhttp = "5.0.0-alpha.2" [libraries] androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" } +androidx-material-icons-extended = { module = "androidx.compose.material:material-icons-extended", version.ref = "materialIconsExtended" } junit = { group = "junit", name = "junit", version.ref = "junit" } androidx-junit = { group = "androidx.test.ext", name = "junit", version.ref = "junitVersion" } androidx-espresso-core = { group = "androidx.test.espresso", name = "espresso-core", version.ref = "espressoCore" } From bceff1f848a03e8448f05fdfb8e5ee98b5a0d5c6 Mon Sep 17 00:00:00 2001 From: jiyu13 Date: Thu, 31 Jul 2025 17:15:38 -0400 Subject: [PATCH 042/261] - create viewModel to manage email input + validation - email mutable state to hold current input - emailHasErrors derived State to automatically recompute whenever email changes. - updateEmail() to updates the email state when the user types --- .../ui/screens/auth/EmailViewModel.kt | 25 +++++++++++++++++++ 1 file changed, 25 insertions(+) create mode 100644 app/src/main/java/com/example/listifyjetapp/ui/screens/auth/EmailViewModel.kt diff --git a/app/src/main/java/com/example/listifyjetapp/ui/screens/auth/EmailViewModel.kt b/app/src/main/java/com/example/listifyjetapp/ui/screens/auth/EmailViewModel.kt new file mode 100644 index 0000000..f5c1fe7 --- /dev/null +++ b/app/src/main/java/com/example/listifyjetapp/ui/screens/auth/EmailViewModel.kt @@ -0,0 +1,25 @@ +package com.example.listifyjetapp.ui.screens.auth + +import androidx.compose.runtime.derivedStateOf +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.setValue +import androidx.lifecycle.ViewModel + +class EmailViewModel: ViewModel() { + var email by mutableStateOf("") + private set // private set ensures that the email can only be modified via updateEmail() + + val emailHasErrors by derivedStateOf { + if (email.isNotEmpty()) { + // Email is considered erroneous until it completely matches EMAIL_ADDRESS. + !android.util.Patterns.EMAIL_ADDRESS.matcher(email).matches() + } else { + false + } + } + + fun updateEmail(input: String) { + email = input + } +} \ No newline at end of file From 96bbfefb3f296a94c946132a378e2e026ce99463 Mon Sep 17 00:00:00 2001 From: jiyu13 Date: Thu, 31 Jul 2025 17:18:17 -0400 Subject: [PATCH 043/261] - display a email field with validation feedback --- .../inputFields/ValidatingInputTextField.kt | 29 +++++++++++++++++++ 1 file changed, 29 insertions(+) create mode 100644 app/src/main/java/com/example/listifyjetapp/components/inputFields/ValidatingInputTextField.kt diff --git a/app/src/main/java/com/example/listifyjetapp/components/inputFields/ValidatingInputTextField.kt b/app/src/main/java/com/example/listifyjetapp/components/inputFields/ValidatingInputTextField.kt new file mode 100644 index 0000000..e79664f --- /dev/null +++ b/app/src/main/java/com/example/listifyjetapp/components/inputFields/ValidatingInputTextField.kt @@ -0,0 +1,29 @@ +package com.example.listifyjetapp.components.inputFields + +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.material3.OutlinedTextField +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.unit.dp + +@Composable +fun ValidatingInputTextField( + email: String, + onValueChange: (String) -> Unit, + validatorHasError: Boolean +) { + OutlinedTextField( + modifier = Modifier.fillMaxWidth().padding(16.dp), + value = email, + onValueChange = onValueChange, + label = { Text("Email") }, + isError = validatorHasError, + supportingText = { + if (validatorHasError) { + Text("Incorrect email format.") + } + } + ) +} \ No newline at end of file From fd82ce8947b5ed19644e4701420919a7bb52065a Mon Sep 17 00:00:00 2001 From: jiyu13 Date: Thu, 31 Jul 2025 20:53:00 -0400 Subject: [PATCH 044/261] - change padding --- .../components/inputFields/ValidatingInputTextField.kt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/com/example/listifyjetapp/components/inputFields/ValidatingInputTextField.kt b/app/src/main/java/com/example/listifyjetapp/components/inputFields/ValidatingInputTextField.kt index e79664f..847a5aa 100644 --- a/app/src/main/java/com/example/listifyjetapp/components/inputFields/ValidatingInputTextField.kt +++ b/app/src/main/java/com/example/listifyjetapp/components/inputFields/ValidatingInputTextField.kt @@ -15,7 +15,8 @@ fun ValidatingInputTextField( validatorHasError: Boolean ) { OutlinedTextField( - modifier = Modifier.fillMaxWidth().padding(16.dp), + modifier = Modifier.fillMaxWidth() + .padding(start = 16.dp, end=16.dp, top = 16.dp), value = email, onValueChange = onValueChange, label = { Text("Email") }, From f1ccc43af8d514420e54c56b0b07aea4e6f05271 Mon Sep 17 00:00:00 2001 From: jiyu13 Date: Thu, 31 Jul 2025 21:15:10 -0400 Subject: [PATCH 045/261] - create password input field with toggle to show/hide password - --- .../inputFields/PasswordTextField.kt | 62 +++++++++++++++++++ 1 file changed, 62 insertions(+) create mode 100644 app/src/main/java/com/example/listifyjetapp/components/inputFields/PasswordTextField.kt diff --git a/app/src/main/java/com/example/listifyjetapp/components/inputFields/PasswordTextField.kt b/app/src/main/java/com/example/listifyjetapp/components/inputFields/PasswordTextField.kt new file mode 100644 index 0000000..126ccd8 --- /dev/null +++ b/app/src/main/java/com/example/listifyjetapp/components/inputFields/PasswordTextField.kt @@ -0,0 +1,62 @@ +package com.example.listifyjetapp.components.inputFields + +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.text.KeyboardOptions +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Visibility +import androidx.compose.material.icons.filled.VisibilityOff +import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton +import androidx.compose.material3.OutlinedTextField +import androidx.compose.material3.Text +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.KeyboardType +import androidx.compose.ui.text.input.PasswordVisualTransformation +import androidx.compose.ui.text.input.VisualTransformation +import androidx.compose.ui.unit.dp + +@Composable +fun PasswordTextField( + password: String, + onPasswordChange: (String) -> Unit +) { + var isShowPassword by remember { mutableStateOf(false) } + + OutlinedTextField( + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 16.dp), + value = password, + onValueChange = onPasswordChange, + label = { Text("Password") }, + visualTransformation = if (isShowPassword) { + VisualTransformation.None + } else { + PasswordVisualTransformation() + }, + keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Password), + trailingIcon = { + if (isShowPassword) { + IconButton(onClick = { isShowPassword = false }) { + Icon( + imageVector = Icons.Filled.Visibility, + contentDescription = "show password" + ) + } + } else { + IconButton(onClick = { isShowPassword = true }) { + Icon( + imageVector = Icons.Filled.VisibilityOff, + contentDescription = "hide password" + ) + } + } + } + ) +} \ No newline at end of file From d2a0c163f791ffde39e871ae59d1a08c59fb043d Mon Sep 17 00:00:00 2001 From: jiyu13 Date: Thu, 31 Jul 2025 21:31:07 -0400 Subject: [PATCH 046/261] - create login screen - add top bar with navigation controls - add email + password input field --- .../ui/screens/auth/ListifyLoginScreen.kt | 57 +++++++++++++++++++ 1 file changed, 57 insertions(+) create mode 100644 app/src/main/java/com/example/listifyjetapp/ui/screens/auth/ListifyLoginScreen.kt diff --git a/app/src/main/java/com/example/listifyjetapp/ui/screens/auth/ListifyLoginScreen.kt b/app/src/main/java/com/example/listifyjetapp/ui/screens/auth/ListifyLoginScreen.kt new file mode 100644 index 0000000..b8dafab --- /dev/null +++ b/app/src/main/java/com/example/listifyjetapp/ui/screens/auth/ListifyLoginScreen.kt @@ -0,0 +1,57 @@ +package com.example.listifyjetapp.ui.screens.auth + +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.material3.Scaffold +import androidx.compose.material3.Surface +import androidx.compose.runtime.Composable +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.ui.Modifier +import androidx.lifecycle.viewmodel.compose.viewModel +import androidx.navigation.NavHostController +import com.example.listifyjetapp.components.inputFields.PasswordTextField +import com.example.listifyjetapp.widgets.ListifyTopBar +import com.example.listifyjetapp.components.inputFields.ValidatingInputTextField + +@Composable +fun ListifyLoginScreen( + navController: NavHostController, +) { + + val emailViewModel: EmailViewModel = viewModel() + val password = remember { mutableStateOf("") } + + Scaffold( + modifier = Modifier.fillMaxSize(), + topBar = { ListifyTopBar( + title = "LOG IN", + isListsScreen = false, + onGoBackButtonClicked = {navController.popBackStack()}, + leftText = "CANCEL", + ) } + ) { innerPadding -> + Surface( + modifier = Modifier.fillMaxSize().padding(innerPadding), + ) { + + Column( + modifier = Modifier.fillMaxWidth() + ) { + ValidatingInputTextField( + email = emailViewModel.email, + onValueChange = { input -> emailViewModel.updateEmail(input) }, + validatorHasError = emailViewModel.emailHasErrors + ) + + PasswordTextField( + password = password.value, + onPasswordChange = { password.value = it } + ) + + } + } + } +} \ No newline at end of file From a598c681a94bcc74269c916e1727c586124d55ed Mon Sep 17 00:00:00 2001 From: jiyu13 Date: Thu, 31 Jul 2025 21:57:31 -0400 Subject: [PATCH 047/261] - create reusable filled button --- .../listifyjetapp/widgets/FilledButton.kt | 56 +++++++++++++++++++ 1 file changed, 56 insertions(+) create mode 100644 app/src/main/java/com/example/listifyjetapp/widgets/FilledButton.kt diff --git a/app/src/main/java/com/example/listifyjetapp/widgets/FilledButton.kt b/app/src/main/java/com/example/listifyjetapp/widgets/FilledButton.kt new file mode 100644 index 0000000..8b5b321 --- /dev/null +++ b/app/src/main/java/com/example/listifyjetapp/widgets/FilledButton.kt @@ -0,0 +1,56 @@ +package com.example.listifyjetapp.widgets + +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.material3.Button +import androidx.compose.material3.ButtonDefaults +import androidx.compose.material3.Icon +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.Shape +import androidx.compose.ui.graphics.vector.ImageVector +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.unit.Dp +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import com.example.listifyjetapp.ui.theme.ListifyColor + +@Composable +fun FilledButton( + modifier: Modifier, + shape: Shape, + containerColor: Color, + contentColor: Color, + text: String, + buttonIcon: ImageVector? = null, + iconDescription: String?, + onClick: () -> Unit, +) { + Button( + modifier = modifier, + shape = shape, + colors = ButtonDefaults.buttonColors( + containerColor = containerColor, + contentColor = contentColor + ), + onClick = { onClick() } + ) { + if (buttonIcon != null) { + Icon( + imageVector = buttonIcon, + modifier = Modifier.size(20.dp), + contentDescription = iconDescription + ) + } + + Text( + text = text, + fontWeight = FontWeight.ExtraBold, + fontSize = 18.sp, + modifier = Modifier.padding(8.dp) + ) + } +} \ No newline at end of file From b597cebefdf4abdd1087429c9125919e094142a1 Mon Sep 17 00:00:00 2001 From: jiyu13 Date: Thu, 31 Jul 2025 21:58:01 -0400 Subject: [PATCH 048/261] - implement reusable filled button - comment out Button --- .../components/authButtons/AuthButtons.kt | 67 ++++++++++++------- 1 file changed, 42 insertions(+), 25 deletions(-) diff --git a/app/src/main/java/com/example/listifyjetapp/components/authButtons/AuthButtons.kt b/app/src/main/java/com/example/listifyjetapp/components/authButtons/AuthButtons.kt index 4fa1c0f..5c40d8a 100644 --- a/app/src/main/java/com/example/listifyjetapp/components/authButtons/AuthButtons.kt +++ b/app/src/main/java/com/example/listifyjetapp/components/authButtons/AuthButtons.kt @@ -26,16 +26,21 @@ import androidx.compose.ui.unit.sp import androidx.navigation.NavController import com.example.listifyjetapp.ui.navigation.ListifyScreens import com.example.listifyjetapp.ui.theme.ListifyColor +import com.example.listifyjetapp.widgets.FilledButton @Composable fun AuthButtons(navController: NavController) { Column( - modifier = Modifier.fillMaxSize().padding(16.dp), + modifier = Modifier + .fillMaxSize() + .padding(16.dp), verticalArrangement = Arrangement.Bottom, horizontalAlignment = Alignment.CenterHorizontally ) { OutlinedButton( - modifier = Modifier.fillMaxWidth().padding(vertical = 6.dp), + modifier = Modifier + .fillMaxWidth() + .padding(vertical = 6.dp), shape = RoundedCornerShape(10.dp), border = BorderStroke(2.dp, color = Color.White), @@ -55,29 +60,41 @@ fun AuthButtons(navController: NavController) { ) } - Button( - modifier = Modifier.fillMaxWidth().padding(vertical = 6.dp), + FilledButton( + modifier = Modifier + .fillMaxWidth() + .padding(vertical = 6.dp), shape = RoundedCornerShape(10.dp), - colors = ButtonDefaults.buttonColors( - containerColor = Color.White, - contentColor = ListifyColor.TextDark - ), - onClick = { - navController.navigate(ListifyScreens.LoginScreen.route) - } - ) { - Icon( - imageVector = Icons.AutoMirrored.Filled.ExitToApp, - modifier = Modifier.size(20.dp), - contentDescription = "Log In" - ) - - Text( - text = "LOG IN ", - fontWeight = FontWeight.ExtraBold, - fontSize = 18.sp, - modifier = Modifier.padding(8.dp) - ) - } + containerColor = Color.White, + contentColor = ListifyColor.TextDark, + text = "LOG IN", + buttonIcon = Icons.AutoMirrored.Filled.ExitToApp, + iconDescription = "Log In", + onClick = { navController.navigate(ListifyScreens.LoginScreen.route) } + ) +// Button( +// modifier = Modifier.fillMaxWidth().padding(vertical = 6.dp), +// shape = RoundedCornerShape(10.dp), +// colors = ButtonDefaults.buttonColors( +// containerColor = Color.White, +// contentColor = ListifyColor.TextDark +// ), +// onClick = { +// navController.navigate(ListifyScreens.LoginScreen.route) +// } +// ) { +// Icon( +// imageVector = Icons.AutoMirrored.Filled.ExitToApp, +// modifier = Modifier.size(20.dp), +// contentDescription = "Log In" +// ) +// +// Text( +// text = "LOG IN", +// fontWeight = FontWeight.ExtraBold, +// fontSize = 18.sp, +// modifier = Modifier.padding(8.dp) +// ) +// } } } \ No newline at end of file From 7d805695a55001ccc0e1c08e140ad99c08649288 Mon Sep 17 00:00:00 2001 From: jiyu13 Date: Thu, 31 Jul 2025 21:58:43 -0400 Subject: [PATCH 049/261] - implement reusable filled button --- .../ui/screens/auth/ListifyLoginScreen.kt | 22 ++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/com/example/listifyjetapp/ui/screens/auth/ListifyLoginScreen.kt b/app/src/main/java/com/example/listifyjetapp/ui/screens/auth/ListifyLoginScreen.kt index b8dafab..98c457b 100644 --- a/app/src/main/java/com/example/listifyjetapp/ui/screens/auth/ListifyLoginScreen.kt +++ b/app/src/main/java/com/example/listifyjetapp/ui/screens/auth/ListifyLoginScreen.kt @@ -4,17 +4,24 @@ import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.automirrored.filled.ExitToApp import androidx.compose.material3.Scaffold import androidx.compose.material3.Surface import androidx.compose.runtime.Composable import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.ui.Modifier +import androidx.compose.ui.unit.dp import androidx.lifecycle.viewmodel.compose.viewModel import androidx.navigation.NavHostController import com.example.listifyjetapp.components.inputFields.PasswordTextField import com.example.listifyjetapp.widgets.ListifyTopBar import com.example.listifyjetapp.components.inputFields.ValidatingInputTextField +import com.example.listifyjetapp.ui.navigation.ListifyScreens +import com.example.listifyjetapp.ui.theme.ListifyColor +import com.example.listifyjetapp.widgets.FilledButton @Composable fun ListifyLoginScreen( @@ -34,7 +41,9 @@ fun ListifyLoginScreen( ) } ) { innerPadding -> Surface( - modifier = Modifier.fillMaxSize().padding(innerPadding), + modifier = Modifier + .fillMaxSize() + .padding(innerPadding), ) { Column( @@ -51,6 +60,17 @@ fun ListifyLoginScreen( onPasswordChange = { password.value = it } ) + FilledButton( + modifier=Modifier.fillMaxWidth().padding(16.dp), + shape=RoundedCornerShape(3.dp), + containerColor=ListifyColor.SplashYellow, + contentColor = ListifyColor.TextDark, + text="LOG IN ", + buttonIcon=Icons.AutoMirrored.Filled.ExitToApp, + iconDescription="Log In", + onClick={ navController.navigate(ListifyScreens.LoginScreen.route) } + ) + } } } From 2918c7167d4c4f6d8c396e32d6e6d91c60b5881c Mon Sep 17 00:00:00 2001 From: jiyu13 Date: Thu, 31 Jul 2025 22:41:14 -0400 Subject: [PATCH 050/261] - add Keyboard Configuration --- .../components/inputFields/PasswordTextField.kt | 10 +++++++++- .../components/inputFields/ValidatingInputTextField.kt | 9 ++++++++- 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/com/example/listifyjetapp/components/inputFields/PasswordTextField.kt b/app/src/main/java/com/example/listifyjetapp/components/inputFields/PasswordTextField.kt index 126ccd8..ec5a4ac 100644 --- a/app/src/main/java/com/example/listifyjetapp/components/inputFields/PasswordTextField.kt +++ b/app/src/main/java/com/example/listifyjetapp/components/inputFields/PasswordTextField.kt @@ -2,6 +2,7 @@ package com.example.listifyjetapp.components.inputFields import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.text.KeyboardActions import androidx.compose.foundation.text.KeyboardOptions import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.Visibility @@ -16,6 +17,7 @@ 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.ImeAction import androidx.compose.ui.text.input.KeyboardType import androidx.compose.ui.text.input.PasswordVisualTransformation import androidx.compose.ui.text.input.VisualTransformation @@ -40,7 +42,13 @@ fun PasswordTextField( } else { PasswordVisualTransformation() }, - keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Password), + keyboardOptions = KeyboardOptions( + keyboardType = KeyboardType.Password, + imeAction = ImeAction.Done, + ), + keyboardActions = KeyboardActions(onDone = { + //TODO: Login request - submit login form + }), trailingIcon = { if (isShowPassword) { IconButton(onClick = { isShowPassword = false }) { diff --git a/app/src/main/java/com/example/listifyjetapp/components/inputFields/ValidatingInputTextField.kt b/app/src/main/java/com/example/listifyjetapp/components/inputFields/ValidatingInputTextField.kt index 847a5aa..26cfa9f 100644 --- a/app/src/main/java/com/example/listifyjetapp/components/inputFields/ValidatingInputTextField.kt +++ b/app/src/main/java/com/example/listifyjetapp/components/inputFields/ValidatingInputTextField.kt @@ -2,10 +2,13 @@ package com.example.listifyjetapp.components.inputFields import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.text.KeyboardOptions import androidx.compose.material3.OutlinedTextField import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier +import androidx.compose.ui.text.input.ImeAction +import androidx.compose.ui.text.input.KeyboardType import androidx.compose.ui.unit.dp @Composable @@ -25,6 +28,10 @@ fun ValidatingInputTextField( if (validatorHasError) { Text("Incorrect email format.") } - } + }, + keyboardOptions = KeyboardOptions( + imeAction = ImeAction.Next, // ** Go to next ** + keyboardType = KeyboardType.Email + ), ) } \ No newline at end of file From 630c73cddc98af9001a5317991fec30970c20421 Mon Sep 17 00:00:00 2001 From: jiyu13 Date: Thu, 31 Jul 2025 22:41:45 -0400 Subject: [PATCH 051/261] - clean code --- .../java/com/example/listifyjetapp/widgets/FilledButton.kt | 3 --- 1 file changed, 3 deletions(-) diff --git a/app/src/main/java/com/example/listifyjetapp/widgets/FilledButton.kt b/app/src/main/java/com/example/listifyjetapp/widgets/FilledButton.kt index 8b5b321..ee2af3e 100644 --- a/app/src/main/java/com/example/listifyjetapp/widgets/FilledButton.kt +++ b/app/src/main/java/com/example/listifyjetapp/widgets/FilledButton.kt @@ -1,6 +1,5 @@ package com.example.listifyjetapp.widgets -import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.material3.Button @@ -13,10 +12,8 @@ import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Shape import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.text.font.FontWeight -import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp -import com.example.listifyjetapp.ui.theme.ListifyColor @Composable fun FilledButton( From ef3ec2947b43a4e03ce91c4a74d17cf6b1fbb53b Mon Sep 17 00:00:00 2001 From: jiyu13 Date: Fri, 1 Aug 2025 11:40:07 -0400 Subject: [PATCH 052/261] - add top padding --- .../java/com/example/listifyjetapp/widgets/ListifySearchBar.kt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/com/example/listifyjetapp/widgets/ListifySearchBar.kt b/app/src/main/java/com/example/listifyjetapp/widgets/ListifySearchBar.kt index 8443b5f..f10d513 100644 --- a/app/src/main/java/com/example/listifyjetapp/widgets/ListifySearchBar.kt +++ b/app/src/main/java/com/example/listifyjetapp/widgets/ListifySearchBar.kt @@ -2,6 +2,7 @@ package com.example.listifyjetapp.widgets import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.text.KeyboardActions import androidx.compose.foundation.text.KeyboardOptions @@ -32,7 +33,7 @@ fun ListifySearchBar( keyboardAction: KeyboardActions = KeyboardActions.Default // what to do when an action is triggered ) { TextField( - modifier = Modifier.fillMaxWidth(), + modifier = Modifier.fillMaxWidth().padding(top = 16.dp), shape = RoundedCornerShape(12.dp), colors = TextFieldDefaults.colors( unfocusedIndicatorColor = Color.Transparent, From 198260f1ac56189cf4b77a78991a864921851683 Mon Sep 17 00:00:00 2001 From: jiyu13 Date: Fri, 1 Aug 2025 11:40:39 -0400 Subject: [PATCH 053/261] - add top bar container color to SplashYellow --- .../java/com/example/listifyjetapp/widgets/ListifyTopBar.kt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/src/main/java/com/example/listifyjetapp/widgets/ListifyTopBar.kt b/app/src/main/java/com/example/listifyjetapp/widgets/ListifyTopBar.kt index 4d88598..ae71ebe 100644 --- a/app/src/main/java/com/example/listifyjetapp/widgets/ListifyTopBar.kt +++ b/app/src/main/java/com/example/listifyjetapp/widgets/ListifyTopBar.kt @@ -37,7 +37,7 @@ fun ListifyTopBar( CenterAlignedTopAppBar( //modifier = Modifier.shadow(elevation = 5.dp), - colors = topAppBarColors(containerColor = Color.Transparent), + colors = topAppBarColors(containerColor = ListifyColor.SplashYellow), title = { Text(text = title, fontWeight = FontWeight.ExtraBold, fontSize = 24.sp) }, @@ -58,7 +58,7 @@ fun ListifyTopBar( text = rightText, modifier = Modifier.padding(horizontal = 16.dp).clickable { onRightButtonClick() }, fontSize = 20.sp, - color = ListifyColor.SplashYellow + color = ListifyColor.TextDark ) } }, @@ -78,7 +78,7 @@ fun ListifyTopBar( .padding(horizontal = 16.dp) .clickable { onGoBackButtonClicked.invoke() }, fontSize = 20.sp, - color = ListifyColor.SplashYellow + color = ListifyColor.TextDark ) } From 84f1679d51768f21c6ec5da51cdfe54611b8169f Mon Sep 17 00:00:00 2001 From: jiyu13 Date: Fri, 1 Aug 2025 12:25:07 -0400 Subject: [PATCH 054/261] - import ListifyScreens --- .../listifyjetapp/ui/screens/splash/ListifySplashScreen.kt | 1 + 1 file changed, 1 insertion(+) diff --git a/app/src/main/java/com/example/listifyjetapp/ui/screens/splash/ListifySplashScreen.kt b/app/src/main/java/com/example/listifyjetapp/ui/screens/splash/ListifySplashScreen.kt index 57da18c..03b794d 100644 --- a/app/src/main/java/com/example/listifyjetapp/ui/screens/splash/ListifySplashScreen.kt +++ b/app/src/main/java/com/example/listifyjetapp/ui/screens/splash/ListifySplashScreen.kt @@ -21,6 +21,7 @@ import androidx.compose.ui.unit.sp import androidx.navigation.NavHostController import com.example.listifyjetapp.R import com.example.listifyjetapp.components.authButtons.AuthButtons +import com.example.listifyjetapp.ui.navigation.ListifyScreens import com.example.listifyjetapp.ui.theme.ListifyColor import com.example.listifyjetapp.ui.theme.barriecitoFont import kotlinx.coroutines.delay From 6c8bd92ac2e47c63606327d550eda028d60da89f Mon Sep 17 00:00:00 2001 From: jiyu13 Date: Tue, 5 Aug 2025 22:03:44 -0400 Subject: [PATCH 055/261] - add new list navigation --- .../com/example/listifyjetapp/ui/navigation/ListifyScreens.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/java/com/example/listifyjetapp/ui/navigation/ListifyScreens.kt b/app/src/main/java/com/example/listifyjetapp/ui/navigation/ListifyScreens.kt index 880719b..1445124 100644 --- a/app/src/main/java/com/example/listifyjetapp/ui/navigation/ListifyScreens.kt +++ b/app/src/main/java/com/example/listifyjetapp/ui/navigation/ListifyScreens.kt @@ -7,7 +7,7 @@ enum class ListifyScreens(val route: String) { ProfileScreen("profile"), LoginScreen("login"), SignupScreen("signup"), - NewListScreen("new-list") + NewListScreen("new-list"), } // navController.navigate(ListifyScreens.LoginScreen.route) \ No newline at end of file From ee31a3bf21ad73f5b760cd1e168d895c5b70f4cc Mon Sep 17 00:00:00 2001 From: jiyu13 Date: Tue, 5 Aug 2025 22:24:44 -0400 Subject: [PATCH 056/261] - add UserWithoutPassword model --- app/src/main/java/com/example/listifyjetapp/model/User.kt | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/app/src/main/java/com/example/listifyjetapp/model/User.kt b/app/src/main/java/com/example/listifyjetapp/model/User.kt index 706255e..369769e 100644 --- a/app/src/main/java/com/example/listifyjetapp/model/User.kt +++ b/app/src/main/java/com/example/listifyjetapp/model/User.kt @@ -10,6 +10,13 @@ data class User( @SerializedName("created_at") val createdAt: String ) +data class UserWithoutPassword( + val id: Int, + val username: String, + val email: String, + @SerializedName("created_at") val createdAt: String +) + data class SharedUsers( @SerializedName("user_id") val userId: Int, val username: String, From 45176f5451cb218d25971996ce14df4c03734f2e Mon Sep 17 00:00:00 2001 From: jiyu13 Date: Tue, 5 Aug 2025 22:25:24 -0400 Subject: [PATCH 057/261] - add models for login --- .../example/listifyjetapp/model/AuthUser.kt | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100644 app/src/main/java/com/example/listifyjetapp/model/AuthUser.kt diff --git a/app/src/main/java/com/example/listifyjetapp/model/AuthUser.kt b/app/src/main/java/com/example/listifyjetapp/model/AuthUser.kt new file mode 100644 index 0000000..eceb83c --- /dev/null +++ b/app/src/main/java/com/example/listifyjetapp/model/AuthUser.kt @@ -0,0 +1,22 @@ +package com.example.listifyjetapp.model + +import com.google.gson.annotations.SerializedName + +data class UserToken( + val id: Int, + @SerializedName("user_id") val userId: Int, + val token: String, + @SerializedName("expires_at") val expiresAt: String, + @SerializedName("created_at") val createdAt: String +) + +data class LoginInfo( + val email: String, + val password: String, +) + +data class LoginSuccess( + val message: String, + val token: String, + val user: UserWithoutPassword +) \ No newline at end of file From 55c8571e2fb00e41020be97be96d31705b229d9c Mon Sep 17 00:00:00 2001 From: jiyu13 Date: Tue, 5 Aug 2025 22:25:50 -0400 Subject: [PATCH 058/261] - add login post request --- .../main/java/com/example/listifyjetapp/network/ListsAPI.kt | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/app/src/main/java/com/example/listifyjetapp/network/ListsAPI.kt b/app/src/main/java/com/example/listifyjetapp/network/ListsAPI.kt index d928a9e..38ba58f 100644 --- a/app/src/main/java/com/example/listifyjetapp/network/ListsAPI.kt +++ b/app/src/main/java/com/example/listifyjetapp/network/ListsAPI.kt @@ -2,6 +2,8 @@ package com.example.listifyjetapp.network import com.example.listifyjetapp.model.ListModel import com.example.listifyjetapp.model.ListName +import com.example.listifyjetapp.model.LoginInfo +import com.example.listifyjetapp.model.LoginSuccess import com.example.listifyjetapp.model.User import retrofit2.http.Body import retrofit2.http.DELETE @@ -13,6 +15,10 @@ import javax.inject.Singleton @Singleton interface ListifyAPI { + // =============================================== Auth ======================================== + @POST("auth/login") + suspend fun login(@Body request: LoginInfo): LoginSuccess + // =============================================== Users ======================================= @PATCH("users/{user_id}") suspend fun patchUserById(@Path("user_id") userId: Int): User From 79e56a2ae876f131b1883951b64640e46497c6fb Mon Sep 17 00:00:00 2001 From: jiyu13 Date: Wed, 6 Aug 2025 11:52:35 -0400 Subject: [PATCH 059/261] - refactor routes - user serializable object / data class to define routes --- .../ui/navigation/ListifyScreens.kt | 21 ++++++++++--------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/app/src/main/java/com/example/listifyjetapp/ui/navigation/ListifyScreens.kt b/app/src/main/java/com/example/listifyjetapp/ui/navigation/ListifyScreens.kt index 1445124..700dda2 100644 --- a/app/src/main/java/com/example/listifyjetapp/ui/navigation/ListifyScreens.kt +++ b/app/src/main/java/com/example/listifyjetapp/ui/navigation/ListifyScreens.kt @@ -1,13 +1,14 @@ package com.example.listifyjetapp.ui.navigation -enum class ListifyScreens(val route: String) { - SplashScreen("splash"), - ListsScreen("lists"), - DetailScreen("detail"), - ProfileScreen("profile"), - LoginScreen("login"), - SignupScreen("signup"), - NewListScreen("new-list"), -} +import kotlinx.serialization.Serializable -// navController.navigate(ListifyScreens.LoginScreen.route) \ No newline at end of file + +object ListifyScreens { + @Serializable object SplashScreen + @Serializable data class ListsScreen(val userId: Int) + @Serializable object DetailScreen + @Serializable object ProfileScreen + @Serializable object LoginScreen + @Serializable object SignupScreen + @Serializable object NewListScreen +} \ No newline at end of file From 3c7d439d520ce1760b7cb005c202474b3ff53bb5 Mon Sep 17 00:00:00 2001 From: jiyu13 Date: Wed, 6 Aug 2025 11:56:03 -0400 Subject: [PATCH 060/261] - refactor navigation graph + destinations --- .../ui/navigation/ListifyNavigation.kt | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/app/src/main/java/com/example/listifyjetapp/ui/navigation/ListifyNavigation.kt b/app/src/main/java/com/example/listifyjetapp/ui/navigation/ListifyNavigation.kt index 69541f3..801d87d 100644 --- a/app/src/main/java/com/example/listifyjetapp/ui/navigation/ListifyNavigation.kt +++ b/app/src/main/java/com/example/listifyjetapp/ui/navigation/ListifyNavigation.kt @@ -18,41 +18,41 @@ fun ListifyNavigation() { // Build the navigation graph NavHost( navController = navController, - startDestination = ListifyScreens.SplashScreen.route + startDestination = ListifyScreens.SplashScreen ) { // TODO: Define a navigation route for SplashScreen - composable(ListifyScreens.SplashScreen.route) { + composable() { ListifySplashScreen(navController = navController) } // TODO: Define a navigation route for ListsScreen - composable(ListifyScreens.ListsScreen.route) { + composable() { ListifyListsScreen(navController = navController) } // TODO: Define a navigation route for NewListScreen - composable(ListifyScreens.NewListScreen.route) { + composable() { ListifyNewListScreen(navController = navController) } // TODO: Define a navigation route for DetailScreen - composable(ListifyScreens.DetailScreen.route) { + composable() { //ListifyDetailScreen(navController = navController) } // TODO: Define a navigation route for ProfileScreen - composable(ListifyScreens.ProfileScreen.route) { + composable() { //ListifyProfileScreen(navController = navController) } // TODO: Define a navigation route for LoginScreen - composable(ListifyScreens.LoginScreen.route) { + composable() { ListifyLoginScreen(navController = navController) } // TODO: Define a navigation route for SignupScreen - composable(ListifyScreens.SignupScreen.route) { + composable() { //ListifySignupScreen(navController = navController) } } From 520803b5a9e6275e67a1fe9d5d0b8077979a85ed Mon Sep 17 00:00:00 2001 From: jiyu13 Date: Wed, 6 Aug 2025 12:10:21 -0400 Subject: [PATCH 061/261] - define a sealed class - it represents all the possible states of a login process --- .../java/com/example/listifyjetapp/data/LoginState.kt | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 app/src/main/java/com/example/listifyjetapp/data/LoginState.kt diff --git a/app/src/main/java/com/example/listifyjetapp/data/LoginState.kt b/app/src/main/java/com/example/listifyjetapp/data/LoginState.kt new file mode 100644 index 0000000..5ff6e08 --- /dev/null +++ b/app/src/main/java/com/example/listifyjetapp/data/LoginState.kt @@ -0,0 +1,10 @@ +package com.example.listifyjetapp.data + +import com.example.listifyjetapp.model.LoginSuccess + +sealed class LoginState { + object Idle : LoginState() + object Loading : LoginState() + data class Success(val data: LoginSuccess) : LoginState() + data class Error(val message: String) : LoginState() +} \ No newline at end of file From 842f9128b3f19aeeb2d304157d3d184638eaa8ad Mon Sep 17 00:00:00 2001 From: jiyu13 Date: Wed, 6 Aug 2025 12:19:00 -0400 Subject: [PATCH 062/261] - define repository to make login request to api - login suspense contains credentials + return a custom wrapper type for either success or failure --- .../repository/AuthUserRepository.kt | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 app/src/main/java/com/example/listifyjetapp/repository/AuthUserRepository.kt diff --git a/app/src/main/java/com/example/listifyjetapp/repository/AuthUserRepository.kt b/app/src/main/java/com/example/listifyjetapp/repository/AuthUserRepository.kt new file mode 100644 index 0000000..875ffb7 --- /dev/null +++ b/app/src/main/java/com/example/listifyjetapp/repository/AuthUserRepository.kt @@ -0,0 +1,18 @@ +package com.example.listifyjetapp.repository + +import com.example.listifyjetapp.data.ListifyResult +import com.example.listifyjetapp.model.LoginInfo +import com.example.listifyjetapp.model.LoginSuccess +import com.example.listifyjetapp.network.ListifyAPI +import javax.inject.Inject + +class AuthUserRepository@Inject constructor(private val api: ListifyAPI){ + suspend fun login(loginInfo: LoginInfo): ListifyResult { + try { + val response = api.login(loginInfo) + return ListifyResult.Success(data = response) + } catch (e: Exception) { + return ListifyResult.Failure(e.message ?: "Error logging in") + } + } +} \ No newline at end of file From e6cd44b98768f3c2018b73898ed1b3438d917e9c Mon Sep 17 00:00:00 2001 From: jiyu13 Date: Wed, 6 Aug 2025 13:42:06 -0400 Subject: [PATCH 063/261] - define login view model - mutable state email and psw + validation and update function - currentUser to hold logged-in user after login - loginState to hold ui state - login function to resets ui state, api call, update state --- .../ui/screens/auth/LoginViewModel.kt | 68 +++++++++++++++++++ 1 file changed, 68 insertions(+) create mode 100644 app/src/main/java/com/example/listifyjetapp/ui/screens/auth/LoginViewModel.kt diff --git a/app/src/main/java/com/example/listifyjetapp/ui/screens/auth/LoginViewModel.kt b/app/src/main/java/com/example/listifyjetapp/ui/screens/auth/LoginViewModel.kt new file mode 100644 index 0000000..9fcffd4 --- /dev/null +++ b/app/src/main/java/com/example/listifyjetapp/ui/screens/auth/LoginViewModel.kt @@ -0,0 +1,68 @@ +package com.example.listifyjetapp.ui.screens.auth + +import androidx.compose.runtime.derivedStateOf +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.setValue +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import com.example.listifyjetapp.data.ListifyResult +import com.example.listifyjetapp.data.LoginState +import com.example.listifyjetapp.model.LoginInfo +import com.example.listifyjetapp.model.UserWithoutPassword +import com.example.listifyjetapp.repository.AuthUserRepository +import dagger.hilt.android.lifecycle.HiltViewModel +import kotlinx.coroutines.launch +import javax.inject.Inject + +@HiltViewModel +class LoginViewModel @Inject constructor( + private val repository: AuthUserRepository +): ViewModel() { + + var email by mutableStateOf("") + private set + var password by mutableStateOf("") + private set + + private val _currentUser = mutableStateOf(null) + val currentUser = _currentUser + + private val _loginState = mutableStateOf(LoginState.Idle) + val loginState = _loginState + + + val emailHasErrors by derivedStateOf { + if (email.isNotEmpty()) { + // Email is considered erroneous until it completely matches EMAIL_ADDRESS. + !android.util.Patterns.EMAIL_ADDRESS.matcher(email).matches() + } else { + false + } + } + fun updateEmail(newEmail:String) { + email = newEmail + } + + fun updatePassword(newPassword: String) { password = newPassword } + + fun login() + = viewModelScope.launch { + _loginState.value = LoginState.Loading + val result = repository.login( + LoginInfo(email = email.trim(), password = password) + ) + + _loginState.value = when (result) { + is ListifyResult.Success -> { + _currentUser.value = result.data.user + // Save token securely + LoginState.Success(result.data) + } + + is ListifyResult.Failure -> { + LoginState.Error(result.errorMessage) + } + } + } +} \ No newline at end of file From b1767be312604c72035bfce3d4237b33c363c953 Mon Sep 17 00:00:00 2001 From: jiyu13 Date: Wed, 6 Aug 2025 13:42:47 -0400 Subject: [PATCH 064/261] - create global object to store current user --- .../java/com/example/listifyjetapp/data/ListifyState.kt | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 app/src/main/java/com/example/listifyjetapp/data/ListifyState.kt diff --git a/app/src/main/java/com/example/listifyjetapp/data/ListifyState.kt b/app/src/main/java/com/example/listifyjetapp/data/ListifyState.kt new file mode 100644 index 0000000..581f64d --- /dev/null +++ b/app/src/main/java/com/example/listifyjetapp/data/ListifyState.kt @@ -0,0 +1,7 @@ +package com.example.listifyjetapp.data + +import com.example.listifyjetapp.model.UserWithoutPassword + +object ListifyState { + var currentUser: UserWithoutPassword? = null +} \ No newline at end of file From 68ec6328a708b4347be11528f03e96b915a94b44 Mon Sep 17 00:00:00 2001 From: jiyu13 Date: Wed, 6 Aug 2025 13:43:39 -0400 Subject: [PATCH 065/261] - set global object currentUser after fetching user successfully --- .../com/example/listifyjetapp/repository/AuthUserRepository.kt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/src/main/java/com/example/listifyjetapp/repository/AuthUserRepository.kt b/app/src/main/java/com/example/listifyjetapp/repository/AuthUserRepository.kt index 875ffb7..bf5e82d 100644 --- a/app/src/main/java/com/example/listifyjetapp/repository/AuthUserRepository.kt +++ b/app/src/main/java/com/example/listifyjetapp/repository/AuthUserRepository.kt @@ -1,6 +1,7 @@ package com.example.listifyjetapp.repository import com.example.listifyjetapp.data.ListifyResult +import com.example.listifyjetapp.data.ListifyState import com.example.listifyjetapp.model.LoginInfo import com.example.listifyjetapp.model.LoginSuccess import com.example.listifyjetapp.network.ListifyAPI @@ -10,6 +11,7 @@ class AuthUserRepository@Inject constructor(private val api: ListifyAPI){ suspend fun login(loginInfo: LoginInfo): ListifyResult { try { val response = api.login(loginInfo) + ListifyState.currentUser = response.user return ListifyResult.Success(data = response) } catch (e: Exception) { return ListifyResult.Failure(e.message ?: "Error logging in") From 606082754ada4aa366173895d2e0ac01853cd70a Mon Sep 17 00:00:00 2001 From: jiyu13 Date: Wed, 6 Aug 2025 13:44:40 -0400 Subject: [PATCH 066/261] - install serializable plugin - install serializable related dependencies --- app/build.gradle.kts | 19 +++++++++++++++++++ gradle/libs.versions.toml | 11 ++++++++++- 2 files changed, 29 insertions(+), 1 deletion(-) diff --git a/app/build.gradle.kts b/app/build.gradle.kts index e420fab..ba6fc78 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -5,6 +5,9 @@ plugins { alias(libs.plugins.hilt) alias(libs.plugins.ksp) + + // Kotlin serialization plugin for type safe routes and navigation arguments + kotlin("plugin.serialization") version "2.0.21" } android { @@ -108,4 +111,20 @@ dependencies { // material icons implementation (libs.androidx.material.icons.extended) + // ================= Kotlin serialization ======================= + // Jetpack Compose integration + implementation(libs.androidx.navigation.compose) + + // Views/Fragments integration + implementation(libs.androidx.navigation.fragment) + implementation(libs.androidx.navigation.ui) + + // Feature module support for Fragments + implementation(libs.androidx.navigation.dynamic.features.fragment) + + // Testing Navigation + androidTestImplementation(libs.androidx.navigation.testing) + + // JSON serialization library, works with the Kotlin serialization plugin + implementation(libs.kotlinx.serialization.json) } \ No newline at end of file diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index f511f22..33f90f5 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -7,11 +7,12 @@ junitVersion = "1.2.1" espressoCore = "3.6.1" lifecycleRuntimeKtx = "2.9.1" activityCompose = "1.10.1" -composeBom = "2024.09.00" +composeBom = "2025.07.00" coilCompose = "1.4.0" converterGson = "2.9.0" materialIconsExtended = "1.7.8" +navigationCompose = "2.9.3" roomKtx = "2.7.2" roomRuntime = "2.7.2" hiltCompiler = "2.56.2" @@ -24,6 +25,7 @@ kotlinxCoroutinesCore = "1.10.1" kotlinxCoroutinesPlayServices = "1.10.1" lifecycleRuntimeKtxVersion = "2.9.1" okhttp = "5.0.0-alpha.2" +kotlinxSerializationJson = "1.7.3" [libraries] @@ -61,6 +63,13 @@ kotlinx-coroutines-core = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-c kotlinx-coroutines-play-services = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-play-services", version.ref = "kotlinxCoroutinesPlayServices" } retrofit = { module = "com.squareup.retrofit2:retrofit", version.ref = "retrofit" } +kotlinx-serialization-json = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json", version.ref = "kotlinxSerializationJson" } +androidx-navigation-compose = { module = "androidx.navigation:navigation-compose", version.ref = "navigationCompose" } +androidx-navigation-dynamic-features-fragment = { module = "androidx.navigation:navigation-dynamic-features-fragment", version.ref = "navigationCompose" } +androidx-navigation-fragment = { module = "androidx.navigation:navigation-fragment", version.ref = "navigationCompose" } +androidx-navigation-testing = { module = "androidx.navigation:navigation-testing", version.ref = "navigationCompose" } +androidx-navigation-ui = { module = "androidx.navigation:navigation-ui", version.ref = "navigationCompose" } + #font androidx-ui-text-google-fonts = { group = "androidx.compose.ui", name = "ui-text-google-fonts", version = "1.8.3" } # or match your Compose BOM From c37390bc753a0339977f0625230fe3ebfff5538e Mon Sep 17 00:00:00 2001 From: jiyu13 Date: Wed, 6 Aug 2025 14:13:19 -0400 Subject: [PATCH 067/261] - fix navigate route --- .../example/listifyjetapp/components/authButtons/AuthButtons.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/java/com/example/listifyjetapp/components/authButtons/AuthButtons.kt b/app/src/main/java/com/example/listifyjetapp/components/authButtons/AuthButtons.kt index 5c40d8a..01c8b0f 100644 --- a/app/src/main/java/com/example/listifyjetapp/components/authButtons/AuthButtons.kt +++ b/app/src/main/java/com/example/listifyjetapp/components/authButtons/AuthButtons.kt @@ -70,7 +70,7 @@ fun AuthButtons(navController: NavController) { text = "LOG IN", buttonIcon = Icons.AutoMirrored.Filled.ExitToApp, iconDescription = "Log In", - onClick = { navController.navigate(ListifyScreens.LoginScreen.route) } + onClick = { navController.navigate(ListifyScreens.LoginScreen) } ) // Button( // modifier = Modifier.fillMaxWidth().padding(vertical = 6.dp), From 9d667d19459507a1f8395c987f1ea9cd501e32ee Mon Sep 17 00:00:00 2001 From: jiyu13 Date: Wed, 6 Aug 2025 14:18:55 -0400 Subject: [PATCH 068/261] - refactor email + password validation and update - finish onLoginClick call back - handle login state --- .../ui/screens/auth/ListifyLoginScreen.kt | 52 ++++++++++++++----- 1 file changed, 40 insertions(+), 12 deletions(-) diff --git a/app/src/main/java/com/example/listifyjetapp/ui/screens/auth/ListifyLoginScreen.kt b/app/src/main/java/com/example/listifyjetapp/ui/screens/auth/ListifyLoginScreen.kt index 98c457b..a4ed928 100644 --- a/app/src/main/java/com/example/listifyjetapp/ui/screens/auth/ListifyLoginScreen.kt +++ b/app/src/main/java/com/example/listifyjetapp/ui/screens/auth/ListifyLoginScreen.kt @@ -7,18 +7,21 @@ import androidx.compose.foundation.layout.padding import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material.icons.Icons import androidx.compose.material.icons.automirrored.filled.ExitToApp +import androidx.compose.material3.CircularProgressIndicator import androidx.compose.material3.Scaffold import androidx.compose.material3.Surface +import androidx.compose.material3.Text import androidx.compose.runtime.Composable -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.remember +import androidx.compose.runtime.LaunchedEffect import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color import androidx.compose.ui.unit.dp -import androidx.lifecycle.viewmodel.compose.viewModel +import androidx.hilt.navigation.compose.hiltViewModel import androidx.navigation.NavHostController import com.example.listifyjetapp.components.inputFields.PasswordTextField import com.example.listifyjetapp.widgets.ListifyTopBar import com.example.listifyjetapp.components.inputFields.ValidatingInputTextField +import com.example.listifyjetapp.data.LoginState import com.example.listifyjetapp.ui.navigation.ListifyScreens import com.example.listifyjetapp.ui.theme.ListifyColor import com.example.listifyjetapp.widgets.FilledButton @@ -26,10 +29,16 @@ import com.example.listifyjetapp.widgets.FilledButton @Composable fun ListifyLoginScreen( navController: NavHostController, + loginViewModel: LoginViewModel = hiltViewModel() ) { - val emailViewModel: EmailViewModel = viewModel() - val password = remember { mutableStateOf("") } + val loginState = loginViewModel.loginState.value + + fun onLoginClick() { + if (loginViewModel.email.isNotBlank() && loginViewModel.password.isNotBlank()) { + loginViewModel.login() + } + } Scaffold( modifier = Modifier.fillMaxSize(), @@ -50,16 +59,15 @@ fun ListifyLoginScreen( modifier = Modifier.fillMaxWidth() ) { ValidatingInputTextField( - email = emailViewModel.email, - onValueChange = { input -> emailViewModel.updateEmail(input) }, - validatorHasError = emailViewModel.emailHasErrors + email = loginViewModel.email, + onValueChange = { input -> loginViewModel.updateEmail(input) }, + validatorHasError = loginViewModel.emailHasErrors ) PasswordTextField( - password = password.value, - onPasswordChange = { password.value = it } + password = loginViewModel.password, + onPasswordChange = loginViewModel::updatePassword ) - FilledButton( modifier=Modifier.fillMaxWidth().padding(16.dp), shape=RoundedCornerShape(3.dp), @@ -68,10 +76,30 @@ fun ListifyLoginScreen( text="LOG IN ", buttonIcon=Icons.AutoMirrored.Filled.ExitToApp, iconDescription="Log In", - onClick={ navController.navigate(ListifyScreens.LoginScreen.route) } + onClick={ onLoginClick() } ) + // handle login state + when (loginState) { + is LoginState.Loading -> { CircularProgressIndicator() } + is LoginState.Success -> { + LaunchedEffect(Unit) { + navController.navigate( + ListifyScreens.ListsScreen(userId = loginState.data.user.id) + ) + } + } + is LoginState.Error -> { + Text( + text = loginState.message, + color = Color.Red, + modifier = Modifier.padding(16.dp) + ) + } + LoginState.Idle -> {} // Explicit idle state + } } } + } } \ No newline at end of file From ca0dfaab73d2cfe3855bf805e5e1d63fcb2a8377 Mon Sep 17 00:00:00 2001 From: jiyu13 Date: Wed, 6 Aug 2025 14:19:47 -0400 Subject: [PATCH 069/261] - fix route for onRightButtonClick - fetch lists with global object currentUser id --- .../ui/screens/lists/ListifyListsScreen.kt | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/app/src/main/java/com/example/listifyjetapp/ui/screens/lists/ListifyListsScreen.kt b/app/src/main/java/com/example/listifyjetapp/ui/screens/lists/ListifyListsScreen.kt index 832f387..f97b323 100644 --- a/app/src/main/java/com/example/listifyjetapp/ui/screens/lists/ListifyListsScreen.kt +++ b/app/src/main/java/com/example/listifyjetapp/ui/screens/lists/ListifyListsScreen.kt @@ -26,8 +26,9 @@ import androidx.compose.ui.unit.dp import androidx.hilt.navigation.compose.hiltViewModel import androidx.navigation.NavController import com.example.listifyjetapp.R -import com.example.listifyjetapp.ui.screens.lists.ListRow +import com.example.listifyjetapp.data.ListifyState import com.example.listifyjetapp.ui.navigation.ListifyScreens +import com.example.listifyjetapp.ui.screens.auth.LoginViewModel import com.example.listifyjetapp.utils.filterListItems import com.example.listifyjetapp.widgets.ListifySearchBar import com.example.listifyjetapp.widgets.ListifyTopBar @@ -35,9 +36,13 @@ import com.example.listifyjetapp.widgets.ListifyTopBar @Composable fun ListifyListsScreen( navController: NavController, - viewModel: ListsViewModel = hiltViewModel() + viewModel: ListsViewModel = hiltViewModel(), + loginViewModel: LoginViewModel = hiltViewModel() ) { - LaunchedEffect(Unit) { viewModel.getUserLists(4) } + LaunchedEffect(Unit) { + ListifyState.currentUser?.id?.let { viewModel.getUserLists(it) } + //loginViewModel.currentUser.value?.let { viewModel.getUserLists(it.id) } + } Scaffold( @@ -47,7 +52,7 @@ fun ListifyListsScreen( isListsScreen = true, rightIcon = Icons.Default.Add, onRightButtonClick = { - navController.navigate(ListifyScreens.NewListScreen.route) + navController.navigate(ListifyScreens.NewListScreen) } ) } ) { innerPadding -> From 630b0d6cbb45bf1f70f32ab7fe23b3870884ffdd Mon Sep 17 00:00:00 2001 From: jiyu13 Date: Tue, 12 Aug 2025 17:01:37 -0400 Subject: [PATCH 070/261] - install dataStore --- app/build.gradle.kts | 3 +++ gradle/libs.versions.toml | 2 ++ 2 files changed, 5 insertions(+) diff --git a/app/build.gradle.kts b/app/build.gradle.kts index ba6fc78..f0bebce 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -127,4 +127,7 @@ dependencies { // JSON serialization library, works with the Kotlin serialization plugin implementation(libs.kotlinx.serialization.json) + + // Typed DataStore (Typed API surface, such as Proto) + implementation(libs.androidx.datastore.preferences) } \ No newline at end of file diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 33f90f5..5561f9b 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,5 +1,6 @@ [versions] agp = "8.10.1" +datastore = "1.1.7" kotlin = "2.1.20" coreKtx = "1.16.0" junit = "4.13.2" @@ -30,6 +31,7 @@ kotlinxSerializationJson = "1.7.3" [libraries] androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" } +androidx-datastore-preferences = { module = "androidx.datastore:datastore-preferences", version.ref = "datastore" } androidx-material-icons-extended = { module = "androidx.compose.material:material-icons-extended", version.ref = "materialIconsExtended" } junit = { group = "junit", name = "junit", version.ref = "junit" } androidx-junit = { group = "androidx.test.ext", name = "junit", version.ref = "junitVersion" } From 45a8fe89ae3de5692edba3c11e2e81f25a5ba898 Mon Sep 17 00:00:00 2001 From: jiyu13 Date: Tue, 12 Aug 2025 19:10:24 -0400 Subject: [PATCH 071/261] create dataStorage - define companion object to manage each preferences key - add methods to save / update data with edit() - define properties to read string type values stored on the device - clear data from dataStore --- .../listifyjetapp/data/ListifyStorage.kt | 77 +++++++++++++++++++ 1 file changed, 77 insertions(+) create mode 100644 app/src/main/java/com/example/listifyjetapp/data/ListifyStorage.kt diff --git a/app/src/main/java/com/example/listifyjetapp/data/ListifyStorage.kt b/app/src/main/java/com/example/listifyjetapp/data/ListifyStorage.kt new file mode 100644 index 0000000..bac3dbe --- /dev/null +++ b/app/src/main/java/com/example/listifyjetapp/data/ListifyStorage.kt @@ -0,0 +1,77 @@ +package com.example.listifyjetapp.data + +import android.content.Context +import androidx.datastore.core.DataStore +import androidx.datastore.preferences.core.Preferences +import androidx.datastore.preferences.core.booleanPreferencesKey +import androidx.datastore.preferences.core.edit +import androidx.datastore.preferences.core.intPreferencesKey +import androidx.datastore.preferences.core.stringPreferencesKey +import androidx.datastore.preferences.preferencesDataStore +import com.example.listifyjetapp.model.UserDataStore +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.map + +// Create a Preferences DataStore +val USER_DATASTORE = "user_data" +val Context.dataStore: DataStore by preferencesDataStore(name = USER_DATASTORE) + +class ListifyStorageManager(val context: Context) { + + private companion object { + val USER_ID = intPreferencesKey("userId") + val EMAIL = stringPreferencesKey("email") + val ACCESS_TOKEN = stringPreferencesKey("accessToken") + val REFRESH_TOKEN = stringPreferencesKey("refreshToken") + val IS_LOGIN = booleanPreferencesKey("isLogin") + } + + // write a preference dataStorage - after login + suspend fun saveToDataStore(userDataStore: UserDataStore) { + context.dataStore.edit { + it[USER_ID] = userDataStore.userId + it[EMAIL] = userDataStore.email + it[ACCESS_TOKEN] = userDataStore.accessToken + it[REFRESH_TOKEN] = userDataStore.refreshToken + it[IS_LOGIN] = userDataStore.isLogin + } + } + + // retrieve data from dataStore + fun getUser(): Flow = context.dataStore.data.map { + UserDataStore( + userId= it[USER_ID] ?: 0, + email = it[EMAIL] ?: "", + accessToken =it[ACCESS_TOKEN] ?: "", + refreshToken =it[REFRESH_TOKEN] ?: "", + isLogin = it[IS_LOGIN] ?: false, + ) + } + + val userIdFlow: Flow = context.dataStore.data.map { it[USER_ID] ?: 0 } + val emailFlow: Flow = context.dataStore.data.map { it[EMAIL] ?: "" } + val isLoggedInFlow: Flow = context.dataStore.data.map { it[IS_LOGIN] ?: false } + + val accessTokenFlow: Flow = context.dataStore.data.map { it[ACCESS_TOKEN] ?: "" } + val refreshTokenFlow: Flow = context.dataStore.data.map { it[REFRESH_TOKEN] ?: "" } + + suspend fun updateIsLoggedIn(isLoggedIn: Boolean) { + context.dataStore.edit { it[IS_LOGIN] = isLoggedIn } + } + + suspend fun updateEmail(email: String) { + context.dataStore.edit { it[EMAIL] = email } + } + + suspend fun updateTokens(accessToken: String, refreshToken: String) { + context.dataStore.edit { + it[ACCESS_TOKEN] = accessToken + it[REFRESH_TOKEN] = refreshToken + } + } + + // clear data from dataStore - logout + suspend fun clearDataStore() = context.dataStore.edit { + it.clear() + } +} \ No newline at end of file From b2dedb7531649dba670a378c231517822c94a19e Mon Sep 17 00:00:00 2001 From: jiyu13 Date: Tue, 12 Aug 2025 19:34:10 -0400 Subject: [PATCH 072/261] okHttp Interceptor + Authenticator - provideOkHttpClient to access token on every request + call refresh api to refresh token - wire OkHttpClient into Retrofit provider - provideDataStorageAPI to wire up dataStoreManager - provideListifyAPI created from Retrofit --- .../com/example/listifyjetapp/di/AppModule.kt | 93 ++++++++++++++++++- 1 file changed, 90 insertions(+), 3 deletions(-) diff --git a/app/src/main/java/com/example/listifyjetapp/di/AppModule.kt b/app/src/main/java/com/example/listifyjetapp/di/AppModule.kt index 569c61b..54d643f 100644 --- a/app/src/main/java/com/example/listifyjetapp/di/AppModule.kt +++ b/app/src/main/java/com/example/listifyjetapp/di/AppModule.kt @@ -1,11 +1,20 @@ package com.example.listifyjetapp.di +import android.content.Context +import com.example.listifyjetapp.data.ListifyStorageManager import com.example.listifyjetapp.network.ListifyAPI import com.example.listifyjetapp.utils.constants.Constants +import dagger.Lazy import dagger.Module import dagger.Provides import dagger.hilt.InstallIn +import dagger.hilt.android.qualifiers.ApplicationContext import dagger.hilt.components.SingletonComponent +import kotlinx.coroutines.flow.first +import kotlinx.coroutines.runBlocking +import okhttp3.Authenticator +import okhttp3.Interceptor +import okhttp3.OkHttpClient import retrofit2.Retrofit import retrofit2.converter.gson.GsonConverterFactory import javax.inject.Singleton @@ -14,14 +23,92 @@ import javax.inject.Singleton @InstallIn(SingletonComponent::class) class AppModule { - // TODO: create a provider to build a Retrofit instance + // TODO: OkHttp with: + // - Interceptor: adds Authorization: Bearer + // - Authenticator: on 401 -> POST /auth/refresh with x-Refresh-Token -> save new access -> retry @Provides @Singleton - fun provideListifyAPI():ListifyAPI { + fun provideOkHttpClient( + storageManager: ListifyStorageManager, + apiLazy: Lazy + ): OkHttpClient { + val authInterceptor = Interceptor {chain -> + // Interceptor is not suspend; grab the latest value synchronously: + val accessToken = runBlocking { storageManager.accessTokenFlow.first() } + val request = chain.request().newBuilder().apply { + if (accessToken.isNotBlank()) { + addHeader("Authorization", "Bearer $accessToken") + } + }.build() + chain.proceed(request) + } + + val authenticator = Authenticator { route, response -> + // prevent infinite loops + if (response.request.header("Authenticator") == null) return@Authenticator null + // Do not try to refresh while calling refresh + if (response.request.url.encodedPath.endsWith("/refresh")) return@Authenticator null + + try { + val refreshToken = runBlocking { storageManager.refreshTokenFlow.first() } + if (refreshToken.isBlank()) return@Authenticator null + + //call refresh synchronously + val login = runBlocking { apiLazy.get().refresh(refreshToken) } + + // save new token + runBlocking { storageManager.updateTokens(login.accessToken, login.refreshToken) } + + // rebuild the original request with new access token + val newAccessToken = login.accessToken + return@Authenticator response.request.newBuilder() + .header("Authorization", "Bearer $newAccessToken") + .build() + + } catch (_: Exception) { + // refresh failed -> no retry + null + } + } + + return OkHttpClient + .Builder() + .addInterceptor(authInterceptor) + .authenticator(authenticator) + .build() + } + + + // TODO: Retrofit with OkHttpClient + @Provides + @Singleton + fun provideRetrofit(client: OkHttpClient): Retrofit { return Retrofit.Builder() .baseUrl(Constants.BASE_URL) + .client(client) .addConverterFactory(GsonConverterFactory.create()) .build() - .create(ListifyAPI::class.java) } + + // TODO: dataStore to read / write tokens + @Provides + @Singleton + fun provideDataStorageAPI(@ApplicationContext context: Context + ): ListifyStorageManager = ListifyStorageManager(context) + + @Provides + @Singleton + fun provideListifyAPI(retrofit: Retrofit): ListifyAPI = + retrofit.create(ListifyAPI::class.java) + +// // TODO: create a provider to build a Retrofit instance +// @Provides +// @Singleton +// fun provideListifyAPI():ListifyAPI { +// return Retrofit.Builder() +// .baseUrl(Constants.BASE_URL) +// .addConverterFactory(GsonConverterFactory.create()) +// .build() +// .create(ListifyAPI::class.java) +// } } \ No newline at end of file From f8f803cf849c1e60bfb66acd4d52efe0f11d4b5d Mon Sep 17 00:00:00 2001 From: jiyu13 Date: Tue, 12 Aug 2025 19:49:27 -0400 Subject: [PATCH 073/261] add refresh token api --- .../main/java/com/example/listifyjetapp/network/ListsAPI.kt | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/app/src/main/java/com/example/listifyjetapp/network/ListsAPI.kt b/app/src/main/java/com/example/listifyjetapp/network/ListsAPI.kt index 38ba58f..b8b18af 100644 --- a/app/src/main/java/com/example/listifyjetapp/network/ListsAPI.kt +++ b/app/src/main/java/com/example/listifyjetapp/network/ListsAPI.kt @@ -8,6 +8,7 @@ import com.example.listifyjetapp.model.User import retrofit2.http.Body import retrofit2.http.DELETE import retrofit2.http.GET +import retrofit2.http.Header import retrofit2.http.PATCH import retrofit2.http.POST import retrofit2.http.Path @@ -19,6 +20,9 @@ interface ListifyAPI { @POST("auth/login") suspend fun login(@Body request: LoginInfo): LoginSuccess + @POST("refresh") + suspend fun refresh(@Header("x-refresh-token") refreshToken: String): LoginSuccess + // =============================================== Users ======================================= @PATCH("users/{user_id}") suspend fun patchUserById(@Path("user_id") userId: Int): User From 2ab1c5ac9fa7273a7113560264f314f7300835f8 Mon Sep 17 00:00:00 2001 From: jiyu13 Date: Tue, 12 Aug 2025 20:00:50 -0400 Subject: [PATCH 074/261] define data models for auth flow --- .../java/com/example/listifyjetapp/model/AuthUser.kt | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/com/example/listifyjetapp/model/AuthUser.kt b/app/src/main/java/com/example/listifyjetapp/model/AuthUser.kt index eceb83c..95c39c0 100644 --- a/app/src/main/java/com/example/listifyjetapp/model/AuthUser.kt +++ b/app/src/main/java/com/example/listifyjetapp/model/AuthUser.kt @@ -10,6 +10,14 @@ data class UserToken( @SerializedName("created_at") val createdAt: String ) +data class UserDataStore( + val userId: Int, + val email: String, + val accessToken: String, + val refreshToken: String, + val isLogin: Boolean = false, +) + data class LoginInfo( val email: String, val password: String, @@ -17,6 +25,7 @@ data class LoginInfo( data class LoginSuccess( val message: String, - val token: String, + val accessToken: String, + val refreshToken: String, val user: UserWithoutPassword ) \ No newline at end of file From 6c1f890c0b9587bc200e33c9d02861ae0fb487f0 Mon Sep 17 00:00:00 2001 From: jiyu13 Date: Tue, 12 Aug 2025 20:48:33 -0400 Subject: [PATCH 075/261] Login repository - suspend fun calls api.login --- .../example/listifyjetapp/repository/AuthUserRepository.kt | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/app/src/main/java/com/example/listifyjetapp/repository/AuthUserRepository.kt b/app/src/main/java/com/example/listifyjetapp/repository/AuthUserRepository.kt index bf5e82d..64741a7 100644 --- a/app/src/main/java/com/example/listifyjetapp/repository/AuthUserRepository.kt +++ b/app/src/main/java/com/example/listifyjetapp/repository/AuthUserRepository.kt @@ -1,17 +1,15 @@ package com.example.listifyjetapp.repository import com.example.listifyjetapp.data.ListifyResult -import com.example.listifyjetapp.data.ListifyState import com.example.listifyjetapp.model.LoginInfo import com.example.listifyjetapp.model.LoginSuccess import com.example.listifyjetapp.network.ListifyAPI import javax.inject.Inject -class AuthUserRepository@Inject constructor(private val api: ListifyAPI){ +class AuthUserRepository @Inject constructor(private val api: ListifyAPI){ suspend fun login(loginInfo: LoginInfo): ListifyResult { try { val response = api.login(loginInfo) - ListifyState.currentUser = response.user return ListifyResult.Success(data = response) } catch (e: Exception) { return ListifyResult.Failure(e.message ?: "Error logging in") From d6c72f0e72aef374989c68882637928408259409 Mon Sep 17 00:00:00 2001 From: jiyu13 Date: Tue, 12 Aug 2025 20:52:49 -0400 Subject: [PATCH 076/261] add LoginViewModel + token persistence - Inject AuthUserRepository and ListifyStorageManager via Hilt - Implement login() using Loading/Success/Error LoginState - Persist userId/email/accessToken/refreshToken to DataStore on success --- .../ui/screens/auth/LoginViewModel.kt | 37 ++++++++++--------- 1 file changed, 20 insertions(+), 17 deletions(-) diff --git a/app/src/main/java/com/example/listifyjetapp/ui/screens/auth/LoginViewModel.kt b/app/src/main/java/com/example/listifyjetapp/ui/screens/auth/LoginViewModel.kt index 9fcffd4..53a0699 100644 --- a/app/src/main/java/com/example/listifyjetapp/ui/screens/auth/LoginViewModel.kt +++ b/app/src/main/java/com/example/listifyjetapp/ui/screens/auth/LoginViewModel.kt @@ -1,5 +1,6 @@ package com.example.listifyjetapp.ui.screens.auth +import android.util.Patterns import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf @@ -7,9 +8,10 @@ import androidx.compose.runtime.setValue import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import com.example.listifyjetapp.data.ListifyResult +import com.example.listifyjetapp.data.ListifyStorageManager import com.example.listifyjetapp.data.LoginState import com.example.listifyjetapp.model.LoginInfo -import com.example.listifyjetapp.model.UserWithoutPassword +import com.example.listifyjetapp.model.UserDataStore import com.example.listifyjetapp.repository.AuthUserRepository import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.launch @@ -17,7 +19,8 @@ import javax.inject.Inject @HiltViewModel class LoginViewModel @Inject constructor( - private val repository: AuthUserRepository + private val repository: AuthUserRepository, + private val storageManager: ListifyStorageManager ): ViewModel() { var email by mutableStateOf("") @@ -25,37 +28,37 @@ class LoginViewModel @Inject constructor( var password by mutableStateOf("") private set - private val _currentUser = mutableStateOf(null) - val currentUser = _currentUser - - private val _loginState = mutableStateOf(LoginState.Idle) - val loginState = _loginState + var loginState by mutableStateOf(LoginState.Idle) val emailHasErrors by derivedStateOf { if (email.isNotEmpty()) { // Email is considered erroneous until it completely matches EMAIL_ADDRESS. - !android.util.Patterns.EMAIL_ADDRESS.matcher(email).matches() + !Patterns.EMAIL_ADDRESS.matcher(email).matches() } else { false } } - fun updateEmail(newEmail:String) { - email = newEmail - } + fun updateEmail(newEmail:String) { email = newEmail } fun updatePassword(newPassword: String) { password = newPassword } fun login() = viewModelScope.launch { - _loginState.value = LoginState.Loading - val result = repository.login( - LoginInfo(email = email.trim(), password = password) - ) + loginState = LoginState.Loading + val result = repository.login( LoginInfo(email = email.trim(), password = password) ) - _loginState.value = when (result) { + loginState = when (result) { is ListifyResult.Success -> { - _currentUser.value = result.data.user + storageManager.saveToDataStore( + UserDataStore( + userId = result.data.user.id, + email = result.data.user.email, + accessToken = result.data.accessToken, + refreshToken = result.data.refreshToken, + isLogin = true, + ) + ) // Save token securely LoginState.Success(result.data) } From 8ed66d97401e9a8cbd60c83dac8169ad0fd1c34b Mon Sep 17 00:00:00 2001 From: jiyu13 Date: Tue, 12 Aug 2025 21:02:46 -0400 Subject: [PATCH 077/261] - remove .value when accessing mutable state --- .../example/listifyjetapp/ui/screens/auth/ListifyLoginScreen.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/java/com/example/listifyjetapp/ui/screens/auth/ListifyLoginScreen.kt b/app/src/main/java/com/example/listifyjetapp/ui/screens/auth/ListifyLoginScreen.kt index a4ed928..9a982c3 100644 --- a/app/src/main/java/com/example/listifyjetapp/ui/screens/auth/ListifyLoginScreen.kt +++ b/app/src/main/java/com/example/listifyjetapp/ui/screens/auth/ListifyLoginScreen.kt @@ -32,7 +32,7 @@ fun ListifyLoginScreen( loginViewModel: LoginViewModel = hiltViewModel() ) { - val loginState = loginViewModel.loginState.value + val loginState = loginViewModel.loginState fun onLoginClick() { if (loginViewModel.email.isNotBlank() && loginViewModel.password.isNotBlank()) { From 8f005bc3dc4847f50fe9e46bb21c871d4983d51c Mon Sep 17 00:00:00 2001 From: jiyu13 Date: Tue, 12 Aug 2025 21:37:54 -0400 Subject: [PATCH 078/261] - inject storageManager - add errorMessage mutable state - wrap calling repository inside try/finally statement --- .../ui/screens/lists/ListsViewModel.kt | 35 ++++++++++++++----- 1 file changed, 26 insertions(+), 9 deletions(-) diff --git a/app/src/main/java/com/example/listifyjetapp/ui/screens/lists/ListsViewModel.kt b/app/src/main/java/com/example/listifyjetapp/ui/screens/lists/ListsViewModel.kt index 16b6c2b..44c84ce 100644 --- a/app/src/main/java/com/example/listifyjetapp/ui/screens/lists/ListsViewModel.kt +++ b/app/src/main/java/com/example/listifyjetapp/ui/screens/lists/ListsViewModel.kt @@ -1,31 +1,43 @@ package com.example.listifyjetapp.ui.screens.lists +import android.util.Log +import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateListOf import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.setValue import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope -import androidx.navigation.NavController import com.example.listifyjetapp.data.ListifyResult +import com.example.listifyjetapp.data.ListifyStorageManager import com.example.listifyjetapp.model.ListModel import com.example.listifyjetapp.model.ListName import com.example.listifyjetapp.repository.ListsRepository import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.flow.first import kotlinx.coroutines.launch import javax.inject.Inject @HiltViewModel -class ListsViewModel @Inject constructor(private val repository: ListsRepository): ViewModel() { +class ListsViewModel @Inject constructor( + private val repository: ListsRepository, + private val storageManager: ListifyStorageManager +): ViewModel() { val lists = mutableStateListOf() - var isLoading = mutableStateOf(false) + var isLoading by mutableStateOf(false) + var errorMessage by mutableStateOf(null) private val _navigateBack = MutableStateFlow(false) val navigateBack = _navigateBack.asStateFlow() - fun getUserLists(userId: Int) { + fun getUserLists() { viewModelScope.launch { - isLoading.value = true + val userId = storageManager.getUser().first().userId + isLoading = true + errorMessage = null + + try { val result = repository.getUserLists(userId = userId) when (result) { is ListifyResult.Success -> { @@ -33,15 +45,20 @@ class ListsViewModel @Inject constructor(private val repository: ListsRepository lists.addAll(result.data) } - is ListifyResult.Failure -> Unit + is ListifyResult.Failure -> { + errorMessage = result.errorMessage + Log.d("Fail to fetch lists by user id", result.toString()) + } } - isLoading.value = false + } finally { + isLoading = false + } } } fun insertListByUser(userId: Int, newListName: ListName) = viewModelScope.launch { - isLoading.value = true + isLoading = true val result = repository.insertListByUser(userId, newListName) when (result) { is ListifyResult.Success -> { @@ -49,7 +66,7 @@ class ListsViewModel @Inject constructor(private val repository: ListsRepository } is ListifyResult.Failure -> Unit } - isLoading.value = false + isLoading = false } fun navigationComplete() { From 2dec3c629bdc8ebe87a21964dbc69cd085655296 Mon Sep 17 00:00:00 2001 From: jiyu13 Date: Tue, 12 Aug 2025 21:39:21 -0400 Subject: [PATCH 079/261] - LaunchedEffect(Unit) to fetch user lists. - show error message if fails to get lists --- .../ui/screens/lists/ListifyListsScreen.kt | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/app/src/main/java/com/example/listifyjetapp/ui/screens/lists/ListifyListsScreen.kt b/app/src/main/java/com/example/listifyjetapp/ui/screens/lists/ListifyListsScreen.kt index f97b323..5c5a2bb 100644 --- a/app/src/main/java/com/example/listifyjetapp/ui/screens/lists/ListifyListsScreen.kt +++ b/app/src/main/java/com/example/listifyjetapp/ui/screens/lists/ListifyListsScreen.kt @@ -11,6 +11,7 @@ import androidx.compose.foundation.text.KeyboardActions import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.Add import androidx.compose.material3.CircularProgressIndicator +import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Scaffold import androidx.compose.material3.Surface import androidx.compose.material3.Text @@ -26,9 +27,7 @@ import androidx.compose.ui.unit.dp import androidx.hilt.navigation.compose.hiltViewModel import androidx.navigation.NavController import com.example.listifyjetapp.R -import com.example.listifyjetapp.data.ListifyState import com.example.listifyjetapp.ui.navigation.ListifyScreens -import com.example.listifyjetapp.ui.screens.auth.LoginViewModel import com.example.listifyjetapp.utils.filterListItems import com.example.listifyjetapp.widgets.ListifySearchBar import com.example.listifyjetapp.widgets.ListifyTopBar @@ -37,13 +36,8 @@ import com.example.listifyjetapp.widgets.ListifyTopBar fun ListifyListsScreen( navController: NavController, viewModel: ListsViewModel = hiltViewModel(), - loginViewModel: LoginViewModel = hiltViewModel() ) { - LaunchedEffect(Unit) { - ListifyState.currentUser?.id?.let { viewModel.getUserLists(it) } - //loginViewModel.currentUser.value?.let { viewModel.getUserLists(it.id) } - } - + LaunchedEffect(Unit) { viewModel.getUserLists() } Scaffold( modifier = Modifier.fillMaxSize(), @@ -79,7 +73,14 @@ fun ListifyListsScreen( } ) - if (viewModel.isLoading.value) { + viewModel.errorMessage?.let { msg -> + Text(msg, + Modifier.padding(16.dp), + color = MaterialTheme.colorScheme.error + ) + } + + if (viewModel.isLoading) { Box( modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center From 3c633806f32f52373e07fbc32c5bf9eaf3794e9c Mon Sep 17 00:00:00 2001 From: jiyu13 Date: Tue, 12 Aug 2025 21:39:33 -0400 Subject: [PATCH 080/261] delete file --- .../java/com/example/listifyjetapp/data/ListifyState.kt | 7 ------- 1 file changed, 7 deletions(-) delete mode 100644 app/src/main/java/com/example/listifyjetapp/data/ListifyState.kt diff --git a/app/src/main/java/com/example/listifyjetapp/data/ListifyState.kt b/app/src/main/java/com/example/listifyjetapp/data/ListifyState.kt deleted file mode 100644 index 581f64d..0000000 --- a/app/src/main/java/com/example/listifyjetapp/data/ListifyState.kt +++ /dev/null @@ -1,7 +0,0 @@ -package com.example.listifyjetapp.data - -import com.example.listifyjetapp.model.UserWithoutPassword - -object ListifyState { - var currentUser: UserWithoutPassword? = null -} \ No newline at end of file From 043516641f85408829e4a019758593a4899afbb7 Mon Sep 17 00:00:00 2001 From: jiyu13 Date: Tue, 12 Aug 2025 23:08:57 -0400 Subject: [PATCH 081/261] - isLoggedIn() reads current isLogin - getUserId() reads stored userId - isExpired(jwt) decodes the JWT payload (Base64 URL-safe) on-device and checks the exp (epoch seconds) - checkAccessToken() gets /checks the current accessToken + refresh /save new tokens --- .../ui/screens/splash/SplashViewModel.kt | 56 +++++++++++++++++++ 1 file changed, 56 insertions(+) create mode 100644 app/src/main/java/com/example/listifyjetapp/ui/screens/splash/SplashViewModel.kt diff --git a/app/src/main/java/com/example/listifyjetapp/ui/screens/splash/SplashViewModel.kt b/app/src/main/java/com/example/listifyjetapp/ui/screens/splash/SplashViewModel.kt new file mode 100644 index 0000000..80fbbcf --- /dev/null +++ b/app/src/main/java/com/example/listifyjetapp/ui/screens/splash/SplashViewModel.kt @@ -0,0 +1,56 @@ +package com.example.listifyjetapp.ui.screens.splash + +import androidx.lifecycle.ViewModel +import com.example.listifyjetapp.data.ListifyStorageManager +import com.example.listifyjetapp.network.ListifyAPI +import dagger.hilt.android.lifecycle.HiltViewModel +import kotlinx.coroutines.flow.first +import javax.inject.Inject + +@HiltViewModel +class SplashViewModel @Inject constructor( + private val storageManager: ListifyStorageManager, + private val api: ListifyAPI +) : ViewModel() { + + suspend fun isLoggedIn(): Boolean = storageManager.isLoggedInFlow.first() + suspend fun getUserId(): Int = storageManager.userIdFlow.first() + + // Optional: decode JWT exp; if you don't want to parse JWT, just try refresh and ignore errors. + private fun isExpired(jwt: String): Boolean { + return try { + val token = jwt.split(".") + if (token.size < 2) return true // treat as expired/invalid + val payload = android.util.Base64.decode( + token[1], + android.util.Base64.URL_SAFE or android.util.Base64.NO_WRAP + ) + val json = org.json.JSONObject(String(payload)) + val exp = json.optLong("exp", 0L) + val nowSec = System.currentTimeMillis() / 1000 + exp != 0L && exp <= nowSec + } catch (_: Exception) { false } + } + + suspend fun accessToken(): String { + return storageManager.accessTokenFlow.first() + } + // Make sure access toke nis valid before navigating + // returns true if we are good to proceed, false if we must show login + suspend fun checkAccessToken(): Boolean { + val accessToken = storageManager.accessTokenFlow.first() + if (accessToken.isNotBlank() && !isExpired(accessToken)) return true + + // try refresh + return try { + val refreshed = api.refresh(storageManager.refreshTokenFlow.first()) + storageManager.updateTokens( + refreshed.accessToken, refreshed.refreshToken + ) + true + } catch (_: Exception) { + storageManager.clearDataStore() + false + } + } +} \ No newline at end of file From 6bdeac4d806911a11862f59753c6048692e60f8f Mon Sep 17 00:00:00 2001 From: jiyu13 Date: Tue, 12 Aug 2025 23:21:59 -0400 Subject: [PATCH 082/261] - check isLoggedIn state + call checkAccessToken() to ensure access token is valid - navigate to lists screen with user id if both are true - show auth buttons if false --- .../ui/screens/splash/ListifySplashScreen.kt | 23 ++++++++++++++----- 1 file changed, 17 insertions(+), 6 deletions(-) diff --git a/app/src/main/java/com/example/listifyjetapp/ui/screens/splash/ListifySplashScreen.kt b/app/src/main/java/com/example/listifyjetapp/ui/screens/splash/ListifySplashScreen.kt index 03b794d..7d4f377 100644 --- a/app/src/main/java/com/example/listifyjetapp/ui/screens/splash/ListifySplashScreen.kt +++ b/app/src/main/java/com/example/listifyjetapp/ui/screens/splash/ListifySplashScreen.kt @@ -18,6 +18,7 @@ import androidx.compose.ui.draw.scale import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.sp +import androidx.hilt.navigation.compose.hiltViewModel import androidx.navigation.NavHostController import com.example.listifyjetapp.R import com.example.listifyjetapp.components.authButtons.AuthButtons @@ -27,7 +28,10 @@ import com.example.listifyjetapp.ui.theme.barriecitoFont import kotlinx.coroutines.delay @Composable -fun ListifySplashScreen(navController: NavHostController) { +fun ListifySplashScreen( + navController: NavHostController, + splashViewModel: SplashViewModel = hiltViewModel() +) { // TODO: Create an Animated object that holds a Float value starting at 0f val scale = remember { Animatable(initialValue = 0f) } val isShowButtons = remember { mutableStateOf(false) } @@ -43,12 +47,19 @@ fun ListifySplashScreen(navController: NavHostController) { // when the animation is over, delay 2s before going to next screen delay(2000L) - // TODO: Navigate to MainScreen - //navController.navigate(ListifyScreens.ListsScreen.route) - - // TODO: Show login + SignIn button at the bottom - isShowButtons.value = true + val isLoggedIn = splashViewModel.isLoggedIn() + if (isLoggedIn && splashViewModel.checkAccessToken()) { + // TODO: Navigate to MainScreen + navController.navigate(ListifyScreens.ListsScreen(splashViewModel.getUserId())) { + // Remove Splash from the back stack when go to Lists Screen + popUpTo(ListifyScreens.SplashScreen) { inclusive = true } + launchSingleTop = true + } + } else { + // TODO: Show login + SignIn button at the bottom + isShowButtons.value = true + } }) Surface( From e65929e5c982448ec9b72fe16ecc764d39940632 Mon Sep 17 00:00:00 2001 From: jiyu13 Date: Wed, 13 Aug 2025 18:57:09 -0400 Subject: [PATCH 083/261] rename file + function --- .../example/listifyjetapp/utils/filterListItems.kt | 14 -------------- .../com/example/listifyjetapp/utils/filters.kt | 13 +++++++++++++ 2 files changed, 13 insertions(+), 14 deletions(-) delete mode 100644 app/src/main/java/com/example/listifyjetapp/utils/filterListItems.kt create mode 100644 app/src/main/java/com/example/listifyjetapp/utils/filters.kt diff --git a/app/src/main/java/com/example/listifyjetapp/utils/filterListItems.kt b/app/src/main/java/com/example/listifyjetapp/utils/filterListItems.kt deleted file mode 100644 index e1c4f94..0000000 --- a/app/src/main/java/com/example/listifyjetapp/utils/filterListItems.kt +++ /dev/null @@ -1,14 +0,0 @@ -package com.example.listifyjetapp.utils - -import android.util.Log -import com.example.listifyjetapp.model.ListModel - -fun filterListItems(input: String, listItems: List): List { - - return if (input.isEmpty()) { - listItems - } else { - listItems.filter { it.name.contains(input, ignoreCase = true) } - } - -} diff --git a/app/src/main/java/com/example/listifyjetapp/utils/filters.kt b/app/src/main/java/com/example/listifyjetapp/utils/filters.kt new file mode 100644 index 0000000..bc9fc39 --- /dev/null +++ b/app/src/main/java/com/example/listifyjetapp/utils/filters.kt @@ -0,0 +1,13 @@ +package com.example.listifyjetapp.utils + +import com.example.listifyjetapp.model.ListModel + +fun filterLists(input: String, lists: List): List { + + return if (input.isEmpty()) { + lists + } else { + lists.filter { it.name.contains(input, ignoreCase = true) } + } + +} From a80699f024186bc1445576a011c856a66f08b699 Mon Sep 17 00:00:00 2001 From: jiyu13 Date: Wed, 13 Aug 2025 21:25:43 -0400 Subject: [PATCH 084/261] - rename api - return a list of ListItem --- .../main/java/com/example/listifyjetapp/network/ListsAPI.kt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/com/example/listifyjetapp/network/ListsAPI.kt b/app/src/main/java/com/example/listifyjetapp/network/ListsAPI.kt index b8b18af..754f9d1 100644 --- a/app/src/main/java/com/example/listifyjetapp/network/ListsAPI.kt +++ b/app/src/main/java/com/example/listifyjetapp/network/ListsAPI.kt @@ -1,5 +1,6 @@ package com.example.listifyjetapp.network +import com.example.listifyjetapp.model.ListItem import com.example.listifyjetapp.model.ListModel import com.example.listifyjetapp.model.ListName import com.example.listifyjetapp.model.LoginInfo @@ -77,7 +78,7 @@ interface ListifyAPI { // Get items by list id @GET("lists/{list_id}") - suspend fun getAList(@Path("list_id") listId: Int) {} + suspend fun getListItems(@Path("list_id") listId: Int): List // update a list @POST("lists/{list_id}") From 3d7064dc23cc059919495d76fe35d686fdc9caeb Mon Sep 17 00:00:00 2001 From: jiyu13 Date: Wed, 13 Aug 2025 21:39:27 -0400 Subject: [PATCH 085/261] - define type-safe route with @Serializable - pass in args --- .../com/example/listifyjetapp/ui/navigation/ListifyScreens.kt | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/com/example/listifyjetapp/ui/navigation/ListifyScreens.kt b/app/src/main/java/com/example/listifyjetapp/ui/navigation/ListifyScreens.kt index 700dda2..465e131 100644 --- a/app/src/main/java/com/example/listifyjetapp/ui/navigation/ListifyScreens.kt +++ b/app/src/main/java/com/example/listifyjetapp/ui/navigation/ListifyScreens.kt @@ -6,7 +6,9 @@ import kotlinx.serialization.Serializable object ListifyScreens { @Serializable object SplashScreen @Serializable data class ListsScreen(val userId: Int) - @Serializable object DetailScreen + @Serializable data class ListItemScreen( + val listId: Int, val listName: String + ) @Serializable object ProfileScreen @Serializable object LoginScreen @Serializable object SignupScreen From a69a97704ccb26836ac5c9f835ca610df137a01c Mon Sep 17 00:00:00 2001 From: jiyu13 Date: Wed, 13 Aug 2025 21:40:47 -0400 Subject: [PATCH 086/261] - extract args with backStateEntry - pass args into ListifyListItemScreen --- .../ui/navigation/ListifyNavigation.kt | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/com/example/listifyjetapp/ui/navigation/ListifyNavigation.kt b/app/src/main/java/com/example/listifyjetapp/ui/navigation/ListifyNavigation.kt index 801d87d..b34bb69 100644 --- a/app/src/main/java/com/example/listifyjetapp/ui/navigation/ListifyNavigation.kt +++ b/app/src/main/java/com/example/listifyjetapp/ui/navigation/ListifyNavigation.kt @@ -4,7 +4,9 @@ import androidx.compose.runtime.Composable import androidx.navigation.compose.NavHost import androidx.navigation.compose.composable import androidx.navigation.compose.rememberNavController +import androidx.navigation.toRoute import com.example.listifyjetapp.ui.screens.auth.ListifyLoginScreen +import com.example.listifyjetapp.ui.screens.listItem.ListifyListItemScreen import com.example.listifyjetapp.ui.screens.lists.ListifyListsScreen import com.example.listifyjetapp.ui.screens.newList.ListifyNewListScreen import com.example.listifyjetapp.ui.screens.splash.ListifySplashScreen @@ -37,8 +39,15 @@ fun ListifyNavigation() { } // TODO: Define a navigation route for DetailScreen - composable() { - //ListifyDetailScreen(navController = navController) + composable {backStackEntry -> + val args = backStackEntry.toRoute() + val listId = args.listId + val listName = args.listName + ListifyListItemScreen( + navController = navController, + listId = listId, + listName = listName + ) } // TODO: Define a navigation route for ProfileScreen From 126caaeffeccd0935eb5473b5e77baa491b4158c Mon Sep 17 00:00:00 2001 From: jiyu13 Date: Wed, 13 Aug 2025 21:42:13 -0400 Subject: [PATCH 087/261] - add on click callback for each list row - navigate to list item screen when on Click is called --- .../ui/screens/lists/ListifyListsScreen.kt | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/app/src/main/java/com/example/listifyjetapp/ui/screens/lists/ListifyListsScreen.kt b/app/src/main/java/com/example/listifyjetapp/ui/screens/lists/ListifyListsScreen.kt index 5c5a2bb..13a94d5 100644 --- a/app/src/main/java/com/example/listifyjetapp/ui/screens/lists/ListifyListsScreen.kt +++ b/app/src/main/java/com/example/listifyjetapp/ui/screens/lists/ListifyListsScreen.kt @@ -1,5 +1,6 @@ package com.example.listifyjetapp.ui.screens.lists +import android.util.Log import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column @@ -28,7 +29,7 @@ import androidx.hilt.navigation.compose.hiltViewModel import androidx.navigation.NavController import com.example.listifyjetapp.R import com.example.listifyjetapp.ui.navigation.ListifyScreens -import com.example.listifyjetapp.utils.filterListItems +import com.example.listifyjetapp.utils.filterLists import com.example.listifyjetapp.widgets.ListifySearchBar import com.example.listifyjetapp.widgets.ListifyTopBar @@ -39,6 +40,10 @@ fun ListifyListsScreen( ) { LaunchedEffect(Unit) { viewModel.getUserLists() } + fun onListRowClick(listId: Int, listName: String) { + navController.navigate(ListifyScreens.ListItemScreen(listId, listName)) + } + Scaffold( modifier = Modifier.fillMaxSize(), topBar = { ListifyTopBar( @@ -95,10 +100,13 @@ fun ListifyListsScreen( horizontal = 8.dp )){ // Filter lists by search input - val results = filterListItems(searchTextState.value, viewModel.lists) - + val results = filterLists(searchTextState.value, viewModel.lists) items(results) {list -> - ListRow(list) + val listName = list.name.replace(" ", "-") + ListRow( + list = list, + onListRowClick = { onListRowClick(list.id, listName) } + ) } } } From 45c1fa16a60a9c07462e13faca7272f4bbe7041e Mon Sep 17 00:00:00 2001 From: jiyu13 Date: Wed, 13 Aug 2025 21:45:54 -0400 Subject: [PATCH 088/261] - create list item model data class --- .../java/com/example/listifyjetapp/model/ListModel.kt | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/app/src/main/java/com/example/listifyjetapp/model/ListModel.kt b/app/src/main/java/com/example/listifyjetapp/model/ListModel.kt index d539814..37c35ee 100644 --- a/app/src/main/java/com/example/listifyjetapp/model/ListModel.kt +++ b/app/src/main/java/com/example/listifyjetapp/model/ListModel.kt @@ -25,3 +25,11 @@ data class ListModel( @SerializedName("item_count") val itemCount: Int, @SerializedName("shared_with") val sharedWith: List ) + +data class ListItem( + val id: Int, + val description: String, + val units: String, + val checked: Boolean, + @SerializedName("list_id") val listId: Int, +) From ae9f464c7bf7ebbe19bd35472810815b4692e42a Mon Sep 17 00:00:00 2001 From: jiyu13 Date: Wed, 13 Aug 2025 22:14:20 -0400 Subject: [PATCH 089/261] - create repository - getItemsByListId to fetch items by listId from api --- .../repository/ListItemRepository.kt | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 app/src/main/java/com/example/listifyjetapp/repository/ListItemRepository.kt diff --git a/app/src/main/java/com/example/listifyjetapp/repository/ListItemRepository.kt b/app/src/main/java/com/example/listifyjetapp/repository/ListItemRepository.kt new file mode 100644 index 0000000..9e0cd7c --- /dev/null +++ b/app/src/main/java/com/example/listifyjetapp/repository/ListItemRepository.kt @@ -0,0 +1,20 @@ +package com.example.listifyjetapp.repository + +import com.example.listifyjetapp.data.ListifyResult +import com.example.listifyjetapp.model.ListItem +import com.example.listifyjetapp.network.ListifyAPI +import javax.inject.Inject + +class ListItemRepository @Inject constructor( + private val api: ListifyAPI +){ + suspend fun getItemsByListId(listId: Int): ListifyResult> { + try { + val response = api.getListItems(listId) + return ListifyResult.Success(data = response) + } catch (e: Exception) { + return ListifyResult.Failure(e.message ?: "Error fetching items by list id") + } + } + +} \ No newline at end of file From 1d5cd9ef2e0a363ba659b5caf842b1790c71254a Mon Sep 17 00:00:00 2001 From: jiyu13 Date: Wed, 13 Aug 2025 22:19:38 -0400 Subject: [PATCH 090/261] - create view model - hold isLoading, errorMessage states + the list of items. - fetch items by listid --- .../ui/screens/listItem/ListItemViewModel.kt | 49 +++++++++++++++++++ 1 file changed, 49 insertions(+) create mode 100644 app/src/main/java/com/example/listifyjetapp/ui/screens/listItem/ListItemViewModel.kt diff --git a/app/src/main/java/com/example/listifyjetapp/ui/screens/listItem/ListItemViewModel.kt b/app/src/main/java/com/example/listifyjetapp/ui/screens/listItem/ListItemViewModel.kt new file mode 100644 index 0000000..136e4f9 --- /dev/null +++ b/app/src/main/java/com/example/listifyjetapp/ui/screens/listItem/ListItemViewModel.kt @@ -0,0 +1,49 @@ +package com.example.listifyjetapp.ui.screens.listItem + +import android.util.Log +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateListOf +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.setValue +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import com.example.listifyjetapp.data.ListifyResult +import com.example.listifyjetapp.model.ListItem +import com.example.listifyjetapp.repository.ListItemRepository +import dagger.hilt.android.lifecycle.HiltViewModel +import kotlinx.coroutines.launch +import javax.inject.Inject + +@HiltViewModel +class ListItemViewModel @Inject constructor( + private val repository: ListItemRepository +): ViewModel() { + val listItems = mutableStateListOf() + var isLoading by mutableStateOf(false) + var errorMessage by mutableStateOf(null) + + + fun getAllItems(listId: Int) { + viewModelScope.launch { + isLoading = true + errorMessage = null + + try { + val result = repository.getItemsByListId(listId) + when (result) { + is ListifyResult.Success -> { + listItems.clear() + listItems.addAll(result.data) + } + + is ListifyResult.Failure -> { + errorMessage = result.errorMessage + Log.d("Fail to fetch list items by list id", result.toString()) + } + } + } finally { + isLoading = false + } + } + } +} \ No newline at end of file From 69a0088a96ddb9fa9d875edc8f54048b1ae26797 Mon Sep 17 00:00:00 2001 From: jiyu13 Date: Wed, 13 Aug 2025 22:20:46 -0400 Subject: [PATCH 091/261] add on click callback --- .../com/example/listifyjetapp/ui/screens/lists/ListRow.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/com/example/listifyjetapp/ui/screens/lists/ListRow.kt b/app/src/main/java/com/example/listifyjetapp/ui/screens/lists/ListRow.kt index 9fd6113..51f7649 100644 --- a/app/src/main/java/com/example/listifyjetapp/ui/screens/lists/ListRow.kt +++ b/app/src/main/java/com/example/listifyjetapp/ui/screens/lists/ListRow.kt @@ -27,11 +27,11 @@ import com.example.listifyjetapp.ui.theme.ListifyColor @Composable fun ListRow( list: ListModel, - //viewModel: User + onListRowClick: () -> Unit ) { Row( modifier = Modifier - .clickable { } + .clickable { onListRowClick() } .padding(vertical = 16.dp) .fillMaxWidth() .background(Color.Transparent) From 61d350ec9855279ef5fadac68e807f118fe521d5 Mon Sep 17 00:00:00 2001 From: jiyu13 Date: Wed, 13 Aug 2025 22:23:27 -0400 Subject: [PATCH 092/261] create util function to filter items by input --- .../main/java/com/example/listifyjetapp/utils/filters.kt | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/app/src/main/java/com/example/listifyjetapp/utils/filters.kt b/app/src/main/java/com/example/listifyjetapp/utils/filters.kt index bc9fc39..7ca5e8a 100644 --- a/app/src/main/java/com/example/listifyjetapp/utils/filters.kt +++ b/app/src/main/java/com/example/listifyjetapp/utils/filters.kt @@ -1,5 +1,6 @@ package com.example.listifyjetapp.utils +import com.example.listifyjetapp.model.ListItem import com.example.listifyjetapp.model.ListModel fun filterLists(input: String, lists: List): List { @@ -11,3 +12,11 @@ fun filterLists(input: String, lists: List): List { } } + +fun filterListItems(input: String, items: List): List { + return if (input.isEmpty()) { + items + } else { + items.filter { it.description.contains(input, ignoreCase = true) } + } +} \ No newline at end of file From c230048842f591e7fff25d39efc34a47c1fbfca4 Mon Sep 17 00:00:00 2001 From: jiyu13 Date: Wed, 13 Aug 2025 22:23:39 -0400 Subject: [PATCH 093/261] commit --- .idea/inspectionProfiles/Project_Default.xml | 61 ++++++++++++++++++++ 1 file changed, 61 insertions(+) create mode 100644 .idea/inspectionProfiles/Project_Default.xml diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml new file mode 100644 index 0000000..7061a0d --- /dev/null +++ b/.idea/inspectionProfiles/Project_Default.xml @@ -0,0 +1,61 @@ + + + + \ No newline at end of file From 1b9a7082c9acfe404c4bfb06ca6d5d0b93f626d8 Mon Sep 17 00:00:00 2001 From: jiyu13 Date: Thu, 14 Aug 2025 11:18:09 -0400 Subject: [PATCH 094/261] Create screen - show item description --- .../screens/listItem/ListifyListItemScreen.kt | 111 ++++++++++++++++++ 1 file changed, 111 insertions(+) create mode 100644 app/src/main/java/com/example/listifyjetapp/ui/screens/listItem/ListifyListItemScreen.kt diff --git a/app/src/main/java/com/example/listifyjetapp/ui/screens/listItem/ListifyListItemScreen.kt b/app/src/main/java/com/example/listifyjetapp/ui/screens/listItem/ListifyListItemScreen.kt new file mode 100644 index 0000000..91f1e5e --- /dev/null +++ b/app/src/main/java/com/example/listifyjetapp/ui/screens/listItem/ListifyListItemScreen.kt @@ -0,0 +1,111 @@ +package com.example.listifyjetapp.ui.screens.listItem + +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.items +import androidx.compose.foundation.text.KeyboardActions +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.automirrored.filled.ArrowBack +import androidx.compose.material.icons.filled.Add +import androidx.compose.material3.CircularProgressIndicator +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Scaffold +import androidx.compose.material3.Surface +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.LocalSoftwareKeyboardController +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.unit.dp +import androidx.hilt.navigation.compose.hiltViewModel +import androidx.navigation.NavController +import com.example.listifyjetapp.R +import com.example.listifyjetapp.utils.filterListItems +import com.example.listifyjetapp.widgets.ListifySearchBar +import com.example.listifyjetapp.widgets.ListifyTopBar + +@Composable +fun ListifyListItemScreen( + navController: NavController, + listId: Int, + listName: String, + viewModel: ListItemViewModel = hiltViewModel() +) { + LaunchedEffect(Unit) { viewModel.getAllItems(listId) } + + Scaffold( + modifier = Modifier.fillMaxSize(), + topBar = { ListifyTopBar( + title = listName.replace("-", " "), + isListsScreen = false, + goBackIcon = Icons.AutoMirrored.Filled.ArrowBack, + onGoBackButtonClicked = {navController.popBackStack()}, + rightIcon = Icons.Default.Add, + onRightButtonClick = { + // TODO: add new item + } + ) } + ) { innerPadding -> + + Surface( + modifier = Modifier.fillMaxSize().padding(innerPadding) + ) { + Column( + modifier = Modifier.padding(horizontal = 16.dp), + verticalArrangement = Arrangement.Top, + horizontalAlignment = Alignment.CenterHorizontally + ) { + + val searchTextState = remember { mutableStateOf("") } + val keyboardController = LocalSoftwareKeyboardController.current + + ListifySearchBar( + searchTextValue = searchTextState, + onValueChange = {searchTextState.value = it}, + keyboardAction = KeyboardActions{ + searchTextState.value.trim() // perform the search + keyboardController?.hide() // hide keyboard + } + ) + + viewModel.errorMessage?.let { msg -> + Text(msg, + Modifier.padding(16.dp), + color = MaterialTheme.colorScheme.error + ) + } + + if (viewModel.isLoading) { + Box( + modifier = Modifier.fillMaxSize(), + contentAlignment = Alignment.Center + ) { + CircularProgressIndicator() + } + } else if (viewModel.listItems.isEmpty()) { + Text(text = stringResource(R.string.no_lists)) + } else { + LazyColumn(modifier = Modifier.padding( + vertical = 16.dp, + horizontal = 8.dp + )){ + val results = filterListItems(searchTextState.value, viewModel.listItems) + items(results) {item -> + // TODO: ItemRow + Text(item.description) + } + } + } + } + + } + } +} \ No newline at end of file From e39528cd8a264c392e0395f327e7d8ba1f8a7e16 Mon Sep 17 00:00:00 2001 From: jiyu13 Date: Thu, 14 Aug 2025 14:49:44 -0400 Subject: [PATCH 095/261] Create screen - show item description + units --- .../ui/screens/listItem/ListifyListItemScreen.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/com/example/listifyjetapp/ui/screens/listItem/ListifyListItemScreen.kt b/app/src/main/java/com/example/listifyjetapp/ui/screens/listItem/ListifyListItemScreen.kt index 91f1e5e..8526cc9 100644 --- a/app/src/main/java/com/example/listifyjetapp/ui/screens/listItem/ListifyListItemScreen.kt +++ b/app/src/main/java/com/example/listifyjetapp/ui/screens/listItem/ListifyListItemScreen.kt @@ -95,12 +95,12 @@ fun ListifyListItemScreen( } else { LazyColumn(modifier = Modifier.padding( vertical = 16.dp, - horizontal = 8.dp + horizontal = 4.dp )){ val results = filterListItems(searchTextState.value, viewModel.listItems) items(results) {item -> // TODO: ItemRow - Text(item.description) + ListItemRow(item) } } } From 4edfedb06250f6ac4be5db6c679db810c2410984 Mon Sep 17 00:00:00 2001 From: jiyu13 Date: Thu, 14 Aug 2025 14:51:19 -0400 Subject: [PATCH 096/261] item row - show description + units - show line through if item isChecked - --- .../ui/screens/listItem/ListItemRow.kt | 76 +++++++++++++++++++ 1 file changed, 76 insertions(+) create mode 100644 app/src/main/java/com/example/listifyjetapp/ui/screens/listItem/ListItemRow.kt diff --git a/app/src/main/java/com/example/listifyjetapp/ui/screens/listItem/ListItemRow.kt b/app/src/main/java/com/example/listifyjetapp/ui/screens/listItem/ListItemRow.kt new file mode 100644 index 0000000..efd69c7 --- /dev/null +++ b/app/src/main/java/com/example/listifyjetapp/ui/screens/listItem/ListItemRow.kt @@ -0,0 +1,76 @@ +package com.example.listifyjetapp.ui.screens.listItem + +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.material3.ColorScheme +import androidx.compose.material3.HorizontalDivider +import androidx.compose.material3.LocalTextStyle +import androidx.compose.material3.Text +import androidx.compose.material3.TextFieldDefaults +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.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.text.TextStyle +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.text.style.TextDecoration +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import com.example.listifyjetapp.model.ListItem +import com.example.listifyjetapp.ui.theme.ListifyColor + +@Composable +fun ListItemRow ( + item: ListItem, +) { + val description = if (item.description.length >= 20) { + item.description.substring(0, 20) + "..." + } else { + item.description + } + val isChecked by remember { mutableStateOf(item.checked) } + + + Row(modifier = Modifier + .padding(vertical = 16.dp) + .fillMaxWidth() + .background(Color.Transparent), + + ) { + Row( + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 8.dp), + verticalAlignment = Alignment.CenterVertically, + //horizontalArrangement = Arrangement.Start + ) { + + Text( + text = description, + color = if (isChecked) {ListifyColor.TextGrey} else {ListifyColor.TextBlack}, + fontSize = 20.sp, + style = TextStyle( + textDecoration = if (isChecked) { TextDecoration.LineThrough } else {null}, + ) + ) + Text( + modifier = Modifier + .weight(1f) + .padding(horizontal = 4.dp), + text = item.units, + textAlign = TextAlign.End, + color = ListifyColor.TextGrey, + fontSize = 16.sp + ) + } + + + } + HorizontalDivider() +} \ No newline at end of file From 40c5f85ffc6461f28bedc5e48acc5eaadbc1971d Mon Sep 17 00:00:00 2001 From: jiyu13 Date: Thu, 14 Aug 2025 19:06:28 -0400 Subject: [PATCH 097/261] create view model - store / update listItems, loading state, error message - store active item id + description --- .../listifyjetapp/ui/screens/listItem/ListItemViewModel.kt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/src/main/java/com/example/listifyjetapp/ui/screens/listItem/ListItemViewModel.kt b/app/src/main/java/com/example/listifyjetapp/ui/screens/listItem/ListItemViewModel.kt index 136e4f9..fd99b2b 100644 --- a/app/src/main/java/com/example/listifyjetapp/ui/screens/listItem/ListItemViewModel.kt +++ b/app/src/main/java/com/example/listifyjetapp/ui/screens/listItem/ListItemViewModel.kt @@ -22,6 +22,8 @@ class ListItemViewModel @Inject constructor( var isLoading by mutableStateOf(false) var errorMessage by mutableStateOf(null) + var activeItemId by mutableStateOf(null) + var activeItemDescription by mutableStateOf("") fun getAllItems(listId: Int) { viewModelScope.launch { From 279a6c3c8e1160f72cbe9cb101c24c7cb30e1e59 Mon Sep 17 00:00:00 2001 From: jiyu13 Date: Thu, 14 Aug 2025 19:10:46 -0400 Subject: [PATCH 098/261] show ListItemActionSheet if activeItemId is not null --- .../ui/screens/listItem/ListifyListItemScreen.kt | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/app/src/main/java/com/example/listifyjetapp/ui/screens/listItem/ListifyListItemScreen.kt b/app/src/main/java/com/example/listifyjetapp/ui/screens/listItem/ListifyListItemScreen.kt index 8526cc9..03c558c 100644 --- a/app/src/main/java/com/example/listifyjetapp/ui/screens/listItem/ListifyListItemScreen.kt +++ b/app/src/main/java/com/example/listifyjetapp/ui/screens/listItem/ListifyListItemScreen.kt @@ -28,6 +28,7 @@ import androidx.compose.ui.unit.dp import androidx.hilt.navigation.compose.hiltViewModel import androidx.navigation.NavController import com.example.listifyjetapp.R +import com.example.listifyjetapp.components.buttomMenus.ListItemActionSheet import com.example.listifyjetapp.utils.filterListItems import com.example.listifyjetapp.widgets.ListifySearchBar import com.example.listifyjetapp.widgets.ListifyTopBar @@ -104,6 +105,13 @@ fun ListifyListItemScreen( } } } + + if (viewModel.activeItemId != null) { + // show ModalBottomSheet + ListItemActionSheet( + onDismissSheet = { viewModel.activeItemId = null } + ) + } } } From d5f31f270e8455d9e98e81c8f7e3b7e043e29af5 Mon Sep 17 00:00:00 2001 From: jiyu13 Date: Thu, 14 Aug 2025 19:24:38 -0400 Subject: [PATCH 099/261] - add haptic feedback (vibration) - combinedClickable() to implement press-and-hold interactions - add check box --- .../ui/screens/listItem/ListItemRow.kt | 35 +++++++++++++++---- 1 file changed, 29 insertions(+), 6 deletions(-) diff --git a/app/src/main/java/com/example/listifyjetapp/ui/screens/listItem/ListItemRow.kt b/app/src/main/java/com/example/listifyjetapp/ui/screens/listItem/ListItemRow.kt index efd69c7..b3b91f7 100644 --- a/app/src/main/java/com/example/listifyjetapp/ui/screens/listItem/ListItemRow.kt +++ b/app/src/main/java/com/example/listifyjetapp/ui/screens/listItem/ListItemRow.kt @@ -1,14 +1,14 @@ package com.example.listifyjetapp.ui.screens.listItem +import android.util.Log import androidx.compose.foundation.background +import androidx.compose.foundation.combinedClickable import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding -import androidx.compose.material3.ColorScheme +import androidx.compose.material3.Checkbox import androidx.compose.material3.HorizontalDivider -import androidx.compose.material3.LocalTextStyle import androidx.compose.material3.Text -import androidx.compose.material3.TextFieldDefaults import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf @@ -17,17 +17,21 @@ import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color +import androidx.compose.ui.platform.LocalHapticFeedback +import androidx.compose.ui.hapticfeedback.HapticFeedbackType import androidx.compose.ui.text.TextStyle import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.text.style.TextDecoration import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp +import androidx.hilt.navigation.compose.hiltViewModel import com.example.listifyjetapp.model.ListItem import com.example.listifyjetapp.ui.theme.ListifyColor @Composable fun ListItemRow ( item: ListItem, + viewModel: ListItemViewModel = hiltViewModel() ) { val description = if (item.description.length >= 20) { item.description.substring(0, 20) + "..." @@ -35,7 +39,7 @@ fun ListItemRow ( item.description } val isChecked by remember { mutableStateOf(item.checked) } - + val haptics = LocalHapticFeedback.current Row(modifier = Modifier .padding(vertical = 16.dp) @@ -46,10 +50,29 @@ fun ListItemRow ( Row( modifier = Modifier .fillMaxWidth() - .padding(horizontal = 8.dp), + .padding(horizontal = 8.dp) + .combinedClickable( + onClick = { + //isChecked = !isChecked + //viewModel.activeItemId = item.id + }, + onLongClick = { + viewModel.activeItemId = item.id + viewModel.activeItemDescription = item.description + haptics.performHapticFeedback(HapticFeedbackType.LongPress) + Log.d("Long press", viewModel.activeItemId.toString()) + }, + onLongClickLabel = item.description + ), verticalAlignment = Alignment.CenterVertically, - //horizontalArrangement = Arrangement.Start + ) { + Checkbox( + checked = isChecked, + onCheckedChange = { + // TODO: call patchListItem() + }, + ) Text( text = description, From 9742cd292114493fde916695659ee9530ad94618 Mon Sep 17 00:00:00 2001 From: jiyu13 Date: Thu, 14 Aug 2025 19:24:50 -0400 Subject: [PATCH 100/261] create ModalBottomSheet --- .../buttomMenus/ListItemActionSheet.kt | 48 +++++++++++++++++++ 1 file changed, 48 insertions(+) create mode 100644 app/src/main/java/com/example/listifyjetapp/components/buttomMenus/ListItemActionSheet.kt diff --git a/app/src/main/java/com/example/listifyjetapp/components/buttomMenus/ListItemActionSheet.kt b/app/src/main/java/com/example/listifyjetapp/components/buttomMenus/ListItemActionSheet.kt new file mode 100644 index 0000000..45477d7 --- /dev/null +++ b/app/src/main/java/com/example/listifyjetapp/components/buttomMenus/ListItemActionSheet.kt @@ -0,0 +1,48 @@ +package com.example.listifyjetapp.components.buttomMenus + +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Delete +import androidx.compose.material.icons.filled.Edit +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.Icon +import androidx.compose.material3.ListItem +import androidx.compose.material3.ModalBottomSheet +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import androidx.hilt.navigation.compose.hiltViewModel +import com.example.listifyjetapp.ui.screens.listItem.ListItemViewModel + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun ListItemActionSheet( + onDismissSheet: () -> Unit, + viewModel: ListItemViewModel = hiltViewModel() +) { + ModalBottomSheet( + onDismissRequest = onDismissSheet, + modifier = Modifier.fillMaxWidth(), + ) { + + Text( + modifier = Modifier.fillMaxWidth().padding(8.dp), + textAlign = TextAlign.Center, + text = viewModel.activeItemDescription, + fontSize = 20.sp + ) + + ListItem( + headlineContent = { Text(text = "Edit", fontSize = 20.sp) }, + leadingContent = { Icon(Icons.Default.Edit, null) } + ) + ListItem( + headlineContent = { Text(text = "Delete", fontSize = 20.sp) }, + leadingContent = { Icon(Icons.Default.Delete, null) } + ) + } +} \ No newline at end of file From 8cf7644a763322b330169953980b8d8220b2053c Mon Sep 17 00:00:00 2001 From: jiyu13 Date: Thu, 14 Aug 2025 21:04:00 -0400 Subject: [PATCH 101/261] update item checked state --- .../example/listifyjetapp/model/ListModel.kt | 4 ++++ .../example/listifyjetapp/network/ListsAPI.kt | 10 ++++++---- .../repository/ListItemRepository.kt | 14 +++++++++++++ .../ui/screens/listItem/ListItemViewModel.kt | 20 +++++++++++++++++++ 4 files changed, 44 insertions(+), 4 deletions(-) diff --git a/app/src/main/java/com/example/listifyjetapp/model/ListModel.kt b/app/src/main/java/com/example/listifyjetapp/model/ListModel.kt index 37c35ee..6226ae8 100644 --- a/app/src/main/java/com/example/listifyjetapp/model/ListModel.kt +++ b/app/src/main/java/com/example/listifyjetapp/model/ListModel.kt @@ -33,3 +33,7 @@ data class ListItem( val checked: Boolean, @SerializedName("list_id") val listId: Int, ) + +data class CheckedItem( + val checked: Boolean +) \ No newline at end of file diff --git a/app/src/main/java/com/example/listifyjetapp/network/ListsAPI.kt b/app/src/main/java/com/example/listifyjetapp/network/ListsAPI.kt index 754f9d1..e30ea5e 100644 --- a/app/src/main/java/com/example/listifyjetapp/network/ListsAPI.kt +++ b/app/src/main/java/com/example/listifyjetapp/network/ListsAPI.kt @@ -1,5 +1,6 @@ package com.example.listifyjetapp.network +import com.example.listifyjetapp.model.CheckedItem import com.example.listifyjetapp.model.ListItem import com.example.listifyjetapp.model.ListModel import com.example.listifyjetapp.model.ListName @@ -89,11 +90,12 @@ interface ListifyAPI { suspend fun createListItem(@Path("list_id") listId: Int) {} // Patch list item - @POST("lists/{list_id}/{item_id}") - suspend fun patchListItem( + @PATCH("lists/{list_id}/{item_id}") + suspend fun checkListItem( @Path("list_id") listId: Int, - @Path("item_id") itemId: Int - ) {} + @Path("item_id") itemId: Int, + @Body request: CheckedItem + ):ListItem // Delete list item @DELETE("/lists//{item_id}") diff --git a/app/src/main/java/com/example/listifyjetapp/repository/ListItemRepository.kt b/app/src/main/java/com/example/listifyjetapp/repository/ListItemRepository.kt index 9e0cd7c..10244f2 100644 --- a/app/src/main/java/com/example/listifyjetapp/repository/ListItemRepository.kt +++ b/app/src/main/java/com/example/listifyjetapp/repository/ListItemRepository.kt @@ -1,6 +1,7 @@ package com.example.listifyjetapp.repository import com.example.listifyjetapp.data.ListifyResult +import com.example.listifyjetapp.model.CheckedItem import com.example.listifyjetapp.model.ListItem import com.example.listifyjetapp.network.ListifyAPI import javax.inject.Inject @@ -17,4 +18,17 @@ class ListItemRepository @Inject constructor( } } + suspend fun checkListItem( + itemId: Int, + listId: Int, + updatedData: CheckedItem + ): ListifyResult { + try { + val response = api.checkListItem(listId = listId, itemId = itemId, request = updatedData) + return ListifyResult.Success(data = response) + } catch (e: Exception) { + return ListifyResult.Failure(e.message ?: "Error checking item") + } + } + } \ No newline at end of file diff --git a/app/src/main/java/com/example/listifyjetapp/ui/screens/listItem/ListItemViewModel.kt b/app/src/main/java/com/example/listifyjetapp/ui/screens/listItem/ListItemViewModel.kt index fd99b2b..9a66bc3 100644 --- a/app/src/main/java/com/example/listifyjetapp/ui/screens/listItem/ListItemViewModel.kt +++ b/app/src/main/java/com/example/listifyjetapp/ui/screens/listItem/ListItemViewModel.kt @@ -8,6 +8,7 @@ import androidx.compose.runtime.setValue import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import com.example.listifyjetapp.data.ListifyResult +import com.example.listifyjetapp.model.CheckedItem import com.example.listifyjetapp.model.ListItem import com.example.listifyjetapp.repository.ListItemRepository import dagger.hilt.android.lifecycle.HiltViewModel @@ -48,4 +49,23 @@ class ListItemViewModel @Inject constructor( } } } + + fun checkListItem( + itemId: Int, + listId: Int, + updatedData: CheckedItem + ) = viewModelScope.launch { + val result = repository.checkListItem(listId = listId, itemId = itemId, updatedData = updatedData) + when (result) { + is ListifyResult.Success -> { + val updated = result.data + listItems.replaceAll { if(it.id == updated.id) updated else it } + } + + is ListifyResult.Failure -> { + errorMessage = result.errorMessage + Log.d("Fail to fetch list items by list id", result.toString()) + } + } + } } \ No newline at end of file From fecace46dc7ba197ee59cf81462532e298aaf337 Mon Sep 17 00:00:00 2001 From: jiyu13 Date: Thu, 14 Aug 2025 21:05:04 -0400 Subject: [PATCH 102/261] create callback to update checked state --- .../ui/screens/listItem/ListItemRow.kt | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/app/src/main/java/com/example/listifyjetapp/ui/screens/listItem/ListItemRow.kt b/app/src/main/java/com/example/listifyjetapp/ui/screens/listItem/ListItemRow.kt index b3b91f7..9a625f3 100644 --- a/app/src/main/java/com/example/listifyjetapp/ui/screens/listItem/ListItemRow.kt +++ b/app/src/main/java/com/example/listifyjetapp/ui/screens/listItem/ListItemRow.kt @@ -25,6 +25,7 @@ import androidx.compose.ui.text.style.TextDecoration import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import androidx.hilt.navigation.compose.hiltViewModel +import com.example.listifyjetapp.model.CheckedItem import com.example.listifyjetapp.model.ListItem import com.example.listifyjetapp.ui.theme.ListifyColor @@ -38,9 +39,18 @@ fun ListItemRow ( } else { item.description } - val isChecked by remember { mutableStateOf(item.checked) } + val isChecked by remember(item) { mutableStateOf(item.checked) } val haptics = LocalHapticFeedback.current + + fun onCheckBoxClick() { + viewModel.checkListItem( + listId = item.listId, + itemId = item.id, + updatedData = CheckedItem(checked = !isChecked), + ) + } + Row(modifier = Modifier .padding(vertical = 16.dp) .fillMaxWidth() @@ -69,9 +79,7 @@ fun ListItemRow ( ) { Checkbox( checked = isChecked, - onCheckedChange = { - // TODO: call patchListItem() - }, + onCheckedChange = { onCheckBoxClick() } ) Text( From 512b3e4dfe846388d73cc07945d09ccf789a2e3f Mon Sep 17 00:00:00 2001 From: jiyu13 Date: Fri, 15 Aug 2025 16:41:45 -0400 Subject: [PATCH 103/261] add data model, api, repository, view model for updating list item info --- .../example/listifyjetapp/model/ListModel.kt | 5 ++++ .../example/listifyjetapp/network/ListsAPI.kt | 8 +++++++ .../repository/ListItemRepository.kt | 14 +++++++++++ .../ui/screens/listItem/ListItemViewModel.kt | 23 ++++++++++++++++++- 4 files changed, 49 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/com/example/listifyjetapp/model/ListModel.kt b/app/src/main/java/com/example/listifyjetapp/model/ListModel.kt index 6226ae8..0bcfa9b 100644 --- a/app/src/main/java/com/example/listifyjetapp/model/ListModel.kt +++ b/app/src/main/java/com/example/listifyjetapp/model/ListModel.kt @@ -36,4 +36,9 @@ data class ListItem( data class CheckedItem( val checked: Boolean +) + +data class UpdateItemInfo( + val description: String, + val units: String ) \ No newline at end of file diff --git a/app/src/main/java/com/example/listifyjetapp/network/ListsAPI.kt b/app/src/main/java/com/example/listifyjetapp/network/ListsAPI.kt index e30ea5e..2f36477 100644 --- a/app/src/main/java/com/example/listifyjetapp/network/ListsAPI.kt +++ b/app/src/main/java/com/example/listifyjetapp/network/ListsAPI.kt @@ -6,6 +6,7 @@ import com.example.listifyjetapp.model.ListModel import com.example.listifyjetapp.model.ListName import com.example.listifyjetapp.model.LoginInfo import com.example.listifyjetapp.model.LoginSuccess +import com.example.listifyjetapp.model.UpdateItemInfo import com.example.listifyjetapp.model.User import retrofit2.http.Body import retrofit2.http.DELETE @@ -97,6 +98,13 @@ interface ListifyAPI { @Body request: CheckedItem ):ListItem + @PATCH("lists/{list_id}/{item_id}") + suspend fun patchListItem( + @Path("list_id") listId: Int, + @Path("item_id") itemId: Int, + @Body request: UpdateItemInfo + ):ListItem + // Delete list item @DELETE("/lists//{item_id}") suspend fun deleteListItem( diff --git a/app/src/main/java/com/example/listifyjetapp/repository/ListItemRepository.kt b/app/src/main/java/com/example/listifyjetapp/repository/ListItemRepository.kt index 10244f2..97075b8 100644 --- a/app/src/main/java/com/example/listifyjetapp/repository/ListItemRepository.kt +++ b/app/src/main/java/com/example/listifyjetapp/repository/ListItemRepository.kt @@ -3,6 +3,7 @@ package com.example.listifyjetapp.repository import com.example.listifyjetapp.data.ListifyResult import com.example.listifyjetapp.model.CheckedItem import com.example.listifyjetapp.model.ListItem +import com.example.listifyjetapp.model.UpdateItemInfo import com.example.listifyjetapp.network.ListifyAPI import javax.inject.Inject @@ -31,4 +32,17 @@ class ListItemRepository @Inject constructor( } } + suspend fun patchListItem( + itemId: Int, + listId: Int, + updatedInfo: UpdateItemInfo + ): ListifyResult { + try { + val response = api.patchListItem(listId = listId, itemId = itemId, request = updatedInfo) + return ListifyResult.Success(data = response) + } catch (e: Exception) { + return ListifyResult.Failure(e.message ?: "Error checking item") + } + } + } \ No newline at end of file diff --git a/app/src/main/java/com/example/listifyjetapp/ui/screens/listItem/ListItemViewModel.kt b/app/src/main/java/com/example/listifyjetapp/ui/screens/listItem/ListItemViewModel.kt index 9a66bc3..b68316b 100644 --- a/app/src/main/java/com/example/listifyjetapp/ui/screens/listItem/ListItemViewModel.kt +++ b/app/src/main/java/com/example/listifyjetapp/ui/screens/listItem/ListItemViewModel.kt @@ -10,6 +10,7 @@ import androidx.lifecycle.viewModelScope import com.example.listifyjetapp.data.ListifyResult import com.example.listifyjetapp.model.CheckedItem import com.example.listifyjetapp.model.ListItem +import com.example.listifyjetapp.model.UpdateItemInfo import com.example.listifyjetapp.repository.ListItemRepository import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.launch @@ -25,6 +26,7 @@ class ListItemViewModel @Inject constructor( var activeItemId by mutableStateOf(null) var activeItemDescription by mutableStateOf("") + var isActionSheetShown by mutableStateOf(false) fun getAllItems(listId: Int) { viewModelScope.launch { @@ -64,7 +66,26 @@ class ListItemViewModel @Inject constructor( is ListifyResult.Failure -> { errorMessage = result.errorMessage - Log.d("Fail to fetch list items by list id", result.toString()) + Log.d("Fail to update item checked state", result.toString()) + } + } + } + + fun patchListItemInfo( + itemId: Int, + listId: Int, + updatedInfo: UpdateItemInfo + ) = viewModelScope.launch { + val result = repository.patchListItem(listId = listId, itemId = itemId, updatedInfo = updatedInfo) + when (result) { + is ListifyResult.Success -> { + val updated = result.data + listItems.replaceAll { if(it.id == updated.id) updated else it } + } + + is ListifyResult.Failure -> { + errorMessage = result.errorMessage + Log.d("Fail to update item description and units", result.toString()) } } } From e39fdcd238d8acc55f66e19fd98edd5d795c516b Mon Sep 17 00:00:00 2001 From: jiyu13 Date: Fri, 15 Aug 2025 16:44:28 -0400 Subject: [PATCH 104/261] create form for editing list description + units --- .../components/formModals/EditItemForm.kt | 50 +++++++++++++++++++ 1 file changed, 50 insertions(+) create mode 100644 app/src/main/java/com/example/listifyjetapp/components/formModals/EditItemForm.kt diff --git a/app/src/main/java/com/example/listifyjetapp/components/formModals/EditItemForm.kt b/app/src/main/java/com/example/listifyjetapp/components/formModals/EditItemForm.kt new file mode 100644 index 0000000..0bf1a0d --- /dev/null +++ b/app/src/main/java/com/example/listifyjetapp/components/formModals/EditItemForm.kt @@ -0,0 +1,50 @@ +package com.example.listifyjetapp.components.formModals + +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.Button +import androidx.compose.runtime.Composable +import androidx.compose.runtime.MutableState +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.unit.dp +import com.example.listifyjetapp.ui.theme.ListifyColor +import com.example.listifyjetapp.widgets.FilledButton +import com.example.listifyjetapp.widgets.FormInputField + +@Composable +fun EditItemForm( + description: MutableState, + units: MutableState, + onDescriptionChange: (String) -> Unit, + onUnitsChange: (String) -> Unit, + onEditFormSubmit: () -> Unit +) { + + Column( + modifier = Modifier.fillMaxWidth() + ) { + FormInputField( + textState = description, + onValueChange = onDescriptionChange + ) + + FormInputField( + textState = units, + onValueChange = onUnitsChange + ) + + FilledButton( + modifier = Modifier.fillMaxWidth().padding(vertical = 6.dp), + shape = RoundedCornerShape(10.dp), + containerColor = ListifyColor.SplashYellow, + contentColor = Color.White, + text = "Update", + buttonIcon = null, + iconDescription = null, + onClick = { onEditFormSubmit() } + ) + } +} \ No newline at end of file From 6619df651915fed9dd31f7dfdf7b0a27844898af Mon Sep 17 00:00:00 2001 From: jiyu13 Date: Fri, 15 Aug 2025 16:47:38 -0400 Subject: [PATCH 105/261] Implement updating item description + units - remove checkbox default paddings - add icon for toggling edit form - submit edit form --- .../ui/screens/listItem/ListItemRow.kt | 113 +++++++++++++----- 1 file changed, 81 insertions(+), 32 deletions(-) diff --git a/app/src/main/java/com/example/listifyjetapp/ui/screens/listItem/ListItemRow.kt b/app/src/main/java/com/example/listifyjetapp/ui/screens/listItem/ListItemRow.kt index 9a625f3..ca233cb 100644 --- a/app/src/main/java/com/example/listifyjetapp/ui/screens/listItem/ListItemRow.kt +++ b/app/src/main/java/com/example/listifyjetapp/ui/screens/listItem/ListItemRow.kt @@ -2,14 +2,24 @@ package com.example.listifyjetapp.ui.screens.listItem import android.util.Log import androidx.compose.foundation.background +import androidx.compose.foundation.clickable import androidx.compose.foundation.combinedClickable +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.KeyboardArrowDown import androidx.compose.material3.Checkbox +import androidx.compose.material3.CheckboxDefaults import androidx.compose.material3.HorizontalDivider +import androidx.compose.material3.Icon +import androidx.compose.material3.LocalMinimumInteractiveComponentSize import androidx.compose.material3.Text import androidx.compose.runtime.Composable +import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember @@ -20,15 +30,20 @@ import androidx.compose.ui.graphics.Color import androidx.compose.ui.platform.LocalHapticFeedback import androidx.compose.ui.hapticfeedback.HapticFeedbackType import androidx.compose.ui.text.TextStyle +import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.text.style.TextDecoration +import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import androidx.hilt.navigation.compose.hiltViewModel +import com.example.listifyjetapp.components.formModals.EditItemForm import com.example.listifyjetapp.model.CheckedItem import com.example.listifyjetapp.model.ListItem +import com.example.listifyjetapp.model.UpdateItemInfo import com.example.listifyjetapp.ui.theme.ListifyColor + @Composable fun ListItemRow ( item: ListItem, @@ -39,9 +54,12 @@ fun ListItemRow ( } else { item.description } - val isChecked by remember(item) { mutableStateOf(item.checked) } val haptics = LocalHapticFeedback.current + val isChecked by remember(item) { mutableStateOf(item.checked) } + var isEditFormShown by remember { mutableStateOf(false) } + val descriptionState = remember(item) { mutableStateOf(item.description) } + val unitsState = remember(item) { mutableStateOf(item.units) } fun onCheckBoxClick() { viewModel.checkListItem( @@ -51,57 +69,88 @@ fun ListItemRow ( ) } + fun onEditFormDismiss() { + isEditFormShown = !isEditFormShown + descriptionState.value = item.description + unitsState.value = item.units + } + + fun onEditFormSubmit() { + val updatedInfo = UpdateItemInfo( + description = descriptionState.value, + units = unitsState.value + ) + viewModel.patchListItemInfo(itemId = item.id, listId = item.listId, updatedInfo = updatedInfo) + isEditFormShown = false + } + Row(modifier = Modifier .padding(vertical = 16.dp) .fillMaxWidth() .background(Color.Transparent), - + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.SpaceBetween ) { Row( - modifier = Modifier - .fillMaxWidth() - .padding(horizontal = 8.dp) - .combinedClickable( - onClick = { - //isChecked = !isChecked - //viewModel.activeItemId = item.id - }, + modifier = Modifier.combinedClickable( + onClick = {}, onLongClick = { viewModel.activeItemId = item.id viewModel.activeItemDescription = item.description + viewModel.isActionSheetShown = true haptics.performHapticFeedback(HapticFeedbackType.LongPress) Log.d("Long press", viewModel.activeItemId.toString()) }, onLongClickLabel = item.description ), verticalAlignment = Alignment.CenterVertically, - ) { - Checkbox( - checked = isChecked, - onCheckedChange = { onCheckBoxClick() } - ) + // remove checkbox default paddings + CompositionLocalProvider(LocalMinimumInteractiveComponentSize provides Dp.Unspecified) { + Checkbox( + colors = CheckboxDefaults.colors(Color.Red), + checked = isChecked, + onCheckedChange = { onCheckBoxClick() } + ) + } - Text( - text = description, - color = if (isChecked) {ListifyColor.TextGrey} else {ListifyColor.TextBlack}, - fontSize = 20.sp, - style = TextStyle( - textDecoration = if (isChecked) { TextDecoration.LineThrough } else {null}, + Column(modifier = Modifier.padding(horizontal = 16.dp)) { + Text( + text = description, + color = if (isChecked) { ListifyColor.TextGrey } else { ListifyColor.TextBlack }, + fontSize = 20.sp, + fontWeight = FontWeight.Bold, + style = TextStyle( + textDecoration = if (isChecked) { TextDecoration.LineThrough } else { null }, + ) ) - ) - Text( - modifier = Modifier - .weight(1f) - .padding(horizontal = 4.dp), - text = item.units, - textAlign = TextAlign.End, - color = ListifyColor.TextGrey, - fontSize = 16.sp - ) + Text( + text = if (item.units.isNotEmpty()) { "QTY ${item.units}" } else { "QTY 1" }, + textAlign = TextAlign.End, + color = ListifyColor.TextGrey, + fontSize = 16.sp + ) + } } - + Icon( + imageVector = Icons.Default.KeyboardArrowDown, + contentDescription = "GO back icon", + modifier = Modifier + .size(24.dp) + .clickable { onEditFormDismiss() } + ) } + + if (isEditFormShown) { + EditItemForm( + description = descriptionState, + units = unitsState, + onDescriptionChange = { descriptionState.value = it }, + onUnitsChange = { unitsState.value = it }, + onEditFormSubmit = { onEditFormSubmit() } + ) + } + HorizontalDivider() } \ No newline at end of file From 95f50727f010e8ef3174541b2f2271c9345698b6 Mon Sep 17 00:00:00 2001 From: jiyu13 Date: Fri, 15 Aug 2025 17:18:44 -0400 Subject: [PATCH 106/261] - refactor rows --- .../ui/screens/listItem/ListItemRow.kt | 68 +++++++++---------- 1 file changed, 34 insertions(+), 34 deletions(-) diff --git a/app/src/main/java/com/example/listifyjetapp/ui/screens/listItem/ListItemRow.kt b/app/src/main/java/com/example/listifyjetapp/ui/screens/listItem/ListItemRow.kt index ca233cb..c2c4271 100644 --- a/app/src/main/java/com/example/listifyjetapp/ui/screens/listItem/ListItemRow.kt +++ b/app/src/main/java/com/example/listifyjetapp/ui/screens/listItem/ListItemRow.kt @@ -88,11 +88,9 @@ fun ListItemRow ( .padding(vertical = 16.dp) .fillMaxWidth() .background(Color.Transparent), - verticalAlignment = Alignment.CenterVertically, - horizontalArrangement = Arrangement.SpaceBetween ) { Row( - modifier = Modifier.combinedClickable( + modifier = Modifier.fillMaxWidth().combinedClickable( onClick = {}, onLongClick = { viewModel.activeItemId = item.id @@ -104,42 +102,44 @@ fun ListItemRow ( onLongClickLabel = item.description ), verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.SpaceBetween ) { - // remove checkbox default paddings - CompositionLocalProvider(LocalMinimumInteractiveComponentSize provides Dp.Unspecified) { - Checkbox( - colors = CheckboxDefaults.colors(Color.Red), - checked = isChecked, - onCheckedChange = { onCheckBoxClick() } - ) - } + Row(verticalAlignment = Alignment.CenterVertically){ + // remove checkbox default paddings + CompositionLocalProvider(LocalMinimumInteractiveComponentSize provides Dp.Unspecified) { + Checkbox( + colors = CheckboxDefaults.colors(Color.Red), + checked = isChecked, + onCheckedChange = { onCheckBoxClick() } + ) + } - Column(modifier = Modifier.padding(horizontal = 16.dp)) { - Text( - text = description, - color = if (isChecked) { ListifyColor.TextGrey } else { ListifyColor.TextBlack }, - fontSize = 20.sp, - fontWeight = FontWeight.Bold, - style = TextStyle( - textDecoration = if (isChecked) { TextDecoration.LineThrough } else { null }, + Column(modifier = Modifier.padding(horizontal = 16.dp)) { + Text( + text = description, + color = if (isChecked) { ListifyColor.TextGrey } else { ListifyColor.TextBlack }, + fontSize = 20.sp, + fontWeight = FontWeight.Bold, + style = TextStyle( + textDecoration = if (isChecked) { TextDecoration.LineThrough } else { null }, + ) + ) + Text( + text = if (item.units.isNotEmpty()) { "QTY ${item.units}" } else { "QTY 1" }, + textAlign = TextAlign.End, + color = ListifyColor.TextGrey, + fontSize = 16.sp ) - ) - Text( - text = if (item.units.isNotEmpty()) { "QTY ${item.units}" } else { "QTY 1" }, - textAlign = TextAlign.End, - color = ListifyColor.TextGrey, - fontSize = 16.sp - ) + } } + Icon( + imageVector = Icons.Default.KeyboardArrowDown, + contentDescription = "GO back icon", + modifier = Modifier + .size(24.dp) + .clickable { onEditFormDismiss() } + ) } - - Icon( - imageVector = Icons.Default.KeyboardArrowDown, - contentDescription = "GO back icon", - modifier = Modifier - .size(24.dp) - .clickable { onEditFormDismiss() } - ) } if (isEditFormShown) { From 3a462f684f054324e6acd5d47502e89ccf5341dc Mon Sep 17 00:00:00 2001 From: jiyu13 Date: Fri, 15 Aug 2025 17:20:55 -0400 Subject: [PATCH 107/261] close edit form when row is clicked --- .../example/listifyjetapp/ui/screens/listItem/ListItemRow.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/java/com/example/listifyjetapp/ui/screens/listItem/ListItemRow.kt b/app/src/main/java/com/example/listifyjetapp/ui/screens/listItem/ListItemRow.kt index c2c4271..c6134a9 100644 --- a/app/src/main/java/com/example/listifyjetapp/ui/screens/listItem/ListItemRow.kt +++ b/app/src/main/java/com/example/listifyjetapp/ui/screens/listItem/ListItemRow.kt @@ -91,7 +91,7 @@ fun ListItemRow ( ) { Row( modifier = Modifier.fillMaxWidth().combinedClickable( - onClick = {}, + onClick = { isEditFormShown = !isEditFormShown }, onLongClick = { viewModel.activeItemId = item.id viewModel.activeItemDescription = item.description From 8e5ecd838c9280ddd6955eec0f05b76a6a9ba4df Mon Sep 17 00:00:00 2001 From: jiyu13 Date: Fri, 15 Aug 2025 17:42:54 -0400 Subject: [PATCH 108/261] remove code related to ModalBottomSheet --- .../listifyjetapp/ui/screens/listItem/ListItemRow.kt | 8 +------- .../ui/screens/listItem/ListItemViewModel.kt | 3 --- .../ui/screens/listItem/ListifyListItemScreen.kt | 9 --------- 3 files changed, 1 insertion(+), 19 deletions(-) diff --git a/app/src/main/java/com/example/listifyjetapp/ui/screens/listItem/ListItemRow.kt b/app/src/main/java/com/example/listifyjetapp/ui/screens/listItem/ListItemRow.kt index c6134a9..d05db3b 100644 --- a/app/src/main/java/com/example/listifyjetapp/ui/screens/listItem/ListItemRow.kt +++ b/app/src/main/java/com/example/listifyjetapp/ui/screens/listItem/ListItemRow.kt @@ -92,13 +92,7 @@ fun ListItemRow ( Row( modifier = Modifier.fillMaxWidth().combinedClickable( onClick = { isEditFormShown = !isEditFormShown }, - onLongClick = { - viewModel.activeItemId = item.id - viewModel.activeItemDescription = item.description - viewModel.isActionSheetShown = true - haptics.performHapticFeedback(HapticFeedbackType.LongPress) - Log.d("Long press", viewModel.activeItemId.toString()) - }, + onLongClick = { viewModel.activeItemDescription = item.description }, onLongClickLabel = item.description ), verticalAlignment = Alignment.CenterVertically, diff --git a/app/src/main/java/com/example/listifyjetapp/ui/screens/listItem/ListItemViewModel.kt b/app/src/main/java/com/example/listifyjetapp/ui/screens/listItem/ListItemViewModel.kt index b68316b..3063938 100644 --- a/app/src/main/java/com/example/listifyjetapp/ui/screens/listItem/ListItemViewModel.kt +++ b/app/src/main/java/com/example/listifyjetapp/ui/screens/listItem/ListItemViewModel.kt @@ -23,10 +23,7 @@ class ListItemViewModel @Inject constructor( val listItems = mutableStateListOf() var isLoading by mutableStateOf(false) var errorMessage by mutableStateOf(null) - - var activeItemId by mutableStateOf(null) var activeItemDescription by mutableStateOf("") - var isActionSheetShown by mutableStateOf(false) fun getAllItems(listId: Int) { viewModelScope.launch { diff --git a/app/src/main/java/com/example/listifyjetapp/ui/screens/listItem/ListifyListItemScreen.kt b/app/src/main/java/com/example/listifyjetapp/ui/screens/listItem/ListifyListItemScreen.kt index 03c558c..03aed15 100644 --- a/app/src/main/java/com/example/listifyjetapp/ui/screens/listItem/ListifyListItemScreen.kt +++ b/app/src/main/java/com/example/listifyjetapp/ui/screens/listItem/ListifyListItemScreen.kt @@ -28,7 +28,6 @@ import androidx.compose.ui.unit.dp import androidx.hilt.navigation.compose.hiltViewModel import androidx.navigation.NavController import com.example.listifyjetapp.R -import com.example.listifyjetapp.components.buttomMenus.ListItemActionSheet import com.example.listifyjetapp.utils.filterListItems import com.example.listifyjetapp.widgets.ListifySearchBar import com.example.listifyjetapp.widgets.ListifyTopBar @@ -105,15 +104,7 @@ fun ListifyListItemScreen( } } } - - if (viewModel.activeItemId != null) { - // show ModalBottomSheet - ListItemActionSheet( - onDismissSheet = { viewModel.activeItemId = null } - ) - } } - } } } \ No newline at end of file From f60f850af50bcb3b9d2f688d9bbb4c35b74755ec Mon Sep 17 00:00:00 2001 From: jiyu13 Date: Fri, 15 Aug 2025 18:14:26 -0400 Subject: [PATCH 109/261] create custom outlined button --- .../widgets/CustomOutlinedButton.kt | 54 +++++++++++++++++++ 1 file changed, 54 insertions(+) create mode 100644 app/src/main/java/com/example/listifyjetapp/widgets/CustomOutlinedButton.kt diff --git a/app/src/main/java/com/example/listifyjetapp/widgets/CustomOutlinedButton.kt b/app/src/main/java/com/example/listifyjetapp/widgets/CustomOutlinedButton.kt new file mode 100644 index 0000000..60687e8 --- /dev/null +++ b/app/src/main/java/com/example/listifyjetapp/widgets/CustomOutlinedButton.kt @@ -0,0 +1,54 @@ +package com.example.listifyjetapp.widgets + +import androidx.compose.foundation.BorderStroke +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.MailOutline +import androidx.compose.material3.Icon +import androidx.compose.material3.OutlinedButton +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.Shape +import androidx.compose.ui.graphics.vector.ImageVector +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp + +@Composable +fun CustomOutlinedButton( + modifier: Modifier, + shape: Shape, + border: BorderStroke, + text: String, + textColor: Color, + buttonIcon: ImageVector? = null, + iconDescription: String? = null, + onClick: () -> Unit, +) { + OutlinedButton( + modifier = modifier, + shape = shape, + border = border, + onClick = { onClick() } + ) { + if (buttonIcon != null) { + Icon( + imageVector = buttonIcon, + modifier = Modifier.size(20.dp), + contentDescription = iconDescription, + ) + } + Text( + text = text, + color = textColor, + modifier = Modifier.padding(8.dp), + fontWeight = FontWeight.Bold, + fontSize = 18.sp, + ) + } +} \ No newline at end of file From e34a866019b8df82e5e17f78ce80cb78a8acf181 Mon Sep 17 00:00:00 2001 From: jiyu13 Date: Fri, 15 Aug 2025 18:15:30 -0400 Subject: [PATCH 110/261] implement custom outlined button --- .../components/formModals/EditItemForm.kt | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/app/src/main/java/com/example/listifyjetapp/components/formModals/EditItemForm.kt b/app/src/main/java/com/example/listifyjetapp/components/formModals/EditItemForm.kt index 0bf1a0d..effa33c 100644 --- a/app/src/main/java/com/example/listifyjetapp/components/formModals/EditItemForm.kt +++ b/app/src/main/java/com/example/listifyjetapp/components/formModals/EditItemForm.kt @@ -1,16 +1,20 @@ package com.example.listifyjetapp.components.formModals +import androidx.compose.foundation.BorderStroke import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material3.Button +import androidx.compose.material3.OutlinedButton +import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.MutableState import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.unit.dp import com.example.listifyjetapp.ui.theme.ListifyColor +import com.example.listifyjetapp.widgets.CustomOutlinedButton import com.example.listifyjetapp.widgets.FilledButton import com.example.listifyjetapp.widgets.FormInputField @@ -46,5 +50,13 @@ fun EditItemForm( iconDescription = null, onClick = { onEditFormSubmit() } ) + CustomOutlinedButton( + modifier = Modifier.fillMaxWidth().padding(vertical = 6.dp), + shape = RoundedCornerShape(10.dp), + border = BorderStroke(2.dp, color = ListifyColor.TextDark), + text = "Delete", + textColor = Color.Red, + onClick = { } + ) } } \ No newline at end of file From dffaa0dbf8f60a726060eb792c81c52e63b30612 Mon Sep 17 00:00:00 2001 From: jiyu13 Date: Fri, 15 Aug 2025 18:28:29 -0400 Subject: [PATCH 111/261] change broder color to red --- .../example/listifyjetapp/components/formModals/EditItemForm.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/java/com/example/listifyjetapp/components/formModals/EditItemForm.kt b/app/src/main/java/com/example/listifyjetapp/components/formModals/EditItemForm.kt index effa33c..db0e43b 100644 --- a/app/src/main/java/com/example/listifyjetapp/components/formModals/EditItemForm.kt +++ b/app/src/main/java/com/example/listifyjetapp/components/formModals/EditItemForm.kt @@ -53,7 +53,7 @@ fun EditItemForm( CustomOutlinedButton( modifier = Modifier.fillMaxWidth().padding(vertical = 6.dp), shape = RoundedCornerShape(10.dp), - border = BorderStroke(2.dp, color = ListifyColor.TextDark), + border = BorderStroke(2.dp, color = Color.Red), text = "Delete", textColor = Color.Red, onClick = { } From c8b66ad0f3ffe84efcb34309379e25f631340480 Mon Sep 17 00:00:00 2001 From: jiyu13 Date: Fri, 15 Aug 2025 22:11:52 -0400 Subject: [PATCH 112/261] handle deleting list item by calling the delete api --- .../components/formModals/EditItemForm.kt | 5 +++-- .../example/listifyjetapp/network/ListsAPI.kt | 4 ++-- .../repository/ListItemRepository.kt | 9 +++++++++ .../ui/screens/listItem/ListItemRow.kt | 11 ++++++++--- .../ui/screens/listItem/ListItemViewModel.kt | 16 ++++++++++++++++ 5 files changed, 38 insertions(+), 7 deletions(-) diff --git a/app/src/main/java/com/example/listifyjetapp/components/formModals/EditItemForm.kt b/app/src/main/java/com/example/listifyjetapp/components/formModals/EditItemForm.kt index db0e43b..3b3dd3a 100644 --- a/app/src/main/java/com/example/listifyjetapp/components/formModals/EditItemForm.kt +++ b/app/src/main/java/com/example/listifyjetapp/components/formModals/EditItemForm.kt @@ -24,7 +24,8 @@ fun EditItemForm( units: MutableState, onDescriptionChange: (String) -> Unit, onUnitsChange: (String) -> Unit, - onEditFormSubmit: () -> Unit + onEditFormSubmit: () -> Unit, + onDeleteItem: () -> Unit ) { Column( @@ -56,7 +57,7 @@ fun EditItemForm( border = BorderStroke(2.dp, color = Color.Red), text = "Delete", textColor = Color.Red, - onClick = { } + onClick = { onDeleteItem() } ) } } \ No newline at end of file diff --git a/app/src/main/java/com/example/listifyjetapp/network/ListsAPI.kt b/app/src/main/java/com/example/listifyjetapp/network/ListsAPI.kt index 2f36477..35389c7 100644 --- a/app/src/main/java/com/example/listifyjetapp/network/ListsAPI.kt +++ b/app/src/main/java/com/example/listifyjetapp/network/ListsAPI.kt @@ -106,9 +106,9 @@ interface ListifyAPI { ):ListItem // Delete list item - @DELETE("/lists//{item_id}") + @DELETE("lists/{list_id}/{item_id}") suspend fun deleteListItem( @Path("list_id") listId: Int, @Path("item_id") itemId: Int - ) {} + ): List } \ No newline at end of file diff --git a/app/src/main/java/com/example/listifyjetapp/repository/ListItemRepository.kt b/app/src/main/java/com/example/listifyjetapp/repository/ListItemRepository.kt index 97075b8..867c6ab 100644 --- a/app/src/main/java/com/example/listifyjetapp/repository/ListItemRepository.kt +++ b/app/src/main/java/com/example/listifyjetapp/repository/ListItemRepository.kt @@ -45,4 +45,13 @@ class ListItemRepository @Inject constructor( } } + suspend fun deleteListItem( itemId: Int, listId: Int ): ListifyResult> { + try { + val response = api.deleteListItem(listId = listId, itemId = itemId) + return ListifyResult.Success(data = response) + } catch (e: Exception) { + return ListifyResult.Failure(e.message ?: "Error deleting item by item id") + } + } + } \ No newline at end of file diff --git a/app/src/main/java/com/example/listifyjetapp/ui/screens/listItem/ListItemRow.kt b/app/src/main/java/com/example/listifyjetapp/ui/screens/listItem/ListItemRow.kt index d05db3b..71c1c94 100644 --- a/app/src/main/java/com/example/listifyjetapp/ui/screens/listItem/ListItemRow.kt +++ b/app/src/main/java/com/example/listifyjetapp/ui/screens/listItem/ListItemRow.kt @@ -54,9 +54,8 @@ fun ListItemRow ( } else { item.description } - val haptics = LocalHapticFeedback.current - val isChecked by remember(item) { mutableStateOf(item.checked) } + val isChecked by remember(item) { mutableStateOf(item.checked) } var isEditFormShown by remember { mutableStateOf(false) } val descriptionState = remember(item) { mutableStateOf(item.description) } val unitsState = remember(item) { mutableStateOf(item.units) } @@ -84,6 +83,11 @@ fun ListItemRow ( isEditFormShown = false } + fun onDeleteItem() { + viewModel.deleteListItem(listId = item.listId, itemId = item.id) + isEditFormShown = false + } + Row(modifier = Modifier .padding(vertical = 16.dp) .fillMaxWidth() @@ -142,7 +146,8 @@ fun ListItemRow ( units = unitsState, onDescriptionChange = { descriptionState.value = it }, onUnitsChange = { unitsState.value = it }, - onEditFormSubmit = { onEditFormSubmit() } + onEditFormSubmit = { onEditFormSubmit() }, + onDeleteItem = { onDeleteItem() } ) } diff --git a/app/src/main/java/com/example/listifyjetapp/ui/screens/listItem/ListItemViewModel.kt b/app/src/main/java/com/example/listifyjetapp/ui/screens/listItem/ListItemViewModel.kt index 3063938..06fd918 100644 --- a/app/src/main/java/com/example/listifyjetapp/ui/screens/listItem/ListItemViewModel.kt +++ b/app/src/main/java/com/example/listifyjetapp/ui/screens/listItem/ListItemViewModel.kt @@ -8,6 +8,7 @@ import androidx.compose.runtime.setValue import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import com.example.listifyjetapp.data.ListifyResult +import com.example.listifyjetapp.data.ListifyStorageManager import com.example.listifyjetapp.model.CheckedItem import com.example.listifyjetapp.model.ListItem import com.example.listifyjetapp.model.UpdateItemInfo @@ -86,4 +87,19 @@ class ListItemViewModel @Inject constructor( } } } + + fun deleteListItem(listId: Int, itemId: Int) = viewModelScope.launch{ + val result = repository.deleteListItem(listId = listId, itemId = itemId) + when (result) { + is ListifyResult.Success -> { + listItems.clear() + listItems.addAll(result.data) + } + + is ListifyResult.Failure -> { + errorMessage = result.errorMessage + Log.d("Fail to delete item.", result.toString()) + } + } + } } \ No newline at end of file From 667a2630bc73cb10849469f159360f45d75e964b Mon Sep 17 00:00:00 2001 From: jiyu13 Date: Fri, 15 Aug 2025 22:21:40 -0400 Subject: [PATCH 113/261] create userId - Takes a regular Flow from storage - Converts it to a StateFlow - Exposes it as a read-only StateFlow to observers --- .../listifyjetapp/ui/screens/lists/ListsViewModel.kt | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/app/src/main/java/com/example/listifyjetapp/ui/screens/lists/ListsViewModel.kt b/app/src/main/java/com/example/listifyjetapp/ui/screens/lists/ListsViewModel.kt index 44c84ce..207c558 100644 --- a/app/src/main/java/com/example/listifyjetapp/ui/screens/lists/ListsViewModel.kt +++ b/app/src/main/java/com/example/listifyjetapp/ui/screens/lists/ListsViewModel.kt @@ -14,8 +14,11 @@ import com.example.listifyjetapp.model.ListName import com.example.listifyjetapp.repository.ListsRepository import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.SharingStarted +import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.first +import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.launch import javax.inject.Inject @@ -31,6 +34,14 @@ class ListsViewModel @Inject constructor( private val _navigateBack = MutableStateFlow(false) val navigateBack = _navigateBack.asStateFlow() + // Convert Flow to StateFlow + private val _userId = storageManager.userIdFlow.stateIn( + scope = viewModelScope, + started = SharingStarted.WhileSubscribed(5000), // Keeps the flow alive while observed + initialValue = 0 + ) + val userId: StateFlow = _userId + fun getUserLists() { viewModelScope.launch { val userId = storageManager.getUser().first().userId From 5648932a0cca1707d2afd8694b8b65a21a65937a Mon Sep 17 00:00:00 2001 From: jiyu13 Date: Sat, 16 Aug 2025 11:52:51 -0400 Subject: [PATCH 114/261] - get userid from viewModel - pass usrId as param --- .../ui/screens/newList/ListifyNewListScreen.kt | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/app/src/main/java/com/example/listifyjetapp/ui/screens/newList/ListifyNewListScreen.kt b/app/src/main/java/com/example/listifyjetapp/ui/screens/newList/ListifyNewListScreen.kt index d71b47b..cdc14d2 100644 --- a/app/src/main/java/com/example/listifyjetapp/ui/screens/newList/ListifyNewListScreen.kt +++ b/app/src/main/java/com/example/listifyjetapp/ui/screens/newList/ListifyNewListScreen.kt @@ -10,6 +10,8 @@ import androidx.compose.material3.Scaffold import androidx.compose.material3.Surface 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.remember import androidx.compose.ui.Modifier @@ -28,6 +30,7 @@ fun ListifyNewListScreen( ) { val formTextState = remember { mutableStateOf("") } + val userId by viewModel.userId.collectAsState() LaunchedEffect(viewModel.navigateBack) { viewModel.navigateBack.collect { navigateBack -> @@ -40,7 +43,7 @@ fun ListifyNewListScreen( fun onSaveClick() { val listName = ListName(name = formTextState.value) - viewModel.insertListByUser(4, listName) + viewModel.insertListByUser(userId, listName) } Scaffold( @@ -52,10 +55,7 @@ fun ListifyNewListScreen( onGoBackButtonClicked = {navController.popBackStack()}, leftText = "Cancel", rightText = "Save", - onRightButtonClick = { - // TODO: save new list - onSaveClick() - } + onRightButtonClick = { onSaveClick() } ) } ) { innerPadding -> From 3b03ebed8f67669a20e78bf3f2a557993a786104 Mon Sep 17 00:00:00 2001 From: jiyu13 Date: Sat, 16 Aug 2025 11:54:28 -0400 Subject: [PATCH 115/261] - call onGoBackButtonClicked --- .../java/com/example/listifyjetapp/widgets/ListifyTopBar.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/com/example/listifyjetapp/widgets/ListifyTopBar.kt b/app/src/main/java/com/example/listifyjetapp/widgets/ListifyTopBar.kt index ae71ebe..9613764 100644 --- a/app/src/main/java/com/example/listifyjetapp/widgets/ListifyTopBar.kt +++ b/app/src/main/java/com/example/listifyjetapp/widgets/ListifyTopBar.kt @@ -68,7 +68,7 @@ fun ListifyTopBar( Icon( imageVector = goBackIcon, contentDescription = "GO back icon", - modifier = Modifier.size(24.dp).clickable { onGoBackButtonClicked.invoke() } + modifier = Modifier.size(24.dp).clickable { onGoBackButtonClicked() } ) } if (leftText.toString().isNotEmpty() && leftText != null) { @@ -76,7 +76,7 @@ fun ListifyTopBar( text = leftText, modifier = Modifier .padding(horizontal = 16.dp) - .clickable { onGoBackButtonClicked.invoke() }, + .clickable { onGoBackButtonClicked() }, fontSize = 20.sp, color = ListifyColor.TextDark ) From d1eef44d04d3b51e438f33a5e07d86c66123426a Mon Sep 17 00:00:00 2001 From: jiyu13 Date: Sat, 16 Aug 2025 11:54:34 -0400 Subject: [PATCH 116/261] commit --- .idea/deploymentTargetSelector.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.idea/deploymentTargetSelector.xml b/.idea/deploymentTargetSelector.xml index 400f821..fe9873e 100644 --- a/.idea/deploymentTargetSelector.xml +++ b/.idea/deploymentTargetSelector.xml @@ -4,7 +4,7 @@