From aa2542a0aca60491fb9d42bf4950ae33dcf79ff5 Mon Sep 17 00:00:00 2001 From: gabriel20xx Date: Sat, 8 Nov 2025 21:24:38 +0100 Subject: [PATCH] feat: update dependencies and enhance sync logic for improved performance and reliability --- CollabTableAndroid/app/build.gradle | 19 +-- .../collabtable/app/CollabTableApplication.kt | 15 ++ .../app/data/repository/SyncRepository.kt | 33 +++- .../app/ui/screens/ListDetailScreen.kt | 147 ++++++++++-------- .../app/ui/screens/ListDetailViewModel.kt | 2 + .../collabtable/app/ui/screens/LogsScreen.kt | 12 +- .../app/ui/screens/SettingsScreen.kt | 30 ++-- CollabTableAndroid/build.gradle | 9 +- .../gradle/wrapper/gradle-wrapper.properties | 2 +- 9 files changed, 164 insertions(+), 105 deletions(-) diff --git a/CollabTableAndroid/app/build.gradle b/CollabTableAndroid/app/build.gradle index 30b9cce..e8d3108 100644 --- a/CollabTableAndroid/app/build.gradle +++ b/CollabTableAndroid/app/build.gradle @@ -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' diff --git a/CollabTableAndroid/app/src/main/java/com/collabtable/app/CollabTableApplication.kt b/CollabTableAndroid/app/src/main/java/com/collabtable/app/CollabTableApplication.kt index 59dea6a..9795621 100644 --- a/CollabTableAndroid/app/src/main/java/com/collabtable/app/CollabTableApplication.kt +++ b/CollabTableAndroid/app/src/main/java/com/collabtable/app/CollabTableApplication.kt @@ -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. + } + } } } diff --git a/CollabTableAndroid/app/src/main/java/com/collabtable/app/data/repository/SyncRepository.kt b/CollabTableAndroid/app/src/main/java/com/collabtable/app/data/repository/SyncRepository.kt index 3cc120a..0a090d5 100644 --- a/CollabTableAndroid/app/src/main/java/com/collabtable/app/data/repository/SyncRepository.kt +++ b/CollabTableAndroid/app/src/main/java/com/collabtable/app/data/repository/SyncRepository.kt @@ -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 = + suspend fun performSync(forceFullDown: Boolean = false): Result = 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 + } } diff --git a/CollabTableAndroid/app/src/main/java/com/collabtable/app/ui/screens/ListDetailScreen.kt b/CollabTableAndroid/app/src/main/java/com/collabtable/app/ui/screens/ListDetailScreen.kt index 00196c1..c2dd30e 100644 --- a/CollabTableAndroid/app/src/main/java/com/collabtable/app/ui/screens/ListDetailScreen.kt +++ b/CollabTableAndroid/app/src/main/java/com/collabtable/app/ui/screens/ListDetailScreen.kt @@ -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(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(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, diff --git a/CollabTableAndroid/app/src/main/java/com/collabtable/app/ui/screens/ListDetailViewModel.kt b/CollabTableAndroid/app/src/main/java/com/collabtable/app/ui/screens/ListDetailViewModel.kt index a5286dc..bc2f2e6 100644 --- a/CollabTableAndroid/app/src/main/java/com/collabtable/app/ui/screens/ListDetailViewModel.kt +++ b/CollabTableAndroid/app/src/main/java/com/collabtable/app/ui/screens/ListDetailViewModel.kt @@ -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, diff --git a/CollabTableAndroid/app/src/main/java/com/collabtable/app/ui/screens/LogsScreen.kt b/CollabTableAndroid/app/src/main/java/com/collabtable/app/ui/screens/LogsScreen.kt index cf1e48c..5793e73 100644 --- a/CollabTableAndroid/app/src/main/java/com/collabtable/app/ui/screens/LogsScreen.kt +++ b/CollabTableAndroid/app/src/main/java/com/collabtable/app/ui/screens/LogsScreen.kt @@ -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( diff --git a/CollabTableAndroid/app/src/main/java/com/collabtable/app/ui/screens/SettingsScreen.kt b/CollabTableAndroid/app/src/main/java/com/collabtable/app/ui/screens/SettingsScreen.kt index 6d0ff7d..2ac96f1 100644 --- a/CollabTableAndroid/app/src/main/java/com/collabtable/app/ui/screens/SettingsScreen.kt +++ b/CollabTableAndroid/app/src/main/java/com/collabtable/app/ui/screens/SettingsScreen.kt @@ -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") } diff --git a/CollabTableAndroid/build.gradle b/CollabTableAndroid/build.gradle index c4673cb..463282b 100644 --- a/CollabTableAndroid/build.gradle +++ b/CollabTableAndroid/build.gradle @@ -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 } diff --git a/CollabTableAndroid/gradle/wrapper/gradle-wrapper.properties b/CollabTableAndroid/gradle/wrapper/gradle-wrapper.properties index eb9ff24..a3396e3 100644 --- a/CollabTableAndroid/gradle/wrapper/gradle-wrapper.properties +++ b/CollabTableAndroid/gradle/wrapper/gradle-wrapper.properties @@ -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