Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
## 1.0.0-BETA27

* Improved watch query internals. Added the ability to throttle watched queries.
* Fixed `uploading` and `downloading` sync status indicators.

## 1.0.0-BETA26

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -207,7 +207,14 @@ class SyncIntegrationTest {
SyncLine.FullCheckpoint(
Checkpoint(
lastOpId = "4",
checksums = listOf(BucketChecksum(bucket = "bkt", priority = BucketPriority(1), checksum = 0)),
checksums =
listOf(
BucketChecksum(
bucket = "bkt",
priority = BucketPriority(1),
checksum = 0,
),
),
),
),
)
Expand All @@ -228,4 +235,39 @@ class SyncIntegrationTest {
database.close()
syncLines.close()
}

@Test
fun setsDownloadingState() =
runTest {
val syncStream = syncStream()
database.connect(syncStream, 1000L)

turbineScope(timeout = 10.0.seconds) {
val turbine = database.currentStatus.asFlow().testIn(this)
turbine.waitFor { it.connected && !it.downloading }

syncLines.send(
SyncLine.FullCheckpoint(
Checkpoint(
lastOpId = "1",
checksums =
listOf(
BucketChecksum(
bucket = "bkt",
checksum = 0,
),
),
),
),
)
turbine.waitFor { it.downloading }

syncLines.send(SyncLine.CheckpointComplete(lastOpId = "1"))
turbine.waitFor { !it.downloading }
turbine.cancel()
}

database.close()
syncLines.close()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,7 @@ internal class PowerSyncDatabaseImpl(
currentStatus.update(
connected = it.connected,
connecting = it.connecting,
uploading = it.uploading,
downloading = it.downloading,
lastSyncedAt = it.lastSyncedAt,
hasSynced = it.hasSynced,
Expand Down
23 changes: 20 additions & 3 deletions core/src/commonMain/kotlin/com/powersync/sync/SyncStream.kt
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,6 @@ internal class SyncStream(
var checkedCrudItem: CrudEntry? = null

while (true) {
status.update(uploading = true)
/**
* This is the first item in the FIFO CRUD queue.
*/
Expand All @@ -146,6 +145,7 @@ internal class SyncStream(
}

checkedCrudItem = nextCrudItem
status.update(uploading = true)
uploadCrud()
} else {
// Uploading is completed
Expand Down Expand Up @@ -256,6 +256,8 @@ internal class SyncStream(
state = handleInstruction(line, value, state)
}

status.update(downloading = false)

return state
}

Expand All @@ -268,7 +270,12 @@ internal class SyncStream(
is SyncLine.FullCheckpoint -> handleStreamingSyncCheckpoint(line, state)
is SyncLine.CheckpointDiff -> handleStreamingSyncCheckpointDiff(line, state)
is SyncLine.CheckpointComplete -> handleStreamingSyncCheckpointComplete(state)
is SyncLine.CheckpointPartiallyComplete -> handleStreamingSyncCheckpointPartiallyComplete(line, state)
is SyncLine.CheckpointPartiallyComplete ->
handleStreamingSyncCheckpointPartiallyComplete(
line,
state,
)

is SyncLine.KeepAlive -> handleStreamingKeepAlive(line, state)
is SyncLine.SyncDataBucket -> handleStreamingSyncData(line, state)
SyncLine.UnknownSyncLine -> {
Expand All @@ -283,6 +290,8 @@ internal class SyncStream(
): SyncStreamState {
val (checkpoint) = line
state.targetCheckpoint = checkpoint
status.update(downloading = true)

val bucketsToDelete = state.bucketSet!!.toMutableList()
val newBuckets = mutableSetOf<String>()

Expand Down Expand Up @@ -323,7 +332,12 @@ internal class SyncStream(
}

state.validatedCheckpoint = state.targetCheckpoint
status.update(lastSyncedAt = Clock.System.now(), hasSynced = true, clearDownloadError = true)
status.update(
lastSyncedAt = Clock.System.now(),
downloading = false,
hasSynced = true,
clearDownloadError = true,
)

return state
}
Expand Down Expand Up @@ -374,6 +388,8 @@ internal class SyncStream(
throw Exception("Checkpoint diff without previous checkpoint")
}

status.update(downloading = true)

val newBuckets = mutableMapOf<String, BucketChecksum>()

state.targetCheckpoint!!.checksums.forEach { checksum ->
Expand Down Expand Up @@ -410,6 +426,7 @@ internal class SyncStream(
data: SyncLine.SyncDataBucket,
state: SyncStreamState,
): SyncStreamState {
status.update(downloading = true)
bucketStorage.saveSyncData(SyncDataBatch(listOf(data)))
return state
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,20 +24,28 @@ import com.powersync.demos.screens.HomeScreen
import com.powersync.demos.screens.SignInScreen
import com.powersync.demos.screens.SignUpScreen
import com.powersync.demos.screens.TodosScreen
import kotlinx.coroutines.flow.debounce
import kotlinx.coroutines.runBlocking


@Composable
fun App(factory: DatabaseDriverFactory, modifier: Modifier = Modifier) {
val supabase = remember {
SupabaseConnector(
powerSyncEndpoint = Config.POWERSYNC_URL,
supabaseUrl = Config.SUPABASE_URL,
supabaseKey = Config.SUPABASE_ANON_KEY
)
}
fun App(
factory: DatabaseDriverFactory,
modifier: Modifier = Modifier,
) {
val supabase =
remember {
SupabaseConnector(
powerSyncEndpoint = Config.POWERSYNC_URL,
supabaseUrl = Config.SUPABASE_URL,
supabaseKey = Config.SUPABASE_ANON_KEY,
)
}
val db = remember { PowerSyncDatabase(factory, schema) }
val status by db.currentStatus.asFlow().collectAsState(initial = db.currentStatus)
// Debouncing the status flow prevents flicker
val status by db.currentStatus
.asFlow()
.debounce(200)
.collectAsState(initial = db.currentStatus)

// This assumes that the buckets for lists has a priority of 1 (but it will work fine with sync
// rules not defining any priorities at all too). When giving lists a higher priority than
Expand All @@ -48,9 +56,10 @@ fun App(factory: DatabaseDriverFactory, modifier: Modifier = Modifier) {
}

val navController = remember { NavController(Screen.Home) }
val authViewModel = remember {
AuthViewModel(supabase, db, navController)
}
val authViewModel =
remember {
AuthViewModel(supabase, db, navController)
}

val authState by authViewModel.authState.collectAsState()
val currentScreen by navController.currentScreen.collectAsState()
Expand Down Expand Up @@ -81,7 +90,7 @@ fun App(factory: DatabaseDriverFactory, modifier: Modifier = Modifier) {

when (currentScreen) {
is Screen.Home -> {
if(authState == AuthState.SignedOut) {
if (authState == AuthState.SignedOut) {
navController.navigate(Screen.SignIn)
}

Expand All @@ -93,14 +102,13 @@ fun App(factory: DatabaseDriverFactory, modifier: Modifier = Modifier) {
HomeScreen(
modifier = modifier.background(MaterialTheme.colors.background),
items = items,
isConnected = status.connected,
onSignOutSelected = { handleSignOut() },
inputText = listsInputText,
onItemClicked = handleOnItemClicked,
onItemDeleteClicked = lists.value::onItemDeleteClicked,
onAddItemClicked = lists.value::onAddItemClicked,
onInputTextChanged = lists.value::onInputTextChanged,
hasSynced = hasSyncedLists
syncStatus = status,
)
}

Expand All @@ -113,7 +121,7 @@ fun App(factory: DatabaseDriverFactory, modifier: Modifier = Modifier) {
modifier = modifier.background(MaterialTheme.colors.background),
navController = navController,
items = todoItems,
isConnected = status.connected,
syncStatus = status,
inputText = todosInputText,
onItemClicked = todos.value::onItemClicked,
onItemDoneChanged = todos.value::onItemDoneChanged,
Expand All @@ -133,24 +141,24 @@ fun App(factory: DatabaseDriverFactory, modifier: Modifier = Modifier) {
}

is Screen.SignIn -> {
if(authState == AuthState.SignedIn) {
if (authState == AuthState.SignedIn) {
navController.navigate(Screen.Home)
}

SignInScreen(
navController,
authViewModel
authViewModel,
)
}

is Screen.SignUp -> {
if(authState == AuthState.SignedIn) {
if (authState == AuthState.SignedIn) {
navController.navigate(Screen.Home)
}

SignUpScreen(
navController,
authViewModel
authViewModel,
)
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,29 @@ package com.powersync.demos.components

import androidx.compose.material.Icon
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Wifi
import androidx.compose.material.icons.filled.WifiOff
import androidx.compose.material.icons.filled.Cloud
import androidx.compose.material.icons.filled.CloudOff
import androidx.compose.material.icons.filled.CloudSync
import androidx.compose.material.icons.filled.LeakAdd
import androidx.compose.material.icons.filled.Thunderstorm
import androidx.compose.runtime.Composable
import com.powersync.sync.SyncStatusData

@Composable
fun WifiIcon(isConnected: Boolean) {
val icon = if (isConnected) {
Icons.Filled.Wifi
} else {
Icons.Filled.WifiOff
}
fun WifiIcon(status: SyncStatusData) {
val icon =
when {
status.downloading || status.uploading -> Icons.Filled.CloudSync
status.connected -> Icons.Filled.Cloud
!status.connected -> Icons.Filled.CloudOff
status.connecting -> Icons.Filled.LeakAdd
else -> {
Icons.Filled.Thunderstorm
}
}

Icon(
imageVector = icon,
contentDescription = if (isConnected) "Online" else "Offline",
contentDescription = status.toString(),
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,14 +22,14 @@ import com.powersync.demos.components.ListContent
import com.powersync.demos.components.Menu
import com.powersync.demos.components.WifiIcon
import com.powersync.demos.powersync.ListItem
import com.powersync.sync.SyncStatusData

@Composable
internal fun HomeScreen(
modifier: Modifier = Modifier,
items: List<ListItem>,
inputText: String,
isConnected: Boolean,
hasSynced: Boolean?,
syncStatus: SyncStatusData,
onSignOutSelected: () -> Unit,
onItemClicked: (item: ListItem) -> Unit,
onItemDeleteClicked: (item: ListItem) -> Unit,
Expand All @@ -40,46 +40,49 @@ internal fun HomeScreen(
TopAppBar(
title = {
Text(
"Todo Lists",
textAlign = TextAlign.Center,
modifier = Modifier.fillMaxWidth().padding(end = 36.dp)
) },
navigationIcon = { Menu(
true,
onSignOutSelected
) },
"Todo Lists",
textAlign = TextAlign.Center,
modifier = Modifier.fillMaxWidth().padding(end = 36.dp),
)
},
navigationIcon = {
Menu(
true,
onSignOutSelected,
)
},
actions = {
WifiIcon(isConnected)
WifiIcon(syncStatus)
Spacer(modifier = Modifier.width(16.dp))
}
},
)

when {
hasSynced == null || hasSynced == false -> {
Box(
modifier = Modifier.fillMaxSize().background(MaterialTheme.colors.background),
contentAlignment = Alignment.Center
) {
Text(
text = "Busy with initial sync...",
style = MaterialTheme.typography.h6
)
}
syncStatus.hasSynced == null || syncStatus.hasSynced == false -> {
Box(
modifier = Modifier.fillMaxSize().background(MaterialTheme.colors.background),
contentAlignment = Alignment.Center,
) {
Text(
text = "Busy with initial sync...",
style = MaterialTheme.typography.h6,
)
}
else -> {
}

else -> {
Input(
text = inputText,
onAddClicked = onAddItemClicked,
onTextChanged = onInputTextChanged,
screen = Screen.Home
screen = Screen.Home,
)

Box(Modifier.weight(1F)) {
ListContent(
items = items,
onItemClicked = onItemClicked,
onItemDeleteClicked = onItemDeleteClicked
onItemDeleteClicked = onItemDeleteClicked,
)
}
}
Expand Down
Loading
Loading