feat: update dependencies and enhance sync logic for improved performance and reliability
This commit is contained in:
@@ -1,25 +1,26 @@
|
||||
plugins {
|
||||
id 'com.android.application'
|
||||
id 'org.jetbrains.kotlin.android'
|
||||
id 'org.jetbrains.kotlin.plugin.compose'
|
||||
id 'com.google.devtools.ksp'
|
||||
id 'org.jlleitschuh.gradle.ktlint'
|
||||
id 'io.gitlab.arturbosch.detekt'
|
||||
}
|
||||
|
||||
android {
|
||||
namespace 'com.collabtable.app'
|
||||
compileSdk 34
|
||||
namespace = 'com.collabtable.app'
|
||||
compileSdk = 34
|
||||
|
||||
defaultConfig {
|
||||
applicationId "com.collabtable.app"
|
||||
minSdk 26
|
||||
targetSdk 34
|
||||
applicationId = "com.collabtable.app"
|
||||
minSdk = 26
|
||||
targetSdk = 34
|
||||
versionCode 1
|
||||
versionName "1.0"
|
||||
|
||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||
vectorDrawables {
|
||||
useSupportLibrary true
|
||||
useSupportLibrary = true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -37,10 +38,10 @@ android {
|
||||
jvmTarget = '21'
|
||||
}
|
||||
buildFeatures {
|
||||
compose true
|
||||
compose = true
|
||||
}
|
||||
composeOptions {
|
||||
kotlinCompilerExtensionVersion '1.5.4'
|
||||
kotlinCompilerExtensionVersion = '1.6.8'
|
||||
}
|
||||
packaging {
|
||||
resources {
|
||||
@@ -56,7 +57,7 @@ dependencies {
|
||||
implementation 'androidx.activity:activity-compose:1.8.1'
|
||||
|
||||
// Compose
|
||||
implementation platform('androidx.compose:compose-bom:2023.10.01')
|
||||
implementation platform('androidx.compose:compose-bom:2024.06.00')
|
||||
implementation 'androidx.compose.ui:ui'
|
||||
implementation 'androidx.compose.ui:ui-graphics'
|
||||
implementation 'androidx.compose.ui:ui-tooling-preview'
|
||||
|
||||
@@ -2,11 +2,26 @@ package com.collabtable.app
|
||||
|
||||
import android.app.Application
|
||||
import com.collabtable.app.data.api.ApiClient
|
||||
import com.collabtable.app.data.repository.SyncRepository
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.SupervisorJob
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
class CollabTableApplication : Application() {
|
||||
override fun onCreate() {
|
||||
super.onCreate()
|
||||
// Initialize API client with saved server URL
|
||||
ApiClient.initialize(this)
|
||||
// Kick off a one-time full down-sync on cold app start if local DB is empty but server has data.
|
||||
// This complements in-screen sync logic and ensures earliest possible hydration.
|
||||
CoroutineScope(SupervisorJob() + Dispatchers.IO).launch {
|
||||
try {
|
||||
// Force a full down-sync on app start when server credentials are present.
|
||||
SyncRepository(this@CollabTableApplication).performSync(forceFullDown = true)
|
||||
} catch (_: Exception) {
|
||||
// Silent; UI layers will surface subsequent sync issues.
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+30
-3
@@ -1,6 +1,8 @@
|
||||
package com.collabtable.app.data.repository
|
||||
|
||||
import android.content.Context
|
||||
import android.net.ConnectivityManager
|
||||
import android.net.NetworkCapabilities
|
||||
import androidx.room.withTransaction
|
||||
import com.collabtable.app.data.api.ApiClient
|
||||
import com.collabtable.app.data.api.SyncRequest
|
||||
@@ -63,11 +65,17 @@ class SyncRepository(context: Context) {
|
||||
prefs.edit().putLong("last_local_sync_ts", ts).apply()
|
||||
}
|
||||
|
||||
suspend fun performSync(): Result<Unit> =
|
||||
suspend fun performSync(forceFullDown: Boolean = false): Result<Unit> =
|
||||
withContext(Dispatchers.IO) {
|
||||
// Prevent overlapping syncs across screens/viewmodels
|
||||
syncMutex.withLock {
|
||||
try {
|
||||
// Only attempt network sync when app is in foreground to avoid background DNS issues
|
||||
// Foreground check removed (lifecycle owner not available in this module); rely solely on network availability.
|
||||
// Skip sync when there's no validated network available
|
||||
if (!isNetworkAvailable()) {
|
||||
return@withContext Result.success(Unit)
|
||||
}
|
||||
// If not authenticated, skip making network calls (e.g., after leaving server)
|
||||
val prefMgr = PreferencesManager.getInstance(appContext)
|
||||
val currentPassword = prefMgr.getServerPassword()?.trim()
|
||||
@@ -100,9 +108,16 @@ class SyncRepository(context: Context) {
|
||||
// lastSyncTimestamp to 0 for this request. This avoids permanently missing historical rows.
|
||||
val hasAnyLists = database.listDao().getAllListIds().isNotEmpty()
|
||||
val hasAnyItems = database.itemDao().getAllItemIds().isNotEmpty()
|
||||
val effectiveLastServerTs = if (lastServerTs > 0 && (!hasAnyLists || !hasAnyItems)) 0L else lastServerTs
|
||||
val effectiveLastServerTs = when {
|
||||
// App start can request a full down-sync regardless of local contents
|
||||
forceFullDown && lastServerTs > 0L -> 0L
|
||||
// Fallback: if local looks empty but we have a watermark, do a one-time full down-sync
|
||||
lastServerTs > 0L && (!hasAnyLists || !hasAnyItems) -> 0L
|
||||
else -> lastServerTs
|
||||
}
|
||||
if (effectiveLastServerTs == 0L && lastServerTs > 0L) {
|
||||
Logger.w("Sync", "Local store empty but watermark set; performing one-time full down-sync")
|
||||
val reason = if (forceFullDown) "startup forced" else "local empty"
|
||||
Logger.w("Sync", "$reason full down-sync requested")
|
||||
}
|
||||
|
||||
// Gather local changes since last sync
|
||||
@@ -273,4 +288,16 @@ class SyncRepository(context: Context) {
|
||||
companion object {
|
||||
private val syncMutex = Mutex()
|
||||
}
|
||||
|
||||
private fun isNetworkAvailable(): Boolean {
|
||||
val cm = appContext.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
|
||||
val network = cm.activeNetwork ?: return false
|
||||
val caps = cm.getNetworkCapabilities(network) ?: return false
|
||||
val hasInternet = caps.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
|
||||
val isValidated = caps.hasCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED)
|
||||
val hasTransport = caps.hasTransport(NetworkCapabilities.TRANSPORT_WIFI) ||
|
||||
caps.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR) ||
|
||||
caps.hasTransport(NetworkCapabilities.TRANSPORT_ETHERNET)
|
||||
return hasInternet && isValidated && hasTransport
|
||||
}
|
||||
}
|
||||
|
||||
+82
-65
@@ -66,11 +66,13 @@ import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.layout.onGloballyPositioned
|
||||
import androidx.compose.ui.input.pointer.pointerInput
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.platform.LocalLayoutDirection
|
||||
import androidx.compose.ui.platform.LocalDensity
|
||||
import androidx.compose.ui.platform.LocalUriHandler
|
||||
import androidx.compose.ui.zIndex
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.SpanStyle
|
||||
import androidx.compose.ui.text.AnnotatedString
|
||||
@@ -124,15 +126,15 @@ fun ListDetailScreen(
|
||||
// Use derivedStateOf to create stable references
|
||||
val stableFields by remember { derivedStateOf { fields } }
|
||||
val stableItems by remember { derivedStateOf { items } }
|
||||
|
||||
var showManageColumnsDialog by remember { mutableStateOf(false) }
|
||||
var showAddItemDialog by remember { mutableStateOf(false) }
|
||||
var pendingScrollToBottom by remember { mutableStateOf(false) }
|
||||
var itemToEdit by remember { mutableStateOf<ItemWithValues?>(null) }
|
||||
var showSortDialog by remember { mutableStateOf(false) }
|
||||
var showGroupDialog by remember { mutableStateOf(false) }
|
||||
var showFilterDialog by remember { mutableStateOf(false) }
|
||||
var showRenameListDialog by remember { mutableStateOf(false) }
|
||||
var showManageColumnsDialog by remember { mutableStateOf(false) }
|
||||
var showAddItemDialog by remember { mutableStateOf(false) }
|
||||
// Flag to trigger scroll to bottom after adding an item
|
||||
var pendingScrollToBottom by remember { mutableStateOf(false) }
|
||||
|
||||
// Filter/Sort state
|
||||
var sortField by remember { mutableStateOf<Field?>(null) }
|
||||
@@ -162,7 +164,7 @@ fun ListDetailScreen(
|
||||
// Scaffold with top bar and FAB
|
||||
Scaffold(
|
||||
topBar = {
|
||||
SmallTopAppBar(
|
||||
TopAppBar(
|
||||
title = {
|
||||
Row(
|
||||
modifier = Modifier.clickable { showRenameListDialog = true },
|
||||
@@ -183,9 +185,9 @@ fun ListDetailScreen(
|
||||
}
|
||||
},
|
||||
actions = {
|
||||
val context = LocalContext.current
|
||||
val prefs = remember { PreferencesManager.getInstance(context) }
|
||||
ConnectionStatusAction(prefs = prefs)
|
||||
val localCtx = LocalContext.current
|
||||
val localPrefs = remember { PreferencesManager.getInstance(localCtx) }
|
||||
ConnectionStatusAction(prefs = localPrefs)
|
||||
IconButton(onClick = { showManageColumnsDialog = true }) {
|
||||
Icon(Icons.Default.Settings, contentDescription = "Manage Columns")
|
||||
}
|
||||
@@ -450,36 +452,7 @@ fun ListDetailScreen(
|
||||
)
|
||||
}
|
||||
|
||||
// Field headers with long-press to delete and resize handles
|
||||
Row(
|
||||
modifier =
|
||||
Modifier
|
||||
.fillMaxWidth()
|
||||
.horizontalScroll(horizontalScrollState),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
stableFields.forEach { field ->
|
||||
key(field.id) {
|
||||
FieldHeader(
|
||||
field = field,
|
||||
width = fieldWidths[field.id] ?: 150.dp,
|
||||
onWidthChange = { delta ->
|
||||
val currentWidth = fieldWidths[field.id] ?: 150.dp
|
||||
// Enforce only a minimum width; remove the previous max (400dp)
|
||||
val newWidth = (currentWidth.value + delta).coerceAtLeast(100f)
|
||||
fieldWidths[field.id] = newWidth.dp
|
||||
// Persist updated widths for this listId
|
||||
prefs.setColumnWidths(
|
||||
listId,
|
||||
fieldWidths.mapValues { it.value.value },
|
||||
)
|
||||
},
|
||||
scrollState = horizontalScrollState,
|
||||
isLast = (field.id == stableFields.lastOrNull()?.id),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
// Header will be rendered as a stickyHeader inside the LazyColumn below
|
||||
|
||||
// Items list with synchronized scrolling
|
||||
if (stableItems.isEmpty()) {
|
||||
@@ -528,11 +501,21 @@ fun ListDetailScreen(
|
||||
pendingScrollToBottom = false
|
||||
}
|
||||
}
|
||||
LazyColumn(
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
state = listState,
|
||||
) {
|
||||
groupedItems.entries.forEach { (groupName, groupItems) ->
|
||||
|
||||
// Measure header height so we can allow content to scroll "behind" it
|
||||
// but still make the last item fully visible by providing extra bottom padding.
|
||||
var headerHeightPx by remember { mutableStateOf(0) }
|
||||
|
||||
Box(modifier = Modifier.fillMaxSize()) {
|
||||
LazyColumn(
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
state = listState,
|
||||
contentPadding = PaddingValues(
|
||||
top = 0.dp,
|
||||
bottom = with(density) { headerHeightPx.toDp() },
|
||||
),
|
||||
) {
|
||||
groupedItems.entries.forEach { (groupName, groupItems) ->
|
||||
// Show group header if grouping is enabled
|
||||
if (groupByField != null) {
|
||||
item(key = "group_$groupName") {
|
||||
@@ -565,26 +548,61 @@ fun ListDetailScreen(
|
||||
}
|
||||
}
|
||||
|
||||
// Filler area that occupies remaining viewport height and supports the same
|
||||
// horizontal scroll physics (drag + fling) as the header/rows.
|
||||
item(key = "hscroll_filler") {
|
||||
val fillerScrollableState = rememberScrollableState { delta ->
|
||||
// Mirror Row.horizontalScroll: rely on reverseDirection for LTR/RTL,
|
||||
// and dispatch deltas directly to the shared ScrollState.
|
||||
horizontalScrollState.dispatchRawDelta(delta)
|
||||
// Filler area that occupies remaining viewport height and supports the same
|
||||
// horizontal scroll physics (drag + fling) as the header/rows.
|
||||
item(key = "hscroll_filler") {
|
||||
val fillerScrollableState = rememberScrollableState { delta ->
|
||||
// Mirror Row.horizontalScroll: rely on reverseDirection for LTR/RTL,
|
||||
// and dispatch deltas directly to the shared ScrollState.
|
||||
horizontalScrollState.dispatchRawDelta(delta)
|
||||
}
|
||||
Box(
|
||||
modifier =
|
||||
Modifier
|
||||
.fillMaxWidth()
|
||||
.fillParentMaxHeight()
|
||||
.scrollable(
|
||||
state = fillerScrollableState,
|
||||
orientation = Orientation.Horizontal,
|
||||
reverseDirection = LocalLayoutDirection.current == androidx.compose.ui.unit.LayoutDirection.Ltr,
|
||||
flingBehavior = androidx.compose.foundation.gestures.ScrollableDefaults.flingBehavior(),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// Overlaid header: content scrolls behind this until the end,
|
||||
// thanks to the extra bottom padding applied above.
|
||||
Row(
|
||||
modifier =
|
||||
Modifier
|
||||
.fillMaxWidth()
|
||||
.onGloballyPositioned { coordinates -> headerHeightPx = coordinates.size.height }
|
||||
.background(MaterialTheme.colorScheme.surface)
|
||||
.horizontalScroll(horizontalScrollState)
|
||||
.align(Alignment.TopStart)
|
||||
.zIndex(1f),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
stableFields.forEach { field ->
|
||||
key(field.id) {
|
||||
FieldHeader(
|
||||
field = field,
|
||||
width = fieldWidths[field.id] ?: 150.dp,
|
||||
onWidthChange = { delta ->
|
||||
val currentWidth = fieldWidths[field.id] ?: 150.dp
|
||||
val newWidth = (currentWidth.value + delta).coerceAtLeast(100f)
|
||||
fieldWidths[field.id] = newWidth.dp
|
||||
prefs.setColumnWidths(
|
||||
listId,
|
||||
fieldWidths.mapValues { it.value.value },
|
||||
)
|
||||
},
|
||||
scrollState = horizontalScrollState,
|
||||
isLast = (field.id == stableFields.lastOrNull()?.id),
|
||||
)
|
||||
}
|
||||
}
|
||||
Box(
|
||||
modifier =
|
||||
Modifier
|
||||
.fillMaxWidth()
|
||||
.fillParentMaxHeight()
|
||||
.scrollable(
|
||||
state = fillerScrollableState,
|
||||
orientation = Orientation.Horizontal,
|
||||
reverseDirection = LocalLayoutDirection.current == androidx.compose.ui.unit.LayoutDirection.Ltr,
|
||||
flingBehavior = androidx.compose.foundation.gestures.ScrollableDefaults.flingBehavior(),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1678,7 +1696,7 @@ fun EditFieldDialog(
|
||||
onUpdate: (String, String, String) -> Unit,
|
||||
) {
|
||||
var name by remember { mutableStateOf(field.name) }
|
||||
var selectedFieldType by remember { mutableStateOf(field.fieldType?.uppercase() ?: "TEXT") }
|
||||
var selectedFieldType by remember { mutableStateOf(field.fieldType.uppercase()) }
|
||||
var dropdownOptions by remember { mutableStateOf(field.getDropdownOptions().joinToString(", ")) }
|
||||
var currency by remember { mutableStateOf(field.getCurrency()) }
|
||||
var expanded by remember { mutableStateOf(false) }
|
||||
@@ -2478,8 +2496,6 @@ fun FieldInput(
|
||||
val options = field.getAutocompleteOptions()
|
||||
if (options.isNotEmpty()) {
|
||||
Text("Suggestions: ${options.take(3).joinToString(", ")}")
|
||||
} else {
|
||||
null
|
||||
}
|
||||
},
|
||||
)
|
||||
@@ -2853,6 +2869,7 @@ fun ManageColumnsDialog(
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("UNUSED_PARAMETER")
|
||||
@Composable
|
||||
fun ColumnItem(
|
||||
field: Field,
|
||||
|
||||
+2
@@ -11,6 +11,7 @@ import com.collabtable.app.data.model.Item
|
||||
import com.collabtable.app.data.model.ItemValue
|
||||
import com.collabtable.app.data.model.ItemWithValues
|
||||
import com.collabtable.app.data.repository.SyncRepository
|
||||
import kotlinx.coroutines.FlowPreview
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.asStateFlow
|
||||
@@ -18,6 +19,7 @@ import kotlinx.coroutines.flow.debounce
|
||||
import kotlinx.coroutines.launch
|
||||
import java.util.UUID
|
||||
|
||||
@OptIn(FlowPreview::class)
|
||||
class ListDetailViewModel(
|
||||
private val database: CollabTableDatabase,
|
||||
private val listId: String,
|
||||
|
||||
@@ -17,13 +17,13 @@ import androidx.compose.foundation.lazy.rememberLazyListState
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.ArrowBack
|
||||
import androidx.compose.material.icons.automirrored.filled.ArrowBack
|
||||
import androidx.compose.material3.HorizontalDivider
|
||||
import androidx.compose.material.icons.filled.Delete
|
||||
import androidx.compose.material.icons.filled.FilterList
|
||||
import androidx.compose.material3.Badge
|
||||
import androidx.compose.material3.Button
|
||||
import androidx.compose.material3.Checkbox
|
||||
import androidx.compose.material3.Divider
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.FilterChip
|
||||
import androidx.compose.material3.Icon
|
||||
@@ -122,7 +122,7 @@ fun LogsScreen(onNavigateBack: () -> Unit) {
|
||||
title = { Text("Logs (${filteredLogs.size}/${logs.size})") },
|
||||
navigationIcon = {
|
||||
IconButton(onClick = onNavigateBack) {
|
||||
Icon(Icons.Filled.ArrowBack, contentDescription = "Back")
|
||||
Icon(Icons.AutoMirrored.Filled.ArrowBack, contentDescription = "Back")
|
||||
}
|
||||
},
|
||||
actions = {
|
||||
@@ -340,7 +340,7 @@ fun FilterBottomSheet(
|
||||
}
|
||||
}
|
||||
|
||||
Divider(modifier = Modifier.padding(vertical = 16.dp))
|
||||
HorizontalDivider(modifier = Modifier.padding(vertical = 16.dp))
|
||||
|
||||
// Time range filter
|
||||
Text(
|
||||
@@ -371,7 +371,7 @@ fun FilterBottomSheet(
|
||||
}
|
||||
}
|
||||
|
||||
Divider(modifier = Modifier.padding(vertical = 16.dp))
|
||||
HorizontalDivider(modifier = Modifier.padding(vertical = 16.dp))
|
||||
|
||||
// Tag filters
|
||||
Text(
|
||||
@@ -443,7 +443,7 @@ fun FilterBottomSheet(
|
||||
}
|
||||
}
|
||||
|
||||
Divider(modifier = Modifier.padding(vertical = 16.dp))
|
||||
HorizontalDivider(modifier = Modifier.padding(vertical = 16.dp))
|
||||
|
||||
// Reset button
|
||||
Button(
|
||||
|
||||
+13
-17
@@ -6,16 +6,16 @@ import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.ArrowBack
|
||||
import androidx.compose.material.icons.filled.DarkMode
|
||||
import androidx.compose.material.icons.filled.LightMode
|
||||
import androidx.compose.material.icons.filled.List
|
||||
import androidx.compose.material.icons.automirrored.filled.ArrowBack
|
||||
import androidx.compose.material.icons.automirrored.filled.List
|
||||
import androidx.compose.material.icons.filled.Brightness4
|
||||
import androidx.compose.material.icons.filled.Brightness7
|
||||
import androidx.compose.material.icons.filled.SettingsBrightness
|
||||
import androidx.compose.material3.AlertDialog
|
||||
import androidx.compose.material3.Button
|
||||
@@ -30,9 +30,9 @@ import androidx.compose.material3.IconButton
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Scaffold
|
||||
import androidx.compose.material3.Switch
|
||||
import androidx.compose.material3.TextField
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TextButton
|
||||
import androidx.compose.material3.TextField
|
||||
import androidx.compose.material3.TopAppBar
|
||||
import androidx.compose.material3.TopAppBarDefaults
|
||||
import androidx.compose.runtime.Composable
|
||||
@@ -42,21 +42,17 @@ import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.runtime.setValue
|
||||
import com.collabtable.app.ui.components.ConnectionStatusAction
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.text.input.PasswordVisualTransformation
|
||||
import com.collabtable.app.R
|
||||
import com.collabtable.app.data.api.ApiClient
|
||||
import com.collabtable.app.data.database.CollabTableDatabase
|
||||
import com.collabtable.app.data.preferences.PreferencesManager
|
||||
import com.collabtable.app.data.repository.SyncRepository
|
||||
import com.collabtable.app.ui.components.ConnectionStatusAction
|
||||
import com.collabtable.app.utils.Logger
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
@@ -95,7 +91,7 @@ fun SettingsScreen(
|
||||
title = { Text(stringResource(R.string.settings)) },
|
||||
navigationIcon = {
|
||||
IconButton(onClick = onNavigateBack) {
|
||||
Icon(Icons.Default.ArrowBack, contentDescription = "Back")
|
||||
Icon(Icons.AutoMirrored.Filled.ArrowBack, contentDescription = "Back")
|
||||
}
|
||||
},
|
||||
actions = {
|
||||
@@ -165,19 +161,19 @@ fun SettingsScreen(
|
||||
selected = themeMode == PreferencesManager.THEME_MODE_SYSTEM,
|
||||
onClick = { preferencesManager.setThemeMode(PreferencesManager.THEME_MODE_SYSTEM) },
|
||||
label = { Text("System") },
|
||||
leadingIcon = { Icon(Icons.Default.SettingsBrightness, contentDescription = null) },
|
||||
leadingIcon = { Icon(Icons.Filled.SettingsBrightness, contentDescription = null) },
|
||||
)
|
||||
FilterChip(
|
||||
selected = themeMode == PreferencesManager.THEME_MODE_LIGHT,
|
||||
onClick = { preferencesManager.setThemeMode(PreferencesManager.THEME_MODE_LIGHT) },
|
||||
label = { Text("Light") },
|
||||
leadingIcon = { Icon(Icons.Default.LightMode, contentDescription = null) },
|
||||
leadingIcon = { Icon(Icons.Filled.Brightness7, contentDescription = null) },
|
||||
)
|
||||
FilterChip(
|
||||
selected = themeMode == PreferencesManager.THEME_MODE_DARK,
|
||||
onClick = { preferencesManager.setThemeMode(PreferencesManager.THEME_MODE_DARK) },
|
||||
label = { Text("Dark") },
|
||||
leadingIcon = { Icon(Icons.Default.DarkMode, contentDescription = null) },
|
||||
leadingIcon = { Icon(Icons.Filled.Brightness4, contentDescription = null) },
|
||||
)
|
||||
}
|
||||
|
||||
@@ -300,7 +296,7 @@ fun SettingsScreen(
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
)
|
||||
Button(onClick = onNavigateToLogs, modifier = Modifier.fillMaxWidth()) {
|
||||
Icon(Icons.Default.List, contentDescription = null)
|
||||
Icon(Icons.AutoMirrored.Filled.List, contentDescription = null)
|
||||
Spacer(modifier = Modifier.width(8.dp))
|
||||
Text("Open Logs")
|
||||
}
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
// Top-level build file where you can add configuration options common to all sub-projects/modules.
|
||||
plugins {
|
||||
id 'com.android.application' version '8.3.0' apply false
|
||||
id 'com.android.library' version '8.3.0' apply false
|
||||
id 'org.jetbrains.kotlin.android' version '1.9.20' apply false
|
||||
id 'com.google.devtools.ksp' version '1.9.20-1.0.14' apply false
|
||||
id 'com.android.application' version '8.8.0' apply false
|
||||
id 'com.android.library' version '8.8.0' apply false
|
||||
id 'org.jetbrains.kotlin.android' version '2.0.21' apply false
|
||||
id 'org.jetbrains.kotlin.plugin.compose' version '2.0.21' apply false
|
||||
id 'com.google.devtools.ksp' version '2.0.21-1.0.27' apply false
|
||||
id 'org.jlleitschuh.gradle.ktlint' version '12.1.0' apply false
|
||||
id 'io.gitlab.arturbosch.detekt' version '1.23.6' apply false
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.10-bin.zip
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-9.2.0-bin.zip
|
||||
networkTimeout=10000
|
||||
validateDistributionUrl=true
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
|
||||
Reference in New Issue
Block a user