diff --git a/.kotlin/sessions/kotlin-compiler-1894979669169952593.salive b/.kotlin/sessions/kotlin-compiler-14714072796190586513.salive
similarity index 100%
rename from .kotlin/sessions/kotlin-compiler-1894979669169952593.salive
rename to .kotlin/sessions/kotlin-compiler-14714072796190586513.salive
diff --git a/app/src/main/kotlin/com/syncflow/ui/addpair/AddPairViewModel.kt b/app/src/main/kotlin/com/syncflow/ui/addpair/AddPairViewModel.kt
index df004ca..7558316 100644
--- a/app/src/main/kotlin/com/syncflow/ui/addpair/AddPairViewModel.kt
+++ b/app/src/main/kotlin/com/syncflow/ui/addpair/AddPairViewModel.kt
@@ -1,5 +1,6 @@
package com.syncflow.ui.addpair
+import android.content.Context
import androidx.lifecycle.SavedStateHandle
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
@@ -7,9 +8,10 @@ import com.syncflow.data.db.CloudAccountDao
import com.syncflow.data.db.SyncPairDao
import com.syncflow.data.db.entities.CloudAccountEntity
import com.syncflow.data.db.entities.SyncPairEntity
-import com.syncflow.data.db.entities.toDomain
import com.syncflow.domain.model.*
+import com.syncflow.worker.FileWatchService
import dagger.hilt.android.lifecycle.HiltViewModel
+import dagger.hilt.android.qualifiers.ApplicationContext
import kotlinx.coroutines.flow.*
import kotlinx.coroutines.launch
import javax.inject.Inject
@@ -31,7 +33,7 @@ data class AddPairUiState(
val scheduleType: ScheduleType = ScheduleType.INTERVAL,
val intervalMinutes: Int = 30,
val dailyTime: String = "02:00",
- val weekdays: Int = 0b1111111, // all 7 days by default
+ val weekdays: Int = 0b1111111,
// ── Constraints ──────────────────────────────────────────────────────────
val wifiOnly: Boolean = true,
val wifiSsid: String = "",
@@ -57,6 +59,7 @@ data class AddPairUiState(
class AddPairViewModel @Inject constructor(
private val syncPairDao: SyncPairDao,
private val accountDao: CloudAccountDao,
+ @ApplicationContext private val context: Context,
savedState: SavedStateHandle,
) : ViewModel() {
@@ -147,7 +150,10 @@ class AddPairViewModel @Inject constructor(
)
if (editPairId == null) syncPairDao.insert(entity) else syncPairDao.update(entity)
}
- .onSuccess { _state.update { it.copy(done = true) } }
+ .onSuccess {
+ if (s.scheduleType == ScheduleType.ON_CHANGE) FileWatchService.start(context)
+ _state.update { it.copy(done = true) }
+ }
.onFailure { e -> _state.update { it.copy(isSaving = false, error = e.message) } }
}
}
diff --git a/app/src/main/kotlin/com/syncflow/ui/home/HomeScreen.kt b/app/src/main/kotlin/com/syncflow/ui/home/HomeScreen.kt
index 1ade924..8799c2d 100644
--- a/app/src/main/kotlin/com/syncflow/ui/home/HomeScreen.kt
+++ b/app/src/main/kotlin/com/syncflow/ui/home/HomeScreen.kt
@@ -1,5 +1,10 @@
package com.syncflow.ui.home
+import androidx.compose.animation.core.LinearEasing
+import androidx.compose.animation.core.animateFloat
+import androidx.compose.animation.core.infiniteRepeatable
+import androidx.compose.animation.core.rememberInfiniteTransition
+import androidx.compose.animation.core.tween
import androidx.compose.foundation.BorderStroke
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.*
@@ -14,6 +19,7 @@ import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.graphicsLayer
import androidx.compose.ui.unit.dp
import androidx.hilt.navigation.compose.hiltViewModel
import com.syncflow.data.db.entities.SyncPairEntity
@@ -159,8 +165,18 @@ private fun SyncPairCard(
color = MaterialTheme.colorScheme.onSurfaceVariant,
)
}
+ val syncRotation by rememberInfiniteTransition(label = "cardSyncSpin").animateFloat(
+ initialValue = 0f, targetValue = 360f,
+ animationSpec = infiniteRepeatable(tween(900, easing = LinearEasing)),
+ label = "cardRotation",
+ )
FilledTonalIconButton(onClick = onSync, modifier = Modifier.size(36.dp)) {
- Icon(Icons.Default.Sync, "Sync now", modifier = Modifier.size(18.dp))
+ Icon(
+ Icons.Default.Sync, "Sync now",
+ modifier = Modifier.size(18.dp).graphicsLayer {
+ if (pair.lastSyncResult == SyncStatus.SYNCING) rotationZ = syncRotation
+ },
+ )
}
}
}
@@ -179,10 +195,11 @@ private fun StatusPill(status: SyncStatus) {
SyncStatus.IDLE -> Pair(Icons.Outlined.Circle, "Idle")
}
val containerColor = status.accentColor
- val contentColor = when (status) {
- SyncStatus.IDLE -> MaterialTheme.colorScheme.onSurfaceVariant
- else -> MaterialTheme.colorScheme.surface
- }
+ val rotation by rememberInfiniteTransition(label = "syncSpin").animateFloat(
+ initialValue = 0f, targetValue = 360f,
+ animationSpec = infiniteRepeatable(tween(1000, easing = LinearEasing)),
+ label = "rotation",
+ )
Surface(
shape = RoundedCornerShape(50),
color = containerColor.copy(alpha = 0.15f),
@@ -192,7 +209,11 @@ private fun StatusPill(status: SyncStatus) {
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(4.dp),
) {
- Icon(icon, null, Modifier.size(12.dp), tint = containerColor)
+ Icon(
+ icon, null,
+ Modifier.size(12.dp).graphicsLayer { if (status == SyncStatus.SYNCING) rotationZ = rotation },
+ tint = containerColor,
+ )
Text(label, style = MaterialTheme.typography.labelSmall, color = containerColor)
}
}
diff --git a/app/src/main/kotlin/com/syncflow/ui/pairdetail/PairDetailScreen.kt b/app/src/main/kotlin/com/syncflow/ui/pairdetail/PairDetailScreen.kt
index f8ba538..61c3da0 100644
--- a/app/src/main/kotlin/com/syncflow/ui/pairdetail/PairDetailScreen.kt
+++ b/app/src/main/kotlin/com/syncflow/ui/pairdetail/PairDetailScreen.kt
@@ -1,5 +1,10 @@
package com.syncflow.ui.pairdetail
+import androidx.compose.animation.core.LinearEasing
+import androidx.compose.animation.core.animateFloat
+import androidx.compose.animation.core.infiniteRepeatable
+import androidx.compose.animation.core.rememberInfiniteTransition
+import androidx.compose.animation.core.tween
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
@@ -13,6 +18,7 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.graphicsLayer
import androidx.compose.ui.unit.dp
import androidx.hilt.navigation.compose.hiltViewModel
import com.syncflow.data.db.entities.SyncEventEntity
@@ -143,6 +149,11 @@ private fun StatusBanner(pair: SyncPairEntity) {
SyncStatus.PARTIAL -> Triple(Icons.Default.WarningAmber,"Partial", MaterialTheme.colorScheme.tertiaryContainer)
SyncStatus.IDLE -> Triple(Icons.Outlined.Circle, "Idle", MaterialTheme.colorScheme.surfaceVariant)
}
+ val rotation by rememberInfiniteTransition(label = "bannerSpin").animateFloat(
+ initialValue = 0f, targetValue = 360f,
+ animationSpec = infiniteRepeatable(tween(1000, easing = LinearEasing)),
+ label = "bannerRotation",
+ )
Surface(
color = containerColor,
shape = RoundedCornerShape(16.dp),
@@ -152,7 +163,12 @@ private fun StatusBanner(pair: SyncPairEntity) {
modifier = Modifier.padding(16.dp),
verticalAlignment = Alignment.CenterVertically,
) {
- Icon(icon, null, modifier = Modifier.size(40.dp))
+ Icon(
+ icon, null,
+ modifier = Modifier.size(40.dp).graphicsLayer {
+ if (pair.lastSyncResult == SyncStatus.SYNCING) rotationZ = rotation
+ },
+ )
Spacer(Modifier.width(16.dp))
Column {
Text(label, style = MaterialTheme.typography.titleMedium)
diff --git a/app/src/main/kotlin/com/syncflow/worker/FileWatchService.kt b/app/src/main/kotlin/com/syncflow/worker/FileWatchService.kt
index 78e0b2a..cb43caf 100644
--- a/app/src/main/kotlin/com/syncflow/worker/FileWatchService.kt
+++ b/app/src/main/kotlin/com/syncflow/worker/FileWatchService.kt
@@ -148,7 +148,7 @@ class FileWatchService : Service() {
val nm = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
if (nm.getNotificationChannel(CHANNEL_WATCH) == null) {
nm.createNotificationChannel(
- NotificationChannel(CHANNEL_WATCH, "File watching", NotificationManager.IMPORTANCE_MIN).apply {
+ NotificationChannel(CHANNEL_WATCH, "File watching", NotificationManager.IMPORTANCE_LOW).apply {
description = "Background service watching folders for changes"
setShowBadge(false)
}
diff --git a/app/src/main/res/drawable/ic_launcher_background.xml b/app/src/main/res/drawable/ic_launcher_background.xml
index b95c9df..9c6f772 100644
--- a/app/src/main/res/drawable/ic_launcher_background.xml
+++ b/app/src/main/res/drawable/ic_launcher_background.xml
@@ -3,6 +3,7 @@
+ android:startColor="#4338CA"
+ android:centerColor="#7C3AED"
+ android:endColor="#0891B2"/>
diff --git a/app/src/main/res/drawable/ic_launcher_foreground.xml b/app/src/main/res/drawable/ic_launcher_foreground.xml
index 1142ffc..d6a09f4 100644
--- a/app/src/main/res/drawable/ic_launcher_foreground.xml
+++ b/app/src/main/res/drawable/ic_launcher_foreground.xml
@@ -1,13 +1,39 @@
+
+
+ android:pathData="M12,12m-7.5,0a7.5,7.5 0 1,0 15,0a7.5,7.5 0 1,0 -15,0"
+ android:fillColor="#22FFFFFF"/>
+
+
+ android:pathData="M12,4V1L8,5l4,4V6c3.31,0 6,2.69 6,6 0,1.01-0.25,1.97-0.7,2.8l1.46,1.46C19.54,15.03 20,13.57 20,12c0-4.42-3.58-8-8-8z">
+
+
+
+
+
+
+
+
+
+
+
diff --git a/releases/latest/SyncFlow.apk b/releases/latest/SyncFlow.apk
index 277a49a..85c0010 100644
Binary files a/releases/latest/SyncFlow.apk and b/releases/latest/SyncFlow.apk differ
diff --git a/version.properties b/version.properties
index e7fab87..c8fafbf 100644
--- a/version.properties
+++ b/version.properties
@@ -1,2 +1,2 @@
-VERSION_NAME=1.0.15
-VERSION_CODE=16
+VERSION_NAME=1.0.16
+VERSION_CODE=17