diff --git a/android/build.gradle.kts b/android/build.gradle.kts index e3890973..fd361215 100644 --- a/android/build.gradle.kts +++ b/android/build.gradle.kts @@ -48,6 +48,7 @@ dependencies { implementation(libs.material.motion.navigation) implementation(libs.sqldelight.extensions.coroutines) implementation(libs.kotlin.coroutines.core) + implementation(libs.kotlinx.datetime) implementation(libs.kotlinx.serialization.json) implementation(libs.retrofit.kotlinxSerializationConverter) implementation(libs.androidx.work.runtime.ktx) diff --git a/android/src/main/kotlin/dev/msfjarvis/claw/android/ui/LobstersApp.kt b/android/src/main/kotlin/dev/msfjarvis/claw/android/ui/LobstersApp.kt index fa835cf1..5ad1b51b 100644 --- a/android/src/main/kotlin/dev/msfjarvis/claw/android/ui/LobstersApp.kt +++ b/android/src/main/kotlin/dev/msfjarvis/claw/android/ui/LobstersApp.kt @@ -29,7 +29,6 @@ import androidx.navigation.navDeepLink import androidx.paging.compose.collectAsLazyPagingItems import com.google.accompanist.systemuicontroller.rememberSystemUiController import dev.msfjarvis.claw.android.R -import dev.msfjarvis.claw.android.ui.decorations.ClawAppBar import dev.msfjarvis.claw.android.ui.decorations.ClawNavigationBar import dev.msfjarvis.claw.android.ui.decorations.NavigationItem import dev.msfjarvis.claw.android.ui.lists.DatabasePosts @@ -42,6 +41,8 @@ import dev.msfjarvis.claw.common.comments.HTMLConverter import dev.msfjarvis.claw.common.comments.LocalHTMLConverter import dev.msfjarvis.claw.common.res.ClawIcons import dev.msfjarvis.claw.common.theme.LobstersTheme +import dev.msfjarvis.claw.common.ui.decorations.ClawAppBar +import dev.msfjarvis.claw.common.ui.surfaceColorAtNavigationBarElevation import dev.msfjarvis.claw.common.urllauncher.UrlLauncher import dev.msfjarvis.claw.common.user.UserProfile import kotlinx.coroutines.launch diff --git a/android/src/main/kotlin/dev/msfjarvis/claw/android/ui/ext.kt b/android/src/main/kotlin/dev/msfjarvis/claw/android/ui/ext.kt index 09e5bb97..10612d50 100644 --- a/android/src/main/kotlin/dev/msfjarvis/claw/android/ui/ext.kt +++ b/android/src/main/kotlin/dev/msfjarvis/claw/android/ui/ext.kt @@ -11,16 +11,12 @@ import androidx.compose.animation.slideInVertically import androidx.compose.animation.slideOutVertically import androidx.compose.foundation.isSystemInDarkTheme import androidx.compose.material3.ColorScheme -import androidx.compose.material3.LocalAbsoluteTonalElevation import androidx.compose.material3.dynamicDarkColorScheme import androidx.compose.material3.dynamicLightColorScheme import androidx.compose.runtime.Composable import androidx.compose.runtime.State import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember -import androidx.compose.ui.graphics.Color -import androidx.compose.ui.graphics.compositeOver -import androidx.compose.ui.unit.dp import androidx.navigation.NavController import dev.msfjarvis.claw.android.ui.navigation.Destinations import dev.msfjarvis.claw.android.viewmodel.ClawViewModel @@ -29,11 +25,10 @@ import dev.msfjarvis.claw.common.theme.DarkThemeColors import dev.msfjarvis.claw.common.theme.LightThemeColors import dev.msfjarvis.claw.common.urllauncher.UrlLauncher import dev.msfjarvis.claw.database.local.SavedPost -import java.text.SimpleDateFormat -import java.time.ZoneId -import java.time.ZonedDateTime -import java.util.Locale -import kotlin.math.ln +import kotlinx.datetime.Instant +import kotlinx.datetime.LocalDateTime +import kotlinx.datetime.TimeZone +import kotlinx.datetime.toLocalDateTime private const val AnimationDuration = 100 @@ -54,13 +49,11 @@ fun slideOutAnimation(): ExitTransition { } /** - * Parses a given [String] into a [ZonedDateTime]. This method only works on dates in the format - * returned by the Lobsters API, and is not a general purpose parsing solution. + * Parses a given [String] into a [LocalDateTime]. This method is only intended to be used for dates + * in the format returned by the Lobsters API, and is not a general purpose parsing solution. */ -fun String.asZonedDateTime(): ZonedDateTime { - val sdf = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS", Locale.US) - val date = checkNotNull(sdf.parse(this)) - return date.toInstant().atZone(ZoneId.systemDefault()) +fun String.toLocalDateTime(): LocalDateTime { + return Instant.parse(this).toLocalDateTime(TimeZone.currentSystemDefault()) } // The destination needs to be tracked like this rather than used directly since @@ -124,21 +117,3 @@ fun rememberPostActions( } } } - -/** - * Returns the [ColorScheme.surface] color with an alpha of the [ColorScheme.primary] color overlaid - * on top of it. Computes the surface tonal color at different elevation levels e.g. surface1 - * through surface5. - * - * Stolen from AndroidX, keep in sync when upgrading Compose. This version is hard-coded to - * replicate the logic used by the Material3 NavigationBar to determine its surface color. - * https://github.com/androidx/androidx/blob/74d3510b608c3cc26b9cf9be8d15a6a6c26192c2/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/ColorScheme.kt#L453-L466 - */ -@Composable -fun ColorScheme.surfaceColorAtNavigationBarElevation(): Color { - // Absolute tonal elevation + NavigationBarTokens.ContainerElevation - val elevation = LocalAbsoluteTonalElevation.current + 3.0.dp - if (elevation == 0.dp) return surface - val alpha = ((4.5f * ln(elevation.value + 1)) + 2f) / 100f - return primary.copy(alpha = alpha).compositeOver(surface) -} diff --git a/android/src/main/kotlin/dev/msfjarvis/claw/android/ui/lists/DatabasePosts.kt b/android/src/main/kotlin/dev/msfjarvis/claw/android/ui/lists/DatabasePosts.kt index bd4dd90c..bddb046c 100644 --- a/android/src/main/kotlin/dev/msfjarvis/claw/android/ui/lists/DatabasePosts.kt +++ b/android/src/main/kotlin/dev/msfjarvis/claw/android/ui/lists/DatabasePosts.kt @@ -6,11 +6,11 @@ import androidx.compose.foundation.lazy.LazyListState import androidx.compose.foundation.lazy.items import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier -import dev.msfjarvis.claw.android.ui.decorations.MonthHeader import dev.msfjarvis.claw.common.posts.PostActions import dev.msfjarvis.claw.common.ui.Divider +import dev.msfjarvis.claw.common.ui.decorations.MonthHeader import dev.msfjarvis.claw.database.local.SavedPost -import java.time.Month +import kotlinx.datetime.Month @OptIn(ExperimentalFoundationApi::class) @Composable diff --git a/android/src/main/kotlin/dev/msfjarvis/claw/android/viewmodel/ClawViewModel.kt b/android/src/main/kotlin/dev/msfjarvis/claw/android/viewmodel/ClawViewModel.kt index 30bc1ac5..6f9a737c 100644 --- a/android/src/main/kotlin/dev/msfjarvis/claw/android/viewmodel/ClawViewModel.kt +++ b/android/src/main/kotlin/dev/msfjarvis/claw/android/viewmodel/ClawViewModel.kt @@ -6,16 +6,16 @@ import androidx.paging.Pager import androidx.paging.PagingConfig import dagger.hilt.android.lifecycle.HiltViewModel import dev.msfjarvis.claw.android.paging.LobstersPagingSource -import dev.msfjarvis.claw.android.ui.asZonedDateTime +import dev.msfjarvis.claw.android.ui.toLocalDateTime import dev.msfjarvis.claw.api.LobstersApi import dev.msfjarvis.claw.database.local.SavedPost -import java.time.Month import javax.inject.Inject import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.map import kotlinx.coroutines.launch import kotlinx.coroutines.withContext +import kotlinx.datetime.Month @HiltViewModel class ClawViewModel @@ -48,8 +48,8 @@ constructor( get() = savedPostsFlow.map(::mapSavedPosts) private fun mapSavedPosts(items: List): Map> { - val sorted = items.sortedByDescending { post -> post.createdAt.asZonedDateTime() } - return sorted.groupBy { post -> post.createdAt.asZonedDateTime().month } + val sorted = items.sortedByDescending { post -> post.createdAt.toLocalDateTime() } + return sorted.groupBy { post -> post.createdAt.toLocalDateTime().month } } suspend fun isPostSaved(post: SavedPost): Boolean { diff --git a/common/build.gradle.kts b/common/build.gradle.kts index 0fc0502d..0bbd653a 100644 --- a/common/build.gradle.kts +++ b/common/build.gradle.kts @@ -36,6 +36,7 @@ kotlin { api(projects.model) api(libs.napier) implementation(libs.kotlin.coroutines.core) + implementation(libs.kotlinx.datetime) implementation(libs.compose.richtext.markdown) implementation(libs.compose.richtext.material3) implementation(libs.compose.richtext.ui) diff --git a/android/src/main/kotlin/dev/msfjarvis/claw/android/ui/decorations/ClawAppBar.kt b/common/src/commonMain/kotlin/dev/msfjarvis/claw/common/ui/decorations/ClawAppBar.kt similarity index 92% rename from android/src/main/kotlin/dev/msfjarvis/claw/android/ui/decorations/ClawAppBar.kt rename to common/src/commonMain/kotlin/dev/msfjarvis/claw/common/ui/decorations/ClawAppBar.kt index b78785bb..024e729c 100644 --- a/android/src/main/kotlin/dev/msfjarvis/claw/android/ui/decorations/ClawAppBar.kt +++ b/common/src/commonMain/kotlin/dev/msfjarvis/claw/common/ui/decorations/ClawAppBar.kt @@ -1,4 +1,4 @@ -package dev.msfjarvis.claw.android.ui.decorations +package dev.msfjarvis.claw.common.ui.decorations import androidx.compose.material3.SmallTopAppBar import androidx.compose.material3.TopAppBarDefaults diff --git a/android/src/main/kotlin/dev/msfjarvis/claw/android/ui/decorations/StickyMonthHeader.kt b/common/src/commonMain/kotlin/dev/msfjarvis/claw/common/ui/decorations/MonthHeader.kt similarity index 72% rename from android/src/main/kotlin/dev/msfjarvis/claw/android/ui/decorations/StickyMonthHeader.kt rename to common/src/commonMain/kotlin/dev/msfjarvis/claw/common/ui/decorations/MonthHeader.kt index f597471c..1352d967 100644 --- a/android/src/main/kotlin/dev/msfjarvis/claw/android/ui/decorations/StickyMonthHeader.kt +++ b/common/src/commonMain/kotlin/dev/msfjarvis/claw/common/ui/decorations/MonthHeader.kt @@ -1,4 +1,4 @@ -package dev.msfjarvis.claw.android.ui.decorations +package dev.msfjarvis.claw.common.ui.decorations import androidx.compose.foundation.background import androidx.compose.foundation.layout.Box @@ -9,11 +9,11 @@ import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier +import androidx.compose.ui.text.capitalize +import androidx.compose.ui.text.intl.Locale import androidx.compose.ui.unit.dp -import dev.msfjarvis.claw.android.ui.surfaceColorAtNavigationBarElevation -import java.time.Month -import java.time.format.TextStyle as JTextStyle -import java.util.Locale +import dev.msfjarvis.claw.common.ui.surfaceColorAtNavigationBarElevation +import kotlinx.datetime.Month @Composable fun MonthHeader(month: Month) { @@ -23,7 +23,7 @@ fun MonthHeader(month: Month) { .background(MaterialTheme.colorScheme.surfaceColorAtNavigationBarElevation()) ) { Text( - text = month.getDisplayName(JTextStyle.FULL, Locale.getDefault()), + text = month.name.lowercase().capitalize(Locale.current), style = MaterialTheme.typography.headlineSmall, modifier = Modifier.padding(horizontal = 12.dp, vertical = 12.dp), ) diff --git a/common/src/commonMain/kotlin/dev/msfjarvis/claw/common/ui/ext.kt b/common/src/commonMain/kotlin/dev/msfjarvis/claw/common/ui/ext.kt new file mode 100644 index 00000000..c89f4837 --- /dev/null +++ b/common/src/commonMain/kotlin/dev/msfjarvis/claw/common/ui/ext.kt @@ -0,0 +1,27 @@ +package dev.msfjarvis.claw.common.ui + +import androidx.compose.material3.ColorScheme +import androidx.compose.material3.LocalAbsoluteTonalElevation +import androidx.compose.runtime.Composable +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.compositeOver +import androidx.compose.ui.unit.dp +import kotlin.math.ln + +/** + * Returns the [ColorScheme.surface] color with an alpha of the [ColorScheme.primary] color overlaid + * on top of it. Computes the surface tonal color at different elevation levels e.g. surface1 + * through surface5. + * + * Stolen from AndroidX, keep in sync when upgrading Compose. This version is hard-coded to + * replicate the logic used by the Material3 NavigationBar to determine its surface color. + * https://github.com/androidx/androidx/blob/74d3510b608c3cc26b9cf9be8d15a6a6c26192c2/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/ColorScheme.kt#L453-L466 + */ +@Composable +fun ColorScheme.surfaceColorAtNavigationBarElevation(): Color { + // Absolute tonal elevation + NavigationBarTokens.ContainerElevation + val elevation = LocalAbsoluteTonalElevation.current + 3.0.dp + if (elevation == 0.dp) return surface + val alpha = ((4.5f * ln(elevation.value + 1)) + 2f) / 100f + return primary.copy(alpha = alpha).compositeOver(surface) +} diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 7f8c0faf..d7f65989 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -53,6 +53,7 @@ dagger-hilt-compiler = { module = "com.google.dagger:hilt-compiler", version.ref javapoet = "com.squareup:javapoet:1.13.0" kamel-image = "com.alialbaali.kamel:kamel-image:0.3.0" kotlin-coroutines-core = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", version.ref = "coroutines" } +kotlinx-datetime = "org.jetbrains.kotlinx:kotlinx-datetime:0.4.0" kotlinx-serialization-core = { module = "org.jetbrains.kotlinx:kotlinx-serialization-core", version.ref = "serialization" } kotlinx-serialization-json = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json", version.ref = "serialization" } material_motion-core = { module = "io.github.fornewid:material-motion-compose-core", version.ref = "material_motion" } diff --git a/paparazzi-tests/build.gradle.kts b/paparazzi-tests/build.gradle.kts index 362e3834..e846b80f 100644 --- a/paparazzi-tests/build.gradle.kts +++ b/paparazzi-tests/build.gradle.kts @@ -15,10 +15,8 @@ android { dependencies { testImplementation(kotlin("test-junit")) - testImplementation(projects.android) + testImplementation(libs.kotlinx.datetime) testImplementation(projects.common) - testImplementation(projects.model) - testImplementation(projects.database) } tasks.withType().configureEach { diff --git a/paparazzi-tests/src/test/kotlin/dev/msfjarvis/claw/android/tests/MonthHeaderTest.kt b/paparazzi-tests/src/test/kotlin/dev/msfjarvis/claw/android/tests/MonthHeaderTest.kt index ca2a0faa..57599a01 100644 --- a/paparazzi-tests/src/test/kotlin/dev/msfjarvis/claw/android/tests/MonthHeaderTest.kt +++ b/paparazzi-tests/src/test/kotlin/dev/msfjarvis/claw/android/tests/MonthHeaderTest.kt @@ -2,10 +2,10 @@ package dev.msfjarvis.claw.android.tests import androidx.compose.material3.MaterialTheme import app.cash.paparazzi.Paparazzi -import dev.msfjarvis.claw.android.ui.decorations.MonthHeader import dev.msfjarvis.claw.common.theme.DarkThemeColors import dev.msfjarvis.claw.common.theme.LightThemeColors -import java.time.Month +import dev.msfjarvis.claw.common.ui.decorations.MonthHeader +import kotlinx.datetime.Month import org.junit.Rule import org.junit.Test diff --git a/paparazzi-tests/src/test/snapshots/images/dev.msfjarvis.claw.android.tests_MonthHeaderTest_dark_mode.png b/paparazzi-tests/src/test/snapshots/images/dev.msfjarvis.claw.android.tests_MonthHeaderTest_dark_mode.png index b0b2dd11..8f572f13 100644 Binary files a/paparazzi-tests/src/test/snapshots/images/dev.msfjarvis.claw.android.tests_MonthHeaderTest_dark_mode.png and b/paparazzi-tests/src/test/snapshots/images/dev.msfjarvis.claw.android.tests_MonthHeaderTest_dark_mode.png differ diff --git a/paparazzi-tests/src/test/snapshots/images/dev.msfjarvis.claw.android.tests_MonthHeaderTest_light_mode.png b/paparazzi-tests/src/test/snapshots/images/dev.msfjarvis.claw.android.tests_MonthHeaderTest_light_mode.png index 39a5a873..80c58f11 100644 Binary files a/paparazzi-tests/src/test/snapshots/images/dev.msfjarvis.claw.android.tests_MonthHeaderTest_light_mode.png and b/paparazzi-tests/src/test/snapshots/images/dev.msfjarvis.claw.android.tests_MonthHeaderTest_light_mode.png differ