From 90342cdf5c4a286d91844b43e24ff5e8ec917644 Mon Sep 17 00:00:00 2001 From: Brandon McAnsh Date: Fri, 2 Oct 2026 14:42:44 -0400 Subject: [PATCH] chore(emojis): remove the unused :ui:emojis module Nothing outside the module called into it except EmojiModal.kt in :ui:components, and nothing called EmojiModalContent. Its EmojiRender and EmojiSearchResults call processEmoji, which reads EmojiCompat.get() without EmojiCompat ever being initialized: EmojiCompatController.init() had no callers, and emoji2-bundled strips emoji2's startup initializer. Rendering either composable would throw IllegalStateException. Removing emoji2-bundled lets EmojiCompatInitializer back into the merged manifest through Compose's emoji2 dependency, which would switch emoji on API 29-34 to the downloadable font. The app manifest now removes it explicitly, so emoji keep using the system font as before. --- .../flipcash/app/src/main/AndroidManifest.xml | 6 + docs/architecture/07-design-system.md | 3 +- settings.gradle.kts | 1 - ui/components/build.gradle.kts | 1 - .../ui/components/emojis/EmojiModal.kt | 110 ------ ui/emojis/.gitignore | 2 - ui/emojis/build.gradle.kts | 29 -- .../ui/emojis/EmojiCompatController.kt | 40 -- .../com/getcode/ui/emojis/EmojiGarden.kt | 362 ------------------ .../com/getcode/ui/emojis/EmojiLocal.kt | 7 - .../com/getcode/ui/emojis/EmojiRender.kt | 72 ---- .../com/getcode/ui/emojis/EmojiSearch.kt | 84 ---- .../getcode/ui/emojis/FrequentlyUsedEmojis.kt | 90 ----- 13 files changed, 7 insertions(+), 800 deletions(-) delete mode 100644 ui/components/src/main/kotlin/com/getcode/ui/components/emojis/EmojiModal.kt delete mode 100644 ui/emojis/.gitignore delete mode 100644 ui/emojis/build.gradle.kts delete mode 100644 ui/emojis/src/main/kotlin/com/getcode/ui/emojis/EmojiCompatController.kt delete mode 100644 ui/emojis/src/main/kotlin/com/getcode/ui/emojis/EmojiGarden.kt delete mode 100644 ui/emojis/src/main/kotlin/com/getcode/ui/emojis/EmojiLocal.kt delete mode 100644 ui/emojis/src/main/kotlin/com/getcode/ui/emojis/EmojiRender.kt delete mode 100644 ui/emojis/src/main/kotlin/com/getcode/ui/emojis/EmojiSearch.kt delete mode 100644 ui/emojis/src/main/kotlin/com/getcode/ui/emojis/FrequentlyUsedEmojis.kt diff --git a/apps/flipcash/app/src/main/AndroidManifest.xml b/apps/flipcash/app/src/main/AndroidManifest.xml index 67662bd553..4b69d97ab8 100644 --- a/apps/flipcash/app/src/main/AndroidManifest.xml +++ b/apps/flipcash/app/src/main/AndroidManifest.xml @@ -379,6 +379,12 @@ android:value="androidx.startup" tools:node="remove" /> + + + diff --git a/docs/architecture/07-design-system.md b/docs/architecture/07-design-system.md index 6d5153dc62..fd692314fe 100644 --- a/docs/architecture/07-design-system.md +++ b/docs/architecture/07-design-system.md @@ -35,7 +35,6 @@ graph TD | `:ui:navigation` | The navigation runtime used by the app: `CodeNavigator`, `BaseViewModel`, flow-route types. (See [02](02-state-and-dependency-injection.md) and [03](03-navigation.md).) | | `:ui:scanner` | Camera preview and Kik-code capture surface used by the scanner feature. | | `:ui:biometrics` | `rememberBiometricsState` and `LocalBiometricsState` for gating sensitive screens. | -| `:ui:emojis` | Emoji rendering/lookup helpers. | | `:ui:testing` | Compose test utilities and `LocalUiTesting` for UI-test affordances. | ## How features consume the UI layer @@ -45,7 +44,7 @@ Feature modules don't declare the UI layer by hand — the `:ui:navigation`, `:ui:resources`, and `:ui:theme` automatically (see [01](01-modules-and-boundaries.md)). So a screen can use the shared components and theme tokens immediately, and only adds an explicit dependency for the less common -pieces (`:ui:scanner`, `:ui:biometrics`, `:ui:emojis`). +pieces (`:ui:scanner`, `:ui:biometrics`). ## Compose conventions diff --git a/settings.gradle.kts b/settings.gradle.kts index 8609b3df08..64590ecf81 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -257,7 +257,6 @@ include( ":ui:biometrics", ":ui:core", ":ui:components", - ":ui:emojis", ":ui:navigation", ":ui:resources", ":ui:scanner", diff --git a/ui/components/build.gradle.kts b/ui/components/build.gradle.kts index 73aaab7188..8fb2586702 100644 --- a/ui/components/build.gradle.kts +++ b/ui/components/build.gradle.kts @@ -26,7 +26,6 @@ dependencies { implementation(project(":libs:opengraph")) implementation(project(":libs:vibrator:public")) api(project(":ui:core")) - implementation(project(":ui:emojis")) implementation(project(":ui:theme")) implementation(project(":ui:resources")) diff --git a/ui/components/src/main/kotlin/com/getcode/ui/components/emojis/EmojiModal.kt b/ui/components/src/main/kotlin/com/getcode/ui/components/emojis/EmojiModal.kt deleted file mode 100644 index 3bff532ef3..0000000000 --- a/ui/components/src/main/kotlin/com/getcode/ui/components/emojis/EmojiModal.kt +++ /dev/null @@ -1,110 +0,0 @@ -package com.getcode.ui.components.emojis - -import androidx.compose.animation.Crossfade -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.PaddingValues -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.size -import androidx.compose.foundation.layout.wrapContentWidth -import androidx.compose.foundation.text.input.TextFieldState -import androidx.compose.foundation.text.input.clearText -import androidx.compose.foundation.text.input.rememberTextFieldState -import androidx.compose.material.Icon -import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.filled.Cancel -import androidx.compose.runtime.Composable -import androidx.compose.runtime.derivedStateOf -import androidx.compose.runtime.getValue -import androidx.compose.runtime.remember -import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.Color -import androidx.compose.ui.res.stringResource -import androidx.compose.ui.unit.dp -import com.getcode.libs.emojis.generated.Emojis -import com.getcode.theme.CodeTheme -import com.getcode.theme.inputColors -import com.getcode.ui.components.R -import com.getcode.ui.components.TextInput -import com.getcode.ui.core.unboundedClickable -import com.getcode.ui.emojis.EmojiGarden -import com.getcode.ui.emojis.EmojiSearchResults -import com.getcode.ui.emojis.fuzzySearch - -@Composable -fun EmojiModalContent(onSelected: (String) -> Unit) { - Column( - modifier = Modifier - .fillMaxWidth(), - ) { - Column( - modifier = Modifier.fillMaxWidth(), - ) { - val textState = rememberTextFieldState() - SearchBar(textState) - - val emojis = remember { Emojis.categorizedNoSkinTones } - val allEmojis = remember(emojis) { - emojis.mapValues { it.value.values.toList().flatten() }.values.toList().flatten() - } - - val searchResults by remember(allEmojis, textState.text) { - derivedStateOf { - if (textState.text.isEmpty()) return@derivedStateOf null - allEmojis.fuzzySearch(textState.text.toString()) - } - } - - Crossfade(searchResults != null) { searching -> - if (searching) { - EmojiSearchResults(searchResults) { onSelected(it) } - } else { - EmojiGarden(onClick = onSelected) - } - } - } - } -} - -@Composable -private fun SearchBar( - state: TextFieldState, -) { - TextInput( - modifier = Modifier - .fillMaxWidth() - .padding(top = CodeTheme.dimens.grid.x2) - .padding(horizontal = CodeTheme.dimens.inset), - shape = CodeTheme.shapes.medium, - state = state, - placeholder = stringResource(R.string.action_search), - minHeight = CodeTheme.dimens.grid.x8, - contentPadding = PaddingValues( - start = 8.dp + CodeTheme.dimens.staticGrid.x2, - top = 8.dp, - end = 8.dp + CodeTheme.dimens.staticGrid.x2, - bottom = 8.dp - ), - maxLines = 1, - colors = inputColors( - backgroundColor = Color.Transparent, - textColor = CodeTheme.colors.textMain, - cursorColor = Color.White, - placeholderColor = CodeTheme.colors.textSecondary - ), - trailingIcon = state.text.takeIf { it.isNotBlank() }?.let { - { - Icon( - imageVector = Icons.Filled.Cancel, - contentDescription = "", - tint = CodeTheme.colors.textSecondary, - modifier = Modifier - .padding(end = CodeTheme.dimens.grid.x2) - .wrapContentWidth() - .size(20.dp) - .unboundedClickable { state.clearText() } - ) - } - } - ) -} diff --git a/ui/emojis/.gitignore b/ui/emojis/.gitignore deleted file mode 100644 index 9f2a078806..0000000000 --- a/ui/emojis/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -build/ -.gradle/ diff --git a/ui/emojis/build.gradle.kts b/ui/emojis/build.gradle.kts deleted file mode 100644 index 2ffb05a349..0000000000 --- a/ui/emojis/build.gradle.kts +++ /dev/null @@ -1,29 +0,0 @@ -plugins { - alias(libs.plugins.flipcash.android.library.compose) -} - -android { - namespace = "${Gradle.codeNamespace}.ui.emojis" - - buildFeatures { - buildConfig = true - } -} - -dependencies { - api(project(":libs:emojis")) - implementation(project(":libs:search")) - implementation(project(":ui:core")) - implementation(project(":ui:theme")) - implementation(project(":ui:resources")) - implementation("androidx.emoji2:emoji2:1.7.0") - // Optional: Include a bundled emoji font (offline support) - implementation("androidx.emoji2:emoji2-bundled:1.7.0") - api(libs.androidx.annotation) - api(libs.kotlin.stdlib) - api(libs.kotlinx.coroutines.core) - implementation(libs.compose.ui.tools.preview) - implementation(libs.compose.animation) - implementation(libs.compose.material) - implementation(libs.compose.material.icons.extended) -} diff --git a/ui/emojis/src/main/kotlin/com/getcode/ui/emojis/EmojiCompatController.kt b/ui/emojis/src/main/kotlin/com/getcode/ui/emojis/EmojiCompatController.kt deleted file mode 100644 index cac6c76de4..0000000000 --- a/ui/emojis/src/main/kotlin/com/getcode/ui/emojis/EmojiCompatController.kt +++ /dev/null @@ -1,40 +0,0 @@ -package com.getcode.ui.emojis - -import android.content.Context -import androidx.emoji2.bundled.BundledEmojiCompatConfig -import androidx.emoji2.text.EmojiCompat -import java.util.concurrent.Executors - -object EmojiCompatController { - private var isInitialized = false - - fun init(context: Context) { - if (!isInitialized) { - // Use bundled config for simplicity (offline support) - val config = BundledEmojiCompatConfig(context, Executors.newSingleThreadExecutor()) - .setReplaceAll(true) - - // Alternative: Use downloadable fonts (requires Google Play Services) - /* - val config = FontRequestEmojiCompatConfig( - context, - FontRequest( - "com.google.android.gms.fonts", - "com.google.android.gms", - "Noto Color Emoji Compat", - R.array.com_google_android_gms_fonts_certs - ) - ) - */ - - EmojiCompat.init(config) - isInitialized = true - } - } - - internal fun processEmoji(text: String): CharSequence { - return EmojiCompat.get().process(text) ?: text - } -} - -fun processEmoji(text: String): CharSequence = EmojiCompatController.processEmoji(text) \ No newline at end of file diff --git a/ui/emojis/src/main/kotlin/com/getcode/ui/emojis/EmojiGarden.kt b/ui/emojis/src/main/kotlin/com/getcode/ui/emojis/EmojiGarden.kt deleted file mode 100644 index 48a13d2f85..0000000000 --- a/ui/emojis/src/main/kotlin/com/getcode/ui/emojis/EmojiGarden.kt +++ /dev/null @@ -1,362 +0,0 @@ -package com.getcode.ui.emojis - -import androidx.compose.animation.animateColorAsState -import androidx.compose.animation.core.Animatable -import androidx.compose.animation.core.FastOutSlowInEasing -import androidx.compose.animation.core.tween -import androidx.compose.foundation.background -import androidx.compose.foundation.clickable -import androidx.compose.foundation.interaction.MutableInteractionSource -import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.PaddingValues -import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.navigationBarsPadding -import androidx.compose.foundation.layout.offset -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.size -import androidx.compose.foundation.layout.wrapContentHeight -import androidx.compose.foundation.lazy.grid.GridCells -import androidx.compose.foundation.lazy.grid.GridItemSpan -import androidx.compose.foundation.lazy.grid.LazyVerticalGrid -import androidx.compose.foundation.lazy.grid.items -import androidx.compose.foundation.lazy.grid.rememberLazyGridState -import androidx.compose.material.Icon -import androidx.compose.material.Surface -import androidx.compose.material.Text -import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.outlined.AccessTime -import androidx.compose.material.icons.outlined.EmojiFlags -import androidx.compose.material.icons.outlined.EmojiObjects -import androidx.compose.material.icons.outlined.EmojiSymbols -import androidx.compose.material.icons.outlined.House -import androidx.compose.material.icons.outlined.LunchDining -import androidx.compose.material.icons.outlined.Pets -import androidx.compose.material.icons.outlined.SentimentSatisfied -import androidx.compose.material.icons.outlined.SportsBasketball -import androidx.compose.runtime.Composable -import androidx.compose.runtime.LaunchedEffect -import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableStateMapOf -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.remember -import androidx.compose.runtime.rememberCoroutineScope -import androidx.compose.runtime.setValue -import androidx.compose.runtime.snapshotFlow -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.composed -import androidx.compose.ui.graphics.Color -import androidx.compose.ui.graphics.vector.rememberVectorPainter -import androidx.compose.ui.layout.onGloballyPositioned -import androidx.compose.ui.layout.positionInParent -import androidx.compose.ui.tooling.preview.Preview -import androidx.compose.ui.unit.IntOffset -import androidx.compose.ui.unit.dp -import androidx.compose.ui.util.fastForEach -import androidx.compose.ui.util.fastForEachIndexed -import androidx.compose.ui.zIndex -import com.getcode.libs.emojis.generated.Category -import com.getcode.libs.emojis.generated.Emoji -import com.getcode.libs.emojis.generated.Emojis -import com.getcode.theme.CodeTheme -import com.getcode.theme.DesignSystem -import com.getcode.ui.core.verticalScrollStateGradient -import kotlinx.coroutines.flow.distinctUntilChanged -import kotlinx.coroutines.flow.launchIn -import kotlinx.coroutines.flow.mapNotNull -import kotlinx.coroutines.flow.onEach -import kotlinx.coroutines.launch -import kotlin.math.roundToInt - -@Composable -fun EmojiGarden(onClick: (String) -> Unit) { - val emojis = remember { Emojis.categorizedNoSkinTones } - val allEmojis = remember(emojis) { - emojis.mapValues { it.value.values.toList().flatten() } - } - val categories = emojis.keys.toList() - val usageController = LocalEmojiUsageController.current - var frequentEmojis by remember { mutableStateOf>(emptyList()) } - val composeScope = rememberCoroutineScope() - - LaunchedEffect(usageController) { - frequentEmojis = usageController.mostUsedEmojis(includeFiller = false) - .mapNotNull { em -> allEmojis.values.flatten().firstOrNull { it.unicode == em } } - } - - val categoryOptions = remember(frequentEmojis, categories) { - if (frequentEmojis.isNotEmpty()) { - listOf(Category.FREQUENT) + categories - } else { - categories - } - } - - var selectedCategory by remember(categoryOptions) { mutableStateOf(categoryOptions.first()) } - - val listState = rememberLazyGridState() - - val categoryHeaderIndexMapping = remember(categoryOptions) { - var cumulativeIndex = 0 - val mapping = mutableMapOf() - - // Iterate over each category and accumulate the count - categoryOptions.forEach { category -> - mapping[category] = cumulativeIndex - cumulativeIndex += allEmojis[category]?.count() ?: 0 - } - - mapping - } - - Box(modifier = Modifier.fillMaxSize()) { - LazyVerticalGrid( - modifier = Modifier - .navigationBarsPadding() - .verticalScrollStateGradient(listState, color = CodeTheme.colors.background), - columns = GridCells.Adaptive(CodeTheme.dimens.grid.x8), - contentPadding = PaddingValues( - top = CodeTheme.dimens.grid.x2, - start = CodeTheme.dimens.inset, - end = CodeTheme.dimens.inset, - bottom = CodeTheme.dimens.grid.x10 - ), - state = listState, - horizontalArrangement = Arrangement.spacedBy(CodeTheme.dimens.grid.x1), - verticalArrangement = Arrangement.spacedBy(CodeTheme.dimens.grid.x1), - ) { - if (frequentEmojis.isNotEmpty()) { - item( - span = { GridItemSpan(maxLineSpan) }, - key = "header_${Category.FREQUENT.name}", - contentType = "header" - ) { - Text( - modifier = Modifier - .fillMaxWidth() - .background(CodeTheme.colors.background) - .padding(vertical = CodeTheme.dimens.grid.x2) - .zIndex(1f), // Ensure it stays above other items, - text = Category.FREQUENT.displayName, - style = CodeTheme.typography.textSmall, - color = CodeTheme.colors.textSecondary - ) - } - items(frequentEmojis, key = { "freq_${it.unicode}" }) { emoji -> - EmojiRender( - emoji = emoji.unicode, - showBackground = false, - onClick = { onClick(emoji.unicode) } - ) - } - } - - emojis.entries.toList().fastForEach { (category, emojis) -> - item( - span = { GridItemSpan(maxLineSpan) }, - key = "header_${category.name}", - contentType = "header" - ) { - Text( - modifier = Modifier - .fillMaxWidth() - .background(CodeTheme.colors.background) - .padding(vertical = CodeTheme.dimens.grid.x2) - .zIndex(1f), // Ensure it stays above other items, - text = category.displayName, - style = CodeTheme.typography.textSmall, - color = CodeTheme.colors.textSecondary - ) - } - items(emojis.values.flatten(), key = { "${category.name}_${it.unicode}" }) { (emoji, _) -> - EmojiRender( - emoji = emoji, - showBackground = false, - onClick = { onClick(emoji) } - ) - } - } - } - - EmojiCategorySelection( - modifier = Modifier - .align(Alignment.BottomCenter) - .padding(horizontal = CodeTheme.dimens.inset) - .navigationBarsPadding() - .padding(bottom = CodeTheme.dimens.inset) - .fillMaxWidth(), - categories = categoryOptions, - selectedCategory = selectedCategory, - onSelection = { category -> - selectedCategory = category - - // Find the first emoji index for this category - val targetIndex = categoryHeaderIndexMapping[category] - - if (targetIndex != null && targetIndex >= 0) { - composeScope.launch { - listState.scrollToItem(targetIndex) - } - } - } - ) - - LaunchedEffect(frequentEmojis) { - listState.scrollToItem(0) - } - - LaunchedEffect(categoryOptions) { - snapshotFlow { listState.layoutInfo } - .mapNotNull { layoutInfo -> - val visibleItems = layoutInfo.visibleItemsInfo - - if (visibleItems.isEmpty()) { - return@mapNotNull null - } - - val lastHeader = visibleItems - .filter { it.key.toString().startsWith("header_") } - .minByOrNull { it.index } // Last visible header - - val lastPassedHeader = if (lastHeader != null) { - val headerKey = lastHeader.key.toString().removePrefix("header_") - categoryOptions.find { it.name == headerKey } - } else { - // If no header is visible, find the last header before the first visible item - val firstVisibleIndex = visibleItems.first().index - (0 until firstVisibleIndex) - .mapNotNull { index -> - val item = listState.layoutInfo.visibleItemsInfo.find { it.index == index } - item?.takeIf { it.key.toString().startsWith("header_") } - } - .maxByOrNull { it.index } - ?.let { lastHeaderItem -> - val headerKey = lastHeaderItem.key.toString().removePrefix("header_") - categoryOptions.find { it.name == headerKey } - } - } - - lastPassedHeader - } - .distinctUntilChanged() - .onEach { newCategory -> - selectedCategory = newCategory - }.launchIn(this) - } - } -} - -@Composable -private fun EmojiCategorySelection( - modifier: Modifier = Modifier, - categories: List, - selectedCategory: Category, - onSelection: (Category) -> Unit, -) { - val itemPositions = remember(categories) { mutableStateMapOf() } - val animatedOffset = remember(categories) { Animatable(0f) } - - Surface( - modifier = modifier - .wrapContentHeight() - .background(CodeTheme.colors.surface.copy(0.87f), CodeTheme.shapes.small), - color = Color.Transparent, - ) { - Box { - Box( - modifier = Modifier - .padding(horizontal = CodeTheme.dimens.grid.x1) - .offset { - IntOffset( - x = animatedOffset.value.roundToInt(), - y = 0 - ) - } - .size(CodeTheme.dimens.grid.x7) - .padding(vertical = 2.dp) - .background( - color = CodeTheme.colors.tertiary.copy(0.54f), - shape = CodeTheme.shapes.small - ) - ) - - Row( - modifier = Modifier - .fillMaxWidth() - .padding( - horizontal = CodeTheme.dimens.grid.x2, - vertical = CodeTheme.dimens.grid.x1, - ), - verticalAlignment = Alignment.CenterVertically, - horizontalArrangement = Arrangement.SpaceBetween - ) { - categories.fastForEachIndexed { index, category -> - val painter = when (category) { - Category.SMILEYSPEOPLE -> rememberVectorPainter(Icons.Outlined.SentimentSatisfied) - Category.ANIMALSNATURE -> rememberVectorPainter(Icons.Outlined.Pets) - Category.FOODDRINK -> rememberVectorPainter(Icons.Outlined.LunchDining) - Category.TRAVELPLACES -> rememberVectorPainter(Icons.Outlined.House) - Category.ACTIVITIES -> rememberVectorPainter(Icons.Outlined.SportsBasketball) - Category.OBJECTS -> rememberVectorPainter(Icons.Outlined.EmojiObjects) - Category.SYMBOLS -> rememberVectorPainter(Icons.Outlined.EmojiSymbols) - Category.FLAGS -> rememberVectorPainter(Icons.Outlined.EmojiFlags) - Category.FREQUENT -> rememberVectorPainter(Icons.Outlined.AccessTime) - } - - val contentColor by animateColorAsState( - if (category == selectedCategory) { - CodeTheme.colors.textMain - } else { - CodeTheme.colors.textSecondary - } - ) - - Icon( - painter = painter, - contentDescription = null, - tint = contentColor, - modifier = Modifier - .noRippleClickable { onSelection(category) } - .onGloballyPositioned { coordinates -> - // Store the x position of each item - itemPositions[index] = coordinates.positionInParent().x - } - ) - } - } - } - - // Animate the background when selection changes - LaunchedEffect(selectedCategory) { - val selectedIndex = categories.indexOf(selectedCategory) - if (selectedIndex >= 0 && itemPositions.containsKey(selectedIndex)) { - animatedOffset.animateTo( - targetValue = itemPositions[selectedIndex] ?: 0f, - animationSpec = tween( - durationMillis = 300, - easing = FastOutSlowInEasing - ) - ) - } - } - } -} - -private fun Modifier.noRippleClickable(enabled: Boolean = true, onClick: () -> Unit) = composed { - this.clickable( - enabled = enabled, - indication = null, - interactionSource = remember { MutableInteractionSource() }, - onClick = onClick - ) -} -@Preview -@Composable -private fun Preview_EmojiGarden() { - DesignSystem { - EmojiGarden { } - } -} \ No newline at end of file diff --git a/ui/emojis/src/main/kotlin/com/getcode/ui/emojis/EmojiLocal.kt b/ui/emojis/src/main/kotlin/com/getcode/ui/emojis/EmojiLocal.kt deleted file mode 100644 index 4d4b4cb983..0000000000 --- a/ui/emojis/src/main/kotlin/com/getcode/ui/emojis/EmojiLocal.kt +++ /dev/null @@ -1,7 +0,0 @@ -package com.getcode.ui.emojis - -import androidx.compose.runtime.staticCompositionLocalOf -import com.getcode.libs.emojis.EmojiUsageTracker -import com.getcode.libs.emojis.StubEmojiTracker - -val LocalEmojiUsageController = staticCompositionLocalOf { StubEmojiTracker } \ No newline at end of file diff --git a/ui/emojis/src/main/kotlin/com/getcode/ui/emojis/EmojiRender.kt b/ui/emojis/src/main/kotlin/com/getcode/ui/emojis/EmojiRender.kt deleted file mode 100644 index c1b36f406a..0000000000 --- a/ui/emojis/src/main/kotlin/com/getcode/ui/emojis/EmojiRender.kt +++ /dev/null @@ -1,72 +0,0 @@ -package com.getcode.ui.emojis - -import androidx.compose.foundation.Canvas -import androidx.compose.foundation.background -import androidx.compose.foundation.clickable -import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.layout.size -import androidx.compose.foundation.shape.CircleShape -import androidx.compose.runtime.Composable -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.draw.clip -import androidx.compose.ui.geometry.Offset -import androidx.compose.ui.graphics.Color -import androidx.compose.ui.platform.LocalInspectionMode -import androidx.compose.ui.text.TextStyle -import androidx.compose.ui.text.drawText -import androidx.compose.ui.text.rememberTextMeasurer -import androidx.compose.ui.unit.DpSize -import androidx.compose.ui.unit.TextUnit -import androidx.compose.ui.unit.sp -import com.getcode.theme.CodeTheme - -@Composable -fun EmojiRender( - emoji: String, - modifier: Modifier = Modifier, - size: DpSize = DpSize( - width = CodeTheme.dimens.grid.x10, - height = CodeTheme.dimens.grid.x9, - ), - textSize: TextUnit = 28.sp, - showBackground: Boolean = true, - onClick: () -> Unit -) { - val textMeasurer = rememberTextMeasurer() - - Box( - contentAlignment = Alignment.Center, - modifier = modifier - .size(size) - .clip(CircleShape) - .clickable { onClick() } - .background( - color = if (showBackground) CodeTheme.colors.brandDark else Color.Transparent, - shape = CircleShape - ) - ) { - - val emojiText = if (LocalInspectionMode.current) { - emoji - } else { - processEmoji(emoji).toString() - } - - Canvas(modifier = Modifier.fillMaxSize()) { - val measuredText = textMeasurer.measure( - text = emojiText, - style = TextStyle(fontSize = textSize) - ) - drawText( - textLayoutResult = measuredText, - topLeft = Offset( - x = (this.size.width - measuredText.size.width) / 2, - y = (this.size.height - measuredText.size.height) / 2 - ), - color = Color.White - ) - } - } -} \ No newline at end of file diff --git a/ui/emojis/src/main/kotlin/com/getcode/ui/emojis/EmojiSearch.kt b/ui/emojis/src/main/kotlin/com/getcode/ui/emojis/EmojiSearch.kt deleted file mode 100644 index 7d033dfe26..0000000000 --- a/ui/emojis/src/main/kotlin/com/getcode/ui/emojis/EmojiSearch.kt +++ /dev/null @@ -1,84 +0,0 @@ -package com.getcode.ui.emojis - -import androidx.compose.foundation.clickable -import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.PaddingValues -import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.WindowInsets -import androidx.compose.foundation.layout.asPaddingValues -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.navigationBars -import androidx.compose.foundation.lazy.LazyColumn -import androidx.compose.foundation.lazy.items -import androidx.compose.foundation.lazy.rememberLazyListState -import androidx.compose.material.Text -import androidx.compose.runtime.Composable -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.platform.LocalInspectionMode -import androidx.compose.ui.text.font.FontWeight -import com.getcode.libs.emojis.generated.Emoji -import com.getcode.libs.search.levenshtein -import com.getcode.theme.CodeTheme -import com.getcode.ui.core.verticalScrollStateGradient - -fun List.fuzzySearch(query: String): List { - return map { emoji -> - emoji to (emoji.keywords.minOfOrNull { keyword -> - if (keyword.lowercase().startsWith(query.lowercase())) return@minOfOrNull 0 - if (keyword.lowercase().contains(query.lowercase())) return@minOfOrNull 0 - levenshtein(query, keyword) - } ?: Int.MAX_VALUE) - }.filter { it.second < 3 }.sortedBy { it.second }.map { it.first } -} - -@Composable -fun EmojiSearchResults( - results: List?, - onSelected: (String) -> Unit, -) { - val state = rememberLazyListState() - LazyColumn( - modifier = Modifier - .fillMaxWidth() - .verticalScrollStateGradient(state, CodeTheme.colors.background), - state = state, - verticalArrangement = Arrangement.spacedBy(CodeTheme.dimens.grid.x3), - contentPadding = PaddingValues( - top = CodeTheme.dimens.inset, - start = CodeTheme.dimens.inset, - end = CodeTheme.dimens.inset, - bottom = WindowInsets.navigationBars.asPaddingValues().calculateBottomPadding() + CodeTheme.dimens.grid.x2 - ) - ) { - items( - items = results.orEmpty(), - key = { it.unicode }, - ) { emoji -> - Row( - modifier = Modifier - .clickable { onSelected(emoji.unicode) }, - verticalAlignment = Alignment.CenterVertically, - horizontalArrangement = Arrangement.spacedBy(CodeTheme.dimens.grid.x2) - ) { - val emojiText = if (LocalInspectionMode.current) { - emoji.unicode - } else { - processEmoji(emoji.unicode).toString() - } - - Text( - text = emojiText, - color = CodeTheme.colors.textMain, - style = CodeTheme.typography.textLarge.copy(fontWeight = FontWeight.W700), - ) - - Text( - text = emoji.name, - color = CodeTheme.colors.textSecondary, - style = CodeTheme.typography.textMedium.copy(fontWeight = FontWeight.W500) - ) - } - } - } -} \ No newline at end of file diff --git a/ui/emojis/src/main/kotlin/com/getcode/ui/emojis/FrequentlyUsedEmojis.kt b/ui/emojis/src/main/kotlin/com/getcode/ui/emojis/FrequentlyUsedEmojis.kt deleted file mode 100644 index 2f3a7fc607..0000000000 --- a/ui/emojis/src/main/kotlin/com/getcode/ui/emojis/FrequentlyUsedEmojis.kt +++ /dev/null @@ -1,90 +0,0 @@ -package com.getcode.ui.emojis - -import androidx.compose.foundation.Image -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.PaddingValues -import androidx.compose.foundation.layout.height -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.size -import androidx.compose.foundation.lazy.grid.GridCells -import androidx.compose.foundation.lazy.grid.LazyHorizontalGrid -import androidx.compose.foundation.lazy.grid.items -import androidx.compose.foundation.shape.CircleShape -import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.filled.AddReaction -import androidx.compose.runtime.Composable -import androidx.compose.runtime.LaunchedEffect -import androidx.compose.runtime.mutableStateListOf -import androidx.compose.runtime.remember -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.draw.clip -import androidx.compose.ui.graphics.ColorFilter -import androidx.compose.ui.unit.DpSize -import androidx.compose.ui.unit.sp -import com.getcode.theme.CodeTheme - -@Composable -fun FrequentlyUsedEmojis( - modifier: Modifier = Modifier, - onSelect: (String) -> Unit, - onViewAll: () -> Unit, -) { - val usageController = LocalEmojiUsageController.current - val frequentlyUsedEmojis = remember { mutableStateListOf() } - - LaunchedEffect(Unit) { - frequentlyUsedEmojis.addAll(usageController.mostUsedEmojis()) - } - - LazyHorizontalGrid( - rows = GridCells.Adaptive(CodeTheme.dimens.grid.x10), - horizontalArrangement = Arrangement.SpaceBetween, - verticalArrangement = Arrangement.Center, - modifier = modifier - .padding(vertical = CodeTheme.dimens.grid.x2) - .height(CodeTheme.dimens.grid.x9), - contentPadding = PaddingValues(horizontal = CodeTheme.dimens.inset) - ) { - items( - items = frequentlyUsedEmojis, - key = { it }, - ) { emoji -> - EmojiRender( - emoji = emoji, - size = DpSize( - width = CodeTheme.dimens.grid.x9, - height = CodeTheme.dimens.grid.x8 - ), - textSize = 24.sp, - onClick = { onSelect(emoji) } - ) - } - item { - Box( - contentAlignment = Alignment.Center, - modifier = modifier - .size( - width = CodeTheme.dimens.grid.x9, - height = CodeTheme.dimens.grid.x8, - ) - .clip(CircleShape) - .clickable { onViewAll() } - .background( - color = CodeTheme.colors.brandDark, - shape = CircleShape - ).padding(CodeTheme.dimens.grid.x2) - ) { - Image( - modifier = Modifier.matchParentSize(), - imageVector = Icons.Default.AddReaction, - contentDescription = null, - colorFilter = ColorFilter.tint(CodeTheme.colors.textSecondary) - ) - } - } - } -}