From 990c621e26cb56391ebd05faaf30521801dc9a44 Mon Sep 17 00:00:00 2001 From: starry-shivam Date: Tue, 7 May 2024 13:56:27 +0530 Subject: [PATCH 1/5] Update database schema and and add inital layout Signed-off-by: starry-shivam --- .../6.json | 190 ++++++++++++++++++ .../greenstash/database/core/AppDatabase.kt | 5 +- .../starry/greenstash/database/goal/Goal.kt | 5 +- .../greenstash/database/goal/GoalDao.kt | 82 +++++++- .../database/transaction/TransactionDao.kt | 12 ++ .../greenstash/database/widget/WidgetDao.kt | 17 ++ .../greenstash/ui/navigation/DrawerScreens.kt | 9 + .../ui/screens/archive/ArchiveViewModel.kt | 62 ++++++ .../archive/composables/ArchiveScreen.kt | 38 ++++ .../ui/screens/home/composables/HomeDrawer.kt | 2 +- app/src/main/res/drawable/ic_nav_archive.xml | 15 ++ app/src/main/res/drawable/ic_nav_backups.xml | 7 +- app/src/main/res/values/strings.xml | 1 + 13 files changed, 433 insertions(+), 12 deletions(-) create mode 100644 app/schemas/com.starry.greenstash.database.core.AppDatabase/6.json create mode 100644 app/src/main/java/com/starry/greenstash/ui/screens/archive/ArchiveViewModel.kt create mode 100644 app/src/main/java/com/starry/greenstash/ui/screens/archive/composables/ArchiveScreen.kt create mode 100644 app/src/main/res/drawable/ic_nav_archive.xml diff --git a/app/schemas/com.starry.greenstash.database.core.AppDatabase/6.json b/app/schemas/com.starry.greenstash.database.core.AppDatabase/6.json new file mode 100644 index 00000000..fcd726ca --- /dev/null +++ b/app/schemas/com.starry.greenstash.database.core.AppDatabase/6.json @@ -0,0 +1,190 @@ +{ + "formatVersion": 1, + "database": { + "version": 6, + "identityHash": "b16df1594b1494e7947402d9e7a822da", + "entities": [ + { + "tableName": "saving_goal", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`title` TEXT NOT NULL, `targetAmount` REAL NOT NULL, `deadline` TEXT NOT NULL, `goalImage` BLOB, `additionalNotes` TEXT NOT NULL, `priority` INTEGER NOT NULL DEFAULT 2, `reminder` INTEGER NOT NULL DEFAULT false, `goalIconId` TEXT DEFAULT 'Image', `archived` INTEGER NOT NULL DEFAULT false, `goalId` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "title", + "columnName": "title", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "targetAmount", + "columnName": "targetAmount", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "deadline", + "columnName": "deadline", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "goalImage", + "columnName": "goalImage", + "affinity": "BLOB", + "notNull": false + }, + { + "fieldPath": "additionalNotes", + "columnName": "additionalNotes", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "priority", + "columnName": "priority", + "affinity": "INTEGER", + "notNull": true, + "defaultValue": "2" + }, + { + "fieldPath": "reminder", + "columnName": "reminder", + "affinity": "INTEGER", + "notNull": true, + "defaultValue": "false" + }, + { + "fieldPath": "goalIconId", + "columnName": "goalIconId", + "affinity": "TEXT", + "notNull": false, + "defaultValue": "'Image'" + }, + { + "fieldPath": "archived", + "columnName": "archived", + "affinity": "INTEGER", + "notNull": true, + "defaultValue": "false" + }, + { + "fieldPath": "goalId", + "columnName": "goalId", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "goalId" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "transaction", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`ownerGoalId` INTEGER NOT NULL, `type` INTEGER NOT NULL, `timeStamp` INTEGER NOT NULL, `amount` REAL NOT NULL, `notes` TEXT NOT NULL, `transactionId` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, FOREIGN KEY(`ownerGoalId`) REFERENCES `saving_goal`(`goalId`) ON UPDATE NO ACTION ON DELETE CASCADE )", + "fields": [ + { + "fieldPath": "ownerGoalId", + "columnName": "ownerGoalId", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "type", + "columnName": "type", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "timeStamp", + "columnName": "timeStamp", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "amount", + "columnName": "amount", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "notes", + "columnName": "notes", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "transactionId", + "columnName": "transactionId", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "transactionId" + ] + }, + "indices": [ + { + "name": "index_transaction_ownerGoalId", + "unique": false, + "columnNames": [ + "ownerGoalId" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_transaction_ownerGoalId` ON `${TABLE_NAME}` (`ownerGoalId`)" + } + ], + "foreignKeys": [ + { + "table": "saving_goal", + "onDelete": "CASCADE", + "onUpdate": "NO ACTION", + "columns": [ + "ownerGoalId" + ], + "referencedColumns": [ + "goalId" + ] + } + ] + }, + { + "tableName": "widget_data", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`appWidgetId` INTEGER NOT NULL, `goalId` INTEGER NOT NULL, PRIMARY KEY(`appWidgetId`))", + "fields": [ + { + "fieldPath": "appWidgetId", + "columnName": "appWidgetId", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "goalId", + "columnName": "goalId", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "appWidgetId" + ] + }, + "indices": [], + "foreignKeys": [] + } + ], + "views": [], + "setupQueries": [ + "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)", + "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, 'b16df1594b1494e7947402d9e7a822da')" + ] + } +} \ No newline at end of file diff --git a/app/src/main/java/com/starry/greenstash/database/core/AppDatabase.kt b/app/src/main/java/com/starry/greenstash/database/core/AppDatabase.kt index 467f996d..8a297391 100644 --- a/app/src/main/java/com/starry/greenstash/database/core/AppDatabase.kt +++ b/app/src/main/java/com/starry/greenstash/database/core/AppDatabase.kt @@ -39,13 +39,14 @@ import com.starry.greenstash.database.widget.WidgetData @Database( entities = [Goal::class, Transaction::class, WidgetData::class], - version = 5, + version = 6, exportSchema = true, autoMigrations = [ AutoMigration(from = 1, to = 2), AutoMigration(from = 2, to = 3), AutoMigration(from = 3, to = 4), - AutoMigration(from = 4, to = 5) + AutoMigration(from = 4, to = 5), + AutoMigration(from = 5, to = 6) ] ) @TypeConverters(Converters::class) diff --git a/app/src/main/java/com/starry/greenstash/database/goal/Goal.kt b/app/src/main/java/com/starry/greenstash/database/goal/Goal.kt index 2ae27b58..63a17c77 100644 --- a/app/src/main/java/com/starry/greenstash/database/goal/Goal.kt +++ b/app/src/main/java/com/starry/greenstash/database/goal/Goal.kt @@ -50,7 +50,10 @@ data class Goal( val reminder: Boolean, // Added in database schema v5 @ColumnInfo(defaultValue = "Image") - val goalIconId: String? + val goalIconId: String?, + // Added in database schema v6 + @ColumnInfo(defaultValue = "false") + val archived: Boolean = false ) { @PrimaryKey(autoGenerate = true) var goalId: Long = 0L diff --git a/app/src/main/java/com/starry/greenstash/database/goal/GoalDao.kt b/app/src/main/java/com/starry/greenstash/database/goal/GoalDao.kt index 39714d4f..56a570fd 100644 --- a/app/src/main/java/com/starry/greenstash/database/goal/GoalDao.kt +++ b/app/src/main/java/com/starry/greenstash/database/goal/GoalDao.kt @@ -37,10 +37,21 @@ import kotlinx.coroutines.flow.Flow @Dao interface GoalDao { + // Insert related functions ========================================================== + /** + * Insert goal. + * @param goal Goal to insert. + * @return Id of inserted goal. + */ @Insert suspend fun insertGoal(goal: Goal): Long + /** + * Insert goal with transactions. + * This method is used when restoring data from backup file. + * @param goalsWithTransactions List of GoalWithTransactions. + */ @Transaction suspend fun insertGoalWithTransaction(goalsWithTransactions: List) { goalsWithTransactions.forEach { goalWithTransactions -> @@ -55,58 +66,119 @@ interface GoalDao { } } + // Update related functions ========================================================== + + /** + * Update goal. + * @param goal Goal to update. + */ @Update suspend fun updateGoal(goal: Goal) + // Delete related functions ========================================================== + + /** + * Delete goal by id. + * @param goalId Id of goal. + */ @Query("DELETE FROM saving_goal WHERE goalId = :goalId") suspend fun deleteGoal(goalId: Long) + // Get related functions ========================================================== + + /** + * Get all unarchived goals. + * @return List of GoalWithTransactions. + */ @Transaction - @Query("SELECT * FROM saving_goal") + @Query("SELECT * FROM saving_goal WHERE archived = 0") suspend fun getAllGoals(): List + /** + * Get all unarchived goals as LiveData. + * @return LiveData of List of GoalWithTransactions. + */ @Transaction - @Query("SELECT * FROM saving_goal") + @Query("SELECT * FROM saving_goal WHERE archived = 0") fun getAllGoalsAsLiveData(): LiveData> + /** + * Get goal by id. + * @param goalId Id of goal. + * @return Goal. + */ @Query("SELECT * FROM saving_goal WHERE goalId = :goalId") suspend fun getGoalById(goalId: Long): Goal? + /** + * Get goal with transactions. + * @param goalId Id of goal. + * @return GoalWithTransactions. + */ @Transaction @Query("SELECT * FROM saving_goal WHERE goalId = :goalId") suspend fun getGoalWithTransactionById(goalId: Long): GoalWithTransactions? + /** + * Get goal with transactions as Flow. + * @param goalId Id of goal. + * @return Flow of GoalWithTransactions. + */ @Transaction @Query("SELECT * FROM saving_goal WHERE goalId = :goalId") fun getGoalWithTransactionByIdAsFlow(goalId: Long): Flow + /** + * Get all unarchived goals sorted by name. + * @param sortOrder 1 for ascending, 2 for descending. + * @return Flow of List of GoalWithTransactions. + */ @Transaction @Query( - "SELECT * FROM saving_goal ORDER BY " + + "SELECT * FROM saving_goal WHERE archived = 0 ORDER BY " + "CASE WHEN :sortOrder = 1 THEN title END ASC, " + "CASE WHEN :sortOrder = 2 THEN title END DESC " ) fun getAllGoalsByTitle(sortOrder: Int): Flow> + /** + * Get all unarchived goals sorted by target amount. + * @param sortOrder 1 for ascending, 2 for descending. + * @return Flow of List of GoalWithTransactions. + */ @Transaction @Query( - "SELECT * FROM saving_goal ORDER BY " + + "SELECT * FROM saving_goal WHERE archived = 0 ORDER BY " + "CASE WHEN :sortOrder = 1 THEN targetAmount END ASC, " + "CASE WHEN :sortOrder = 2 THEN targetAmount END DESC " ) fun getAllGoalsByAmount(sortOrder: Int): Flow> + /** + * Get all unarchived goals sorted by priority. + * @param sortOrder 1 for ascending, 2 for descending. + * @return Flow of List of GoalWithTransactions. + */ @Transaction @Query( - "SELECT * FROM saving_goal ORDER BY " + + "SELECT * FROM saving_goal WHERE archived = 0 ORDER BY " + "CASE WHEN :sortOrder = 1 THEN priority END ASC, " + "CASE WHEN :sortOrder = 2 THEN priority END DESC " ) fun getAllGoalsByPriority(sortOrder: Int): Flow> + /** + * Get all archived goals. + * @return Flow of List of GoalWithTransactions. + */ + @Transaction + @Query("SELECT * FROM saving_goal WHERE archived = 1") + fun getAllArchivedGoals(): Flow> + /** * For internal use with insertGoalWithTransaction() method only, * Please use Transaction Dao for transaction related operations. + * @param transactions List of transactions. */ @Insert suspend fun insertTransactions( diff --git a/app/src/main/java/com/starry/greenstash/database/transaction/TransactionDao.kt b/app/src/main/java/com/starry/greenstash/database/transaction/TransactionDao.kt index c710cd56..cebcde2c 100644 --- a/app/src/main/java/com/starry/greenstash/database/transaction/TransactionDao.kt +++ b/app/src/main/java/com/starry/greenstash/database/transaction/TransactionDao.kt @@ -33,12 +33,24 @@ import androidx.room.Update @Dao interface TransactionDao { + /** + * Insert transaction. + * @param transaction Transaction to insert. + */ @Insert suspend fun insertTransaction(transaction: Transaction) + /** + * Delete transaction. + * @param transaction Transaction to delete. + */ @Delete suspend fun deleteTransaction(transaction: Transaction) + /** + * Update transaction. + * @param transaction Transaction to update. + */ @Update suspend fun updateTransaction(transaction: Transaction) } \ No newline at end of file diff --git a/app/src/main/java/com/starry/greenstash/database/widget/WidgetDao.kt b/app/src/main/java/com/starry/greenstash/database/widget/WidgetDao.kt index 6ef80717..1e3fe97d 100644 --- a/app/src/main/java/com/starry/greenstash/database/widget/WidgetDao.kt +++ b/app/src/main/java/com/starry/greenstash/database/widget/WidgetDao.kt @@ -35,15 +35,32 @@ import androidx.room.Update @Dao interface WidgetDao { + /** + * Insert widget data. + * @param widgetData WidgetData to insert. + */ @Insert(onConflict = OnConflictStrategy.REPLACE) suspend fun insertWidgetData(widgetData: WidgetData) + /** + * Delete widget data. + * @param widgetData WidgetData to delete. + */ @Delete suspend fun deleteWidgetData(widgetData: WidgetData) + /** + * Update widget data. + * @param widgetData WidgetData to update. + */ @Update suspend fun updateWidgetData(widgetData: WidgetData) + /** + * Get widget data by appWidgetId. + * @param appWidgetId AppWidgetId to get widget data. + * @return WidgetData. + */ @Query("SELECT * FROM widget_data WHERE appWidgetId = :appWidgetId") suspend fun getWidgetData(appWidgetId: Int): WidgetData? } \ No newline at end of file diff --git a/app/src/main/java/com/starry/greenstash/ui/navigation/DrawerScreens.kt b/app/src/main/java/com/starry/greenstash/ui/navigation/DrawerScreens.kt index addb8463..577b5356 100644 --- a/app/src/main/java/com/starry/greenstash/ui/navigation/DrawerScreens.kt +++ b/app/src/main/java/com/starry/greenstash/ui/navigation/DrawerScreens.kt @@ -28,7 +28,16 @@ package com.starry.greenstash.ui.navigation import com.starry.greenstash.R sealed class DrawerScreens(val route: String, val nameResId: Int, val iconResId: Int) { + + companion object { + fun getAllItems() = listOf(Home, Archive, Backups, Settings) + } + data object Home : DrawerScreens("home", R.string.drawer_home, R.drawable.ic_nav_home) + + data object Archive : + DrawerScreens("archive", R.string.drawer_archive, R.drawable.ic_nav_archive) + data object Backups : DrawerScreens("backups", R.string.drawer_backups, R.drawable.ic_nav_backups) diff --git a/app/src/main/java/com/starry/greenstash/ui/screens/archive/ArchiveViewModel.kt b/app/src/main/java/com/starry/greenstash/ui/screens/archive/ArchiveViewModel.kt new file mode 100644 index 00000000..7c7eb7d9 --- /dev/null +++ b/app/src/main/java/com/starry/greenstash/ui/screens/archive/ArchiveViewModel.kt @@ -0,0 +1,62 @@ +/** + * MIT License + * + * Copyright (c) [2022 - Present] Stɑrry Shivɑm + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + + +package com.starry.greenstash.ui.screens.archive + +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import com.starry.greenstash.database.goal.Goal +import com.starry.greenstash.database.goal.GoalDao +import com.starry.greenstash.reminder.ReminderManager +import dagger.hilt.android.lifecycle.HiltViewModel +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import javax.inject.Inject + +@HiltViewModel +class ArchiveViewModel @Inject constructor( + private val goalDao: GoalDao, + private val reminderManager: ReminderManager +) : ViewModel() { + + val archivedGoals = goalDao.getAllArchivedGoals() + + fun deleteGoal(goal: Goal) { + viewModelScope.launch(Dispatchers.IO) { + goalDao.deleteGoal(goal.goalId) + // Stop the reminder if it is set for the goal + if (reminderManager.isReminderSet(goal.goalId)) { + reminderManager.stopReminder(goal.goalId) + } + } + } + + fun restoreGoal(goal: Goal) { + viewModelScope.launch(Dispatchers.IO) { + val updatedGoal = goal.copy(archived = false) + goalDao.updateGoal(updatedGoal) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/starry/greenstash/ui/screens/archive/composables/ArchiveScreen.kt b/app/src/main/java/com/starry/greenstash/ui/screens/archive/composables/ArchiveScreen.kt new file mode 100644 index 00000000..b0ead732 --- /dev/null +++ b/app/src/main/java/com/starry/greenstash/ui/screens/archive/composables/ArchiveScreen.kt @@ -0,0 +1,38 @@ +/** + * MIT License + * + * Copyright (c) [2022 - Present] Stɑrry Shivɑm + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + + +package com.starry.greenstash.ui.screens.archive.composables + +import androidx.compose.runtime.Composable +import androidx.hilt.navigation.compose.hiltViewModel +import androidx.navigation.NavController +import com.starry.greenstash.ui.screens.archive.ArchiveViewModel + +@Composable +fun ArchiveScreen(navController: NavController) { + val viewModel: ArchiveViewModel = hiltViewModel() + + // TODO: Implement the Archive Screen +} \ No newline at end of file diff --git a/app/src/main/java/com/starry/greenstash/ui/screens/home/composables/HomeDrawer.kt b/app/src/main/java/com/starry/greenstash/ui/screens/home/composables/HomeDrawer.kt index c39e77cb..f0eac01a 100644 --- a/app/src/main/java/com/starry/greenstash/ui/screens/home/composables/HomeDrawer.kt +++ b/app/src/main/java/com/starry/greenstash/ui/screens/home/composables/HomeDrawer.kt @@ -81,7 +81,7 @@ import kotlinx.coroutines.launch @Composable fun HomeDrawer(drawerState: DrawerState, navController: NavController, themeMode: ThemeMode) { - val items = listOf(DrawerScreens.Home, DrawerScreens.Backups, DrawerScreens.Settings) + val items = DrawerScreens.getAllItems() val selectedItem = remember { mutableStateOf(items[0]) } val view = LocalView.current diff --git a/app/src/main/res/drawable/ic_nav_archive.xml b/app/src/main/res/drawable/ic_nav_archive.xml new file mode 100644 index 00000000..65bc34fd --- /dev/null +++ b/app/src/main/res/drawable/ic_nav_archive.xml @@ -0,0 +1,15 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_nav_backups.xml b/app/src/main/res/drawable/ic_nav_backups.xml index 65bc34fd..924cd4fe 100644 --- a/app/src/main/res/drawable/ic_nav_backups.xml +++ b/app/src/main/res/drawable/ic_nav_backups.xml @@ -1,4 +1,3 @@ - + android:pathData="M6.5,18L6.5,17.91C6.5,17.045 6.5,16.251 6.587,15.606C6.682,14.895 6.907,14.143 7.525,13.525C8.143,12.907 8.895,12.682 9.606,12.587C10.251,12.5 11.045,12.5 11.91,12.5H12.09C12.955,12.5 13.749,12.5 14.394,12.587C15.105,12.682 15.857,12.907 16.475,13.525C17.093,14.143 17.318,14.895 17.413,15.606C17.499,16.242 17.5,17.021 17.5,17.872C20.073,17.322 22,15.06 22,12.353C22,9.881 20.393,7.78 18.155,7.015C17.837,4.194 15.416,2 12.476,2C9.32,2 6.762,4.528 6.762,7.647C6.762,8.337 6.887,8.998 7.116,9.609C6.847,9.557 6.57,9.529 6.286,9.529C3.919,9.529 2,11.426 2,13.765C2,16.104 3.919,18 6.286,18L6.5,18Z" /> + + android:pathData="M12,14C10.114,14 9.172,14 8.586,14.586C8,15.172 8,16.114 8,18C8,19.886 8,20.828 8.586,21.414C9.172,22 10.114,22 12,22C13.886,22 14.828,22 15.414,21.414C16,20.828 16,19.886 16,18C16,16.114 16,15.172 15.414,14.586C14.828,14 13.886,14 12,14ZM13.805,17.084L12.471,15.751C12.211,15.491 11.789,15.491 11.529,15.751L10.195,17.084C9.935,17.344 9.935,17.767 10.195,18.027C10.456,18.287 10.878,18.287 11.138,18.027L11.333,17.832V19.778C11.333,20.146 11.632,20.444 12,20.444C12.368,20.444 12.667,20.146 12.667,19.778V17.832L12.862,18.027C13.122,18.287 13.544,18.287 13.805,18.027C14.065,17.767 14.065,17.344 13.805,17.084Z" /> + \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 3b707d31..0844b311 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -9,6 +9,7 @@ Home + Archive Backups Settings Rate Us From a919d894da707d4cd13a455514cafe2aba6cf3bc Mon Sep 17 00:00:00 2001 From: starry-shivam Date: Tue, 7 May 2024 15:45:04 +0530 Subject: [PATCH 2/5] Add archived goal item card Signed-off-by: starry-shivam --- .../greenstash/ui/navigation/DrawerScreens.kt | 2 +- .../greenstash/ui/navigation/NavGraph.kt | 12 ++ .../archive/composables/ArchiveScreen.kt | 146 +++++++++++++++++- .../ui/screens/home/composables/GoalItems.kt | 2 +- .../screens/home/composables/GoalLazyItem.kt | 4 +- app/src/main/res/drawable/ic_nav_archive.xml | 6 +- app/src/main/res/values-es/strings.xml | 6 +- app/src/main/res/values-ru/strings.xml | 6 +- app/src/main/res/values-tr/strings.xml | 6 +- app/src/main/res/values-zh-rCN/strings.xml | 6 +- app/src/main/res/values-zh-rTW/strings.xml | 6 +- app/src/main/res/values/strings.xml | 5 +- 12 files changed, 192 insertions(+), 15 deletions(-) diff --git a/app/src/main/java/com/starry/greenstash/ui/navigation/DrawerScreens.kt b/app/src/main/java/com/starry/greenstash/ui/navigation/DrawerScreens.kt index 577b5356..7ce7e06a 100644 --- a/app/src/main/java/com/starry/greenstash/ui/navigation/DrawerScreens.kt +++ b/app/src/main/java/com/starry/greenstash/ui/navigation/DrawerScreens.kt @@ -39,7 +39,7 @@ sealed class DrawerScreens(val route: String, val nameResId: Int, val iconResId: DrawerScreens("archive", R.string.drawer_archive, R.drawable.ic_nav_archive) data object Backups : - DrawerScreens("backups", R.string.drawer_backups, R.drawable.ic_nav_backups) + DrawerScreens("backups", R.string.drawer_backup, R.drawable.ic_nav_backups) data object Settings : DrawerScreens("settings", R.string.drawer_settings, R.drawable.ic_nav_settings) diff --git a/app/src/main/java/com/starry/greenstash/ui/navigation/NavGraph.kt b/app/src/main/java/com/starry/greenstash/ui/navigation/NavGraph.kt index 60533874..5e46af8f 100644 --- a/app/src/main/java/com/starry/greenstash/ui/navigation/NavGraph.kt +++ b/app/src/main/java/com/starry/greenstash/ui/navigation/NavGraph.kt @@ -34,6 +34,7 @@ import androidx.navigation.NavType import androidx.navigation.compose.NavHost import androidx.navigation.compose.composable import androidx.navigation.navArgument +import com.starry.greenstash.ui.screens.archive.composables.ArchiveScreen import com.starry.greenstash.ui.screens.backups.composables.BackupScreen import com.starry.greenstash.ui.screens.dwscreen.composables.DWScreen import com.starry.greenstash.ui.screens.home.composables.HomeScreen @@ -144,6 +145,17 @@ fun NavGraph( CongratsScreen(navController = navController) } + /** Archive Screen */ + composable( + route = DrawerScreens.Archive.route, + enterTransition = { enterTransition() }, + exitTransition = { exitTransition() }, + popEnterTransition = { popEnterTransition() }, + popExitTransition = { popExitTransition() }, + ) { + ArchiveScreen(navController = navController) + } + /** Backup Screen */ composable( route = DrawerScreens.Backups.route, diff --git a/app/src/main/java/com/starry/greenstash/ui/screens/archive/composables/ArchiveScreen.kt b/app/src/main/java/com/starry/greenstash/ui/screens/archive/composables/ArchiveScreen.kt index b0ead732..a8ffb3ef 100644 --- a/app/src/main/java/com/starry/greenstash/ui/screens/archive/composables/ArchiveScreen.kt +++ b/app/src/main/java/com/starry/greenstash/ui/screens/archive/composables/ArchiveScreen.kt @@ -25,14 +25,156 @@ package com.starry.greenstash.ui.screens.archive.composables +import androidx.compose.foundation.background +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.shape.CircleShape +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Face2 +import androidx.compose.material.icons.filled.Image +import androidx.compose.material.icons.filled.Refresh +import androidx.compose.material.icons.outlined.Delete +import androidx.compose.material3.Card +import androidx.compose.material3.Icon +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.graphics.vector.ImageVector +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.style.TextOverflow +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp import androidx.hilt.navigation.compose.hiltViewModel import androidx.navigation.NavController import com.starry.greenstash.ui.screens.archive.ArchiveViewModel +import com.starry.greenstash.ui.theme.greenstashFont +import com.starry.greenstash.ui.theme.greenstashNumberFont @Composable fun ArchiveScreen(navController: NavController) { val viewModel: ArchiveViewModel = hiltViewModel() + ArchivedGoalItem( + title = "Home Decorations", + icon = Icons.Filled.Face2, + savedAmount = "₹10,000", + onRestoreClicked = {}, + onDeleteClicked = {} + ) +} + +@Composable +fun ArchivedGoalItem( + title: String, + icon: ImageVector?, + savedAmount: String, + onRestoreClicked: () -> Unit, + onDeleteClicked: () -> Unit +) { + Card( + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 12.dp, vertical = 8.dp) + ) { + Column( + Modifier + .fillMaxWidth() + .padding(12.dp) + ) { + Row( + modifier = Modifier + .fillMaxWidth() + ) { + Icon( + imageVector = icon ?: Icons.Filled.Image, + contentDescription = title, + modifier = Modifier.size(40.dp) + ) + + Spacer(modifier = Modifier.weight(1f)) + + Box( + modifier = Modifier + .size(40.dp) + .background( + color = MaterialTheme.colorScheme.primaryContainer, + shape = CircleShape + ) + .clip(CircleShape) + .clickable { onRestoreClicked() } + ) { + Icon( + imageVector = Icons.Filled.Refresh, + contentDescription = title, + modifier = Modifier + .fillMaxSize() + .padding(10.dp), + ) + } + Spacer(modifier = Modifier.width(16.dp)) + + Box( + modifier = Modifier + .size(40.dp) + .background( + color = MaterialTheme.colorScheme.primaryContainer, + shape = CircleShape + ) + .clip(CircleShape) + .clickable { onDeleteClicked() } + ) { + Icon( + imageVector = Icons.Outlined.Delete, + contentDescription = title, + modifier = Modifier + .fillMaxSize() + .padding(10.dp), + ) + } + } + + Spacer(modifier = Modifier.height(14.dp)) + + Text( + text = title, + fontWeight = FontWeight.Medium, + fontFamily = greenstashFont, + fontSize = 18.sp, + maxLines = 1, + overflow = TextOverflow.Ellipsis, + ) + + Text( + text = savedAmount, + fontSize = 22.sp, + fontFamily = greenstashNumberFont, + fontWeight = FontWeight.Bold, + maxLines = 2, + ) + } + } +} + +@Preview +@Composable +fun ArchiveItemPV() { + ArchivedGoalItem( + title = "Home Decorations", + icon = Icons.Filled.Face2, + savedAmount = "₹10,000", + onRestoreClicked = {}, + onDeleteClicked = {} + ) +} - // TODO: Implement the Archive Screen -} \ No newline at end of file diff --git a/app/src/main/java/com/starry/greenstash/ui/screens/home/composables/GoalItems.kt b/app/src/main/java/com/starry/greenstash/ui/screens/home/composables/GoalItems.kt index acec502b..e80f22b9 100644 --- a/app/src/main/java/com/starry/greenstash/ui/screens/home/composables/GoalItems.kt +++ b/app/src/main/java/com/starry/greenstash/ui/screens/home/composables/GoalItems.kt @@ -477,7 +477,7 @@ fun GoalItemsPV() { savedAmount = "$1,000.00", daysLeftText = "Goal Achieved! 🎉", goalProgress = 0.8f, - goalIcon = ImageVector.vectorResource(id = R.drawable.ic_nav_backups), + goalIcon = ImageVector.vectorResource(id = R.drawable.ic_nav_rating), onDepositClicked = {}, onWithdrawClicked = {}, onInfoClicked = {}, diff --git a/app/src/main/java/com/starry/greenstash/ui/screens/home/composables/GoalLazyItem.kt b/app/src/main/java/com/starry/greenstash/ui/screens/home/composables/GoalLazyItem.kt index cba270d3..b574c9a3 100644 --- a/app/src/main/java/com/starry/greenstash/ui/screens/home/composables/GoalLazyItem.kt +++ b/app/src/main/java/com/starry/greenstash/ui/screens/home/composables/GoalLazyItem.kt @@ -230,9 +230,9 @@ fun GoalLazyColumnItem( openDeleteDialog = openDeleteDialog, onDeleteConfirmed = { viewModel.deleteGoal(item.goal) - coroutineScope.launch { + /*coroutineScope.launch { snackBarHostState.showSnackbar(context.getString(R.string.goal_delete_success)) - } + }*/ } ) } diff --git a/app/src/main/res/drawable/ic_nav_archive.xml b/app/src/main/res/drawable/ic_nav_archive.xml index 65bc34fd..6be2cff5 100644 --- a/app/src/main/res/drawable/ic_nav_archive.xml +++ b/app/src/main/res/drawable/ic_nav_archive.xml @@ -7,9 +7,9 @@ + android:fillType="evenOdd" + android:pathData="M2.54497 8.73005C2 9.79961 2 11.1997 2 14C2 16.8003 2 18.2004 2.54497 19.27C3.02433 20.2108 3.78924 20.9757 4.73005 21.455C5.79961 22 7.19974 22 10 22H14C16.8003 22 18.2004 22 19.27 21.455C20.2108 20.9757 20.9757 20.2108 21.455 19.27C22 18.2004 22 16.8003 22 14C22 11.1997 22 9.79961 21.455 8.73005C20.9757 7.78924 20.2108 7.02433 19.27 6.54497C18.2004 6 16.8003 6 14 6H10C7.19974 6 5.79961 6 4.73005 6.54497C3.78924 7.02433 3.02433 7.78924 2.54497 8.73005ZM15.0595 12.4995C15.3353 12.1905 15.3085 11.7164 14.9995 11.4406C14.6905 11.1647 14.2164 11.1915 13.9406 11.5005L10.9286 14.8739L10.0595 13.9005C9.78359 13.5915 9.30947 13.5647 9.0005 13.8406C8.69152 14.1164 8.66468 14.5905 8.94055 14.8995L10.3691 16.4995C10.5114 16.6589 10.7149 16.75 10.9286 16.75C11.1422 16.75 11.3457 16.6589 11.488 16.4995L15.0595 12.4995Z" /> + android:pathData="M20.5348 3.46447C19.0704 2 16.7133 2 11.9993 2C7.28525 2 4.92823 2 3.46377 3.46447C2.70628 4.22195 2.3406 5.21824 2.16406 6.65598C2.69473 6.06532 3.33236 5.57328 4.04836 5.20846C4.82984 4.81027 5.66664 4.6488 6.59316 4.5731C7.48829 4.49997 8.58971 4.49998 9.93646 4.5H14.0621C15.4089 4.49998 16.5103 4.49997 17.4054 4.5731C18.332 4.6488 19.1688 4.81027 19.9502 5.20846C20.6662 5.57328 21.3039 6.06532 21.8345 6.65598C21.658 5.21824 21.2923 4.22195 20.5348 3.46447Z" /> \ No newline at end of file diff --git a/app/src/main/res/values-es/strings.xml b/app/src/main/res/values-es/strings.xml index de44a5d0..1951437e 100644 --- a/app/src/main/res/values-es/strings.xml +++ b/app/src/main/res/values-es/strings.xml @@ -9,7 +9,8 @@ Inicio - Respaldo + Archivo + Respaldo Ajustes Calificarnos Compartir @@ -70,6 +71,9 @@ Ya has completado esta meta. Retirar No has depositado nada aún. + Archivar + ¿Quieres archivar esta meta? + Archivado correctamente. ¿Estás seguro/a? Eliminación exitosa. Ver información de la meta. diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml index 16c69080..bc9a879e 100644 --- a/app/src/main/res/values-ru/strings.xml +++ b/app/src/main/res/values-ru/strings.xml @@ -10,7 +10,8 @@ Главная - Резервные копии + Архив + Резервные копии Настройки Оценить нас Поделиться @@ -71,6 +72,9 @@ Вы уже достигли этой цели. Вывести Вы еще не вносили никакой суммы. + Архивировать + Хотите ли вы архивировать эту цель? + Успешно архивировано. Вы уверены? Удалено успешно. Просмотр информации о цели. diff --git a/app/src/main/res/values-tr/strings.xml b/app/src/main/res/values-tr/strings.xml index e97e9151..7dd071a6 100644 --- a/app/src/main/res/values-tr/strings.xml +++ b/app/src/main/res/values-tr/strings.xml @@ -9,7 +9,8 @@ Ana Sayfa - Yedekler + Arşiv + Yedekler Ayarlar Bizi Değerlendir Paylaş @@ -70,6 +71,9 @@ Birikim hedefine zaten ulaştın. çek Henüz herhangi bir tutar yatırmadın. + Arşivle + Bu hedefi arşivlemek istiyor musunuz? + Başarıyla arşivlendi. Emin misin? Başarıyla Silindi. Hedef bilgilerini görüntüle. diff --git a/app/src/main/res/values-zh-rCN/strings.xml b/app/src/main/res/values-zh-rCN/strings.xml index 951c6809..83480e7f 100644 --- a/app/src/main/res/values-zh-rCN/strings.xml +++ b/app/src/main/res/values-zh-rCN/strings.xml @@ -9,7 +9,8 @@ 主页 - 备份 + 存档 + 备份 设置 给我们评分 分享 @@ -69,6 +70,9 @@ 您已经实现了此目标。 取钱 您尚未存入任何金额。 + 存档 + 您要存档此目标吗? + 已成功存档。 确定吗? 删除成功。 查看目标信息。 diff --git a/app/src/main/res/values-zh-rTW/strings.xml b/app/src/main/res/values-zh-rTW/strings.xml index 130cb650..7f289a87 100644 --- a/app/src/main/res/values-zh-rTW/strings.xml +++ b/app/src/main/res/values-zh-rTW/strings.xml @@ -9,7 +9,8 @@ 首頁 - 備份 + 存檔 + 備份 設定 給我們評分 分享 @@ -68,6 +69,9 @@ 您已達成此目標。 提款 您尚未存入任何金額。 + 存檔 + 您要將此目標存檔嗎? + 已成功存檔。 您確定嗎? 成功刪除。 檢視目標資訊。 diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 0844b311..c9ea5367 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -10,7 +10,7 @@ Home Archive - Backups + Backup Settings Rate Us Share @@ -70,6 +70,9 @@ You\'ve already achieved this goal. Withdraw You haven\'t deposited any amount yet. + Archive + Do you want to archive this goal? + Archived Successfully. Are you sure? Deleted Successfully. View goal information. From 6485c3502153c339f8bd61c71c1423b512523b98 Mon Sep 17 00:00:00 2001 From: starry-shivam Date: Tue, 7 May 2024 22:19:56 +0530 Subject: [PATCH 3/5] Replace deposit button with archive is goal is completed Signed-off-by: starry-shivam --- .../7.json | 190 ++++++++++++++++++ .../greenstash/database/core/AppDatabase.kt | 5 +- .../starry/greenstash/database/goal/Goal.kt | 4 +- .../archive/composables/ArchiveScreen.kt | 146 +++++++++++++- .../ui/screens/home/HomeViewModel.kt | 10 + .../ui/screens/home/composables/GoalItems.kt | 96 ++++++--- .../screens/home/composables/GoalLazyItem.kt | 33 ++- .../screens/home/composables/HomeDialogs.kt | 53 ++++- .../ui/screens/home/composables/HomeScreen.kt | 31 ++- .../settings/composables/GoalCardStyle.kt | 11 +- .../res/drawable/ic_compact_goal_archve.xml | 7 + ...t_plus.xml => ic_compact_goal_deposit.xml} | 0 ...minus.xml => ic_compact_goal_withdraw.xml} | 0 .../res/drawable/ic_transaction_deposit.xml | 20 -- .../res/drawable/ic_transaction_withdraw.xml | 20 -- app/src/main/res/values/strings.xml | 4 + 16 files changed, 518 insertions(+), 112 deletions(-) create mode 100644 app/schemas/com.starry.greenstash.database.core.AppDatabase/7.json create mode 100644 app/src/main/res/drawable/ic_compact_goal_archve.xml rename app/src/main/res/drawable/{ic_deposit_plus.xml => ic_compact_goal_deposit.xml} (100%) rename app/src/main/res/drawable/{ic_withdraw_minus.xml => ic_compact_goal_withdraw.xml} (100%) delete mode 100644 app/src/main/res/drawable/ic_transaction_deposit.xml delete mode 100644 app/src/main/res/drawable/ic_transaction_withdraw.xml diff --git a/app/schemas/com.starry.greenstash.database.core.AppDatabase/7.json b/app/schemas/com.starry.greenstash.database.core.AppDatabase/7.json new file mode 100644 index 00000000..024b5124 --- /dev/null +++ b/app/schemas/com.starry.greenstash.database.core.AppDatabase/7.json @@ -0,0 +1,190 @@ +{ + "formatVersion": 1, + "database": { + "version": 7, + "identityHash": "ee83f4215d1464f23a88d9e4f44a2300", + "entities": [ + { + "tableName": "saving_goal", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`title` TEXT NOT NULL, `targetAmount` REAL NOT NULL, `deadline` TEXT NOT NULL, `goalImage` BLOB, `additionalNotes` TEXT NOT NULL, `priority` INTEGER NOT NULL DEFAULT 2, `reminder` INTEGER NOT NULL DEFAULT 0, `goalIconId` TEXT DEFAULT 'Image', `archived` INTEGER NOT NULL DEFAULT 0, `goalId` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "title", + "columnName": "title", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "targetAmount", + "columnName": "targetAmount", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "deadline", + "columnName": "deadline", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "goalImage", + "columnName": "goalImage", + "affinity": "BLOB", + "notNull": false + }, + { + "fieldPath": "additionalNotes", + "columnName": "additionalNotes", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "priority", + "columnName": "priority", + "affinity": "INTEGER", + "notNull": true, + "defaultValue": "2" + }, + { + "fieldPath": "reminder", + "columnName": "reminder", + "affinity": "INTEGER", + "notNull": true, + "defaultValue": "0" + }, + { + "fieldPath": "goalIconId", + "columnName": "goalIconId", + "affinity": "TEXT", + "notNull": false, + "defaultValue": "'Image'" + }, + { + "fieldPath": "archived", + "columnName": "archived", + "affinity": "INTEGER", + "notNull": true, + "defaultValue": "0" + }, + { + "fieldPath": "goalId", + "columnName": "goalId", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "goalId" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "transaction", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`ownerGoalId` INTEGER NOT NULL, `type` INTEGER NOT NULL, `timeStamp` INTEGER NOT NULL, `amount` REAL NOT NULL, `notes` TEXT NOT NULL, `transactionId` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, FOREIGN KEY(`ownerGoalId`) REFERENCES `saving_goal`(`goalId`) ON UPDATE NO ACTION ON DELETE CASCADE )", + "fields": [ + { + "fieldPath": "ownerGoalId", + "columnName": "ownerGoalId", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "type", + "columnName": "type", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "timeStamp", + "columnName": "timeStamp", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "amount", + "columnName": "amount", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "notes", + "columnName": "notes", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "transactionId", + "columnName": "transactionId", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "transactionId" + ] + }, + "indices": [ + { + "name": "index_transaction_ownerGoalId", + "unique": false, + "columnNames": [ + "ownerGoalId" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_transaction_ownerGoalId` ON `${TABLE_NAME}` (`ownerGoalId`)" + } + ], + "foreignKeys": [ + { + "table": "saving_goal", + "onDelete": "CASCADE", + "onUpdate": "NO ACTION", + "columns": [ + "ownerGoalId" + ], + "referencedColumns": [ + "goalId" + ] + } + ] + }, + { + "tableName": "widget_data", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`appWidgetId` INTEGER NOT NULL, `goalId` INTEGER NOT NULL, PRIMARY KEY(`appWidgetId`))", + "fields": [ + { + "fieldPath": "appWidgetId", + "columnName": "appWidgetId", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "goalId", + "columnName": "goalId", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "appWidgetId" + ] + }, + "indices": [], + "foreignKeys": [] + } + ], + "views": [], + "setupQueries": [ + "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)", + "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, 'ee83f4215d1464f23a88d9e4f44a2300')" + ] + } +} \ No newline at end of file diff --git a/app/src/main/java/com/starry/greenstash/database/core/AppDatabase.kt b/app/src/main/java/com/starry/greenstash/database/core/AppDatabase.kt index 8a297391..ac0ae7a8 100644 --- a/app/src/main/java/com/starry/greenstash/database/core/AppDatabase.kt +++ b/app/src/main/java/com/starry/greenstash/database/core/AppDatabase.kt @@ -39,14 +39,15 @@ import com.starry.greenstash.database.widget.WidgetData @Database( entities = [Goal::class, Transaction::class, WidgetData::class], - version = 6, + version = 7, exportSchema = true, autoMigrations = [ AutoMigration(from = 1, to = 2), AutoMigration(from = 2, to = 3), AutoMigration(from = 3, to = 4), AutoMigration(from = 4, to = 5), - AutoMigration(from = 5, to = 6) + AutoMigration(from = 5, to = 6), + AutoMigration(from = 6, to = 7) ] ) @TypeConverters(Converters::class) diff --git a/app/src/main/java/com/starry/greenstash/database/goal/Goal.kt b/app/src/main/java/com/starry/greenstash/database/goal/Goal.kt index 63a17c77..d2308007 100644 --- a/app/src/main/java/com/starry/greenstash/database/goal/Goal.kt +++ b/app/src/main/java/com/starry/greenstash/database/goal/Goal.kt @@ -46,13 +46,13 @@ data class Goal( @ColumnInfo(defaultValue = "2") val priority: GoalPriority, // Added in database schema v4 - @ColumnInfo(defaultValue = "false") + @ColumnInfo(defaultValue = "0") val reminder: Boolean, // Added in database schema v5 @ColumnInfo(defaultValue = "Image") val goalIconId: String?, // Added in database schema v6 - @ColumnInfo(defaultValue = "false") + @ColumnInfo(defaultValue = "0") val archived: Boolean = false ) { @PrimaryKey(autoGenerate = true) diff --git a/app/src/main/java/com/starry/greenstash/ui/screens/archive/composables/ArchiveScreen.kt b/app/src/main/java/com/starry/greenstash/ui/screens/archive/composables/ArchiveScreen.kt index a8ffb3ef..94ea73af 100644 --- a/app/src/main/java/com/starry/greenstash/ui/screens/archive/composables/ArchiveScreen.kt +++ b/app/src/main/java/com/starry/greenstash/ui/screens/archive/composables/ArchiveScreen.kt @@ -25,6 +25,9 @@ package com.starry.greenstash.ui.screens.archive.composables +import androidx.compose.animation.AnimatedVisibility +import androidx.compose.animation.fadeIn +import androidx.compose.animation.fadeOut import androidx.compose.foundation.background import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Box @@ -39,18 +42,36 @@ import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.width import androidx.compose.foundation.shape.CircleShape import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.automirrored.filled.ArrowBack import androidx.compose.material.icons.filled.Face2 import androidx.compose.material.icons.filled.Image import androidx.compose.material.icons.filled.Refresh import androidx.compose.material.icons.outlined.Delete import androidx.compose.material3.Card +import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton +import androidx.compose.material3.LargeTopAppBar import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Scaffold +import androidx.compose.material3.SnackbarHost +import androidx.compose.material3.SnackbarHostState import androidx.compose.material3.Text +import androidx.compose.material3.TopAppBarDefaults 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.runtime.rememberCoroutineScope +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.vector.ImageVector +import androidx.compose.ui.platform.LocalView +import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.tooling.preview.Preview @@ -58,24 +79,89 @@ import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import androidx.hilt.navigation.compose.hiltViewModel import androidx.navigation.NavController +import com.airbnb.lottie.compose.LottieAnimation +import com.airbnb.lottie.compose.LottieCompositionResult +import com.airbnb.lottie.compose.LottieCompositionSpec +import com.airbnb.lottie.compose.LottieConstants +import com.airbnb.lottie.compose.animateLottieCompositionAsState +import com.airbnb.lottie.compose.rememberLottieComposition +import com.starry.greenstash.R import com.starry.greenstash.ui.screens.archive.ArchiveViewModel import com.starry.greenstash.ui.theme.greenstashFont import com.starry.greenstash.ui.theme.greenstashNumberFont +import com.starry.greenstash.utils.weakHapticFeedback +import kotlinx.coroutines.delay +@OptIn(ExperimentalMaterial3Api::class) @Composable fun ArchiveScreen(navController: NavController) { + val view = LocalView.current val viewModel: ArchiveViewModel = hiltViewModel() - ArchivedGoalItem( - title = "Home Decorations", - icon = Icons.Filled.Face2, - savedAmount = "₹10,000", - onRestoreClicked = {}, - onDeleteClicked = {} - ) + + val scrollBehavior = TopAppBarDefaults.exitUntilCollapsedScrollBehavior() + val snackBarHostState = remember { SnackbarHostState() } + val coroutineScope = rememberCoroutineScope() + + val archivedGoals by viewModel.archivedGoals.collectAsState(initial = listOf()) + + Scaffold(modifier = Modifier.fillMaxSize(), + snackbarHost = { SnackbarHost(snackBarHostState) }, + topBar = { + LargeTopAppBar( + title = { + Text( + stringResource(id = R.string.archive_screen_header), + maxLines = 1, + overflow = TextOverflow.Ellipsis, + fontFamily = greenstashFont + ) + }, + navigationIcon = { + IconButton(onClick = { + view.weakHapticFeedback() + navController.navigateUp() + }) { + Icon( + imageVector = Icons.AutoMirrored.Filled.ArrowBack, + contentDescription = null + ) + } + }, + scrollBehavior = scrollBehavior, + colors = TopAppBarDefaults.largeTopAppBarColors( + containerColor = MaterialTheme.colorScheme.surface, + scrolledContainerColor = MaterialTheme.colorScheme.surface, + ) + ) + } + ) { paddingValues -> + Column( + modifier = Modifier + .fillMaxSize() + .padding(paddingValues) + ) { + if (archivedGoals.isEmpty()) { + var showNoGoalsAnimation by remember { mutableStateOf(false) } + LaunchedEffect(key1 = true, block = { + delay(200) + showNoGoalsAnimation = true + }) + AnimatedVisibility( + visible = showNoGoalsAnimation, + enter = fadeIn(), + exit = fadeOut() + ) { + NoArchivedGoals() + } + } else { + // TODO: Add a message for empty archived goals + } + } + } } @Composable -fun ArchivedGoalItem( +private fun ArchivedGoalItem( title: String, icon: ImageVector?, savedAmount: String, @@ -108,7 +194,7 @@ fun ArchivedGoalItem( modifier = Modifier .size(40.dp) .background( - color = MaterialTheme.colorScheme.primaryContainer, + color = MaterialTheme.colorScheme.primary, shape = CircleShape ) .clip(CircleShape) @@ -120,6 +206,7 @@ fun ArchivedGoalItem( modifier = Modifier .fillMaxSize() .padding(10.dp), + tint = MaterialTheme.colorScheme.onPrimary ) } Spacer(modifier = Modifier.width(16.dp)) @@ -128,7 +215,7 @@ fun ArchivedGoalItem( modifier = Modifier .size(40.dp) .background( - color = MaterialTheme.colorScheme.primaryContainer, + color = MaterialTheme.colorScheme.primary, shape = CircleShape ) .clip(CircleShape) @@ -140,6 +227,7 @@ fun ArchivedGoalItem( modifier = Modifier .fillMaxSize() .padding(10.dp), + tint = MaterialTheme.colorScheme.onPrimary ) } } @@ -166,6 +254,44 @@ fun ArchivedGoalItem( } } +@Composable +private fun NoArchivedGoals() { + Column( + modifier = Modifier.fillMaxSize(), + horizontalAlignment = Alignment.CenterHorizontally + ) { + val compositionResult: LottieCompositionResult = + rememberLottieComposition( + spec = LottieCompositionSpec.RawRes(R.raw.no_goal_found_lottie) + ) + val progressAnimation by animateLottieCompositionAsState( + compositionResult.value, + isPlaying = true, + iterations = 1, + speed = 1f + ) + + Spacer(modifier = Modifier.weight(1f)) + + LottieAnimation( + composition = compositionResult.value, + progress = { progressAnimation }, + modifier = Modifier.size(320.dp), + enableMergePaths = true + ) + + Text( + text = stringResource(id = R.string.archive_empty), + fontWeight = FontWeight.Medium, + fontFamily = greenstashFont, + fontSize = 18.sp, + modifier = Modifier.padding(start = 12.dp, end = 12.dp), + ) + + Spacer(modifier = Modifier.weight(2f)) + } +} + @Preview @Composable fun ArchiveItemPV() { diff --git a/app/src/main/java/com/starry/greenstash/ui/screens/home/HomeViewModel.kt b/app/src/main/java/com/starry/greenstash/ui/screens/home/HomeViewModel.kt index 6c571904..9d5f4376 100644 --- a/app/src/main/java/com/starry/greenstash/ui/screens/home/HomeViewModel.kt +++ b/app/src/main/java/com/starry/greenstash/ui/screens/home/HomeViewModel.kt @@ -138,6 +138,16 @@ class HomeViewModel @Inject constructor( } + fun archiveGoal(goal: Goal) { + viewModelScope.launch(Dispatchers.IO) { + val updatedGoal = goal.copy(archived = true) + goalDao.updateGoal(updatedGoal) + if (reminderManager.isReminderSet(goal.goalId)) { + reminderManager.stopReminder(goal.goalId) + } + } + } + fun deleteGoal(goal: Goal) { viewModelScope.launch(Dispatchers.IO) { goalDao.deleteGoal(goal.goalId) diff --git a/app/src/main/java/com/starry/greenstash/ui/screens/home/composables/GoalItems.kt b/app/src/main/java/com/starry/greenstash/ui/screens/home/composables/GoalItems.kt index e80f22b9..a5e73246 100644 --- a/app/src/main/java/com/starry/greenstash/ui/screens/home/composables/GoalItems.kt +++ b/app/src/main/java/com/starry/greenstash/ui/screens/home/composables/GoalItems.kt @@ -94,11 +94,13 @@ fun GoalItemClassic( secondaryText: String, goalProgress: Float, goalImage: Bitmap?, + isGoalCompleted: Boolean, onDepositClicked: () -> Unit, onWithdrawClicked: () -> Unit, onInfoClicked: () -> Unit, onEditClicked: () -> Unit, onDeleteClicked: () -> Unit, + onArchivedClicked: () -> Unit ) { val progress by animateFloatAsState(targetValue = goalProgress, label = "goal progress") @@ -166,17 +168,30 @@ fun GoalItemClassic( /** Goal Buttons */ Row(modifier = Modifier.padding(3.dp)) { - - TextButton( - onClick = { onDepositClicked() }, - modifier = Modifier.padding(end = 2.dp) - ) { - Text( - text = stringResource(id = R.string.deposit_button).uppercase(), - fontWeight = FontWeight.SemiBold, - color = MaterialTheme.colorScheme.onSurface, - fontFamily = greenstashFont - ) + if (isGoalCompleted) { + TextButton( + onClick = { onArchivedClicked() }, + modifier = Modifier.padding(end = 2.dp) + ) { + Text( + text = stringResource(id = R.string.archive_button).uppercase(), + fontWeight = FontWeight.SemiBold, + color = MaterialTheme.colorScheme.onSurface, + fontFamily = greenstashFont + ) + } + } else { + TextButton( + onClick = { onDepositClicked() }, + modifier = Modifier.padding(end = 2.dp) + ) { + Text( + text = stringResource(id = R.string.deposit_button).uppercase(), + fontWeight = FontWeight.SemiBold, + color = MaterialTheme.colorScheme.onSurface, + fontFamily = greenstashFont + ) + } } TextButton( onClick = { onWithdrawClicked() }, @@ -233,11 +248,13 @@ fun GoalItemCompact( daysLeftText: String, goalProgress: Float, goalIcon: ImageVector, + isGoalCompleted: Boolean, onDepositClicked: () -> Unit, onWithdrawClicked: () -> Unit, onInfoClicked: () -> Unit, onEditClicked: () -> Unit, onDeleteClicked: () -> Unit, + onArchivedClicked: () -> Unit ) { val coroutineScope = rememberCoroutineScope() val swipeState = rememberSwipeToDismissBoxState( @@ -381,19 +398,36 @@ fun GoalItemCompact( } Row { - IconButton( - onClick = { onDepositClicked() }, - modifier = Modifier - .padding(top = 4.dp) - .offset((10).dp) - .size(54.dp) - ) { - Icon( - modifier = Modifier.size(20.dp), - imageVector = ImageVector.vectorResource(id = R.drawable.ic_deposit_plus), - contentDescription = stringResource(id = R.string.deposit_button), - tint = MaterialTheme.colorScheme.onSecondaryContainer - ) + if (isGoalCompleted) { + IconButton( + onClick = { onArchivedClicked() }, + modifier = Modifier + .padding(top = 4.dp) + .offset((10).dp) + .size(54.dp) + ) { + Icon( + modifier = Modifier.size(20.dp), + imageVector = ImageVector.vectorResource(id = R.drawable.ic_compact_goal_archve), + contentDescription = stringResource(id = R.string.archive_button), + tint = MaterialTheme.colorScheme.onSecondaryContainer + ) + } + } else { + IconButton( + onClick = { onDepositClicked() }, + modifier = Modifier + .padding(top = 4.dp) + .offset((10).dp) + .size(54.dp) + ) { + Icon( + modifier = Modifier.size(20.dp), + imageVector = ImageVector.vectorResource(id = R.drawable.ic_compact_goal_deposit), + contentDescription = stringResource(id = R.string.deposit_button), + tint = MaterialTheme.colorScheme.onSecondaryContainer + ) + } } IconButton( onClick = { onWithdrawClicked() }, modifier = Modifier @@ -403,7 +437,7 @@ fun GoalItemCompact( ) { Icon( modifier = Modifier.size(20.dp), - imageVector = ImageVector.vectorResource(R.drawable.ic_withdraw_minus), + imageVector = ImageVector.vectorResource(R.drawable.ic_compact_goal_withdraw), contentDescription = stringResource(id = R.string.withdraw_button), tint = MaterialTheme.colorScheme.onSecondaryContainer ) @@ -463,12 +497,14 @@ fun GoalItemsPV() { secondaryText = "You have until 26/05/2023 (85) days left.\nYou need to save around $58.83/day, $416.67/week, $2,500.00/month.", goalProgress = 0.6f, goalImage = null, + isGoalCompleted = false, onDepositClicked = { }, onWithdrawClicked = { }, onInfoClicked = { }, - onEditClicked = { }) { - - } + onEditClicked = { }, + onDeleteClicked = { }, + onArchivedClicked = { }, + ) Spacer(modifier = Modifier.height(10.dp)) @@ -478,11 +514,13 @@ fun GoalItemsPV() { daysLeftText = "Goal Achieved! 🎉", goalProgress = 0.8f, goalIcon = ImageVector.vectorResource(id = R.drawable.ic_nav_rating), + isGoalCompleted = true, onDepositClicked = {}, onWithdrawClicked = {}, onInfoClicked = {}, onEditClicked = {}, - onDeleteClicked = {} + onDeleteClicked = {}, + onArchivedClicked = {}, ) } } \ No newline at end of file diff --git a/app/src/main/java/com/starry/greenstash/ui/screens/home/composables/GoalLazyItem.kt b/app/src/main/java/com/starry/greenstash/ui/screens/home/composables/GoalLazyItem.kt index b574c9a3..63721ad9 100644 --- a/app/src/main/java/com/starry/greenstash/ui/screens/home/composables/GoalLazyItem.kt +++ b/app/src/main/java/com/starry/greenstash/ui/screens/home/composables/GoalLazyItem.kt @@ -25,7 +25,6 @@ package com.starry.greenstash.ui.screens.home.composables -import android.content.Context import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.height import androidx.compose.material3.SnackbarHostState @@ -34,8 +33,8 @@ import androidx.compose.runtime.getValue import androidx.compose.runtime.livedata.observeAsState import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember -import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalView import androidx.compose.ui.unit.dp import androidx.navigation.NavController @@ -53,27 +52,32 @@ import com.starry.greenstash.utils.Utils import com.starry.greenstash.utils.getActivity import com.starry.greenstash.utils.strongHapticFeedback import com.starry.greenstash.utils.weakHapticFeedback +import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.launch @Composable fun GoalLazyColumnItem( - context: Context, viewModel: HomeViewModel, item: GoalWithTransactions, snackBarHostState: SnackbarHostState, + coroutineScope: CoroutineScope, navController: NavController, currentIndex: Int ) { + val context = LocalContext.current val settingsVM = (context.getActivity() as MainActivity).settingsViewModel val goalCardStyle = settingsVM.goalCardStyle.observeAsState().value!! - val coroutineScope = rememberCoroutineScope() + val isGoalCompleted = remember(item.goal.goalId) { + item.getCurrentlySavedAmount() >= item.goal.targetAmount + } val progressPercent = remember(item.goal.goalId) { ((item.getCurrentlySavedAmount() / item.goal.targetAmount) * 100).toInt() } val openDeleteDialog = remember { mutableStateOf(false) } + val openArchiveDialog = remember { mutableStateOf(false) } val localView = LocalView.current when (goalCardStyle) { @@ -93,6 +97,7 @@ fun GoalLazyColumnItem( ), goalProgress = progressPercent.toFloat() / 100, goalImage = item.goal.goalImage, + isGoalCompleted = isGoalCompleted, onDepositClicked = { localView.weakHapticFeedback() if (item.getCurrentlySavedAmount() >= item.goal.targetAmount) { @@ -142,6 +147,10 @@ fun GoalLazyColumnItem( onDeleteClicked = { localView.strongHapticFeedback() openDeleteDialog.value = true + }, + onArchivedClicked = { + localView.weakHapticFeedback() + openArchiveDialog.value = true } ) @@ -172,6 +181,7 @@ fun GoalLazyColumnItem( ), goalProgress = progressPercent.toFloat() / 100, goalIcon = goalIcon, + isGoalCompleted = isGoalCompleted, onDepositClicked = { localView.weakHapticFeedback() if (item.getCurrentlySavedAmount() >= item.goal.targetAmount) { @@ -221,6 +231,10 @@ fun GoalLazyColumnItem( onDeleteClicked = { localView.strongHapticFeedback() openDeleteDialog.value = true + }, + onArchivedClicked = { + localView.weakHapticFeedback() + openArchiveDialog.value = true } ) } @@ -228,11 +242,18 @@ fun GoalLazyColumnItem( HomeDialogs( openDeleteDialog = openDeleteDialog, + openArchiveDialog = openArchiveDialog, onDeleteConfirmed = { viewModel.deleteGoal(item.goal) - /*coroutineScope.launch { + coroutineScope.launch { snackBarHostState.showSnackbar(context.getString(R.string.goal_delete_success)) - }*/ + } + }, + onArchiveConfirmed = { + viewModel.archiveGoal(item.goal) + coroutineScope.launch { + snackBarHostState.showSnackbar(context.getString(R.string.goal_archive_success)) + } } ) } diff --git a/app/src/main/java/com/starry/greenstash/ui/screens/home/composables/HomeDialogs.kt b/app/src/main/java/com/starry/greenstash/ui/screens/home/composables/HomeDialogs.kt index fb71eedb..c202ebb7 100644 --- a/app/src/main/java/com/starry/greenstash/ui/screens/home/composables/HomeDialogs.kt +++ b/app/src/main/java/com/starry/greenstash/ui/screens/home/composables/HomeDialogs.kt @@ -25,8 +25,6 @@ package com.starry.greenstash.ui.screens.home.composables -import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.rounded.Delete import androidx.compose.material3.AlertDialog import androidx.compose.material3.ButtonDefaults import androidx.compose.material3.FilledTonalButton @@ -36,7 +34,10 @@ import androidx.compose.material3.Text import androidx.compose.material3.TextButton import androidx.compose.runtime.Composable import androidx.compose.runtime.MutableState +import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.res.stringResource +import androidx.compose.ui.res.vectorResource +import androidx.compose.ui.unit.sp import com.starry.greenstash.R import com.starry.greenstash.ui.theme.greenstashFont @@ -44,7 +45,9 @@ import com.starry.greenstash.ui.theme.greenstashFont @Composable fun HomeDialogs( openDeleteDialog: MutableState, + openArchiveDialog: MutableState, onDeleteConfirmed: () -> Unit, + onArchiveConfirmed: () -> Unit ) { if (openDeleteDialog.value) { @@ -55,6 +58,7 @@ fun HomeDialogs( text = stringResource(id = R.string.goal_delete_confirmation), color = MaterialTheme.colorScheme.onSurface, fontFamily = greenstashFont, + fontSize = 18.sp ) }, confirmButton = { FilledTonalButton( @@ -77,7 +81,50 @@ fun HomeDialogs( } }, icon = { - Icon(imageVector = Icons.Rounded.Delete, contentDescription = null) + Icon( + imageVector = ImageVector.vectorResource(id = R.drawable.ic_goal_delete), + contentDescription = null + ) + } + ) + } + + if (openArchiveDialog.value) { + + AlertDialog(onDismissRequest = { + openArchiveDialog.value = false + }, title = { + Text( + text = stringResource(id = R.string.goal_archive_confirmation), + color = MaterialTheme.colorScheme.onSurface, + fontFamily = greenstashFont, + fontSize = 18.sp + ) + }, confirmButton = { + FilledTonalButton( + onClick = { + openArchiveDialog.value = false + onArchiveConfirmed() + }, + colors = ButtonDefaults.filledTonalButtonColors( + containerColor = MaterialTheme.colorScheme.primaryContainer, + contentColor = MaterialTheme.colorScheme.onPrimaryContainer + ) + ) { + Text(stringResource(id = R.string.confirm), fontFamily = greenstashFont) + } + }, dismissButton = { + TextButton(onClick = { + openArchiveDialog.value = false + }) { + Text(stringResource(id = R.string.cancel), fontFamily = greenstashFont) + } + }, + icon = { + Icon( + imageVector = ImageVector.vectorResource(id = R.drawable.ic_compact_goal_archve), + contentDescription = null + ) } ) } diff --git a/app/src/main/java/com/starry/greenstash/ui/screens/home/composables/HomeScreen.kt b/app/src/main/java/com/starry/greenstash/ui/screens/home/composables/HomeScreen.kt index 5825fe04..f047c257 100644 --- a/app/src/main/java/com/starry/greenstash/ui/screens/home/composables/HomeScreen.kt +++ b/app/src/main/java/com/starry/greenstash/ui/screens/home/composables/HomeScreen.kt @@ -110,6 +110,7 @@ import com.starry.greenstash.ui.theme.greenstashFont import com.starry.greenstash.utils.getActivity import com.starry.greenstash.utils.isScrollingUp import com.starry.greenstash.utils.weakHapticFeedback +import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.delay import kotlinx.coroutines.launch import java.util.Locale @@ -243,7 +244,8 @@ fun HomeScreen(navController: NavController) { searchTextState = searchTextState, viewModel = viewModel, navController = navController, - snackBarHostState = snackBarHostState + snackBarHostState = snackBarHostState, + coroutineScope = coroutineScope ) } else { AllGoalsList( @@ -251,7 +253,8 @@ fun HomeScreen(navController: NavController) { allGoalState = allGoalState, viewModel = viewModel, navController = navController, - snackBarHostState = snackBarHostState + snackBarHostState = snackBarHostState, + coroutineScope = coroutineScope ) } } @@ -270,19 +273,15 @@ private fun GoalSearchResults( searchTextState: String, viewModel: HomeViewModel, navController: NavController, - snackBarHostState: SnackbarHostState + snackBarHostState: SnackbarHostState, + coroutineScope: CoroutineScope ) { val allGoals = allGoalState.value - val context = LocalContext.current - val filteredList: ArrayList = ArrayList() - - for (goalItem in allGoals) { - if (goalItem.goal.title.lowercase(Locale.getDefault()) - .contains(searchTextState.lowercase(Locale.getDefault())) - ) { - filteredList.add(goalItem) - } + val filteredList = allGoals.filter { goalItem -> + goalItem.goal.title.lowercase(Locale.getDefault()) + .contains(searchTextState.lowercase(Locale.getDefault())) } + if (allGoals.isNotEmpty() && filteredList.isEmpty()) { Column( modifier = Modifier.fillMaxSize(), @@ -333,10 +332,10 @@ private fun GoalSearchResults( val item = filteredList[idx] Box(modifier = Modifier.animateItemPlacement()) { GoalLazyColumnItem( - context = context, viewModel = viewModel, item = item, snackBarHostState = snackBarHostState, + coroutineScope= coroutineScope, navController = navController, currentIndex = idx ) @@ -355,10 +354,10 @@ private fun AllGoalsList( allGoalState: State>, viewModel: HomeViewModel, navController: NavController, - snackBarHostState: SnackbarHostState + snackBarHostState: SnackbarHostState, + coroutineScope: CoroutineScope ) { val allGoals = allGoalState.value - val context = LocalContext.current LazyColumn( modifier = Modifier .fillMaxSize() @@ -373,10 +372,10 @@ private fun AllGoalsList( val item = allGoals[idx] Box(modifier = Modifier.animateItemPlacement()) { GoalLazyColumnItem( - context = context, viewModel = viewModel, item = item, snackBarHostState = snackBarHostState, + coroutineScope = coroutineScope, navController = navController, currentIndex = idx ) diff --git a/app/src/main/java/com/starry/greenstash/ui/screens/settings/composables/GoalCardStyle.kt b/app/src/main/java/com/starry/greenstash/ui/screens/settings/composables/GoalCardStyle.kt index 77e173f5..ecbff699 100644 --- a/app/src/main/java/com/starry/greenstash/ui/screens/settings/composables/GoalCardStyle.kt +++ b/app/src/main/java/com/starry/greenstash/ui/screens/settings/composables/GoalCardStyle.kt @@ -160,12 +160,13 @@ fun GoalCardStyle(navController: NavController) { secondaryText = "You have until 26/05/2023 (85) days left.\nYou need to save around $58.83/day, $416.67/week, $2,500.00/month.", goalProgress = 0.6f, goalImage = null, + isGoalCompleted = false, onDepositClicked = { }, onWithdrawClicked = { }, onInfoClicked = { }, - onEditClicked = { }) { - - } + onEditClicked = { }, + onDeleteClicked = { }, + onArchivedClicked = { }) } GoalCardStyle.Compact -> { @@ -175,11 +176,13 @@ fun GoalCardStyle(navController: NavController) { daysLeftText = "12 days left", goalProgress = 0.8f, goalIcon = ImageVector.vectorResource(id = R.drawable.ic_nav_rating), + isGoalCompleted = false, onDepositClicked = {}, onWithdrawClicked = {}, onInfoClicked = {}, onEditClicked = {}, - onDeleteClicked = {} + onDeleteClicked = {}, + onArchivedClicked = { } ) } } diff --git a/app/src/main/res/drawable/ic_compact_goal_archve.xml b/app/src/main/res/drawable/ic_compact_goal_archve.xml new file mode 100644 index 00000000..2895c855 --- /dev/null +++ b/app/src/main/res/drawable/ic_compact_goal_archve.xml @@ -0,0 +1,7 @@ + + + + + + + diff --git a/app/src/main/res/drawable/ic_deposit_plus.xml b/app/src/main/res/drawable/ic_compact_goal_deposit.xml similarity index 100% rename from app/src/main/res/drawable/ic_deposit_plus.xml rename to app/src/main/res/drawable/ic_compact_goal_deposit.xml diff --git a/app/src/main/res/drawable/ic_withdraw_minus.xml b/app/src/main/res/drawable/ic_compact_goal_withdraw.xml similarity index 100% rename from app/src/main/res/drawable/ic_withdraw_minus.xml rename to app/src/main/res/drawable/ic_compact_goal_withdraw.xml diff --git a/app/src/main/res/drawable/ic_transaction_deposit.xml b/app/src/main/res/drawable/ic_transaction_deposit.xml deleted file mode 100644 index 6d77011a..00000000 --- a/app/src/main/res/drawable/ic_transaction_deposit.xml +++ /dev/null @@ -1,20 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_transaction_withdraw.xml b/app/src/main/res/drawable/ic_transaction_withdraw.xml deleted file mode 100644 index 73163371..00000000 --- a/app/src/main/res/drawable/ic_transaction_withdraw.xml +++ /dev/null @@ -1,20 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index c9ea5367..f625d647 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -141,6 +141,10 @@ Do you want to remove deadline from this goal? Tip! You can remove the deadline from existing goals by long-pressing on the deadline field. + + Archived Goals + No archived goals yet. + Backup & Restore Backup app data including all of your saving goals, current progress, transactions etc and easily restore it whenever you want. From cde891cf258c255169cbcca7da9e214fc74e1de8 Mon Sep 17 00:00:00 2001 From: starry-shivam Date: Wed, 8 May 2024 12:12:15 +0530 Subject: [PATCH 4/5] Implement goal archive screen and functionality Signed-off-by: starry-shivam --- .../greenstash/ui/navigation/NavGraph.kt | 2 + .../ui/screens/archive/ArchiveViewModel.kt | 9 +- .../archive/composables/ArchiveScreen.kt | 234 +++++++++++++- .../ui/screens/home/HomeViewModel.kt | 3 + .../screens/home/composables/GoalLazyItem.kt | 13 +- .../ui/screens/home/composables/HomeDrawer.kt | 305 ++++++++++-------- .../ui/screens/home/composables/HomeScreen.kt | 10 +- .../info/composables/GoalInfoScreen.kt | 75 +++-- .../screens/input/composables/InputScreen.kt | 33 +- .../ui/screens/other/CongratsScreen.kt | 5 +- .../settings/composables/AboutScreen.kt | 13 +- .../screens/settings/composables/OSLScreen.kt | 58 ++-- .../settings/composables/SettingsScreen.kt | 5 +- app/src/main/res/values-es/strings.xml | 2 +- app/src/main/res/values-ru/strings.xml | 2 +- app/src/main/res/values-tr/strings.xml | 2 +- app/src/main/res/values-zh-rCN/strings.xml | 2 +- app/src/main/res/values-zh-rTW/strings.xml | 2 +- app/src/main/res/values/strings.xml | 4 +- 19 files changed, 553 insertions(+), 226 deletions(-) diff --git a/app/src/main/java/com/starry/greenstash/ui/navigation/NavGraph.kt b/app/src/main/java/com/starry/greenstash/ui/navigation/NavGraph.kt index 5e46af8f..66535de1 100644 --- a/app/src/main/java/com/starry/greenstash/ui/navigation/NavGraph.kt +++ b/app/src/main/java/com/starry/greenstash/ui/navigation/NavGraph.kt @@ -71,8 +71,10 @@ fun NavGraph( /** Home Screen */ composable( route = DrawerScreens.Home.route, + enterTransition = { enterTransition() }, exitTransition = { exitTransition() }, popEnterTransition = { popEnterTransition() }, + popExitTransition = { popExitTransition() }, ) { HomeScreen(navController) } diff --git a/app/src/main/java/com/starry/greenstash/ui/screens/archive/ArchiveViewModel.kt b/app/src/main/java/com/starry/greenstash/ui/screens/archive/ArchiveViewModel.kt index 7c7eb7d9..71785f1f 100644 --- a/app/src/main/java/com/starry/greenstash/ui/screens/archive/ArchiveViewModel.kt +++ b/app/src/main/java/com/starry/greenstash/ui/screens/archive/ArchiveViewModel.kt @@ -30,6 +30,7 @@ import androidx.lifecycle.viewModelScope import com.starry.greenstash.database.goal.Goal import com.starry.greenstash.database.goal.GoalDao import com.starry.greenstash.reminder.ReminderManager +import com.starry.greenstash.utils.PreferenceUtil import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch @@ -38,7 +39,8 @@ import javax.inject.Inject @HiltViewModel class ArchiveViewModel @Inject constructor( private val goalDao: GoalDao, - private val reminderManager: ReminderManager + private val reminderManager: ReminderManager, + private val preferenceUtil: PreferenceUtil ) : ViewModel() { val archivedGoals = goalDao.getAllArchivedGoals() @@ -56,7 +58,12 @@ class ArchiveViewModel @Inject constructor( fun restoreGoal(goal: Goal) { viewModelScope.launch(Dispatchers.IO) { val updatedGoal = goal.copy(archived = false) + updatedGoal.goalId = goal.goalId goalDao.updateGoal(updatedGoal) } } + + fun getDefaultCurrency(): String { + return preferenceUtil.getString(PreferenceUtil.DEFAULT_CURRENCY_STR, "")!! + } } \ No newline at end of file diff --git a/app/src/main/java/com/starry/greenstash/ui/screens/archive/composables/ArchiveScreen.kt b/app/src/main/java/com/starry/greenstash/ui/screens/archive/composables/ArchiveScreen.kt index 94ea73af..1dc848b8 100644 --- a/app/src/main/java/com/starry/greenstash/ui/screens/archive/composables/ArchiveScreen.kt +++ b/app/src/main/java/com/starry/greenstash/ui/screens/archive/composables/ArchiveScreen.kt @@ -28,6 +28,7 @@ package com.starry.greenstash.ui.screens.archive.composables import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.fadeIn import androidx.compose.animation.fadeOut +import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.background import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Box @@ -37,9 +38,11 @@ import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.offset import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.width +import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.shape.CircleShape import androidx.compose.material.icons.Icons import androidx.compose.material.icons.automirrored.filled.ArrowBack @@ -47,19 +50,25 @@ import androidx.compose.material.icons.filled.Face2 import androidx.compose.material.icons.filled.Image import androidx.compose.material.icons.filled.Refresh import androidx.compose.material.icons.outlined.Delete +import androidx.compose.material3.AlertDialog +import androidx.compose.material3.ButtonDefaults import androidx.compose.material3.Card import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.FilledTonalButton import androidx.compose.material3.Icon import androidx.compose.material3.IconButton import androidx.compose.material3.LargeTopAppBar import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Scaffold +import androidx.compose.material3.SnackbarDuration import androidx.compose.material3.SnackbarHost import androidx.compose.material3.SnackbarHostState import androidx.compose.material3.Text +import androidx.compose.material3.TextButton import androidx.compose.material3.TopAppBarDefaults import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.MutableState import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf @@ -70,6 +79,8 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.vector.ImageVector +import androidx.compose.ui.input.nestedscroll.nestedScroll +import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalView import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.font.FontWeight @@ -82,20 +93,25 @@ import androidx.navigation.NavController import com.airbnb.lottie.compose.LottieAnimation import com.airbnb.lottie.compose.LottieCompositionResult import com.airbnb.lottie.compose.LottieCompositionSpec -import com.airbnb.lottie.compose.LottieConstants import com.airbnb.lottie.compose.animateLottieCompositionAsState import com.airbnb.lottie.compose.rememberLottieComposition import com.starry.greenstash.R +import com.starry.greenstash.database.core.GoalWithTransactions import com.starry.greenstash.ui.screens.archive.ArchiveViewModel import com.starry.greenstash.ui.theme.greenstashFont import com.starry.greenstash.ui.theme.greenstashNumberFont +import com.starry.greenstash.utils.Constants +import com.starry.greenstash.utils.ImageUtils +import com.starry.greenstash.utils.Utils import com.starry.greenstash.utils.weakHapticFeedback import kotlinx.coroutines.delay +import kotlinx.coroutines.launch -@OptIn(ExperimentalMaterial3Api::class) +@OptIn(ExperimentalMaterial3Api::class, ExperimentalFoundationApi::class) @Composable fun ArchiveScreen(navController: NavController) { val view = LocalView.current + val context = LocalContext.current val viewModel: ArchiveViewModel = hiltViewModel() val scrollBehavior = TopAppBarDefaults.exitUntilCollapsedScrollBehavior() @@ -104,7 +120,9 @@ fun ArchiveScreen(navController: NavController) { val archivedGoals by viewModel.archivedGoals.collectAsState(initial = listOf()) - Scaffold(modifier = Modifier.fillMaxSize(), + Scaffold(modifier = Modifier + .fillMaxSize() + .nestedScroll(scrollBehavior.nestedScrollConnection), snackbarHost = { SnackbarHost(snackBarHostState) }, topBar = { LargeTopAppBar( @@ -154,12 +172,101 @@ fun ArchiveScreen(navController: NavController) { NoArchivedGoals() } } else { - // TODO: Add a message for empty archived goals + var showArchivedGoals by remember { mutableStateOf(false) } + LaunchedEffect(key1 = true, block = { + delay(180) + showArchivedGoals = true + }) + AnimatedVisibility( + visible = showArchivedGoals, + enter = fadeIn(), + exit = fadeOut() + ) { + + LazyColumn(modifier = Modifier.fillMaxSize()) { + items( + count = archivedGoals.size, + key = { index -> archivedGoals[index].goal.goalId } + ) { index -> + val goalItem = archivedGoals[index] + ArchivedLazyItem( + modifier = Modifier.animateItemPlacement(), + goalItem = goalItem, + defaultCurrency = viewModel.getDefaultCurrency(), + onRestoreConfirmed = { + viewModel.restoreGoal(goalItem.goal) + coroutineScope.launch { + snackBarHostState.showSnackbar( + message = context.getString(R.string.goal_restore_success), + actionLabel = context.getString(R.string.ok), + duration = SnackbarDuration.Short + ) + } + }, + onDeleteConfirmed = { + viewModel.deleteGoal(goalItem.goal) + coroutineScope.launch { + snackBarHostState.showSnackbar( + message = context.getString(R.string.goal_delete_success), + actionLabel = context.getString(R.string.ok), + duration = SnackbarDuration.Short + ) + } + } + ) + } + + } + } } } } } +@Composable +private fun ArchivedLazyItem( + modifier: Modifier, + goalItem: GoalWithTransactions, + defaultCurrency: String, + onRestoreConfirmed: () -> Unit, + onDeleteConfirmed: () -> Unit +) { + Box(modifier = modifier) { + val goalIcon by remember(goalItem.goal.goalIconId) { + mutableStateOf( + ImageUtils.createIconVector( + goalItem.goal.goalIconId ?: Constants.DEFAULT_GOAL_ICON_ID + )!! + ) + } + + val showRestoreDialog = remember { mutableStateOf(false) } + val showDeleteDialog = remember { mutableStateOf(false) } + + ArchivedGoalItem( + title = goalItem.goal.title, + icon = goalIcon, + savedAmount = Utils.formatCurrency( + goalItem.getCurrentlySavedAmount(), + defaultCurrency + ), + onRestoreClicked = { + showRestoreDialog.value = true + }, + onDeleteClicked = { + showDeleteDialog.value = true + } + ) + + ArchiveDialogs( + showRestoreDialog = showRestoreDialog, + showDeleteDialog = showDeleteDialog, + onRestoreConfirmed = onRestoreConfirmed, + onDeleteConfirmed = onDeleteConfirmed + ) + } +} + @Composable private fun ArchivedGoalItem( title: String, @@ -168,6 +275,8 @@ private fun ArchivedGoalItem( onRestoreClicked: () -> Unit, onDeleteClicked: () -> Unit ) { + val view = LocalView.current + Card( modifier = Modifier .fillMaxWidth() @@ -176,7 +285,7 @@ private fun ArchivedGoalItem( Column( Modifier .fillMaxWidth() - .padding(12.dp) + .padding(16.dp) ) { Row( modifier = Modifier @@ -185,48 +294,55 @@ private fun ArchivedGoalItem( Icon( imageVector = icon ?: Icons.Filled.Image, contentDescription = title, - modifier = Modifier.size(40.dp) + modifier = Modifier.size(48.dp) ) Spacer(modifier = Modifier.weight(1f)) Box( modifier = Modifier - .size(40.dp) + .size(35.dp) .background( color = MaterialTheme.colorScheme.primary, shape = CircleShape ) .clip(CircleShape) - .clickable { onRestoreClicked() } + .clickable { + view.weakHapticFeedback() + onRestoreClicked() + } ) { Icon( imageVector = Icons.Filled.Refresh, contentDescription = title, modifier = Modifier .fillMaxSize() - .padding(10.dp), + .padding(9.dp), tint = MaterialTheme.colorScheme.onPrimary ) } + Spacer(modifier = Modifier.width(16.dp)) Box( modifier = Modifier - .size(40.dp) + .size(35.dp) .background( color = MaterialTheme.colorScheme.primary, shape = CircleShape ) .clip(CircleShape) - .clickable { onDeleteClicked() } + .clickable { + view.weakHapticFeedback() + onDeleteClicked() + } ) { Icon( imageVector = Icons.Outlined.Delete, contentDescription = title, modifier = Modifier .fillMaxSize() - .padding(10.dp), + .padding(9.dp), tint = MaterialTheme.colorScheme.onPrimary ) } @@ -254,6 +370,93 @@ private fun ArchivedGoalItem( } } +@Composable +private fun ArchiveDialogs( + showRestoreDialog: MutableState, + showDeleteDialog: MutableState, + onRestoreConfirmed: () -> Unit, + onDeleteConfirmed: () -> Unit +) { + if (showRestoreDialog.value) { + AlertDialog(onDismissRequest = { + showRestoreDialog.value = false + }, title = { + Text( + text = stringResource(id = R.string.goal_restore_confirmation), + color = MaterialTheme.colorScheme.onSurface, + fontFamily = greenstashFont, + fontSize = 18.sp + ) + }, confirmButton = { + FilledTonalButton( + onClick = { + showRestoreDialog.value = false + onRestoreConfirmed() + }, + colors = ButtonDefaults.filledTonalButtonColors( + containerColor = MaterialTheme.colorScheme.primaryContainer, + contentColor = MaterialTheme.colorScheme.onPrimaryContainer + ) + ) { + Text(stringResource(id = R.string.confirm), fontFamily = greenstashFont) + } + }, dismissButton = { + TextButton(onClick = { + showRestoreDialog.value = false + }) { + Text(stringResource(id = R.string.cancel), fontFamily = greenstashFont) + } + }, + icon = { + Icon( + imageVector = Icons.Filled.Refresh, + contentDescription = null + ) + } + ) + } + + if (showDeleteDialog.value) { + AlertDialog(onDismissRequest = { + showDeleteDialog.value = false + }, title = { + Text( + text = stringResource(id = R.string.goal_delete_confirmation), + color = MaterialTheme.colorScheme.onSurface, + fontFamily = greenstashFont, + fontSize = 18.sp + ) + }, confirmButton = { + FilledTonalButton( + onClick = { + showDeleteDialog.value = false + onDeleteConfirmed() + }, + colors = ButtonDefaults.filledTonalButtonColors( + containerColor = MaterialTheme.colorScheme.errorContainer, + contentColor = MaterialTheme.colorScheme.onErrorContainer + ) + ) { + Text(stringResource(id = R.string.confirm), fontFamily = greenstashFont) + } + }, dismissButton = { + TextButton(onClick = { + showDeleteDialog.value = false + }) { + Text(stringResource(id = R.string.cancel), fontFamily = greenstashFont) + } + }, + icon = { + Icon( + imageVector = Icons.Outlined.Delete, + contentDescription = null + ) + } + ) + } +} + + @Composable private fun NoArchivedGoals() { Column( @@ -282,10 +485,11 @@ private fun NoArchivedGoals() { Text( text = stringResource(id = R.string.archive_empty), - fontWeight = FontWeight.Medium, fontFamily = greenstashFont, - fontSize = 18.sp, - modifier = Modifier.padding(start = 12.dp, end = 12.dp), + style = MaterialTheme.typography.titleMedium, + modifier = Modifier + .padding(start = 12.dp, end = 12.dp) + .offset(y = (-16).dp) ) Spacer(modifier = Modifier.weight(2f)) diff --git a/app/src/main/java/com/starry/greenstash/ui/screens/home/HomeViewModel.kt b/app/src/main/java/com/starry/greenstash/ui/screens/home/HomeViewModel.kt index 9d5f4376..670a3f32 100644 --- a/app/src/main/java/com/starry/greenstash/ui/screens/home/HomeViewModel.kt +++ b/app/src/main/java/com/starry/greenstash/ui/screens/home/HomeViewModel.kt @@ -141,7 +141,9 @@ class HomeViewModel @Inject constructor( fun archiveGoal(goal: Goal) { viewModelScope.launch(Dispatchers.IO) { val updatedGoal = goal.copy(archived = true) + updatedGoal.goalId = goal.goalId goalDao.updateGoal(updatedGoal) + // Stop reminder if set for this goal if (reminderManager.isReminderSet(goal.goalId)) { reminderManager.stopReminder(goal.goalId) } @@ -151,6 +153,7 @@ class HomeViewModel @Inject constructor( fun deleteGoal(goal: Goal) { viewModelScope.launch(Dispatchers.IO) { goalDao.deleteGoal(goal.goalId) + // Stop reminder if set for this goal if (reminderManager.isReminderSet(goal.goalId)) { reminderManager.stopReminder(goal.goalId) } diff --git a/app/src/main/java/com/starry/greenstash/ui/screens/home/composables/GoalLazyItem.kt b/app/src/main/java/com/starry/greenstash/ui/screens/home/composables/GoalLazyItem.kt index 63721ad9..253f46bd 100644 --- a/app/src/main/java/com/starry/greenstash/ui/screens/home/composables/GoalLazyItem.kt +++ b/app/src/main/java/com/starry/greenstash/ui/screens/home/composables/GoalLazyItem.kt @@ -27,6 +27,7 @@ package com.starry.greenstash.ui.screens.home.composables import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.height +import androidx.compose.material3.SnackbarDuration import androidx.compose.material3.SnackbarHostState import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue @@ -246,13 +247,21 @@ fun GoalLazyColumnItem( onDeleteConfirmed = { viewModel.deleteGoal(item.goal) coroutineScope.launch { - snackBarHostState.showSnackbar(context.getString(R.string.goal_delete_success)) + snackBarHostState.showSnackbar( + message = context.getString(R.string.goal_delete_success), + actionLabel = context.getString(R.string.ok), + duration = SnackbarDuration.Short + ) } }, onArchiveConfirmed = { viewModel.archiveGoal(item.goal) coroutineScope.launch { - snackBarHostState.showSnackbar(context.getString(R.string.goal_archive_success)) + snackBarHostState.showSnackbar( + message = context.getString(R.string.goal_archive_success), + actionLabel = context.getString(R.string.ok), + duration = SnackbarDuration.Short + ) } } ) diff --git a/app/src/main/java/com/starry/greenstash/ui/screens/home/composables/HomeDrawer.kt b/app/src/main/java/com/starry/greenstash/ui/screens/home/composables/HomeDrawer.kt index f0eac01a..90e23f08 100644 --- a/app/src/main/java/com/starry/greenstash/ui/screens/home/composables/HomeDrawer.kt +++ b/app/src/main/java/com/starry/greenstash/ui/screens/home/composables/HomeDrawer.kt @@ -31,6 +31,7 @@ import android.content.Intent import android.net.Uri import androidx.compose.foundation.background import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxSize @@ -39,8 +40,10 @@ import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.width +import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.shape.CircleShape import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.foundation.verticalScroll import androidx.compose.material3.DrawerState import androidx.compose.material3.DrawerValue import androidx.compose.material3.HorizontalDivider @@ -52,6 +55,7 @@ import androidx.compose.material3.NavigationDrawerItemDefaults import androidx.compose.material3.Text import androidx.compose.material3.rememberDrawerState import androidx.compose.runtime.Composable +import androidx.compose.runtime.MutableState import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope @@ -76,16 +80,16 @@ import com.starry.greenstash.ui.screens.settings.composables.AboutLinks import com.starry.greenstash.ui.theme.greenstashFont import com.starry.greenstash.utils.Utils import com.starry.greenstash.utils.weakHapticFeedback +import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.launch @Composable fun HomeDrawer(drawerState: DrawerState, navController: NavController, themeMode: ThemeMode) { + val context = LocalContext.current + val items = DrawerScreens.getAllItems() val selectedItem = remember { mutableStateOf(items[0]) } - - val view = LocalView.current - val context = LocalContext.current val coroutineScope = rememberCoroutineScope() ModalDrawerSheet( @@ -93,170 +97,209 @@ fun HomeDrawer(drawerState: DrawerState, navController: NavController, themeMode drawerShape = RoundedCornerShape(topEnd = 14.dp, bottomEnd = 14.dp), drawerTonalElevation = 2.dp, ) { - Row( + Column( modifier = Modifier - .height(140.dp) - .fillMaxWidth(), - verticalAlignment = Alignment.CenterVertically + .fillMaxSize() + .verticalScroll(rememberScrollState()) ) { - Spacer(modifier = Modifier.width(20.dp)) - Box( - modifier = Modifier - .size(60.dp) - .background( - color = if (themeMode == ThemeMode.Light) MaterialTheme.colorScheme.onSurface - else MaterialTheme.colorScheme.onSurface.copy(alpha = 0.15f), - shape = CircleShape - ) - ) { - AsyncImage( - model = R.drawable.ic_launcher_foreground, - contentDescription = stringResource(id = R.string.app_name), - modifier = Modifier.fillMaxSize(), - ) - } + DrawerHeader(themeMode) - Spacer(modifier = Modifier.width(18.dp)) - Text( - text = stringResource(id = R.string.app_name), - fontFamily = greenstashFont, - fontSize = 22.sp, - fontWeight = FontWeight.Bold + HorizontalDivider( + modifier = Modifier + .fillMaxWidth() + .padding(bottom = 16.dp), + thickness = 0.5.dp, + color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.4f) ) - } - - HorizontalDivider( - modifier = Modifier - .fillMaxWidth() - .padding(bottom = 16.dp), - thickness = 0.5.dp, - color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.4f) - ) + DrawerItems(items, selectedItem, drawerState, navController, coroutineScope) - items.forEach { item -> - NavigationDrawerItem( - icon = { - Icon( - imageVector = ImageVector.vectorResource(id = item.iconResId), - contentDescription = null - ) - }, - label = { - Text( - text = stringResource(id = item.nameResId), fontFamily = greenstashFont - ) - }, - selected = item == selectedItem.value, - onClick = { - view.weakHapticFeedback() - selectedItem.value = item - coroutineScope.launch { - drawerState.close() - navController.navigate(item.route) - } - }, + HorizontalDivider( modifier = Modifier - .width(280.dp) - .padding(NavigationDrawerItemDefaults.ItemPadding) + .fillMaxWidth() + .padding(top = 14.dp, bottom = 14.dp), + thickness = 0.5.dp, + color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.4f) ) - Spacer(modifier = Modifier.height(4.dp)) - } - HorizontalDivider( - modifier = Modifier - .fillMaxWidth() - .padding(top = 14.dp, bottom = 14.dp), - thickness = 0.5.dp, - color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.4f) - ) + NonNavigationalDrawerItems( + onRatingClick = { onRatingClick(context) }, + onShareClick = { onShareClick(context) }, + onPrivacyClick = { onPrivacyClick(context) } + ) - // Non-navigational items in the drawer ======================================== + Spacer(Modifier.weight(1f)) - NavigationDrawerItem( + DrawerFooter() + } + } +} + + +@Composable +private fun DrawerHeader(themeMode: ThemeMode) { + Row( + modifier = Modifier + .height(140.dp) + .fillMaxWidth(), + verticalAlignment = Alignment.CenterVertically + ) { + Spacer(modifier = Modifier.width(20.dp)) + // Logo or image + Box( modifier = Modifier - .width(280.dp) - .padding(NavigationDrawerItemDefaults.ItemPadding), - selected = false, - onClick = { - view.weakHapticFeedback() - onRatingClick(context) - }, - icon = { - Icon( - imageVector = ImageVector.vectorResource(id = R.drawable.ic_nav_rating), - contentDescription = null + .size(60.dp) + .background( + color = if (themeMode == ThemeMode.Light) MaterialTheme.colorScheme.onSurface + else MaterialTheme.colorScheme.onSurface.copy(alpha = 0.15f), + shape = CircleShape ) - }, - label = { - Text( - text = stringResource(id = R.string.drawer_rating), - fontFamily = greenstashFont - ) - }, + ) { + AsyncImage( + model = R.drawable.ic_launcher_foreground, + contentDescription = stringResource(id = R.string.app_name), + modifier = Modifier.fillMaxSize(), + ) + } + Spacer(modifier = Modifier.width(18.dp)) + // App name + Text( + text = stringResource(id = R.string.app_name), + fontFamily = greenstashFont, + fontSize = 22.sp, + fontWeight = FontWeight.Bold ) + } +} +@Composable +private fun DrawerItems( + items: List, + selectedItem: MutableState, + drawerState: DrawerState, + navController: NavController, + coroutineScope: CoroutineScope +) { + val view = LocalView.current + items.forEach { item -> NavigationDrawerItem( - modifier = Modifier - .width(280.dp) - .padding(NavigationDrawerItemDefaults.ItemPadding), - selected = false, - onClick = { - view.weakHapticFeedback() - onShareClick(context) - }, icon = { Icon( - imageVector = ImageVector.vectorResource(id = R.drawable.ic_nav_share), + imageVector = ImageVector.vectorResource(id = item.iconResId), contentDescription = null ) }, label = { Text( - text = stringResource(id = R.string.drawer_share), - fontFamily = greenstashFont + text = stringResource(id = item.nameResId), fontFamily = greenstashFont ) }, - ) - - NavigationDrawerItem( - modifier = Modifier - .width(280.dp) - .padding(NavigationDrawerItemDefaults.ItemPadding), - selected = false, + selected = item == selectedItem.value, onClick = { view.weakHapticFeedback() - onPrivacyClick(context) - }, - icon = { - Icon( - imageVector = ImageVector.vectorResource(id = R.drawable.ic_nav_privacy), - contentDescription = null - ) - }, - label = { - Text( - text = stringResource(id = R.string.drawer_privacy), - fontFamily = greenstashFont - ) + selectedItem.value = item + coroutineScope.launch { + drawerState.close() + navController.navigate(item.route) + } }, + modifier = Modifier + .width(280.dp) + .padding(NavigationDrawerItemDefaults.ItemPadding) ) + Spacer(modifier = Modifier.height(4.dp)) + } +} - Spacer(Modifier.weight(1f)) - - Box(modifier = Modifier.fillMaxWidth(), contentAlignment = Alignment.Center) { +@Composable +private fun NonNavigationalDrawerItems( + onRatingClick: () -> Unit, + onShareClick: () -> Unit, + onPrivacyClick: () -> Unit +) { + val view = LocalView.current + NavigationDrawerItem( + modifier = Modifier + .width(280.dp) + .padding(NavigationDrawerItemDefaults.ItemPadding), + selected = false, + onClick = { + view.weakHapticFeedback() + onRatingClick() + }, + icon = { + Icon( + imageVector = ImageVector.vectorResource(id = R.drawable.ic_nav_rating), + contentDescription = null + ) + }, + label = { Text( - text = stringResource(id = R.string.drawer_footer_text), - modifier = Modifier.padding(bottom = 18.dp), - fontSize = 11.sp, - fontFamily = greenstashFont, - color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.69f) + text = stringResource(id = R.string.drawer_rating), + fontFamily = greenstashFont ) - } + }, + ) + NavigationDrawerItem( + modifier = Modifier + .width(280.dp) + .padding(NavigationDrawerItemDefaults.ItemPadding), + selected = false, + onClick = { + view.weakHapticFeedback() + onShareClick() + }, + icon = { + Icon( + imageVector = ImageVector.vectorResource(id = R.drawable.ic_nav_share), + contentDescription = null + ) + }, + label = { + Text( + text = stringResource(id = R.string.drawer_share), + fontFamily = greenstashFont + ) + }, + ) + NavigationDrawerItem( + modifier = Modifier + .width(280.dp) + .padding(NavigationDrawerItemDefaults.ItemPadding), + selected = false, + onClick = { + view.weakHapticFeedback() + onPrivacyClick() + }, + icon = { + Icon( + imageVector = ImageVector.vectorResource(id = R.drawable.ic_nav_privacy), + contentDescription = null + ) + }, + label = { + Text( + text = stringResource(id = R.string.drawer_privacy), + fontFamily = greenstashFont + ) + }, + ) +} +@Composable +fun DrawerFooter() { + Box(modifier = Modifier.fillMaxWidth(), contentAlignment = Alignment.Center) { + Text( + text = stringResource(id = R.string.drawer_footer_text), + modifier = Modifier.padding(bottom = 18.dp), + fontSize = 11.sp, + fontFamily = greenstashFont, + color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.69f) + ) } } + private fun onRatingClick(context: Context) { try { context.startActivity( diff --git a/app/src/main/java/com/starry/greenstash/ui/screens/home/composables/HomeScreen.kt b/app/src/main/java/com/starry/greenstash/ui/screens/home/composables/HomeScreen.kt index f047c257..3ee1eb38 100644 --- a/app/src/main/java/com/starry/greenstash/ui/screens/home/composables/HomeScreen.kt +++ b/app/src/main/java/com/starry/greenstash/ui/screens/home/composables/HomeScreen.kt @@ -40,6 +40,7 @@ import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.offset import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.width @@ -335,7 +336,7 @@ private fun GoalSearchResults( viewModel = viewModel, item = item, snackBarHostState = snackBarHostState, - coroutineScope= coroutineScope, + coroutineScope = coroutineScope, navController = navController, currentIndex = idx ) @@ -529,10 +530,11 @@ private fun NoGoalAnimation() { Text( text = stringResource(id = R.string.no_goal_set), - fontWeight = FontWeight.Medium, fontFamily = greenstashFont, - fontSize = 18.sp, - modifier = Modifier.padding(start = 12.dp, end = 12.dp), + style = MaterialTheme.typography.titleMedium, + modifier = Modifier + .padding(start = 12.dp, end = 12.dp) + .offset(y = (-35).dp), ) Spacer(modifier = Modifier.weight(2f)) diff --git a/app/src/main/java/com/starry/greenstash/ui/screens/info/composables/GoalInfoScreen.kt b/app/src/main/java/com/starry/greenstash/ui/screens/info/composables/GoalInfoScreen.kt index 282481a6..ba7724fc 100644 --- a/app/src/main/java/com/starry/greenstash/ui/screens/info/composables/GoalInfoScreen.kt +++ b/app/src/main/java/com/starry/greenstash/ui/screens/info/composables/GoalInfoScreen.kt @@ -36,6 +36,7 @@ import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.offset import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.wrapContentHeight @@ -217,40 +218,7 @@ fun GoalInfoScreen(goalId: String, navController: NavController) { ) } else { - Column( - modifier = Modifier.fillMaxWidth(), - horizontalAlignment = Alignment.CenterHorizontally - ) { - val compositionResult: LottieCompositionResult = - rememberLottieComposition( - spec = LottieCompositionSpec.RawRes(R.raw.no_transaction_found_lottie) - ) - val progressAnimation by animateLottieCompositionAsState( - compositionResult.value, - isPlaying = true, - iterations = 1, - speed = 1f - ) - - Spacer(modifier = Modifier.weight(1f)) - - LottieAnimation( - composition = compositionResult.value, - progress = { progressAnimation }, - modifier = Modifier.size(320.dp), - enableMergePaths = true - ) - - Text( - text = stringResource(id = R.string.info_goal_no_transactions), - fontWeight = FontWeight.SemiBold, - fontFamily = greenstashFont, - fontSize = 20.sp, - modifier = Modifier.padding(start = 12.dp, end = 12.dp) - ) - - Spacer(modifier = Modifier.weight(2f)) - } + NoTransactionAnim() } } } @@ -413,6 +381,45 @@ fun GoalNotesCard(notesText: String) { ) } +@Composable +private fun NoTransactionAnim() { + Column( + modifier = Modifier.fillMaxWidth(), + horizontalAlignment = Alignment.CenterHorizontally + ) { + val compositionResult: LottieCompositionResult = + rememberLottieComposition( + spec = LottieCompositionSpec.RawRes(R.raw.no_transaction_found_lottie) + ) + val progressAnimation by animateLottieCompositionAsState( + compositionResult.value, + isPlaying = true, + iterations = 1, + speed = 1f + ) + + Spacer(modifier = Modifier.weight(1f)) + + LottieAnimation( + composition = compositionResult.value, + progress = { progressAnimation }, + modifier = Modifier.size(320.dp), + enableMergePaths = true + ) + + Text( + text = stringResource(id = R.string.info_goal_no_transactions), + fontFamily = greenstashFont, + style = MaterialTheme.typography.titleMedium, + modifier = Modifier + .padding(start = 12.dp, end = 12.dp) + .offset(y = (-16).dp) + ) + + Spacer(modifier = Modifier.weight(2f)) + } +} + @Composable @Preview diff --git a/app/src/main/java/com/starry/greenstash/ui/screens/input/composables/InputScreen.kt b/app/src/main/java/com/starry/greenstash/ui/screens/input/composables/InputScreen.kt index 5615665b..932213b6 100644 --- a/app/src/main/java/com/starry/greenstash/ui/screens/input/composables/InputScreen.kt +++ b/app/src/main/java/com/starry/greenstash/ui/screens/input/composables/InputScreen.kt @@ -63,10 +63,12 @@ import androidx.compose.material.icons.filled.Check import androidx.compose.material.icons.filled.Image import androidx.compose.material3.AlertDialog import androidx.compose.material3.Button +import androidx.compose.material3.ButtonDefaults import androidx.compose.material3.Card import androidx.compose.material3.CardDefaults import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.ExtendedFloatingActionButton +import androidx.compose.material3.FilledTonalButton import androidx.compose.material3.FloatingActionButtonDefaults import androidx.compose.material3.Icon import androidx.compose.material3.IconButton @@ -149,8 +151,10 @@ import com.starry.greenstash.utils.toToast import com.starry.greenstash.utils.validateAmount import com.starry.greenstash.utils.weakHapticFeedback import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.delay import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext import java.time.LocalDate import java.time.format.DateTimeFormatter @@ -242,13 +246,19 @@ fun InputScreen(editGoalId: String?, navController: NavController) { Text( text = stringResource(id = R.string.input_goal_remove_deadline), color = MaterialTheme.colorScheme.onSurface, - fontFamily = greenstashFont + fontFamily = greenstashFont, + fontSize = 18.sp ) }, confirmButton = { - TextButton(onClick = { - showRemoveDeadlineDialog.value = false - viewModel.removeDeadLine() - }) { + FilledTonalButton( + onClick = { + showRemoveDeadlineDialog.value = false + viewModel.removeDeadLine() + }, colors = ButtonDefaults.filledTonalButtonColors( + containerColor = MaterialTheme.colorScheme.errorContainer, + contentColor = MaterialTheme.colorScheme.onErrorContainer + ) + ) { Text(stringResource(id = R.string.confirm), fontFamily = greenstashFont) } }, dismissButton = { @@ -462,8 +472,13 @@ fun InputScreen(editGoalId: String?, navController: NavController) { coroutineScope.launch { showGoalAddedAnim.value = true delay(1050) - navController.popBackStack(DrawerScreens.Home.route, true) - navController.navigate(DrawerScreens.Home.route) + withContext(Dispatchers.Main) { + navController.popBackStack( + DrawerScreens.Home.route, + true + ) + navController.navigate(DrawerScreens.Home.route) + } } } }, @@ -991,8 +1006,8 @@ private fun GoalAddedOREditedAnimation(editGoalId: String?) { } Text( text = textStr, - fontWeight = FontWeight.SemiBold, - fontSize = 20.sp, + fontWeight = FontWeight.Medium, + fontSize = 18.sp, fontFamily = greenstashFont ) diff --git a/app/src/main/java/com/starry/greenstash/ui/screens/other/CongratsScreen.kt b/app/src/main/java/com/starry/greenstash/ui/screens/other/CongratsScreen.kt index 2c8aa3a4..c3111a88 100644 --- a/app/src/main/java/com/starry/greenstash/ui/screens/other/CongratsScreen.kt +++ b/app/src/main/java/com/starry/greenstash/ui/screens/other/CongratsScreen.kt @@ -75,10 +75,11 @@ fun CongratsScreen(navController: NavController) { val context = LocalContext.current LaunchedEffect(key1 = true, block = { val mediaPlayer = MediaPlayer.create(context, R.raw.congrats_sound) - mediaPlayer.start() mediaPlayer.setOnCompletionListener { + println("Media Player Completed") mediaPlayer.release() // release the media player on completion. } + mediaPlayer.start() }) BackHandler { @@ -144,7 +145,7 @@ fun CongratsScreen(navController: NavController) { } } - SlideInAnimatedContainer(initialDelay = 3600) { + SlideInAnimatedContainer(initialDelay = 2000) { FilledTonalButton( onClick = { navController.popBackStack(DrawerScreens.Home.route, true) diff --git a/app/src/main/java/com/starry/greenstash/ui/screens/settings/composables/AboutScreen.kt b/app/src/main/java/com/starry/greenstash/ui/screens/settings/composables/AboutScreen.kt index 956833af..191d8658 100644 --- a/app/src/main/java/com/starry/greenstash/ui/screens/settings/composables/AboutScreen.kt +++ b/app/src/main/java/com/starry/greenstash/ui/screens/settings/composables/AboutScreen.kt @@ -26,6 +26,7 @@ package com.starry.greenstash.ui.screens.settings.composables import android.os.Build +import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.padding import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.material.icons.Icons @@ -48,6 +49,7 @@ import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.input.nestedscroll.nestedScroll import androidx.compose.ui.platform.LocalClipboardManager import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.platform.LocalView import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.vectorResource import androidx.compose.ui.text.AnnotatedString @@ -57,6 +59,7 @@ import com.starry.greenstash.BuildConfig import com.starry.greenstash.R import com.starry.greenstash.ui.theme.greenstashFont import com.starry.greenstash.utils.Utils +import com.starry.greenstash.utils.weakHapticFeedback sealed class AboutLinks(val url: String) { data object ReadMe : AboutLinks("https://github.com/Pool-Of-Tears/GreenStash") @@ -72,11 +75,14 @@ sealed class AboutLinks(val url: String) { @OptIn(ExperimentalMaterial3Api::class) @Composable fun AboutScreen(navController: NavController) { + val view = LocalView.current val context = LocalContext.current val clipboardManager = LocalClipboardManager.current val scrollBehavior = TopAppBarDefaults.exitUntilCollapsedScrollBehavior() - Scaffold(modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection), + Scaffold(modifier = Modifier + .fillMaxSize() + .nestedScroll(scrollBehavior.nestedScrollConnection), topBar = { LargeTopAppBar( title = { @@ -88,7 +94,10 @@ fun AboutScreen(navController: NavController) { ) }, navigationIcon = { - IconButton(onClick = { navController.navigateUp() }) { + IconButton(onClick = { + view.weakHapticFeedback() + navController.navigateUp() + }) { Icon( imageVector = Icons.AutoMirrored.Filled.ArrowBack, contentDescription = null diff --git a/app/src/main/java/com/starry/greenstash/ui/screens/settings/composables/OSLScreen.kt b/app/src/main/java/com/starry/greenstash/ui/screens/settings/composables/OSLScreen.kt index c60f14fe..14d2ac7a 100644 --- a/app/src/main/java/com/starry/greenstash/ui/screens/settings/composables/OSLScreen.kt +++ b/app/src/main/java/com/starry/greenstash/ui/screens/settings/composables/OSLScreen.kt @@ -26,19 +26,21 @@ package com.starry.greenstash.ui.screens.settings.composables 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.material3.ExperimentalMaterial3Api import androidx.compose.material3.Icon import androidx.compose.material3.IconButton +import androidx.compose.material3.LargeTopAppBar import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Scaffold import androidx.compose.material3.Text -import androidx.compose.material3.TopAppBar +import androidx.compose.material3.TopAppBarDefaults import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier +import androidx.compose.ui.input.nestedscroll.nestedScroll +import androidx.compose.ui.platform.LocalView import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.style.TextOverflow import androidx.navigation.NavController @@ -46,29 +48,47 @@ import com.mikepenz.aboutlibraries.ui.compose.LibrariesContainer import com.mikepenz.aboutlibraries.ui.compose.LibraryDefaults import com.starry.greenstash.R import com.starry.greenstash.ui.theme.greenstashFont +import com.starry.greenstash.utils.weakHapticFeedback @OptIn(ExperimentalMaterial3Api::class) @Composable fun OSLScreen(navController: NavController) { - Scaffold(modifier = Modifier.fillMaxSize(), topBar = { - TopAppBar( - modifier = Modifier.fillMaxWidth(), - title = { - Text( - text = stringResource(id = R.string.osl_screen_header), - maxLines = 1, - overflow = TextOverflow.Ellipsis, - fontFamily = greenstashFont - ) - }, navigationIcon = { - IconButton(onClick = { navController.navigateUp() }) { - Icon( - imageVector = Icons.AutoMirrored.Filled.ArrowBack, contentDescription = null + val view = LocalView.current + val scrollBehavior = TopAppBarDefaults.exitUntilCollapsedScrollBehavior() + + Scaffold( + modifier = Modifier + .fillMaxSize() + .nestedScroll(scrollBehavior.nestedScrollConnection), + topBar = { + LargeTopAppBar( + title = { + Text( + stringResource(id = R.string.osl_screen_header), + maxLines = 1, + overflow = TextOverflow.Ellipsis, + fontFamily = greenstashFont ) - } - }) - }) { + }, + navigationIcon = { + IconButton(onClick = { + view.weakHapticFeedback() + navController.navigateUp() + }) { + Icon( + imageVector = Icons.AutoMirrored.Filled.ArrowBack, + contentDescription = null + ) + } + }, + scrollBehavior = scrollBehavior, + colors = TopAppBarDefaults.largeTopAppBarColors( + containerColor = MaterialTheme.colorScheme.surface, + scrolledContainerColor = MaterialTheme.colorScheme.surface, + ) + ) + }) { LibrariesContainer( modifier = Modifier .fillMaxSize() diff --git a/app/src/main/java/com/starry/greenstash/ui/screens/settings/composables/SettingsScreen.kt b/app/src/main/java/com/starry/greenstash/ui/screens/settings/composables/SettingsScreen.kt index 4ccbf1b7..406efa1f 100644 --- a/app/src/main/java/com/starry/greenstash/ui/screens/settings/composables/SettingsScreen.kt +++ b/app/src/main/java/com/starry/greenstash/ui/screens/settings/composables/SettingsScreen.kt @@ -31,6 +31,7 @@ import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding @@ -109,7 +110,9 @@ fun SettingsScreen(navController: NavController) { val scrollBehavior = TopAppBarDefaults.exitUntilCollapsedScrollBehavior() Scaffold( - modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection), + modifier = Modifier + .fillMaxSize() + .nestedScroll(scrollBehavior.nestedScrollConnection), topBar = { LargeTopAppBar(title = { Text( diff --git a/app/src/main/res/values-es/strings.xml b/app/src/main/res/values-es/strings.xml index 1951437e..b18dfe21 100644 --- a/app/src/main/res/values-es/strings.xml +++ b/app/src/main/res/values-es/strings.xml @@ -72,7 +72,7 @@ Retirar No has depositado nada aún. Archivar - ¿Quieres archivar esta meta? + ¡Esta meta ha sido completada! ¿Te gustaría archivarla? Archivado correctamente. ¿Estás seguro/a? Eliminación exitosa. diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml index bc9a879e..809633d6 100644 --- a/app/src/main/res/values-ru/strings.xml +++ b/app/src/main/res/values-ru/strings.xml @@ -73,7 +73,7 @@ Вывести Вы еще не вносили никакой суммы. Архивировать - Хотите ли вы архивировать эту цель? + Эта цель достигнута! Хотите ли вы архивировать её? Успешно архивировано. Вы уверены? Удалено успешно. diff --git a/app/src/main/res/values-tr/strings.xml b/app/src/main/res/values-tr/strings.xml index 7dd071a6..74a317a2 100644 --- a/app/src/main/res/values-tr/strings.xml +++ b/app/src/main/res/values-tr/strings.xml @@ -72,7 +72,7 @@ çek Henüz herhangi bir tutar yatırmadın. Arşivle - Bu hedefi arşivlemek istiyor musunuz? + Bu hedef tamamlandı! Onu arşivlemek ister misiniz? Başarıyla arşivlendi. Emin misin? Başarıyla Silindi. diff --git a/app/src/main/res/values-zh-rCN/strings.xml b/app/src/main/res/values-zh-rCN/strings.xml index 83480e7f..4a960032 100644 --- a/app/src/main/res/values-zh-rCN/strings.xml +++ b/app/src/main/res/values-zh-rCN/strings.xml @@ -71,7 +71,7 @@ 取钱 您尚未存入任何金额。 存档 - 您要存档此目标吗? + 该目标已完成!您要将其存档吗? 已成功存档。 确定吗? 删除成功。 diff --git a/app/src/main/res/values-zh-rTW/strings.xml b/app/src/main/res/values-zh-rTW/strings.xml index 7f289a87..f49064e7 100644 --- a/app/src/main/res/values-zh-rTW/strings.xml +++ b/app/src/main/res/values-zh-rTW/strings.xml @@ -70,7 +70,7 @@ 提款 您尚未存入任何金額。 存檔 - 您要將此目標存檔嗎? + 這個目標已完成!您要將其存檔嗎? 已成功存檔。 您確定嗎? 成功刪除。 diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index f625d647..319d0cd6 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -71,7 +71,7 @@ Withdraw You haven\'t deposited any amount yet. Archive - Do you want to archive this goal? + This goal has been completed! Would you like to archive it? Archived Successfully. Are you sure? Deleted Successfully. @@ -144,6 +144,8 @@ Archived Goals No archived goals yet. + Do you want to restore this goal? + Restored Successfully. Backup & Restore From c7958a5ca61ef680919acfa388e2ee14449be6ea Mon Sep 17 00:00:00 2001 From: starry-shivam Date: Wed, 8 May 2024 13:39:50 +0530 Subject: [PATCH 5/5] Add missing translations and some cleanup Signed-off-by: starry-shivam --- .idea/deploymentTargetSelector.xml | 4 +- .../greenstash/database/core/AppDatabase.kt | 2 +- .../greenstash/ui/common/ExpandableCard.kt | 2 - .../archive/composables/ArchiveScreen.kt | 15 +- .../backups/composables/BackupScreen.kt | 5 +- .../ui/screens/home/HomeViewModel.kt | 2 +- .../ui/screens/home/composables/HomeDrawer.kt | 6 +- .../ui/screens/home/composables/HomeScreen.kt | 4 +- .../configuration/WidgetConfigActivity.kt | 192 +++++++++--------- .../res/drawable/ic_compact_goal_archve.xml | 21 +- app/src/main/res/drawable/ic_nav_privacy.xml | 15 +- app/src/main/res/values-es/strings.xml | 7 + app/src/main/res/values-ru/strings.xml | 6 + app/src/main/res/values-tr/strings.xml | 6 + app/src/main/res/values-zh-rCN/strings.xml | 6 + app/src/main/res/values-zh-rTW/strings.xml | 6 + 16 files changed, 185 insertions(+), 114 deletions(-) diff --git a/.idea/deploymentTargetSelector.xml b/.idea/deploymentTargetSelector.xml index 495ef5f9..c7d93f7d 100644 --- a/.idea/deploymentTargetSelector.xml +++ b/.idea/deploymentTargetSelector.xml @@ -4,10 +4,10 @@