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)
- )
- }
- }
- }
-}