Implement major visual overhaul and brand identity transition to "Aubergine Nocturne" style across all screens.
### Design & Theming
- Implement a fixed dark editorial theme with a palette of deep aubergine, warm copper (Accent), and cream (Text) using OKLCH-to-sRGB color tokens.
- Integrate premium typography using Google Fonts:
- **Cormorant Garamond:** Editorial display serif for headlines and status labels.
- **Instrument Sans:** Clean UI sans for body text and actions.
- **JetBrains Mono:** Monospaced typeface for dates, counters, and ledger-style labels.
- Replace standard Material3 components with custom "Aubergine" styled components (cards, chips, and dividers).
### Calendar & Insights
- Redesign the Calendar grid with refined "bubble" day cells, high-contrast period indicators, and a horizontal "Today" footer.
- Add a new `PhaseArc` visualization in Cycle Insights to show the current phase progress within the overall cycle length.
- Enhance `CycleHistoryChart` with dashed average lines and active-state highlighting for the latest cycle.
- Refine the cycle status banner with a pulsing `GlowDot` and detailed phase descriptions.
### Day Detail & Health Trends
- Reorganize Day Detail into numbered sections (Cycle, Symptoms, Notes, Intimacy).
- Redesign the symptom logging interface with expanded category drawers and custom rating boxes.
- Transform Health Trends with a sparkline-based condition frequency view and a mono-styled range selector.
- Update `AddIntimacyForm` to follow the new typography and color scheme within its modal bottom sheet.
### Onboarding & Settings
- Rewrite the Onboarding flow to feature an editorial "Welcome" step with a monogram header and private-ledger tagline.
- Overhaul Settings with custom toggle tabs for preferences and a redesigned profile management card.
### Domain & Logic
- Update `CyclePredictionEngine` to include logic for `avgPeriodLength` and a more precise phase mapping algorithm that eliminates gaps between cycle windows.
- Refine `CycleRepository` to set `cycleLength` only when a new cycle starts, rather than at period end, to ensure accurate start-to-next-start calculations.
- Add `ui-text-google-fonts` dependency to support the new typeface requirements.
Signed-off-by: whitlocktech <whitlocktech@gmail.com>
This commit is contained in:
@@ -1,13 +1,19 @@
|
||||
package com.hsdiary.ui.components
|
||||
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.border
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material3.*
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.text.font.FontStyle
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import com.hsdiary.ui.theme.*
|
||||
|
||||
data class ProfileSwitchItem(
|
||||
val id: Long,
|
||||
@@ -25,14 +31,47 @@ fun ProfileSwitchSheet(
|
||||
) {
|
||||
var confirmTarget by remember { mutableStateOf<ProfileSwitchItem?>(null) }
|
||||
|
||||
ModalBottomSheet(onDismissRequest = onDismiss) {
|
||||
Column(modifier = Modifier.padding(horizontal = 24.dp).padding(bottom = 32.dp)) {
|
||||
Text(
|
||||
text = "Switch Profile",
|
||||
style = MaterialTheme.typography.titleMedium,
|
||||
modifier = Modifier.padding(bottom = 16.dp)
|
||||
ModalBottomSheet(
|
||||
onDismissRequest = onDismiss,
|
||||
containerColor = SurfaceColor,
|
||||
dragHandle = {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.padding(top = 12.dp)
|
||||
.size(width = 36.dp, height = 4.dp)
|
||||
.background(BorderSoftColor, RoundedCornerShape(2.dp))
|
||||
)
|
||||
}
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = 18.dp)
|
||||
.padding(bottom = 28.dp)
|
||||
) {
|
||||
Spacer(Modifier.height(4.dp))
|
||||
|
||||
// Title
|
||||
Text(
|
||||
text = "Switch diary",
|
||||
fontFamily = CormorantGaramond,
|
||||
fontStyle = FontStyle.Italic,
|
||||
fontSize = 20.sp,
|
||||
color = FgColor
|
||||
)
|
||||
val lastOpened = "last opened just now"
|
||||
Text(
|
||||
text = "${profiles.size} profiles · $lastOpened".uppercase(),
|
||||
fontFamily = JetBrainsMono,
|
||||
fontSize = 10.sp,
|
||||
color = FgSubtleColor,
|
||||
letterSpacing = 1.2.sp,
|
||||
modifier = Modifier.padding(top = 2.dp, bottom = 14.dp)
|
||||
)
|
||||
|
||||
// Profile rows
|
||||
profiles.forEach { profile ->
|
||||
HorizontalDivider(color = BorderSoftColor, thickness = 0.5.dp)
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
@@ -40,38 +79,112 @@ fun ProfileSwitchSheet(
|
||||
if (!profile.isActive) confirmTarget = profile
|
||||
else onDismiss()
|
||||
}
|
||||
.padding(vertical = 12.dp),
|
||||
.padding(vertical = 14.dp, horizontal = 4.dp),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.spacedBy(16.dp)
|
||||
horizontalArrangement = Arrangement.spacedBy(14.dp)
|
||||
) {
|
||||
AvatarDot(name = profile.name, avatarColor = profile.avatarColor)
|
||||
Text(
|
||||
text = profile.name,
|
||||
style = MaterialTheme.typography.bodyLarge,
|
||||
modifier = Modifier.weight(1f)
|
||||
AvatarDot(
|
||||
name = profile.name,
|
||||
avatarColor = profile.avatarColor,
|
||||
size = 36
|
||||
)
|
||||
Column(modifier = Modifier.weight(1f)) {
|
||||
Text(
|
||||
text = profile.name,
|
||||
fontFamily = CormorantGaramond,
|
||||
fontStyle = FontStyle.Italic,
|
||||
fontSize = 17.sp,
|
||||
color = FgColor
|
||||
)
|
||||
Text(
|
||||
text = if (profile.isActive) "CURRENTLY OPEN" else "TAP TO SWITCH",
|
||||
fontFamily = JetBrainsMono,
|
||||
fontSize = 10.sp,
|
||||
color = FgFaintColor,
|
||||
letterSpacing = 1.sp,
|
||||
modifier = Modifier.padding(top = 2.dp)
|
||||
)
|
||||
}
|
||||
if (profile.isActive) {
|
||||
Badge { Text("Active") }
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.background(Color.Transparent, RoundedCornerShape(999.dp))
|
||||
.border(1.dp, AccentFaintColor, RoundedCornerShape(999.dp))
|
||||
.padding(horizontal = 8.dp, vertical = 3.dp)
|
||||
) {
|
||||
Text(
|
||||
"ACTIVE",
|
||||
fontFamily = JetBrainsMono,
|
||||
fontSize = 9.sp,
|
||||
color = AccentColor,
|
||||
letterSpacing = 1.2.sp
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Spacer(Modifier.height(14.dp))
|
||||
|
||||
// Add profile button — outline style
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.border(1.dp, BorderColor, RoundedCornerShape(999.dp))
|
||||
.padding(vertical = 12.dp),
|
||||
contentAlignment = Alignment.Center
|
||||
) {
|
||||
Text(
|
||||
text = "+ Add a profile",
|
||||
fontFamily = InstrumentSans,
|
||||
fontSize = 13.sp,
|
||||
color = FgColor
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Confirm switch dialog
|
||||
confirmTarget?.let { target ->
|
||||
AlertDialog(
|
||||
onDismissRequest = { confirmTarget = null },
|
||||
title = { Text("Switch Profile") },
|
||||
text = { Text("Switch to ${target.name}?") },
|
||||
containerColor = SurfaceColor,
|
||||
title = {
|
||||
Text(
|
||||
"Switch diary",
|
||||
fontFamily = CormorantGaramond,
|
||||
fontStyle = FontStyle.Italic,
|
||||
fontSize = 20.sp,
|
||||
color = FgColor
|
||||
)
|
||||
},
|
||||
text = {
|
||||
Text(
|
||||
"Switch to ${target.name}?",
|
||||
fontFamily = InstrumentSans,
|
||||
fontSize = 14.sp,
|
||||
color = FgMutedColor
|
||||
)
|
||||
},
|
||||
confirmButton = {
|
||||
TextButton(onClick = {
|
||||
onProfileSelected(target.id)
|
||||
confirmTarget = null
|
||||
onDismiss()
|
||||
}) { Text("Switch") }
|
||||
TextButton(
|
||||
onClick = {
|
||||
onProfileSelected(target.id)
|
||||
confirmTarget = null
|
||||
onDismiss()
|
||||
},
|
||||
colors = ButtonDefaults.textButtonColors(contentColor = AccentColor)
|
||||
) {
|
||||
Text("Switch", fontFamily = InstrumentSans)
|
||||
}
|
||||
},
|
||||
dismissButton = {
|
||||
TextButton(onClick = { confirmTarget = null }) { Text("Cancel") }
|
||||
TextButton(
|
||||
onClick = { confirmTarget = null },
|
||||
colors = ButtonDefaults.textButtonColors(contentColor = FgMutedColor)
|
||||
) {
|
||||
Text("Cancel", fontFamily = InstrumentSans)
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user