Release 1.0.3: refine tablet layouts, pointer selection and account sync cleanup
Build iOS / build (push) Successful in 4m45s
Build App Bundle / build (push) Successful in 7m7s

This commit is contained in:
Ayushya Amitabh
2026-10-09 00:36:42 -04:00
parent 79085874a1
commit 0a4608e6c3
67 changed files with 1740 additions and 451 deletions
@@ -57,6 +57,7 @@ class ConflictResolveWorker(appContext: Context, params: WorkerParameters) :
)
val request = OneTimeWorkRequestBuilder<ConflictResolveWorker>()
.setInputData(data)
.addTag("noo_sync_account_$accountId")
.build()
WorkManager.getInstance(context).enqueue(request)
}
@@ -1,5 +1,7 @@
package dev.ayushya.noo
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.sync.withLock
import android.Manifest
import android.content.ClipData
import android.content.Intent
@@ -140,6 +142,7 @@ class MainActivity : FlutterFragmentActivity() {
syncStatusMap(SyncStatusBus.snapshot(), call.argument<String>("accountId")),
)
"resolveConflict" -> resolveConflict(call, result)
"removeAccountData" -> removeAccountData(call, result)
"removeLocalSync" -> removeLocalSync(call, result)
else -> result.notImplemented()
}
@@ -478,6 +481,32 @@ class MainActivity : FlutterFragmentActivity() {
/// walks/deletes a directory tree; `result.success` is posted back via
/// [mainHandler] since MethodChannel results must be delivered on the
/// platform thread.
private fun removeAccountData(call: MethodCall, result: MethodChannel.Result) {
val accountId = call.argument<String>("accountId")
if (accountId == null) {
result.error("bad_args", "Missing accountId", null)
return
}
Thread {
try {
val manager = WorkManager.getInstance(applicationContext)
manager.cancelAllWorkByTag("noo_sync_account_$accountId").result.get()
manager.cancelUniqueWork(SyncWorker.periodicNameFor(accountId)).result.get()
manager.cancelUniqueWork(SyncWorker.oneOffNameFor(accountId)).result.get()
manager.cancelUniqueWork(SyncWorker.UNIQUE_PERIODIC_NAME).result.get()
runBlocking {
SyncEngine.syncLock.withLock {
SyncEngine.removeAccountData(applicationContext, accountId)
SyncStatusBus.clearAccount(accountId)
}
}
mainHandler.post { result.success(null) }
} catch (error: Exception) {
mainHandler.post { result.error("cleanup_failed", error.message, null) }
}
}.start()
}
private fun removeLocalSync(call: MethodCall, result: MethodChannel.Result) {
val accountId = call.argument<String>("accountId")
val path = call.argument<String>("path")
@@ -509,6 +509,16 @@ object SyncEngine {
* (see [diffFolder]'s `!localExists && !serverChanged` branch) instead
* of pulling it back down.
*/
fun removeAccountData(context: Context, accountId: String) {
val root = syncRoot(context, accountId)
check(!root.exists() || root.deleteRecursively()) { "Could not remove synced files" }
check(context.getSharedPreferences(STATE_PREFS, Context.MODE_PRIVATE).edit()
.remove(stateKey(accountId)).remove(rootsKey(accountId))
.remove(rootsVersionKey(accountId)).remove(missingKey(accountId)).commit()) {
"Could not remove sync records"
}
}
fun removeLocalSync(context: Context, accountId: String, path: String) {
val root = syncRoot(context, accountId)
val state = loadState(context, accountId).toMutableMap()
@@ -74,6 +74,16 @@ object SyncStatusBus {
publish()
}
@Synchronized
fun clearAccount(accountId: String) {
current = current.copy(
syncing = if (current.accountId == accountId) false else current.syncing,
syncingFileIds = if (current.accountId == accountId) emptySet() else current.syncingFileIds,
conflicts = current.conflicts.filter { it.accountId != accountId },
)
publish()
}
private fun publish() {
listeners.toList().forEach { it(current) }
}