ui: improve Compose stability and message UI

- Add minute/second abbreviations and kotlin.time-based relative time formatter
- Introduce FastPreview and update previews to use AppTheme with dark/dynamic colors
- Refactor attachments preview grid & waveform to use ImmutableList and reduce recompositions
- Tweak message bubble reply styling and swipe-to-reply animation/haptics
- Add Compose Stability Analyzer plugin and enable it in debug builds
- Cache shared images by sha256 and improve share intent/chooser text
- Minor UX polish (e.g., “No views”) and immutability annotations
This commit is contained in:
2026-02-06 22:14:01 +03:00
parent e3e9157dd5
commit 96b4fc8539
30 changed files with 341 additions and 187 deletions
@@ -4,6 +4,8 @@ import android.app.Application
import androidx.preference.PreferenceManager
import coil.ImageLoader
import coil.ImageLoaderFactory
import com.skydoves.compose.stability.runtime.ComposeStabilityAnalyzer
import dev.meloda.fast.auth.BuildConfig
import dev.meloda.fast.common.di.applicationModule
import dev.meloda.fast.datastore.AppSettings
import org.koin.android.ext.android.get
@@ -20,6 +22,8 @@ class AppGlobal : Application(), ImageLoaderFactory {
AppSettings.init(preferences)
initKoin()
ComposeStabilityAnalyzer.setEnabled(BuildConfig.DEBUG)
}
private fun initKoin() {
@@ -69,9 +69,7 @@ import dev.meloda.fast.ui.theme.LocalNavController
import dev.meloda.fast.ui.theme.LocalNavRootController
import dev.meloda.fast.ui.theme.LocalThemeConfig
import dev.meloda.fast.ui.theme.LocalUser
import dev.meloda.fast.ui.util.ImmutableList
import dev.meloda.fast.ui.util.ImmutableList.Companion.toImmutableList
import dev.meloda.fast.ui.util.immutableListOf
import dev.meloda.fast.ui.util.isNeedToEnableDarkMode
import org.koin.androidx.compose.koinViewModel
import org.koin.compose.koinInject
@@ -309,7 +307,7 @@ fun RootScreen(
LocalNavController provides navController
) {
var photoViewerInfo by rememberSaveable {
mutableStateOf<Pair<ImmutableList<String>, Int?>?>(null)
mutableStateOf<Pair<List<String>, Int?>?>(null)
}
Box(modifier = Modifier.fillMaxSize()) {
@@ -333,7 +331,7 @@ fun RootScreen(
onSettingsButtonClicked = navController::navigateToSettings,
onNavigateToMessagesHistory = navController::navigateToMessagesHistory,
onPhotoClicked = { url ->
photoViewerInfo = immutableListOf(url) to null
photoViewerInfo = listOf(url) to null
},
onMessageClicked = navController::navigateToMessagesHistory,
onNavigateToCreateChat = navController::navigateToCreateChat
@@ -344,13 +342,13 @@ fun RootScreen(
onBack = navController::navigateUp,
onNavigateToChatMaterials = navController::navigateToChatMaterials,
onNavigateToPhotoViewer = { photos, index ->
photoViewerInfo = photos.toImmutableList() to index
photoViewerInfo = photos to index
}
)
chatMaterialsScreen(
onBack = navController::navigateUp,
onPhotoClicked = { url ->
photoViewerInfo = immutableListOf(url) to null
photoViewerInfo = listOf(url) to null
}
)
createChatScreen(
@@ -378,7 +376,9 @@ fun RootScreen(
}
PhotoViewDialog(
photoViewerInfo = photoViewerInfo,
photoViewerInfo = photoViewerInfo?.let { info ->
info.first.toImmutableList() to info.second
},
onDismiss = { photoViewerInfo = null }
)
}