Restore deleted
This commit is contained in:
@@ -16,8 +16,13 @@ import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.core.app.NavUtils
|
||||
import androidx.preference.PreferenceManager
|
||||
import net.helcel.cowspent.R
|
||||
import net.helcel.cowspent.android.about.AboutActivity
|
||||
import net.helcel.cowspent.android.account.AccountActivity
|
||||
import net.helcel.cowspent.android.helper.showToast
|
||||
import net.helcel.cowspent.model.DBAccountProject
|
||||
import net.helcel.cowspent.persistence.CowspentSQLiteOpenHelper
|
||||
import net.helcel.cowspent.persistence.CowspentServerSyncHelper
|
||||
import net.helcel.cowspent.theme.ThemeUtils
|
||||
import net.helcel.cowspent.util.ColorUtils
|
||||
|
||||
@@ -34,6 +39,17 @@ class PreferencesActivity : AppCompatActivity() {
|
||||
}
|
||||
}
|
||||
|
||||
private fun restoreDeletedProjects(projects: List<DBAccountProject>) {
|
||||
if (projects.isEmpty()) return
|
||||
val syncHelper = CowspentSQLiteOpenHelper.getInstance(applicationContext).cowspentServerSyncHelper
|
||||
syncHelper.restoreAccountProjects(projects)
|
||||
if (CowspentServerSyncHelper.isNextcloudAccountConfigured(applicationContext)) {
|
||||
syncHelper.runAccountProjectsSync()
|
||||
}
|
||||
setResult(RESULT_OK)
|
||||
showToast(this, getString(R.string.settings_restore_deleted_projects_done))
|
||||
}
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
enableEdgeToEdge()
|
||||
super.onCreate(savedInstanceState)
|
||||
@@ -74,7 +90,8 @@ class PreferencesActivity : AppCompatActivity() {
|
||||
startActivity(Intent(this, AboutActivity::class.java))
|
||||
},
|
||||
onColorSelected = { appColor = it },
|
||||
onNightModeChanged = { nightMode = it }
|
||||
onNightModeChanged = { nightMode = it },
|
||||
onRestoreDeletedProjects = { restoreDeletedProjects(it) }
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,6 +21,7 @@ import androidx.compose.material.MaterialTheme
|
||||
import androidx.compose.material.RadioButton
|
||||
import androidx.compose.material.Scaffold
|
||||
import androidx.compose.material.Slider
|
||||
import androidx.compose.material.Checkbox
|
||||
import androidx.compose.material.Switch
|
||||
import androidx.compose.material.SwitchDefaults
|
||||
import androidx.compose.material.Text
|
||||
@@ -34,11 +35,14 @@ import androidx.compose.material.icons.filled.Brightness2
|
||||
import androidx.compose.material.icons.filled.Group
|
||||
import androidx.compose.material.icons.filled.Info
|
||||
import androidx.compose.material.icons.filled.Palette
|
||||
import androidx.compose.material.icons.filled.Restore
|
||||
import androidx.compose.material.icons.filled.Sync
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.DisposableEffect
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableIntStateOf
|
||||
import androidx.compose.runtime.mutableStateListOf
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.setValue
|
||||
@@ -47,6 +51,7 @@ import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.painter.Painter
|
||||
import androidx.compose.ui.graphics.vector.ImageVector
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.pluralStringResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
@@ -56,6 +61,9 @@ import androidx.core.content.edit
|
||||
import androidx.preference.PreferenceManager
|
||||
import net.helcel.cowspent.R
|
||||
import net.helcel.cowspent.android.helper.ColorPicker
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
import net.helcel.cowspent.model.DBAccountProject
|
||||
import net.helcel.cowspent.persistence.CowspentSQLiteOpenHelper
|
||||
import net.helcel.cowspent.persistence.CowspentServerSyncHelper
|
||||
import net.helcel.cowspent.util.ColorUtils
|
||||
@@ -68,7 +76,8 @@ fun SettingsScreen(
|
||||
onAccountSettingsClick: () -> Unit,
|
||||
onAboutClick: () -> Unit,
|
||||
onColorSelected: (Int) -> Unit,
|
||||
onNightModeChanged: (String) -> Unit = {}
|
||||
onNightModeChanged: (String) -> Unit = {},
|
||||
onRestoreDeletedProjects: (List<DBAccountProject>) -> Unit = {}
|
||||
) {
|
||||
val context = LocalContext.current
|
||||
val sharedPreferences = remember { PreferenceManager.getDefaultSharedPreferences(context) }
|
||||
@@ -92,6 +101,16 @@ fun SettingsScreen(
|
||||
|
||||
val isNextcloudConfigured = CowspentServerSyncHelper.isNextcloudAccountConfigured(context)
|
||||
|
||||
var deletedProjects by remember { mutableStateOf(emptyList<DBAccountProject>()) }
|
||||
var showRestoreDialog by remember { mutableStateOf(false) }
|
||||
|
||||
// Off the main thread: this reads the account projects table.
|
||||
LaunchedEffect(Unit) {
|
||||
deletedProjects = withContext(Dispatchers.IO) {
|
||||
CowspentSQLiteOpenHelper.getInstance(context).cowspentServerSyncHelper.deletedAccountProjects()
|
||||
}
|
||||
}
|
||||
|
||||
// States for preferences
|
||||
var nightMode by remember(keyNightMode) {
|
||||
mutableStateOf(sharedPreferences.getString(keyNightMode, "-1") ?: "-1")
|
||||
@@ -257,6 +276,32 @@ fun SettingsScreen(
|
||||
onClick = onAccountSettingsClick
|
||||
)
|
||||
|
||||
// Nothing held back means nothing to undo, so the row is not offered at all.
|
||||
if (deletedProjects.isNotEmpty()) {
|
||||
SettingsPreference(
|
||||
title = stringResource(R.string.settings_restore_deleted_projects),
|
||||
summary = pluralStringResource(
|
||||
R.plurals.settings_restore_deleted_projects_summary,
|
||||
deletedProjects.size,
|
||||
deletedProjects.size
|
||||
),
|
||||
icon = Icons.Default.Restore,
|
||||
onClick = { showRestoreDialog = true }
|
||||
)
|
||||
}
|
||||
|
||||
if (showRestoreDialog) {
|
||||
RestoreDeletedProjectsDialog(
|
||||
projects = deletedProjects,
|
||||
onDismiss = { showRestoreDialog = false },
|
||||
onConfirm = { chosen ->
|
||||
showRestoreDialog = false
|
||||
onRestoreDeletedProjects(chosen)
|
||||
deletedProjects = deletedProjects - chosen.toSet()
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
// Other
|
||||
SettingsCategory(stringResource(R.string.settings_other))
|
||||
|
||||
@@ -349,6 +394,60 @@ fun SettingsScreen(
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun RestoreDeletedProjectsDialog(
|
||||
projects: List<DBAccountProject>,
|
||||
onDismiss: () -> Unit,
|
||||
onConfirm: (List<DBAccountProject>) -> Unit
|
||||
) {
|
||||
val selected = remember(projects) { mutableStateListOf<DBAccountProject>().apply { addAll(projects) } }
|
||||
|
||||
AlertDialog(
|
||||
onDismissRequest = onDismiss,
|
||||
title = { Text(stringResource(R.string.settings_restore_deleted_projects)) },
|
||||
text = {
|
||||
Column(modifier = Modifier.verticalScroll(rememberScrollState())) {
|
||||
projects.forEach { project ->
|
||||
val isSelected = project in selected
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.selectable(
|
||||
selected = isSelected,
|
||||
onClick = {
|
||||
if (isSelected) selected.remove(project) else selected.add(project)
|
||||
}
|
||||
)
|
||||
.padding(vertical = 8.dp),
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
Checkbox(checked = isSelected, onCheckedChange = null)
|
||||
Spacer(Modifier.width(16.dp))
|
||||
Column {
|
||||
Text(
|
||||
text = project.name.ifEmpty { project.remoteId },
|
||||
style = MaterialTheme.typography.subtitle1
|
||||
)
|
||||
Text(text = project.ncUrl, style = MaterialTheme.typography.caption)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
confirmButton = {
|
||||
TextButton(
|
||||
onClick = { onConfirm(selected.toList()) },
|
||||
enabled = selected.isNotEmpty()
|
||||
) {
|
||||
Text(stringResource(R.string.settings_restore_deleted_projects_confirm))
|
||||
}
|
||||
},
|
||||
dismissButton = {
|
||||
TextButton(onClick = onDismiss) { Text(stringResource(R.string.simple_cancel)) }
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun SettingsCategory(title: String) {
|
||||
Text(
|
||||
|
||||
@@ -6,6 +6,7 @@ import android.content.SharedPreferences
|
||||
import android.net.ConnectivityManager
|
||||
import android.util.Log
|
||||
import androidx.annotation.VisibleForTesting
|
||||
import androidx.annotation.WorkerThread
|
||||
import androidx.core.content.edit
|
||||
import androidx.core.graphics.toColorInt
|
||||
import androidx.preference.PreferenceManager
|
||||
@@ -27,6 +28,7 @@ import net.helcel.cowspent.R
|
||||
import net.helcel.cowspent.android.account.AccountActivity
|
||||
import net.helcel.cowspent.android.main.BillsListViewActivity
|
||||
import net.helcel.cowspent.android.main.MainConstants
|
||||
import net.helcel.cowspent.model.DBAccountProject
|
||||
import net.helcel.cowspent.model.DBBill
|
||||
import net.helcel.cowspent.model.DBMember
|
||||
import net.helcel.cowspent.model.DBProject
|
||||
@@ -1475,6 +1477,21 @@ class CowspentServerSyncHelper private constructor(private val dbHelper: Cowspen
|
||||
}
|
||||
}
|
||||
|
||||
@WorkerThread
|
||||
fun deletedAccountProjects(): List<DBAccountProject> {
|
||||
val forgotten = forgottenAccountProjects(preferences)
|
||||
if (forgotten.isEmpty()) return emptyList()
|
||||
return dbHelper.accountProjects.filter { accountProjectKey(it.remoteId, it.ncUrl) in forgotten }
|
||||
}
|
||||
|
||||
fun restoreAccountProjects(projects: List<DBAccountProject>) {
|
||||
if (projects.isEmpty()) return
|
||||
val restored = projects.map { accountProjectKey(it.remoteId, it.ncUrl) }.toSet()
|
||||
preferences.edit {
|
||||
putStringSet(FORGOTTEN_ACCOUNT_PROJECTS, forgottenAccountProjects(preferences) - restored)
|
||||
}
|
||||
}
|
||||
|
||||
fun runAccountProjectsSync() {
|
||||
Log.d(TAG, "Account projects sync requested; ${if (syncAccountProjectsActive) "sync active" else "sync NOT active"}) ...")
|
||||
updateNetworkStatus()
|
||||
|
||||
@@ -137,6 +137,9 @@
|
||||
<string name="settings_night_mode">Theme</string>
|
||||
<string name="settings_offline_mode">Offline mode</string>
|
||||
<string name="settings_offline_mode_summary">Only sync manually.</string>
|
||||
<string name="settings_restore_deleted_projects">Restore deleted projects</string>
|
||||
<string name="settings_restore_deleted_projects_done">Restoring from the account</string>
|
||||
<string name="settings_restore_deleted_projects_confirm">Restore</string>
|
||||
<string name="settings_color_custom">Custom color</string>
|
||||
<string name="settings_color_mode">Color Selection</string>
|
||||
<string name="settings_show_archived">Show archived projects</string>
|
||||
@@ -291,4 +294,9 @@
|
||||
<string name="share_intent_title">Project %1$s</string>
|
||||
<string name="share_chooser_title">Share %1$s</string>
|
||||
|
||||
|
||||
<plurals name="settings_restore_deleted_projects_summary">
|
||||
<item quantity="one">%d project the account offers is kept off this device.</item>
|
||||
<item quantity="other">%d projects the account offers are kept off this device.</item>
|
||||
</plurals>
|
||||
</resources>
|
||||
|
||||
Reference in New Issue
Block a user