Compare commits

..

1 Commits

10 changed files with 96 additions and 91 deletions

View File

@@ -4,10 +4,10 @@
<selectionStates>
<SelectionState runConfigName="app">
<option name="selectionMode" value="DROPDOWN" />
<DropdownSelection timestamp="2026-02-15T19:51:37.987601800Z">
<DropdownSelection timestamp="2026-02-16T10:13:39.492968600Z">
<Target type="DEFAULT_BOOT">
<handle>
<DeviceId pluginId="LocalEmulator" identifier="path=C:\Users\jonas\.android\avd\Medium_Phone_28.avd" />
<DeviceId pluginId="PhysicalDevice" identifier="serial=RFCW11ML1WV" />
</handle>
</Target>
</DropdownSelection>

View File

@@ -6,7 +6,6 @@ import java.util.Locale
plugins {
alias(libs.plugins.android.application)
alias(libs.plugins.kotlin.android)
alias(libs.plugins.kotlin.compose)
alias(libs.plugins.hilt.android)
id("kotlin-parcelize")
@@ -62,11 +61,8 @@ android {
"-opt-in=androidx.compose.ui.ExperimentalComposeUiApi"
)
}
kotlin {
compilerOptions {
jvmTarget.set(org.jetbrains.kotlin.gradle.dsl.JvmTarget.JVM_11)
}
}
buildFeatures {
compose = true
viewBinding = false
@@ -130,7 +126,6 @@ dependencies {
implementation(libs.androidx.room.runtime) // ADDED: Explicitly add runtime
implementation(libs.androidx.room.ktx)
implementation(libs.core.ktx)
implementation(libs.androidx.compose.foundation.layout)
ksp(libs.room.compiler) // CHANGED: Use ksp instead of implementation
// Networking

View File

@@ -35,7 +35,7 @@ import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.rotate
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.draw.shadow
import androidx.compose.ui.graphics.painter.Painter
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.res.stringResource
@@ -159,14 +159,14 @@ private fun MenuItem(
) {
Surface(
modifier = Modifier
.glassmorphic(shape = RoundedCornerShape(16.dp), alpha = 0.4f)
.shadow(elevation = 8.dp, shape = ComponentDefaults.DefaultShape)
.clickable(
onClick = onClick,
interactionSource = remember { MutableInteractionSource() },
indication = null
),
shape = RoundedCornerShape(16.dp),
color = Color.Transparent // Allow glassmorphic modifier to handle color
color = MaterialTheme.colorScheme.surfaceContainer
) {
Row(
modifier = Modifier.padding(horizontal = 16.dp, vertical = 12.dp),
@@ -196,4 +196,16 @@ private fun MenuItem(
)
}
}
}
@Preview
@Composable
fun MenuItemPreview() {
@Suppress("HardCodedStringLiteral")
MenuItem(
text = "Menu Item",
imageVector = AppIcons.Add,
painter = null,
onClick = {}
)
}

View File

@@ -69,8 +69,10 @@ fun <T : TabItem> AppTabLayout(
.fillMaxWidth()
.padding(vertical = 8.dp, horizontal = 8.dp)
.height(56.dp)
// Replace background with glassmorphic extension
.glassmorphic(shape = ComponentDefaults.CardShape, alpha = 0.3f)
.background(
color = MaterialTheme.colorScheme.surfaceVariant.copy(alpha = 0.5f),
shape = ComponentDefaults.CardShape
)
) {
val tabWidth = maxWidth / tabs.size
@@ -87,7 +89,7 @@ fun <T : TabItem> AppTabLayout(
.fillMaxHeight()
.padding(4.dp)
.background(
color = MaterialTheme.colorScheme.primary.copy(alpha = 0.7f), // Glassy indicator
color = MaterialTheme.colorScheme.primary,
shape = RoundedCornerShape(12.dp)
)
)

View File

@@ -25,8 +25,6 @@ import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.RectangleShape
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
@@ -43,23 +41,16 @@ fun AppTopAppBar(
onNavigateBack: (() -> Unit)? = null,
navigationIcon: @Composable (() -> Unit)? = null,
actions: @Composable RowScope.() -> Unit = {},
colors: TopAppBarColors = TopAppBarDefaults.topAppBarColors(
containerColor = Color.Transparent,
scrolledContainerColor = Color.Transparent
),
colors: TopAppBarColors = TopAppBarDefaults.topAppBarColors(),
hintContent: Hint? = null
) {
val sheetState = rememberModalBottomSheetState()
var showBottomSheet by remember { mutableStateOf(false) }
Surface(
modifier = modifier.glassmorphic(shape = RectangleShape, alpha = 0.2f),
color = Color.Transparent
) {
TopAppBar(
modifier = Modifier.height(56.dp),
windowInsets = WindowInsets(0.dp),
colors = colors,
TopAppBar(
modifier = modifier.height(56.dp),
windowInsets = WindowInsets(0.dp),
colors = colors,
title = {
Box(
modifier = Modifier.fillMaxHeight(),
@@ -111,9 +102,8 @@ fun AppTopAppBar(
// No navigation icon
}
},
actions = actions
)
}
actions = actions
)
if (showBottomSheet) {
HintBottomSheet(

View File

@@ -16,7 +16,6 @@ import androidx.compose.foundation.layout.WindowInsets
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.navigationBars
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.NavigationBar
@@ -29,7 +28,6 @@ import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.scale
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.hapticfeedback.HapticFeedbackType
import androidx.compose.ui.platform.LocalDensity
@@ -102,25 +100,24 @@ fun BottomNavigationBar(
targetOffsetY = { it }
)
) {
val baseHeight = if (showLabels) 80.dp else 56.dp
val density = LocalDensity.current
val navBarDp = with(density) { WindowInsets.navigationBars.getBottom(this).toDp() }
val height = baseHeight + navBarDp
NavigationBar(
modifier = modifier
.height(height)
// Apply glassmorphism on the top corners
.glassmorphic(shape = RoundedCornerShape(topStart = 24.dp, topEnd = 24.dp), alpha = 0.35f),
containerColor = Color.Transparent, // Let the glass shine through
tonalElevation = 0.dp,
modifier = modifier.height(height),
containerColor = MaterialTheme.colorScheme.surface, // Cleaner look than surfaceVariant
tonalElevation = 8.dp, // Slight elevation for depth
) {
screens.forEach { screen ->
val isSelected = screen == selectedItem
val title = stringResource(id = screen.title)
// 1. Spring Animation for the Icon Scale
val scale by animateFloatAsState(
targetValue = if (isSelected) 1.2f else 1.0f,
targetValue = if (isSelected) 1.2f else 1.0f, // Subtle pop effect
animationSpec = spring(
dampingRatio = Spring.DampingRatioMediumBouncy,
stiffness = Spring.StiffnessLow
@@ -132,7 +129,7 @@ fun BottomNavigationBar(
selected = isSelected,
onClick = {
if (!isSelected) {
haptic.performHapticFeedback(HapticFeedbackType.LongPress)
haptic.performHapticFeedback(HapticFeedbackType.LongPress) // 2. Tactile feedback
onItemSelected(screen)
}
},
@@ -148,16 +145,17 @@ fun BottomNavigationBar(
}
} else null,
icon = {
// 3. Crossfade between Outlined and Filled icons
Crossfade(targetState = isSelected, label = "iconFade") { selected ->
Icon(
imageVector = if (selected) screen.selectedIcon else screen.unselectedIcon,
contentDescription = title,
modifier = Modifier.scale(scale)
modifier = Modifier.scale(scale) // Apply the spring scale
)
}
},
colors = NavigationBarItemDefaults.colors(
indicatorColor = MaterialTheme.colorScheme.primaryContainer.copy(alpha = 0.5f), // Glassy indicator
indicatorColor = MaterialTheme.colorScheme.primaryContainer,
selectedIconColor = MaterialTheme.colorScheme.onPrimaryContainer,
selectedTextColor = MaterialTheme.colorScheme.primary,
unselectedIconColor = MaterialTheme.colorScheme.onSurfaceVariant,

View File

@@ -5,8 +5,6 @@ package eu.gaudian.translator.view.composable
import androidx.compose.animation.animateContentSize
import androidx.compose.animation.core.animateFloatAsState
import androidx.compose.foundation.BorderStroke
import androidx.compose.foundation.background
import androidx.compose.foundation.border
import androidx.compose.foundation.clickable
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.Arrangement
@@ -45,7 +43,6 @@ import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.composed
import androidx.compose.ui.draw.clip
import androidx.compose.ui.draw.rotate
import androidx.compose.ui.draw.shadow
@@ -58,51 +55,49 @@ import androidx.compose.ui.unit.dp
import eu.gaudian.translator.R
import eu.gaudian.translator.ui.theme.ThemePreviews
import eu.gaudian.translator.ui.theme.semanticColors
import eu.gaudian.translator.view.composable.ComponentDefaults.DefaultElevation
object ComponentDefaults {
// Sizing
val DefaultButtonHeight = 48.dp
val CardPadding = 8.dp
// Elevation
val DefaultElevation = 0.dp
val NoElevation = 0.dp
// Borders
val DefaultBorderWidth = 1.dp
// Shapes
val DefaultCornerRadius = 16.dp
val CardClipRadius = 16.dp // Increased slightly for softer glass look
val CardClipRadius = 8.dp
val NoRounding = 0.dp
val DefaultShape = RoundedCornerShape(DefaultCornerRadius)
val CardClipShape = RoundedCornerShape(CardClipRadius)
val CardShape = RoundedCornerShape(DefaultCornerRadius)
val NoShape = RoundedCornerShape(NoRounding)
// Opacity Levels
const val ALPHA_HIGH = 0.6f
const val ALPHA_MEDIUM = 0.4f
const val ALPHA_LOW = 0.2f // Adjusted for glass
const val ALPHA_MEDIUM = 0.5f
const val ALPHA_LOW = 0.3f
}
/**
* Standard Glassmorphism Modifier
* A styled card container for displaying content with a consistent floating look.
*
* @param modifier The modifier to be applied to the card.
* @param content The content to be displayed inside the card.
*/
fun Modifier.glassmorphic(
shape: Shape = ComponentDefaults.DefaultShape,
alpha: Float = ComponentDefaults.ALPHA_LOW,
borderAlpha: Float = 0.15f
): Modifier = composed {
this
.shadow(elevation = 8.dp, shape = shape, spotColor = Color.Black.copy(alpha = 0.05f))
.clip(shape)
.background(MaterialTheme.colorScheme.surface.copy(alpha = alpha))
.border(
width = 1.dp,
color = MaterialTheme.colorScheme.onSurface.copy(alpha = borderAlpha),
shape = shape
)
}
@Composable
fun AppCard(
modifier: Modifier = Modifier,
title: String? = null,
icon: ImageVector? = null,
icon: ImageVector? = null, // New optional icon parameter
text: String? = null,
expandable: Boolean = false,
initiallyExpanded: Boolean = false,
@@ -115,17 +110,25 @@ fun AppCard(
label = "Chevron Rotation"
)
// Check if we need to render the header row
// Updated to include icon in the check
val hasHeader = title != null || text != null || expandable || icon != null
Surface(
modifier = modifier
.fillMaxWidth()
.glassmorphic(shape = ComponentDefaults.CardShape, alpha = 0.25f)
.shadow(
DefaultElevation,
shape = ComponentDefaults.CardShape
)
.clip(ComponentDefaults.CardClipShape)
// Animate height changes when expanding/collapsing
.animateContentSize(),
shape = ComponentDefaults.CardShape,
color = Color.Transparent // Let glassmorphic handle the background
color = MaterialTheme.colorScheme.surfaceContainer
) {
Column {
// --- Header Row ---
if (hasHeader) {
Row(
modifier = Modifier
@@ -134,6 +137,7 @@ fun AppCard(
.padding(ComponentDefaults.CardPadding),
verticalAlignment = Alignment.CenterVertically
) {
// 1. Optional Icon on the left
if (icon != null) {
Icon(
imageVector = icon,
@@ -144,6 +148,7 @@ fun AppCard(
Spacer(modifier = Modifier.width(16.dp))
}
// 2. Title and Text Column
Column(modifier = Modifier.weight(1f)) {
if (!title.isNullOrBlank()) {
Text(
@@ -152,9 +157,12 @@ fun AppCard(
color = MaterialTheme.colorScheme.onSurface
)
}
// Only show spacer if both title and text exist
if (!title.isNullOrBlank() && !text.isNullOrBlank()) {
Spacer(Modifier.size(4.dp))
}
if (!text.isNullOrBlank()) {
Text(
text = text,
@@ -164,6 +172,7 @@ fun AppCard(
}
}
// 3. Expand Chevron (Far right)
if (expandable) {
Icon(
imageVector = AppIcons.ArrowDropDown,
@@ -175,12 +184,15 @@ fun AppCard(
}
}
// --- Content Area ---
if (!expandable || isExpanded) {
Column(
modifier = Modifier.padding(
start = ComponentDefaults.CardPadding,
end = ComponentDefaults.CardPadding,
bottom = ComponentDefaults.CardPadding,
// If we have a header, remove the top padding so content sits closer to the title.
// If no header (legacy behavior), keep the top padding.
top = if (hasHeader) 0.dp else ComponentDefaults.CardPadding
),
content = content
@@ -292,27 +304,31 @@ fun AppButton(
modifier: Modifier? = Modifier,
enabled: Boolean = true,
shape: Shape? = null,
colors: ButtonColors = ButtonDefaults.buttonColors(
containerColor = MaterialTheme.colorScheme.primary.copy(alpha = 0.8f) // Glassy primary
),
elevation: ButtonElevation? = ButtonDefaults.buttonElevation(defaultElevation = 0.dp),
colors: ButtonColors = ButtonDefaults.buttonColors(),
elevation: ButtonElevation? = ButtonDefaults.buttonElevation(),
border: BorderStroke? = null,
contentPadding: PaddingValues = ButtonDefaults.ContentPadding,
interactionSource: MutableInteractionSource? = null,
content: @Composable RowScope.() -> Unit
) {
val m = modifier ?: Modifier.height(ComponentDefaults.DefaultButtonHeight)
val s = shape ?: ComponentDefaults.DefaultShape
Button(
onClick = onClick,
modifier = m.border(1.dp, MaterialTheme.colorScheme.onPrimary.copy(alpha = 0.2f), s),
modifier = m,
enabled = enabled,
shape = s,
colors = colors,
elevation = elevation,
border = border,
contentPadding = PaddingValues(start = 8.dp, end = 8.dp, top = 8.dp, bottom = 8.dp),
contentPadding = PaddingValues(
start = 8.dp, // More horizontal padding
end = 8.dp,
top = 8.dp, // Default vertical padding
bottom = 8.dp
),
interactionSource = interactionSource
) {
content()
@@ -352,7 +368,11 @@ fun AppOutlinedButton(
)
}
@Preview
@Composable
fun PrimaryButtonWithIconPreview() {
PrimaryButton(onClick = { }, text = stringResource(R.string.primary_with_icon), icon = AppIcons.Add)
}
/**
* The secondary button for less prominent actions.

View File

@@ -1,9 +1,8 @@
// Top-level build file where you can add configuration options common to all sub-projects/modules.
plugins {
alias(libs.plugins.android.application) apply false
alias(libs.plugins.kotlin.android) apply false
alias(libs.plugins.kotlin.compose) apply false
id("androidx.navigation.safeargs.kotlin") version "2.9.7" apply false
id("org.jetbrains.kotlin.plugin.serialization") version "2.3.10"
id("com.google.devtools.ksp") version "2.3.4" apply false
}
}

View File

@@ -21,13 +21,4 @@ kotlin.code.style=official
# resources declared in the library itself and none from the library's dependencies,
# thereby reducing the size of the R class for that library
android.nonTransitiveRClass=true
android.defaults.buildfeatures.resvalues=true
android.sdk.defaultTargetSdkToCompileSdkIfUnset=false
android.enableAppCompileTimeRClass=false
android.usesSdkInManifest.disallowed=false
android.uniquePackageNames=false
android.dependency.useConstraints=true
android.r8.strictFullModeForKeepRules=false
android.r8.optimizedResourceShrinking=false
android.builtInKotlin=false
android.newDsl=false
android.dependency.useConstraints=false

View File

@@ -43,7 +43,6 @@ truth = "1.4.5"
zstdJni = "1.5.7-7"
composeMarkdown = "0.5.8"
jitpack = "1.0.10"
foundationLayoutVersion = "1.10.3"
[libraries]
@@ -104,7 +103,6 @@ hilt-android-compiler = { module = "com.google.dagger:hilt-android-compiler", ve
hilt-navigation-compose = { module = "androidx.hilt:hilt-navigation-compose", version = "1.3.0" }
mockk = { module = "io.mockk:mockk", version = "1.14.9" }
compose-markdown = { module = "com.github.jeziellago:compose-markdown", version.ref = "composeMarkdown" }
androidx-compose-foundation-layout = { group = "androidx.compose.foundation", name = "foundation-layout", version.ref = "foundationLayoutVersion" }
[plugins]
android-application = { id = "com.android.application", version.ref = "agp" }