From 1d17510a310b1cc39bea04f234dcd235a3b4dfe0 Mon Sep 17 00:00:00 2001 From: Kris Johnson Date: Thu, 15 May 2025 16:12:13 -0400 Subject: [PATCH 1/3] android-cpp: Restore Sync control which was removed due to 4.9 SDK sync bug --- .../QuickStartTasksCPP/app/build.gradle.kts | 3 +- .../app/src/main/cpp/tasks_peer.cpp | 8 ++-- .../app/src/main/cpp/tasks_peer.h | 2 +- .../quickstart/tasks/list/TasksListScreen.kt | 22 ++++++++- .../tasks/list/TasksListScreenViewModel.kt | 46 +++++++++++++++++++ 5 files changed, 75 insertions(+), 6 deletions(-) diff --git a/android-cpp/QuickStartTasksCPP/app/build.gradle.kts b/android-cpp/QuickStartTasksCPP/app/build.gradle.kts index 6a60dff9f..3370a2eae 100644 --- a/android-cpp/QuickStartTasksCPP/app/build.gradle.kts +++ b/android-cpp/QuickStartTasksCPP/app/build.gradle.kts @@ -158,5 +158,6 @@ dependencies { debugImplementation(libs.androidx.ui.test.manifest) // Ditto C++ SDK - implementation("live.ditto:ditto-cpp:4.10.0") + implementation("live.ditto:ditto-cpp:4.10.2") + } diff --git a/android-cpp/QuickStartTasksCPP/app/src/main/cpp/tasks_peer.cpp b/android-cpp/QuickStartTasksCPP/app/src/main/cpp/tasks_peer.cpp index f73032ebe..b7b318817 100644 --- a/android-cpp/QuickStartTasksCPP/app/src/main/cpp/tasks_peer.cpp +++ b/android-cpp/QuickStartTasksCPP/app/src/main/cpp/tasks_peer.cpp @@ -131,7 +131,7 @@ class TasksPeer::Impl { // NOLINT(cppcoreguidelines-special-member-functions) ditto->start_sync(); tasks_subscription = - ditto->sync().register_subscription("SELECT * FROM tasks"); + ditto->get_sync().register_subscription("SELECT * FROM tasks"); } void stop_sync() { @@ -139,8 +139,10 @@ class TasksPeer::Impl { // NOLINT(cppcoreguidelines-special-member-functions) return; } - tasks_subscription->cancel(); - tasks_subscription.reset(); + if (tasks_subscription) { + tasks_subscription->cancel(); + tasks_subscription.reset(); + } ditto->stop_sync(); } diff --git a/android-cpp/QuickStartTasksCPP/app/src/main/cpp/tasks_peer.h b/android-cpp/QuickStartTasksCPP/app/src/main/cpp/tasks_peer.h index 9fd66ba6c..a1c6a2f01 100644 --- a/android-cpp/QuickStartTasksCPP/app/src/main/cpp/tasks_peer.h +++ b/android-cpp/QuickStartTasksCPP/app/src/main/cpp/tasks_peer.h @@ -45,7 +45,7 @@ class TasksPeer { void stop_sync(); /// Return true if peer is currently syncing tasks with other devices. - bool is_sync_active() const; + [[nodiscard]] bool is_sync_active() const; /// Create a new task and add it to the collection. /// diff --git a/android-cpp/QuickStartTasksCPP/app/src/main/java/live/ditto/quickstart/tasks/list/TasksListScreen.kt b/android-cpp/QuickStartTasksCPP/app/src/main/java/live/ditto/quickstart/tasks/list/TasksListScreen.kt index e66f9a8f3..b1bf01894 100644 --- a/android-cpp/QuickStartTasksCPP/app/src/main/java/live/ditto/quickstart/tasks/list/TasksListScreen.kt +++ b/android-cpp/QuickStartTasksCPP/app/src/main/java/live/ditto/quickstart/tasks/list/TasksListScreen.kt @@ -2,6 +2,7 @@ package live.ditto.quickstart.tasks.list import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding @@ -18,6 +19,7 @@ import androidx.compose.material3.FloatingActionButtonDefaults import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Scaffold +import androidx.compose.material3.Switch import androidx.compose.material3.Text import androidx.compose.material3.TextButton import androidx.compose.material3.TopAppBar @@ -28,6 +30,7 @@ import androidx.compose.runtime.livedata.observeAsState import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.res.colorResource @@ -48,6 +51,7 @@ import java.util.UUID fun TasksListScreen(navController: NavController) { val tasksListViewModel: TasksListScreenViewModel = viewModel() val tasks: List by tasksListViewModel.tasks.observeAsState(emptyList()) + val syncEnabled: Boolean by tasksListViewModel.syncEnabled.observeAsState(true) var showDeleteDialog by remember { mutableStateOf(false) } var deleteDialogTaskId by remember { mutableStateOf("") } @@ -80,7 +84,23 @@ fun TasksListScreen(navController: NavController) { colors = TopAppBarDefaults.topAppBarColors( containerColor = colorResource(id = R.color.blue_700), titleContentColor = Color.White - ) + ), + actions = { + Row(verticalAlignment = Alignment.CenterVertically) { + Text( + text = "Sync", + style = MaterialTheme.typography.bodySmall, + modifier = Modifier.padding(end = 10.dp), + color = Color.White + ) + Switch( + checked = syncEnabled, + onCheckedChange = { isChecked -> + tasksListViewModel.setSyncEnabled(isChecked) + } + ) + } + } ) }, floatingActionButton = { diff --git a/android-cpp/QuickStartTasksCPP/app/src/main/java/live/ditto/quickstart/tasks/list/TasksListScreenViewModel.kt b/android-cpp/QuickStartTasksCPP/app/src/main/java/live/ditto/quickstart/tasks/list/TasksListScreenViewModel.kt index e52a5557b..49c22c4a1 100644 --- a/android-cpp/QuickStartTasksCPP/app/src/main/java/live/ditto/quickstart/tasks/list/TasksListScreenViewModel.kt +++ b/android-cpp/QuickStartTasksCPP/app/src/main/java/live/ditto/quickstart/tasks/list/TasksListScreenViewModel.kt @@ -1,14 +1,26 @@ package live.ditto.quickstart.tasks.list +import android.content.Context import android.util.Log +import androidx.datastore.preferences.core.booleanPreferencesKey +import androidx.datastore.preferences.core.edit +import androidx.datastore.preferences.preferencesDataStore +import androidx.lifecycle.LiveData import androidx.lifecycle.MutableLiveData import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope +import kotlinx.coroutines.flow.first +import kotlinx.coroutines.flow.map import kotlinx.coroutines.launch +import live.ditto.quickstart.tasks.TasksApplication import live.ditto.quickstart.tasks.TasksLib import live.ditto.quickstart.tasks.TasksObserver import live.ditto.quickstart.tasks.data.Task +// The value of the Sync switch is stored in persistent settings +private val Context.preferencesDataStore by preferencesDataStore("tasks_list_settings") +private val SYNC_ENABLED_KEY = booleanPreferencesKey("sync_enabled") + class TasksListScreenViewModel : ViewModel() { companion object { @@ -24,13 +36,47 @@ class TasksListScreenViewModel : ViewModel() { } } + private val preferencesDataStore = TasksApplication.applicationContext().preferencesDataStore + val tasks: MutableLiveData> = MutableLiveData(emptyList()) private val updateHandler: UpdateHandler = UpdateHandler() + private val _syncEnabled = MutableLiveData(true) + val syncEnabled: LiveData = _syncEnabled + + + fun setSyncEnabled(enabled: Boolean) { + viewModelScope.launch { + preferencesDataStore.edit { settings -> + settings[SYNC_ENABLED_KEY] = enabled + } + _syncEnabled.value = enabled + + if (enabled && !TasksLib.isSyncActive()) { + try { + TasksLib.startSync() + } catch (e: Exception) { + Log.e(TAG, "Unable to start sync", e) + } + } else if (!enabled && TasksLib.isSyncActive()) { + try { + TasksLib.stopSync() + } catch (e: Exception) { + Log.e(TAG, "Unable to stop sync", e) + } + } + } + } + init { viewModelScope.launch { TasksLib.insertInitialDocuments() + + setSyncEnabled( + preferencesDataStore.data.map { prefs -> prefs[SYNC_ENABLED_KEY] ?: true }.first() + ) + TasksLib.setTasksObserver(updateHandler) } } From 362bc2bfddb905f121ef27e248878e7430100c7b Mon Sep 17 00:00:00 2001 From: Kris Johnson Date: Thu, 22 May 2025 15:32:23 -0400 Subject: [PATCH 2/3] Remove `remove_observer()` call from JNI `stopSync()` --- android-cpp/QuickStartTasksCPP/app/src/main/cpp/taskslib.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/android-cpp/QuickStartTasksCPP/app/src/main/cpp/taskslib.cpp b/android-cpp/QuickStartTasksCPP/app/src/main/cpp/taskslib.cpp index 364d60398..b0dcebde0 100644 --- a/android-cpp/QuickStartTasksCPP/app/src/main/cpp/taskslib.cpp +++ b/android-cpp/QuickStartTasksCPP/app/src/main/cpp/taskslib.cpp @@ -172,7 +172,6 @@ Java_live_ditto_quickstart_tasks_TasksLib_stopSync(JNIEnv *env, jobject thiz) { throw_java_illegal_state_exception(env, "TasksLib has not been initialized"); return; } - remove_observer(env); peer->stop_sync(); } catch (const std::exception &err) { __android_log_print(ANDROID_LOG_ERROR, TAG, "stopSync failed: %s", err.what()); From 8e3fbeb371ae060eda0e20e277fe04a630465231 Mon Sep 17 00:00:00 2001 From: Kris Johnson Date: Thu, 22 May 2025 15:39:27 -0400 Subject: [PATCH 3/3] Cleanup --- android-cpp/QuickStartTasksCPP/app/src/main/cpp/tasks_peer.h | 2 +- .../ditto/quickstart/tasks/list/TasksListScreenViewModel.kt | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/android-cpp/QuickStartTasksCPP/app/src/main/cpp/tasks_peer.h b/android-cpp/QuickStartTasksCPP/app/src/main/cpp/tasks_peer.h index a1c6a2f01..9fd66ba6c 100644 --- a/android-cpp/QuickStartTasksCPP/app/src/main/cpp/tasks_peer.h +++ b/android-cpp/QuickStartTasksCPP/app/src/main/cpp/tasks_peer.h @@ -45,7 +45,7 @@ class TasksPeer { void stop_sync(); /// Return true if peer is currently syncing tasks with other devices. - [[nodiscard]] bool is_sync_active() const; + bool is_sync_active() const; /// Create a new task and add it to the collection. /// diff --git a/android-cpp/QuickStartTasksCPP/app/src/main/java/live/ditto/quickstart/tasks/list/TasksListScreenViewModel.kt b/android-cpp/QuickStartTasksCPP/app/src/main/java/live/ditto/quickstart/tasks/list/TasksListScreenViewModel.kt index 49c22c4a1..20ce0ea65 100644 --- a/android-cpp/QuickStartTasksCPP/app/src/main/java/live/ditto/quickstart/tasks/list/TasksListScreenViewModel.kt +++ b/android-cpp/QuickStartTasksCPP/app/src/main/java/live/ditto/quickstart/tasks/list/TasksListScreenViewModel.kt @@ -45,7 +45,6 @@ class TasksListScreenViewModel : ViewModel() { private val _syncEnabled = MutableLiveData(true) val syncEnabled: LiveData = _syncEnabled - fun setSyncEnabled(enabled: Boolean) { viewModelScope.launch { preferencesDataStore.edit { settings ->