From a9900e059c73cb69ff5bfe62bb939b1b27129d80 Mon Sep 17 00:00:00 2001 From: CodePandaaAI Date: Sat, 19 Sep 2026 16:03:34 +0530 Subject: [PATCH 1/3] refactor(ui): modernize file receive code input and simplify file selection MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replace the custom digit-by-digit `BasicTextField` implementation in `FileReceiveCodeSheet` with a styled Material 3 `TextField` to improve maintainability and input behavior. Key changes to the receive code sheet: * Use `displayLarge` typography with bold weight and tabular numerals (`tnum`) to ensure layout stability while typing. * Add a "0000" placeholder and support `ImeAction.Done` to trigger confirmation directly from the keyboard. * Style the input with `surfaceContainerHigh` background, `extraExtraLarge` corner shapes, and transparent indicators. * Simplify the layout by removing the manual `ReceiveCodeDigit` border animations and box logic. Key changes to file selection: * Replace custom `Sync360Surface` containers with standard Material 3 `Button` and `OutlinedButton` components. * Streamline the UI by removing secondary labels (e.g., "Photos · Videos", "Pdf · Docs · Excel") in favor of a cleaner, primary action-focused design. Also includes a minor cleanup in `NetworkServices` to remove a redundant documentation comment. --- .../sync360/domain/service/NetworkServices.kt | 1 - .../send/components/FileReceiveCodeSheet.kt | 137 +++++++----------- .../send/components/FileSelectionContent.kt | 69 +++------ 3 files changed, 71 insertions(+), 136 deletions(-) diff --git a/shared/src/commonMain/kotlin/com/liftley/sync360/domain/service/NetworkServices.kt b/shared/src/commonMain/kotlin/com/liftley/sync360/domain/service/NetworkServices.kt index 2581324..ac1bf00 100644 --- a/shared/src/commonMain/kotlin/com/liftley/sync360/domain/service/NetworkServices.kt +++ b/shared/src/commonMain/kotlin/com/liftley/sync360/domain/service/NetworkServices.kt @@ -12,6 +12,5 @@ interface NetworkServices { suspend fun startDiscoveryAndAdvertising(httpServerPort: Int, fileTransferPort: Int) - /** Stops discovery and advertising, not transfer listeners. */ suspend fun stopDiscoveryAndAdvertising() } diff --git a/shared/src/commonMain/kotlin/com/liftley/sync360/presentation/send/components/FileReceiveCodeSheet.kt b/shared/src/commonMain/kotlin/com/liftley/sync360/presentation/send/components/FileReceiveCodeSheet.kt index 356cfe8..0384577 100644 --- a/shared/src/commonMain/kotlin/com/liftley/sync360/presentation/send/components/FileReceiveCodeSheet.kt +++ b/shared/src/commonMain/kotlin/com/liftley/sync360/presentation/send/components/FileReceiveCodeSheet.kt @@ -1,51 +1,50 @@ package com.liftley.sync360.presentation.send.components -import androidx.compose.animation.core.animateDpAsState -import androidx.compose.foundation.border import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.heightIn import androidx.compose.foundation.layout.imePadding import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.widthIn -import androidx.compose.foundation.shape.RoundedCornerShape -import androidx.compose.foundation.text.BasicTextField +import androidx.compose.foundation.text.KeyboardActions import androidx.compose.foundation.text.KeyboardOptions import androidx.compose.material3.Button import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi import androidx.compose.material3.Icon import androidx.compose.material3.IconButton import androidx.compose.material3.IconButtonDefaults import androidx.compose.material3.MaterialTheme import androidx.compose.material3.ModalBottomSheet import androidx.compose.material3.Text +import androidx.compose.material3.TextField +import androidx.compose.material3.TextFieldDefaults import androidx.compose.material3.rememberModalBottomSheetState import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect -import androidx.compose.runtime.getValue import androidx.compose.runtime.remember import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.draw.alpha import androidx.compose.ui.focus.FocusRequester import androidx.compose.ui.focus.focusRequester -import androidx.compose.ui.graphics.SolidColor +import androidx.compose.ui.graphics.Color import androidx.compose.ui.platform.LocalSoftwareKeyboardController -import androidx.compose.ui.semantics.clearAndSetSemantics import androidx.compose.ui.semantics.contentDescription import androidx.compose.ui.semantics.semantics +import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.input.ImeAction import androidx.compose.ui.text.input.KeyboardType +import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp import com.liftley.sync360.core.designsystem.icons.Close import com.liftley.sync360.domain.model.FileReceiveCode import com.liftley.sync360.presentation.send.model.FileReceiveCodePrompt -@OptIn(ExperimentalMaterial3Api::class) +@OptIn(ExperimentalMaterial3Api::class, ExperimentalMaterial3ExpressiveApi::class) @Composable fun FileReceiveCodeSheet( prompt: FileReceiveCodePrompt, @@ -58,6 +57,21 @@ fun FileReceiveCodeSheet( val sheetState = rememberModalBottomSheetState(skipPartiallyExpanded = true) val isCodeValid = FileReceiveCode.isValid(prompt.code) + // Big, bold, widely tracked digits. Tabular numerals keep the width + // stable so the caret doesn't jitter as you type. + val codeStyle = MaterialTheme.typography.displayLarge.copy( + fontWeight = FontWeight.Bold, + textAlign = TextAlign.Center, + fontFeatureSettings = "tnum" + ) + + val submit = { + if (isCodeValid) { + keyboardController?.hide() + onConfirm() + } + } + LaunchedEffect(Unit) { focusRequester.requestFocus() keyboardController?.show() @@ -78,8 +92,6 @@ fun FileReceiveCodeSheet( horizontalAlignment = Alignment.CenterHorizontally, verticalArrangement = Arrangement.spacedBy(20.dp) ) { - // Header: title + close button in one row instead of a separate - // "Cancel" link buried under the keyboard. Row( modifier = Modifier.fillMaxWidth(), verticalAlignment = Alignment.CenterVertically @@ -100,7 +112,8 @@ fun FileReceiveCodeSheet( ) } IconButton( - onClick = onDismiss, colors = IconButtonDefaults.iconButtonColors( + onClick = onDismiss, + colors = IconButtonDefaults.iconButtonColors( MaterialTheme.colorScheme.surfaceContainer ) ) { @@ -111,58 +124,45 @@ fun FileReceiveCodeSheet( } } - BasicTextField( + TextField( value = prompt.code, onValueChange = { raw -> - val digitsOnly = raw.filter { it.isDigit() } - .take(FileReceiveCode.DIGIT_COUNT) - onCodeChange(digitsOnly) + onCodeChange( + raw.filter { it.isDigit() }.take(FileReceiveCode.DIGIT_COUNT) + ) }, modifier = Modifier .fillMaxWidth() + .heightIn(min = 104.dp) .focusRequester(focusRequester) - .semantics { - contentDescription = "Four-digit transfer code" - }, + .semantics { contentDescription = "Four-digit transfer code" }, singleLine = true, + textStyle = codeStyle, + placeholder = { + Text( + text = "0".repeat(FileReceiveCode.DIGIT_COUNT), + modifier = Modifier.fillMaxWidth(), + style = codeStyle, + color = MaterialTheme.colorScheme.outlineVariant + ) + }, keyboardOptions = KeyboardOptions( keyboardType = KeyboardType.Number, imeAction = ImeAction.Done ), - cursorBrush = SolidColor(MaterialTheme.colorScheme.primary), - decorationBox = { innerTextField -> - Box { - Row( - modifier = Modifier - .fillMaxWidth() - .clearAndSetSemantics { }, - horizontalArrangement = Arrangement.spacedBy(12.dp) - ) { - repeat(FileReceiveCode.DIGIT_COUNT) { index -> - ReceiveCodeDigit( - digit = prompt.code.getOrNull(index), - isActive = index == prompt.code.length, - modifier = Modifier.weight(1f) - ) - } - } - - Box( - modifier = Modifier - .matchParentSize() - .alpha(0f) - ) { - innerTextField() - } - } - } + keyboardActions = KeyboardActions(onDone = { submit() }), + shape = MaterialTheme.shapes.extraExtraLarge, + colors = TextFieldDefaults.colors( + focusedContainerColor = MaterialTheme.colorScheme.surfaceContainerHigh, + unfocusedContainerColor = MaterialTheme.colorScheme.surfaceContainerHigh, + focusedIndicatorColor = Color.Transparent, + unfocusedIndicatorColor = Color.Transparent, + cursorColor = MaterialTheme.colorScheme.primary + ) ) Button( - onClick = { - keyboardController?.hide() - onConfirm() - }, + onClick = { submit() }, enabled = isCodeValid, modifier = Modifier .fillMaxWidth() @@ -172,37 +172,4 @@ fun FileReceiveCodeSheet( } } } -} - -@Composable -private fun ReceiveCodeDigit( - digit: Char?, - isActive: Boolean, - modifier: Modifier = Modifier -) { - val borderColor = when { - digit != null -> MaterialTheme.colorScheme.primary - isActive -> MaterialTheme.colorScheme.primary - else -> MaterialTheme.colorScheme.outlineVariant - } - val borderWidth by animateDpAsState( - targetValue = if (isActive || digit != null) 2.dp else 1.dp, - label = "digitBorderWidth" - ) - - Box( - modifier = modifier - .height(72.dp) - .border( - width = borderWidth, - color = borderColor, - shape = RoundedCornerShape(12.dp) - ), - contentAlignment = Alignment.Center - ) { - Text( - text = digit?.toString().orEmpty(), - style = MaterialTheme.typography.displaySmall - ) - } -} +} \ No newline at end of file diff --git a/shared/src/commonMain/kotlin/com/liftley/sync360/presentation/send/components/FileSelectionContent.kt b/shared/src/commonMain/kotlin/com/liftley/sync360/presentation/send/components/FileSelectionContent.kt index 9882080..e31c61e 100644 --- a/shared/src/commonMain/kotlin/com/liftley/sync360/presentation/send/components/FileSelectionContent.kt +++ b/shared/src/commonMain/kotlin/com/liftley/sync360/presentation/send/components/FileSelectionContent.kt @@ -12,10 +12,12 @@ import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.lazy.LazyRow import androidx.compose.foundation.lazy.items +import androidx.compose.material3.Button import androidx.compose.material3.Icon import androidx.compose.material3.IconButton import androidx.compose.material3.IconButtonDefaults import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.OutlinedButton import androidx.compose.material3.Text import androidx.compose.material3.TextButton import androidx.compose.runtime.Composable @@ -184,55 +186,29 @@ private fun FilePickerActions( horizontalArrangement = Arrangement.spacedBy(8.dp) ) { if (onPickMedia != null) { - Sync360Surface( - containerColor = MaterialTheme.colorScheme.primaryContainer, - modifier = Modifier.weight(1f).clip(MaterialTheme.shapes.extraLarge).clickable { + Button( + modifier = Modifier.weight(1f), + onClick = { onPickMedia() } ) { - Column( - Modifier.fillMaxWidth().padding(16.dp), - horizontalAlignment = Alignment.CenterHorizontally, - verticalArrangement = Arrangement.spacedBy(8.dp) - ) { - Text( - "Select Media", - maxLines = 1, - overflow = TextOverflow.Ellipsis, - color = MaterialTheme.colorScheme.onPrimaryContainer - ) - Text( - "Photos · Videos", - maxLines = 1, - style = MaterialTheme.typography.bodyMedium, - color = MaterialTheme.colorScheme.onPrimaryContainer, - overflow = TextOverflow.Ellipsis, - ) - } + Text( + "Select Media", + maxLines = 1, + overflow = TextOverflow.Ellipsis + ) } - Sync360Surface( - containerColor = MaterialTheme.colorScheme.secondaryContainer, - modifier = Modifier.weight(1f).clip(MaterialTheme.shapes.extraLarge).clickable { + + OutlinedButton( + modifier = Modifier.weight(1f), + onClick = { onPickFiles() } ) { - Column( - Modifier.fillMaxWidth().padding(16.dp), - horizontalAlignment = Alignment.CenterHorizontally, - verticalArrangement = Arrangement.spacedBy(8.dp) - ) { - Text("Select docs", maxLines = 1, - overflow = TextOverflow.Ellipsis, - color = MaterialTheme.colorScheme.onSecondaryContainer - ) - Text( - "Pdf · Docs · Excel", - maxLines = 1, - style = MaterialTheme.typography.bodyMedium, - color = MaterialTheme.colorScheme.onSecondaryContainer, - overflow = TextOverflow.Ellipsis - ) - } + Text( + "Select docs", maxLines = 1, + overflow = TextOverflow.Ellipsis + ) } } else { Sync360Surface( @@ -244,7 +220,7 @@ private fun FilePickerActions( Column( Modifier.fillMaxWidth().padding(16.dp), horizontalAlignment = Alignment.CenterHorizontally, - verticalArrangement = Arrangement.spacedBy(8.dp) + verticalArrangement = Arrangement.Center ) { Text( if (hasSelectedFiles) "Add more files" else "Select files", @@ -252,13 +228,6 @@ private fun FilePickerActions( overflow = TextOverflow.Ellipsis, color = MaterialTheme.colorScheme.onSecondaryContainer ) - Text( - "Pdf · Docs · Excel", - maxLines = 1, - style = MaterialTheme.typography.bodyMedium, - color = MaterialTheme.colorScheme.onSecondaryContainer, - overflow = TextOverflow.Ellipsis - ) } } } From 1316d79c58ba56d8b4e73d761f00dfb24b39e348 Mon Sep 17 00:00:00 2001 From: CodePandaaAI Date: Sat, 19 Sep 2026 18:05:56 +0530 Subject: [PATCH 2/3] style(theme): establish Sync360's blue visual identity MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replace the previous olive-green color scheme with a deliberate blue-violet palette across both light and dark themes. Although the implementation is primarily a change to semantic color values, its effect reaches the entire product. The cooler backgrounds, surfaces, containers, and accents create clearer separation between interface regions and make the visual hierarchy easier to follow. The previous green palette felt calm and natural, but its muted, earthy surfaces made the interface appear flatter and less decisive. The blue palette gives selected controls, discovery states, and primary actions a stronger presence while keeping the surrounding surfaces quiet enough for the content to remain comfortable to read. Blue also fits Sync360's purpose more naturally. It commonly suggests connectivity, technology, trust, and reliability—the qualities users want to feel when discovering another device and transferring personal content. The stronger contrast and cooler tone make the application feel crisper, more confident, and more responsive without changing its simple interaction model. The same improvement carries into dark mode. Blue and violet accents remain vibrant against the darker neutral surfaces, giving controls a clearer identity and making the overall experience feel more polished and intentional. Use the Sync360 palette consistently on Android instead of replacing it with wallpaper-derived dynamic colors. This preserves the product's visual identity across devices and keeps Android aligned with the other platforms. Adjust the nearby-discovery icon and file-selection label to use foreground colors that remain clear against the updated containers. This is a visual identity change only. Discovery, navigation, transfer behavior, resource ownership, and the wire protocol remain unchanged. --- CHANGELOG.md | 5 + .../com/liftley/sync360/MainActivity.kt | 2 +- .../main/kotlin/com/liftley/sync360/main.kt | 4 +- .../sync360/core/designsystem/Color.kt | 113 +++++++++--------- .../send/components/FileSelectionContent.kt | 2 +- .../send/components/NearbyDevicesSection.kt | 2 +- 6 files changed, 66 insertions(+), 62 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5345260..edbad9a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,11 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/). ## [Unreleased] +### Changed + +- Replace the olive-green theme with a blue-violet palette across light and dark modes, and use the Sync360 palette consistently on Android instead of substituting system dynamic colors. +- Adjust discovery and file-selection foreground colors to preserve clear contrast with the updated surfaces and containers. + ## [0.5.1] - 2026-09-09 ### Changed diff --git a/androidApp/src/main/kotlin/com/liftley/sync360/MainActivity.kt b/androidApp/src/main/kotlin/com/liftley/sync360/MainActivity.kt index bb93167..fc376b5 100644 --- a/androidApp/src/main/kotlin/com/liftley/sync360/MainActivity.kt +++ b/androidApp/src/main/kotlin/com/liftley/sync360/MainActivity.kt @@ -11,7 +11,7 @@ class MainActivity : ComponentActivity() { enableEdgeToEdge() super.onCreate(savedInstanceState) setContent { - Sync360Theme(dynamicColor = true) { + Sync360Theme { Sync360Root() } } diff --git a/desktopApp/src/main/kotlin/com/liftley/sync360/main.kt b/desktopApp/src/main/kotlin/com/liftley/sync360/main.kt index 6cda9b8..c644a37 100644 --- a/desktopApp/src/main/kotlin/com/liftley/sync360/main.kt +++ b/desktopApp/src/main/kotlin/com/liftley/sync360/main.kt @@ -23,8 +23,8 @@ fun main() { icon = painterResource(Res.drawable.app_icon) ) { Sync360Theme( - darkTheme = false, - dynamicColor = false + dynamicColor = false, + darkTheme = false ) { Sync360Root() } diff --git a/shared/src/commonMain/kotlin/com/liftley/sync360/core/designsystem/Color.kt b/shared/src/commonMain/kotlin/com/liftley/sync360/core/designsystem/Color.kt index e781cf1..f78d02b 100644 --- a/shared/src/commonMain/kotlin/com/liftley/sync360/core/designsystem/Color.kt +++ b/shared/src/commonMain/kotlin/com/liftley/sync360/core/designsystem/Color.kt @@ -2,75 +2,74 @@ package com.liftley.sync360.core.designsystem import androidx.compose.ui.graphics.Color -val primaryLight = Color(0xFF4C662B) +val primaryLight = Color(0xFF2346D5) val onPrimaryLight = Color(0xFFFFFFFF) -val primaryContainerLight = Color(0xFFCDEDA3) -val onPrimaryContainerLight = Color(0xFF354E16) -val secondaryLight = Color(0xFF586249) +val primaryContainerLight = Color(0xFF4361EE) +val onPrimaryContainerLight = Color(0xFFF4F2FF) +val secondaryLight = Color(0xFF4F5A95) val onSecondaryLight = Color(0xFFFFFFFF) -val secondaryContainerLight = Color(0xFFDCE7C8) -val onSecondaryContainerLight = Color(0xFF404A33) -val tertiaryLight = Color(0xFF386663) +val secondaryContainerLight = Color(0xFFB2BDFF) +val onSecondaryContainerLight = Color(0xFF404B85) +val tertiaryLight = Color(0xFF8E239F) val onTertiaryLight = Color(0xFFFFFFFF) -val tertiaryContainerLight = Color(0xFFBCECE7) -val onTertiaryContainerLight = Color(0xFF1F4E4B) +val tertiaryContainerLight = Color(0xFFAA41BA) +val onTertiaryContainerLight = Color(0xFFFFEFFB) val errorLight = Color(0xFFBA1A1A) val onErrorLight = Color(0xFFFFFFFF) val errorContainerLight = Color(0xFFFFDAD6) val onErrorContainerLight = Color(0xFF93000A) -val backgroundLight = Color(0xFFF9FAEF) -val onBackgroundLight = Color(0xFF1A1C16) -val surfaceLight = Color(0xFFF9FAEF) -val onSurfaceLight = Color(0xFF1A1C16) -val surfaceVariantLight = Color(0xFFE1E4D5) -val onSurfaceVariantLight = Color(0xFF44483D) -val outlineLight = Color(0xFF75796C) -val outlineVariantLight = Color(0xFFC5C8BA) +val backgroundLight = Color(0xFFFBF8FF) +val onBackgroundLight = Color(0xFF1A1B23) +val surfaceLight = Color(0xFFFBF8FF) +val onSurfaceLight = Color(0xFF1A1B23) +val surfaceVariantLight = Color(0xFFE1E1F4) +val onSurfaceVariantLight = Color(0xFF444655) +val outlineLight = Color(0xFF747686) +val outlineVariantLight = Color(0xFFC4C5D7) val scrimLight = Color(0xFF000000) -val inverseSurfaceLight = Color(0xFF2F312A) -val inverseOnSurfaceLight = Color(0xFFF1F2E6) -val inversePrimaryLight = Color(0xFFB1D18A) -val surfaceDimLight = Color(0xFFDADBD0) -val surfaceBrightLight = Color(0xFFF9FAEF) +val inverseSurfaceLight = Color(0xFF2F3039) +val inverseOnSurfaceLight = Color(0xFFF1EFFC) +val inversePrimaryLight = Color(0xFFBAC3FF) +val surfaceDimLight = Color(0xFFDAD9E5) +val surfaceBrightLight = Color(0xFFFBF8FF) val surfaceContainerLowestLight = Color(0xFFFFFFFF) -val surfaceContainerLowLight = Color(0xFFF3F4E9) -val surfaceContainerLight = Color(0xFFEEEFE3) -val surfaceContainerHighLight = Color(0xFFE8E9DE) -val surfaceContainerHighestLight = Color(0xFFE2E3D8) +val surfaceContainerLowLight = Color(0xFFF4F2FE) +val surfaceContainerLight = Color(0xFFEEEDF9) +val surfaceContainerHighLight = Color(0xFFE8E7F3) +val surfaceContainerHighestLight = Color(0xFFE2E1ED) - -val primaryDark = Color(0xFFB1D18A) -val onPrimaryDark = Color(0xFF1F3701) -val primaryContainerDark = Color(0xFF354E16) -val onPrimaryContainerDark = Color(0xFFCDEDA3) -val secondaryDark = Color(0xFFBFCBAD) -val onSecondaryDark = Color(0xFF2A331E) -val secondaryContainerDark = Color(0xFF404A33) -val onSecondaryContainerDark = Color(0xFFDCE7C8) -val tertiaryDark = Color(0xFFA0D0CB) -val onTertiaryDark = Color(0xFF003735) -val tertiaryContainerDark = Color(0xFF1F4E4B) -val onTertiaryContainerDark = Color(0xFFBCECE7) +val primaryDark = Color(0xFFBAC3FF) +val onPrimaryDark = Color(0xFF00218D) +val primaryContainerDark = Color(0xFF4361EE) +val onPrimaryContainerDark = Color(0xFFF4F2FF) +val secondaryDark = Color(0xFFBAC3FF) +val onSecondaryDark = Color(0xFF202C64) +val secondaryContainerDark = Color(0xFF3A457E) +val onSecondaryContainerDark = Color(0xFFAAB5F6) +val tertiaryDark = Color(0xFFFAABFF) +val onTertiaryDark = Color(0xFF570066) +val tertiaryContainerDark = Color(0xFFAA41BA) +val onTertiaryContainerDark = Color(0xFFFFEFFB) val errorDark = Color(0xFFFFB4AB) val onErrorDark = Color(0xFF690005) val errorContainerDark = Color(0xFF93000A) val onErrorContainerDark = Color(0xFFFFDAD6) -val backgroundDark = Color(0xFF12140E) -val onBackgroundDark = Color(0xFFE2E3D8) -val surfaceDark = Color(0xFF12140E) -val onSurfaceDark = Color(0xFFE2E3D8) -val surfaceVariantDark = Color(0xFF44483D) -val onSurfaceVariantDark = Color(0xFFC5C8BA) -val outlineDark = Color(0xFF8F9285) -val outlineVariantDark = Color(0xFF44483D) +val backgroundDark = Color(0xFF12131B) +val onBackgroundDark = Color(0xFFE2E1ED) +val surfaceDark = Color(0xFF12131B) +val onSurfaceDark = Color(0xFFE2E1ED) +val surfaceVariantDark = Color(0xFF444655) +val onSurfaceVariantDark = Color(0xFFC4C5D7) +val outlineDark = Color(0xFF8E8FA1) +val outlineVariantDark = Color(0xFF444655) val scrimDark = Color(0xFF000000) -val inverseSurfaceDark = Color(0xFFE2E3D8) -val inverseOnSurfaceDark = Color(0xFF2F312A) -val inversePrimaryDark = Color(0xFF4C662B) -val surfaceDimDark = Color(0xFF12140E) -val surfaceBrightDark = Color(0xFF383A32) -val surfaceContainerLowestDark = Color(0xFF0C0F09) -val surfaceContainerLowDark = Color(0xFF1A1C16) -val surfaceContainerDark = Color(0xFF1E201A) -val surfaceContainerHighDark = Color(0xFF282B24) -val surfaceContainerHighestDark = Color(0xFF33362E) \ No newline at end of file +val inverseSurfaceDark = Color(0xFFE2E1ED) +val inverseOnSurfaceDark = Color(0xFF2F3039) +val inversePrimaryDark = Color(0xFF2E4EDC) +val surfaceDimDark = Color(0xFF12131B) +val surfaceBrightDark = Color(0xFF383942) +val surfaceContainerLowestDark = Color(0xFF0C0E16) +val surfaceContainerLowDark = Color(0xFF1A1B23) +val surfaceContainerDark = Color(0xFF1E1F28) +val surfaceContainerHighDark = Color(0xFF282932) +val surfaceContainerHighestDark = Color(0xFF33343D) \ No newline at end of file diff --git a/shared/src/commonMain/kotlin/com/liftley/sync360/presentation/send/components/FileSelectionContent.kt b/shared/src/commonMain/kotlin/com/liftley/sync360/presentation/send/components/FileSelectionContent.kt index e31c61e..f7f4c96 100644 --- a/shared/src/commonMain/kotlin/com/liftley/sync360/presentation/send/components/FileSelectionContent.kt +++ b/shared/src/commonMain/kotlin/com/liftley/sync360/presentation/send/components/FileSelectionContent.kt @@ -226,7 +226,7 @@ private fun FilePickerActions( if (hasSelectedFiles) "Add more files" else "Select files", maxLines = 1, overflow = TextOverflow.Ellipsis, - color = MaterialTheme.colorScheme.onSecondaryContainer + color = MaterialTheme.colorScheme.onSurface ) } } diff --git a/shared/src/commonMain/kotlin/com/liftley/sync360/presentation/send/components/NearbyDevicesSection.kt b/shared/src/commonMain/kotlin/com/liftley/sync360/presentation/send/components/NearbyDevicesSection.kt index 937780f..6e7422e 100644 --- a/shared/src/commonMain/kotlin/com/liftley/sync360/presentation/send/components/NearbyDevicesSection.kt +++ b/shared/src/commonMain/kotlin/com/liftley/sync360/presentation/send/components/NearbyDevicesSection.kt @@ -137,7 +137,7 @@ fun NearbyDevicesSection( Wifi, contentDescription = null, modifier = Modifier.padding(16.dp).size(24.dp), - tint = MaterialTheme.colorScheme.primary + tint = MaterialTheme.colorScheme.onPrimaryContainer ) } Text( From 3ddb9757a8498ffea89c035ede4fe6bea4c36ae8 Mon Sep 17 00:00:00 2001 From: CodePandaaAI Date: Sat, 19 Sep 2026 21:00:45 +0530 Subject: [PATCH 3/3] style(ui): polish sharing controls and prepare 0.5.2 Refine the file picker actions into consistent outlined controls with fixed heights across form factors. Use neutral surface containers with primary-colored discovery icons so nearby-device states remain crisp in both light and dark themes. Bump Android, Desktop, and iOS package metadata to 0.5.2, advance Android and iOS build numbers to 9, and update the changelog and development release references. --- CHANGELOG.md | 8 +++- androidApp/build.gradle.kts | 4 +- desktopApp/build.gradle.kts | 2 +- docs/DEVELOPMENT.md | 6 +-- iosApp/Configuration/Config.xcconfig | 4 +- .../send/components/FileSelectionContent.kt | 39 +++++++------------ .../send/components/NearbyDevicesSection.kt | 8 ++-- 7 files changed, 34 insertions(+), 37 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index edbad9a..331929f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,10 +6,16 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/). ## [Unreleased] +## [0.5.2] - 2026-09-19 + ### Changed - Replace the olive-green theme with a blue-violet palette across light and dark modes, and use the Sync360 palette consistently on Android instead of substituting system dynamic colors. -- Adjust discovery and file-selection foreground colors to preserve clear contrast with the updated surfaces and containers. +- Refine discovery and file-selection colors so primary icons remain crisp on neutral surfaces in both light and dark themes. +- Use consistent outlined, fixed-height file-picker actions across Android, iOS, and Desktop. +- Prepare Android, Desktop, and iOS version metadata as `0.5.2`; Android and iOS build numbers are `9`. + +Release preparation only: new package builds and device validation have not been recorded in this task. ## [0.5.1] - 2026-09-09 diff --git a/androidApp/build.gradle.kts b/androidApp/build.gradle.kts index 5d7eb95..6f7a13e 100644 --- a/androidApp/build.gradle.kts +++ b/androidApp/build.gradle.kts @@ -36,8 +36,8 @@ android { applicationId = "com.liftley.sync360" minSdk = libs.versions.android.minSdk.get().toInt() targetSdk = libs.versions.android.targetSdk.get().toInt() - versionCode = 8 - versionName = "0.5.1" + versionCode = 9 + versionName = "0.5.2" } buildFeatures { diff --git a/desktopApp/build.gradle.kts b/desktopApp/build.gradle.kts index 4031212..2be51e4 100644 --- a/desktopApp/build.gradle.kts +++ b/desktopApp/build.gradle.kts @@ -37,7 +37,7 @@ compose.desktop { nativeDistributions { targetFormats(TargetFormat.Dmg, TargetFormat.Msi, TargetFormat.Deb) packageName = "Sync360" - packageVersion = "0.5.1" + packageVersion = "0.5.2" appResourcesRootDir.set( project.layout.projectDirectory.dir("packaging/app-resources") ) diff --git a/docs/DEVELOPMENT.md b/docs/DEVELOPMENT.md index c671b09..b13843c 100644 --- a/docs/DEVELOPMENT.md +++ b/docs/DEVELOPMENT.md @@ -58,7 +58,7 @@ The Desktop `hotRun` task is configured to use a Java 23 toolchain when it is a ## Preparing public packages -The current package version is `0.5.1`. +The current package version is `0.5.2`. Android release APKs must use the maintainer's permanent private signing key. Copy `keystore.properties.example` to the ignored `keystore.properties` file and set: @@ -156,8 +156,8 @@ Automated coverage is still minimal. Add focused tests for pure Kotlin logic whe There is no stable release yet. Treat current builds as development software. -## 0.5.1 UI validation +## 0.5.2 UI validation Before publishing the final packages, check the grouped Nearby devices header, rows, and footer with zero, one, and multiple devices. Check discovery off, scanning, and failure/retry states; light and dark themes; compact and wide windows; and the intentionally limited empty-state text at larger font sizes. Check media/document selection and adding more files. The shared default surface shape changed, so inspect other screens that use its default corners too. -Build and device validation for 0.5.1 have not been recorded in this preparation task. +Build and device validation for 0.5.2 have not been recorded in this preparation task. diff --git a/iosApp/Configuration/Config.xcconfig b/iosApp/Configuration/Config.xcconfig index c0019a3..802f738 100644 --- a/iosApp/Configuration/Config.xcconfig +++ b/iosApp/Configuration/Config.xcconfig @@ -3,5 +3,5 @@ TEAM_ID= PRODUCT_NAME=Sync360 PRODUCT_BUNDLE_IDENTIFIER=com.liftley.sync360.Sync360$(TEAM_ID) -CURRENT_PROJECT_VERSION=8 -MARKETING_VERSION=0.5.1 +CURRENT_PROJECT_VERSION=9 +MARKETING_VERSION=0.5.2 diff --git a/shared/src/commonMain/kotlin/com/liftley/sync360/presentation/send/components/FileSelectionContent.kt b/shared/src/commonMain/kotlin/com/liftley/sync360/presentation/send/components/FileSelectionContent.kt index f7f4c96..433762d 100644 --- a/shared/src/commonMain/kotlin/com/liftley/sync360/presentation/send/components/FileSelectionContent.kt +++ b/shared/src/commonMain/kotlin/com/liftley/sync360/presentation/send/components/FileSelectionContent.kt @@ -1,18 +1,17 @@ package com.liftley.sync360.presentation.send.components import androidx.compose.foundation.background -import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.BoxWithConstraints import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.lazy.LazyRow import androidx.compose.foundation.lazy.items -import androidx.compose.material3.Button import androidx.compose.material3.Icon import androidx.compose.material3.IconButton import androidx.compose.material3.IconButtonDefaults @@ -186,11 +185,11 @@ private fun FilePickerActions( horizontalArrangement = Arrangement.spacedBy(8.dp) ) { if (onPickMedia != null) { - Button( - modifier = Modifier.weight(1f), + OutlinedButton( + modifier = Modifier.weight(1f).height(48.dp), onClick = { onPickMedia() - } + }, ) { Text( "Select Media", @@ -200,7 +199,7 @@ private fun FilePickerActions( } OutlinedButton( - modifier = Modifier.weight(1f), + modifier = Modifier.weight(1f).height(48.dp), onClick = { onPickFiles() } @@ -211,24 +210,16 @@ private fun FilePickerActions( ) } } else { - Sync360Surface( - containerColor = MaterialTheme.colorScheme.secondaryContainer, - modifier = Modifier.weight(1f).clip(MaterialTheme.shapes.extraLarge).clickable { - onPickFiles() - } + OutlinedButton( + modifier = Modifier.weight(1f).height(48.dp), + onClick = { onPickFiles() }, ) { - Column( - Modifier.fillMaxWidth().padding(16.dp), - horizontalAlignment = Alignment.CenterHorizontally, - verticalArrangement = Arrangement.Center - ) { - Text( - if (hasSelectedFiles) "Add more files" else "Select files", - maxLines = 1, - overflow = TextOverflow.Ellipsis, - color = MaterialTheme.colorScheme.onSurface - ) - } + Text( + if (hasSelectedFiles) "Add more files" else "Select files", + maxLines = 1, + overflow = TextOverflow.Ellipsis, + color = MaterialTheme.colorScheme.onSurface + ) } } } @@ -262,4 +253,4 @@ private fun truncateKeepingExtension(name: String, maxBaseChars: Int = 12): Stri } else { name } -} \ No newline at end of file +} diff --git a/shared/src/commonMain/kotlin/com/liftley/sync360/presentation/send/components/NearbyDevicesSection.kt b/shared/src/commonMain/kotlin/com/liftley/sync360/presentation/send/components/NearbyDevicesSection.kt index 6e7422e..9f346cd 100644 --- a/shared/src/commonMain/kotlin/com/liftley/sync360/presentation/send/components/NearbyDevicesSection.kt +++ b/shared/src/commonMain/kotlin/com/liftley/sync360/presentation/send/components/NearbyDevicesSection.kt @@ -132,12 +132,12 @@ fun NearbyDevicesSection( horizontalAlignment = Alignment.CenterHorizontally, verticalArrangement = Arrangement.spacedBy(16.dp) ) { - Sync360Surface(containerColor = MaterialTheme.colorScheme.primaryContainer) { + Sync360Surface(containerColor = MaterialTheme.colorScheme.surfaceContainer) { Icon( Wifi, contentDescription = null, modifier = Modifier.padding(16.dp).size(24.dp), - tint = MaterialTheme.colorScheme.onPrimaryContainer + tint = MaterialTheme.colorScheme.primary ) } Text( @@ -226,7 +226,7 @@ private fun NearbyDeviceRow( horizontalArrangement = Arrangement.spacedBy(16.dp), verticalAlignment = Alignment.CenterVertically ) { - Surface(shape = CircleShape, color = MaterialTheme.colorScheme.primaryContainer) { + Surface(shape = CircleShape, color = MaterialTheme.colorScheme.surfaceContainer) { Icon( imageVector = when (device.deviceType) { "Android" -> Android @@ -235,7 +235,7 @@ private fun NearbyDeviceRow( }, contentDescription = null, modifier = Modifier.padding(16.dp).size(24.dp), - tint = MaterialTheme.colorScheme.onPrimaryContainer + tint = MaterialTheme.colorScheme.primary ) } Column(