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.compose.ui.res.stringResource
|
||||||
import androidx.core.app.NavUtils
|
import androidx.core.app.NavUtils
|
||||||
import androidx.preference.PreferenceManager
|
import androidx.preference.PreferenceManager
|
||||||
|
import net.helcel.cowspent.R
|
||||||
import net.helcel.cowspent.android.about.AboutActivity
|
import net.helcel.cowspent.android.about.AboutActivity
|
||||||
import net.helcel.cowspent.android.account.AccountActivity
|
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.theme.ThemeUtils
|
||||||
import net.helcel.cowspent.util.ColorUtils
|
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?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
enableEdgeToEdge()
|
enableEdgeToEdge()
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
@@ -74,7 +90,8 @@ class PreferencesActivity : AppCompatActivity() {
|
|||||||
startActivity(Intent(this, AboutActivity::class.java))
|
startActivity(Intent(this, AboutActivity::class.java))
|
||||||
},
|
},
|
||||||
onColorSelected = { appColor = it },
|
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.RadioButton
|
||||||
import androidx.compose.material.Scaffold
|
import androidx.compose.material.Scaffold
|
||||||
import androidx.compose.material.Slider
|
import androidx.compose.material.Slider
|
||||||
|
import androidx.compose.material.Checkbox
|
||||||
import androidx.compose.material.Switch
|
import androidx.compose.material.Switch
|
||||||
import androidx.compose.material.SwitchDefaults
|
import androidx.compose.material.SwitchDefaults
|
||||||
import androidx.compose.material.Text
|
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.Group
|
||||||
import androidx.compose.material.icons.filled.Info
|
import androidx.compose.material.icons.filled.Info
|
||||||
import androidx.compose.material.icons.filled.Palette
|
import androidx.compose.material.icons.filled.Palette
|
||||||
|
import androidx.compose.material.icons.filled.Restore
|
||||||
import androidx.compose.material.icons.filled.Sync
|
import androidx.compose.material.icons.filled.Sync
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.DisposableEffect
|
import androidx.compose.runtime.DisposableEffect
|
||||||
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.mutableIntStateOf
|
import androidx.compose.runtime.mutableIntStateOf
|
||||||
|
import androidx.compose.runtime.mutableStateListOf
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.runtime.setValue
|
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.painter.Painter
|
||||||
import androidx.compose.ui.graphics.vector.ImageVector
|
import androidx.compose.ui.graphics.vector.ImageVector
|
||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
|
import androidx.compose.ui.res.pluralStringResource
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.text.font.FontWeight
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
import androidx.compose.ui.tooling.preview.Preview
|
import androidx.compose.ui.tooling.preview.Preview
|
||||||
@@ -56,6 +61,9 @@ import androidx.core.content.edit
|
|||||||
import androidx.preference.PreferenceManager
|
import androidx.preference.PreferenceManager
|
||||||
import net.helcel.cowspent.R
|
import net.helcel.cowspent.R
|
||||||
import net.helcel.cowspent.android.helper.ColorPicker
|
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.CowspentSQLiteOpenHelper
|
||||||
import net.helcel.cowspent.persistence.CowspentServerSyncHelper
|
import net.helcel.cowspent.persistence.CowspentServerSyncHelper
|
||||||
import net.helcel.cowspent.util.ColorUtils
|
import net.helcel.cowspent.util.ColorUtils
|
||||||
@@ -68,7 +76,8 @@ fun SettingsScreen(
|
|||||||
onAccountSettingsClick: () -> Unit,
|
onAccountSettingsClick: () -> Unit,
|
||||||
onAboutClick: () -> Unit,
|
onAboutClick: () -> Unit,
|
||||||
onColorSelected: (Int) -> Unit,
|
onColorSelected: (Int) -> Unit,
|
||||||
onNightModeChanged: (String) -> Unit = {}
|
onNightModeChanged: (String) -> Unit = {},
|
||||||
|
onRestoreDeletedProjects: (List<DBAccountProject>) -> Unit = {}
|
||||||
) {
|
) {
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
val sharedPreferences = remember { PreferenceManager.getDefaultSharedPreferences(context) }
|
val sharedPreferences = remember { PreferenceManager.getDefaultSharedPreferences(context) }
|
||||||
@@ -92,6 +101,16 @@ fun SettingsScreen(
|
|||||||
|
|
||||||
val isNextcloudConfigured = CowspentServerSyncHelper.isNextcloudAccountConfigured(context)
|
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
|
// States for preferences
|
||||||
var nightMode by remember(keyNightMode) {
|
var nightMode by remember(keyNightMode) {
|
||||||
mutableStateOf(sharedPreferences.getString(keyNightMode, "-1") ?: "-1")
|
mutableStateOf(sharedPreferences.getString(keyNightMode, "-1") ?: "-1")
|
||||||
@@ -257,6 +276,32 @@ fun SettingsScreen(
|
|||||||
onClick = onAccountSettingsClick
|
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
|
// Other
|
||||||
SettingsCategory(stringResource(R.string.settings_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
|
@Composable
|
||||||
fun SettingsCategory(title: String) {
|
fun SettingsCategory(title: String) {
|
||||||
Text(
|
Text(
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import android.content.SharedPreferences
|
|||||||
import android.net.ConnectivityManager
|
import android.net.ConnectivityManager
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import androidx.annotation.VisibleForTesting
|
import androidx.annotation.VisibleForTesting
|
||||||
|
import androidx.annotation.WorkerThread
|
||||||
import androidx.core.content.edit
|
import androidx.core.content.edit
|
||||||
import androidx.core.graphics.toColorInt
|
import androidx.core.graphics.toColorInt
|
||||||
import androidx.preference.PreferenceManager
|
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.account.AccountActivity
|
||||||
import net.helcel.cowspent.android.main.BillsListViewActivity
|
import net.helcel.cowspent.android.main.BillsListViewActivity
|
||||||
import net.helcel.cowspent.android.main.MainConstants
|
import net.helcel.cowspent.android.main.MainConstants
|
||||||
|
import net.helcel.cowspent.model.DBAccountProject
|
||||||
import net.helcel.cowspent.model.DBBill
|
import net.helcel.cowspent.model.DBBill
|
||||||
import net.helcel.cowspent.model.DBMember
|
import net.helcel.cowspent.model.DBMember
|
||||||
import net.helcel.cowspent.model.DBProject
|
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() {
|
fun runAccountProjectsSync() {
|
||||||
Log.d(TAG, "Account projects sync requested; ${if (syncAccountProjectsActive) "sync active" else "sync NOT active"}) ...")
|
Log.d(TAG, "Account projects sync requested; ${if (syncAccountProjectsActive) "sync active" else "sync NOT active"}) ...")
|
||||||
updateNetworkStatus()
|
updateNetworkStatus()
|
||||||
|
|||||||
@@ -137,6 +137,9 @@
|
|||||||
<string name="settings_night_mode">Theme</string>
|
<string name="settings_night_mode">Theme</string>
|
||||||
<string name="settings_offline_mode">Offline mode</string>
|
<string name="settings_offline_mode">Offline mode</string>
|
||||||
<string name="settings_offline_mode_summary">Only sync manually.</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_custom">Custom color</string>
|
||||||
<string name="settings_color_mode">Color Selection</string>
|
<string name="settings_color_mode">Color Selection</string>
|
||||||
<string name="settings_show_archived">Show archived projects</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_intent_title">Project %1$s</string>
|
||||||
<string name="share_chooser_title">Share %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>
|
</resources>
|
||||||
|
|||||||
Reference in New Issue
Block a user