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