mirror of
https://github.com/johannesjo/super-productivity.git
synced 2026-07-20 18:08:55 +00:00
* docs: add plan for Android background sync via WorkManager Outlines the implementation plan for using WorkManager to periodically sync with SuperSync server in the background, cancelling stale reminders for tasks that were completed/deleted on other devices. https://claude.ai/code/session_01RkrVBB492k8RBA8zt5swwe * docs: refine plan based on multi-agent architecture review - Pin frontend hook to SyncProviderManager.currentProviderPrivateCfg$ - Add skipWhileApplyingRemoteOps() guard for selector-based effect - Add HRX (dismiss reminder) and deadlineRemindAt to parser - Add batch operation ds field handling - Add ProGuard rules step for release builds - Add error handling note for BootReceiver WorkManager enqueue https://claude.ai/code/session_01RkrVBB492k8RBA8zt5swwe * docs: improve plan after second review round Key changes: - Remove Step 5 (ReminderAlarmStore lookup) - hash-based cancellation is sufficient, no store lookup needed - Add BackgroundSyncProvider interface for extensibility to Dropbox/WebDAV - Per-account lastServerSeq keyed by baseUrl hash - Add SyncReminderScheduler helper to deduplicate scheduling logic - Document edge cases: account switching, token expiry, force-kill, gapDetected, notification race conditions - Add extensibility section explaining Dropbox feasibility and architecture https://claude.ai/code/session_01RkrVBB492k8RBA8zt5swwe * feat(android): add background sync to cancel stale reminders via WorkManager When a task is completed/deleted on desktop, Android reminders for that task would still fire because the mobile app didn't know about the change. This adds a WorkManager periodic job (every 15 min) that fetches operations from the SuperSync server and cancels AlarmManager alarms + notifications for tasks that are done, deleted, archived, or had their reminders dismissed. Architecture: - BackgroundSyncProvider interface for extensibility (Dropbox/WebDAV possible later) - SuperSyncBackgroundProvider: lightweight HTTP client + operation parser - SyncReminderWorker: CoroutineWorker that fetches ops and cancels reminders - SyncReminderScheduler: helper to schedule/cancel the WorkManager job - AndroidSyncBridgeEffects: mirrors SuperSync credentials to native SharedPreferences - Per-account lastServerSeq prevents account-switching bugs https://claude.ai/code/session_01RkrVBB492k8RBA8zt5swwe * chore: update package-lock.json after npm install https://claude.ai/code/session_01RkrVBB492k8RBA8zt5swwe * fix(android): address review issues and add tests for background sync Fixes from code review: - Fix distinctUntilChanged to properly handle provider switching (non-SuperSync emissions now treated as equal to prevent repeated clears) - Add null-safe ops array parsing in SuperSyncBackgroundProvider - Add write/call timeouts to OkHttpClient (30s write, 60s call) - Remove no-op `hash and hash` line in Kotlin hash function Tests: - Add cross-platform parity tests for notification ID hash function (pinned expected values that must match Kotlin implementation) - Add AndroidSyncBridgeEffects unit tests covering: - distinctUntilChanged comparator behavior - credential set/clear decision logic - observable filtering integration (dedup, null filtering) https://claude.ai/code/session_01RkrVBB492k8RBA8zt5swwe * fix(android): fix hash overflow and operation parser payload paths Two critical bugs found during final review: 1. Hash function abs(Int.MIN_VALUE) bug: In Kotlin, abs(Int.MIN_VALUE) returns Int.MIN_VALUE (still negative) due to 32-bit overflow. Fixed by converting to Long before abs(), matching JS behavior where Math.abs works on 64-bit floats. 2. Operation parser payload structure: The parser was looking at p.isDone and p.task.changes directly, but the actual compact operation format wraps everything in p.entityChanges[] and p.actionPayload. Rewrote to use p.entityChanges as primary source (consistent structure with entityType, entityId, changes fields) and p.actionPayload.task.changes as secondary source. https://claude.ai/code/session_01RkrVBB492k8RBA8zt5swwe * docs: add long-term plan for Android background sync improvements Covers three phases: fast app startup via cached sync seq, push-based notification cancellation via FCM, and extending background sync to Dropbox/WebDAV providers. https://claude.ai/code/session_01RkrVBB492k8RBA8zt5swwe * fix(android): address review feedback for background reminder sync - Remove PLAN.md from repo root (C1) - Use EncryptedSharedPreferences for access token storage with fallback to standard SharedPreferences on broken KeyStore (C2) - Reset lastServerSeq when access token changes to prevent stale seq on account switch with same server URL (I1) - Add max iteration guard (100) to pagination loop to prevent infinite loops from server bugs (I2) - Use type predicate filter instead of non-null assertions (I3) - Add exponential backoff (5min) to WorkManager schedule (S1) - Add comments linking action type codes to frontend source (S2) - Extract distinctUntilChanged comparator to named function with JSDoc explaining the suppression semantics (S3) https://claude.ai/code/session_01RkrVBB492k8RBA8zt5swwe --------- Co-authored-by: Claude <noreply@anthropic.com>
128 lines
4.4 KiB
Groovy
128 lines
4.4 KiB
Groovy
plugins {
|
|
|
|
id "com.android.application"
|
|
id "org.jetbrains.kotlin.android"
|
|
}
|
|
|
|
// Apply the external configuration script
|
|
apply from: "config.gradle"
|
|
|
|
// Load the application configuration properties
|
|
def appConfig = loadAppConfig()
|
|
|
|
// Retrieve the build configuration fields for Debug and Release builds
|
|
def debugConfigFields = getBuildConfigFields(appConfig, true)
|
|
def releaseConfigFields = getBuildConfigFields(appConfig, false)
|
|
|
|
android {
|
|
defaultConfig {
|
|
applicationId "com.superproductivity.superproductivity"
|
|
minSdkVersion 24
|
|
targetSdkVersion 35
|
|
compileSdk 35
|
|
versionCode 17_05_02_9000
|
|
versionName "17.5.2"
|
|
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
|
|
manifestPlaceholders = [
|
|
hostName : "app.super-productivity.com",
|
|
defaultUrl : "https://app.super-productivity.com",
|
|
launcherName : "Super Productivity",
|
|
assetStatements: '[{ "relation": ["delegate_permission/common.handle_all_urls"], ' +
|
|
'"target": {"namespace": "web", "site": "https://app.super-productivity.com"}}]'
|
|
]
|
|
}
|
|
|
|
signingConfigs{
|
|
release{
|
|
storeFile file("../../keystore.jks")
|
|
storePassword System.getenv("RELEASE_KEYSTORE_PASSWORD")
|
|
keyAlias System.getenv("RELEASE_KEYSTORE_ALIAS")
|
|
keyPassword System.getenv("RELEASE_KEY_PASSWORD")
|
|
}
|
|
}
|
|
|
|
// Configure the build types (Debug and Release)
|
|
buildTypes {
|
|
debug {
|
|
// Define each field in BuildConfig for the Debug build
|
|
debugConfigFields.each { key, value ->
|
|
buildConfigField "String", key, value.toString()
|
|
}
|
|
}
|
|
release {
|
|
// Define each field in BuildConfig for the Release build
|
|
releaseConfigFields.each { key, value ->
|
|
buildConfigField "String", key, value.toString()
|
|
}
|
|
|
|
signingConfig signingConfigs.release
|
|
minifyEnabled false
|
|
proguardFiles getDefaultProguardFile("proguard-android.txt"), "proguard-rules.pro"
|
|
}
|
|
}
|
|
|
|
flavorDimensions "version"
|
|
productFlavors {
|
|
play {
|
|
dimension "version"
|
|
Properties properties = new Properties()
|
|
file("google.properties").withInputStream {
|
|
properties.load(it)
|
|
}
|
|
buildConfigField "String", "CLIENT_ID_WEB", "\"" + properties.webToken + "\""
|
|
}
|
|
fdroid {
|
|
dimension "version"
|
|
}
|
|
}
|
|
compileOptions {
|
|
sourceCompatibility = JavaVersion.VERSION_21
|
|
targetCompatibility = JavaVersion.VERSION_21
|
|
}
|
|
kotlinOptions {
|
|
jvmTarget = "21"
|
|
}
|
|
|
|
buildFeatures {
|
|
buildConfig = true
|
|
}
|
|
|
|
namespace "com.superproductivity.superproductivity"
|
|
}
|
|
|
|
dependencies {
|
|
implementation 'com.squareup.okhttp3:okhttp:4.12.0'
|
|
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.7.3'
|
|
implementation "androidx.appcompat:appcompat:1.7.0"
|
|
implementation "com.google.androidbrowserhelper:androidbrowserhelper:2.5.0"
|
|
implementation "com.google.code.gson:gson:2.10.1"
|
|
implementation "androidx.core:core-ktx:1.16.0"
|
|
implementation "androidx.localbroadcastmanager:localbroadcastmanager:1.1.0"
|
|
implementation 'androidx.work:work-runtime-ktx:2.10.0'
|
|
implementation 'androidx.security:security-crypto:1.1.0-alpha06'
|
|
implementation 'androidx.lifecycle:lifecycle-process:2.8.7'
|
|
implementation 'androidx.preference:preference-ktx:1.2.1'
|
|
implementation "com.anggrayudi:storage:1.5.4"
|
|
|
|
playImplementation "com.google.android.gms:play-services-auth:21.3.0"
|
|
|
|
implementation project(':capacitor-android')
|
|
|
|
// Force androidx.webkit to 1.11.0 for better API 28 compatibility
|
|
// This avoids NoClassDefFoundError for WebViewRenderProcessClient on Android 9
|
|
// while retaining audio controls and URL utils added in 1.11.0
|
|
// See: https://github.com/super-productivity/super-productivity/issues/5285
|
|
configurations.all {
|
|
resolutionStrategy {
|
|
force "androidx.webkit:webkit:1.11.0"
|
|
}
|
|
}
|
|
|
|
testImplementation "junit:junit:4.13.2"
|
|
|
|
androidTestImplementation "androidx.test:rules:1.6.1"
|
|
androidTestImplementation "com.android.support.test:runner:1.0.2"
|
|
androidTestImplementation "com.android.support.test.espresso:espresso-core:3.0.2"
|
|
}
|
|
|
|
apply from: 'capacitor.build.gradle'
|