From 990c621e26cb56391ebd05faaf30521801dc9a44 Mon Sep 17 00:00:00 2001 From: starry-shivam Date: Tue, 7 May 2024 13:56:27 +0530 Subject: [PATCH] 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