From 138d3a78863e8a1f201ad5c2b1d66e557449dca7 Mon Sep 17 00:00:00 2001 From: abb128 <65567823+abb128@users.noreply.github.com> Date: Tue, 15 Aug 2023 19:48:27 +0300 Subject: [PATCH] Replace SuggestionStripView with ActionBar --- build.gradle | 3 + java/res/drawable/chevron_right.xml | 13 + java/res/drawable/mic_fill.xml | 39 ++ java/res/drawable/top_suggestion.xml | 15 + java/res/layout/main_keyboard_frame.xml | 11 - .../org/futo/inputmethod/latin/InputView.java | 24 +- .../org/futo/inputmethod/latin/LatinIME.kt | 34 +- .../inputmethod/latin/LatinIMELegacy.java | 25 +- .../futo/inputmethod/latin/uix/ActionBar.kt | 402 ++++++++++++++++++ .../futo/inputmethod/latin/uix/theme/Color.kt | 292 +++++++++++++ .../futo/inputmethod/latin/uix/theme/Theme.kt | 77 ++++ .../futo/inputmethod/latin/uix/theme/Type.kt | 34 ++ .../latin/LatinIMELegacyForTests.java | 2 +- 13 files changed, 920 insertions(+), 51 deletions(-) create mode 100644 java/res/drawable/chevron_right.xml create mode 100644 java/res/drawable/mic_fill.xml create mode 100644 java/res/drawable/top_suggestion.xml create mode 100644 java/src/org/futo/inputmethod/latin/uix/ActionBar.kt create mode 100644 java/src/org/futo/inputmethod/latin/uix/theme/Color.kt create mode 100644 java/src/org/futo/inputmethod/latin/uix/theme/Theme.kt create mode 100644 java/src/org/futo/inputmethod/latin/uix/theme/Type.kt diff --git a/build.gradle b/build.gradle index 907ce24cd..7c422a2ce 100644 --- a/build.gradle +++ b/build.gradle @@ -128,6 +128,9 @@ dependencies { implementation 'com.google.code.findbugs:jsr305:3.0.2' + debugImplementation 'androidx.compose.ui:ui-tooling' + debugImplementation 'androidx.compose.ui:ui-test-manifest' + testImplementation 'junit:junit:4.13.2' androidTestImplementation "org.mockito:mockito-core:1.9.5" androidTestImplementation 'com.google.dexmaker:dexmaker:1.2' diff --git a/java/res/drawable/chevron_right.xml b/java/res/drawable/chevron_right.xml new file mode 100644 index 000000000..a333ad413 --- /dev/null +++ b/java/res/drawable/chevron_right.xml @@ -0,0 +1,13 @@ + + + diff --git a/java/res/drawable/mic_fill.xml b/java/res/drawable/mic_fill.xml new file mode 100644 index 000000000..b866a57d0 --- /dev/null +++ b/java/res/drawable/mic_fill.xml @@ -0,0 +1,39 @@ + + + + + + + diff --git a/java/res/drawable/top_suggestion.xml b/java/res/drawable/top_suggestion.xml new file mode 100644 index 000000000..2e02a2679 --- /dev/null +++ b/java/res/drawable/top_suggestion.xml @@ -0,0 +1,15 @@ + + + + + diff --git a/java/res/layout/main_keyboard_frame.xml b/java/res/layout/main_keyboard_frame.xml index dc5ba7819..412e89a8a 100644 --- a/java/res/layout/main_keyboard_frame.xml +++ b/java/res/layout/main_keyboard_frame.xml @@ -24,17 +24,6 @@ android:layout_height="wrap_content" android:layout_gravity="bottom" android:orientation="vertical" > - - - - mActiveForwarder; public InputView(final Context context, final AttributeSet attrs) { @@ -41,17 +39,11 @@ public final class InputView extends FrameLayout { @Override protected void onFinishInflate() { - final SuggestionStripView suggestionStripView = - (SuggestionStripView)findViewById(R.id.suggestion_strip_view); mMainKeyboardView = (MainKeyboardView)findViewById(R.id.keyboard_view); - mKeyboardTopPaddingForwarder = new KeyboardTopPaddingForwarder( - mMainKeyboardView, suggestionStripView); - mMoreSuggestionsViewCanceler = new MoreSuggestionsViewCanceler( - mMainKeyboardView, suggestionStripView); } public void setKeyboardTopPadding(final int keyboardTopPadding) { - mKeyboardTopPaddingForwarder.setKeyboardTopPadding(keyboardTopPadding); + } @Override @@ -73,20 +65,6 @@ public final class InputView extends FrameLayout { final int x = (int)me.getX(index) + rect.left; final int y = (int)me.getY(index) + rect.top; - // The touch events that hit the top padding of keyboard should be forwarded to - // {@link SuggestionStripView}. - if (mKeyboardTopPaddingForwarder.onInterceptTouchEvent(x, y, me)) { - mActiveForwarder = mKeyboardTopPaddingForwarder; - return true; - } - - // To cancel {@link MoreSuggestionsView}, we should intercept a touch event to - // {@link MainKeyboardView} and dismiss the {@link MoreSuggestionsView}. - if (mMoreSuggestionsViewCanceler.onInterceptTouchEvent(x, y, me)) { - mActiveForwarder = mMoreSuggestionsViewCanceler; - return true; - } - mActiveForwarder = null; return false; } diff --git a/java/src/org/futo/inputmethod/latin/LatinIME.kt b/java/src/org/futo/inputmethod/latin/LatinIME.kt index b0ed92e21..46f7b9d27 100644 --- a/java/src/org/futo/inputmethod/latin/LatinIME.kt +++ b/java/src/org/futo/inputmethod/latin/LatinIME.kt @@ -13,11 +13,7 @@ import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Spacer import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Surface -import androidx.compose.material3.Text -import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.key -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.remember import androidx.compose.ui.Modifier import androidx.compose.ui.layout.onSizeChanged import androidx.compose.ui.platform.ComposeView @@ -37,9 +33,13 @@ import androidx.savedstate.SavedStateRegistryController import androidx.savedstate.SavedStateRegistryOwner import androidx.savedstate.findViewTreeSavedStateRegistryOwner import androidx.savedstate.setViewTreeSavedStateRegistryOwner +import org.futo.inputmethod.latin.uix.ActionBar -class LatinIME : InputMethodService(), LifecycleOwner, ViewModelStoreOwner, SavedStateRegistryOwner { - private val latinIMELegacy = LatinIMELegacy(this as InputMethodService) +class LatinIME : InputMethodService(), LifecycleOwner, ViewModelStoreOwner, SavedStateRegistryOwner, LatinIMELegacy.SuggestionStripController { + private val latinIMELegacy = LatinIMELegacy( + this as InputMethodService, + this as LatinIMELegacy.SuggestionStripController + ) private val mSavedStateRegistryController = SavedStateRegistryController.create(this) @@ -115,6 +115,8 @@ class LatinIME : InputMethodService(), LifecycleOwner, ViewModelStoreOwner, Save return composeView!! } + private var shouldShowSuggestionStrip: Boolean = true + private var suggestedWords: SuggestedWords? = null private fun setContent() { composeView?.setContent { Column { @@ -123,6 +125,12 @@ class LatinIME : InputMethodService(), LifecycleOwner, ViewModelStoreOwner, Save touchableHeight = it.height }, color = MaterialTheme.colorScheme.surface) { Column { + if(shouldShowSuggestionStrip) { + ActionBar( + suggestedWords, + latinIMELegacy + ) + } key(legacyInputView) { AndroidView(factory = { legacyInputView!! @@ -276,4 +284,18 @@ class LatinIME : InputMethodService(), LifecycleOwner, ViewModelStoreOwner, Save override fun onKeyUp(keyCode: Int, event: KeyEvent?): Boolean { return latinIMELegacy.onKeyUp(keyCode, event) || super.onKeyUp(keyCode, event) } + + override fun updateVisibility(shouldShowSuggestionsStrip: Boolean, fullscreenMode: Boolean) { + this.shouldShowSuggestionStrip = shouldShowSuggestionsStrip + setContent() + } + + override fun setSuggestions(suggestedWords: SuggestedWords?, rtlSubtype: Boolean) { + this.suggestedWords = suggestedWords + setContent() + } + + override fun maybeShowImportantNoticeTitle(): Boolean { + return false + } } \ No newline at end of file diff --git a/java/src/org/futo/inputmethod/latin/LatinIMELegacy.java b/java/src/org/futo/inputmethod/latin/LatinIMELegacy.java index 8ba7f0536..a5c35065c 100644 --- a/java/src/org/futo/inputmethod/latin/LatinIMELegacy.java +++ b/java/src/org/futo/inputmethod/latin/LatinIMELegacy.java @@ -119,6 +119,14 @@ public class LatinIMELegacy implements KeyboardActionListener, DictionaryFacilitator.DictionaryInitializationListener, PermissionsManager.PermissionsResultCallback { + public interface SuggestionStripController { + public void updateVisibility(boolean shouldShowSuggestionsStrip, boolean fullscreenMode); + + public void setSuggestions(SuggestedWords suggestedWords, boolean rtlSubtype); + + public boolean maybeShowImportantNoticeTitle(); + } + private final InputMethodService mInputMethodService; static final String TAG = LatinIMELegacy.class.getSimpleName(); @@ -160,7 +168,7 @@ public class LatinIMELegacy implements KeyboardActionListener, private View mInputView; private View mComposeInputView; private InsetsUpdater mInsetsUpdater; - private SuggestionStripView mSuggestionStripView; + private final SuggestionStripController mSuggestionStripController; private RichInputMethodManager mRichImm; @UsedForTesting final KeyboardSwitcher mKeyboardSwitcher; @@ -586,9 +594,10 @@ public class LatinIMELegacy implements KeyboardActionListener, JniUtils.loadNativeLibrary(); } - public LatinIMELegacy(InputMethodService inputMethodService) { + public LatinIMELegacy(InputMethodService inputMethodService, SuggestionStripController suggestionStripController) { super(); mInputMethodService = inputMethodService; + mSuggestionStripController = suggestionStripController; mSettings = Settings.getInstance(); mKeyboardSwitcher = KeyboardSwitcher.getInstance(); mStatsUtilsManager = StatsUtilsManager.getInstance(); @@ -846,10 +855,6 @@ public class LatinIMELegacy implements KeyboardActionListener, public void setInputView(final View view) { mInputView = view; - mSuggestionStripView = (SuggestionStripView)view.findViewById(R.id.suggestion_strip_view); - if (hasSuggestionStripView()) { - mSuggestionStripView.setListener(this, view); - } } public void setCandidatesView(final View view) { @@ -1495,7 +1500,7 @@ public class LatinIMELegacy implements KeyboardActionListener, } public boolean hasSuggestionStripView() { - return null != mSuggestionStripView; + return null != mSuggestionStripController; } private void setSuggestedWords(final SuggestedWords suggestedWords) { @@ -1520,7 +1525,7 @@ public class LatinIMELegacy implements KeyboardActionListener, || currentSettingsValues.isApplicationSpecifiedCompletionsOn(); final boolean shouldShowSuggestionsStrip = shouldShowSuggestionsStripUnlessPassword && !currentSettingsValues.mInputAttributes.mIsPasswordField; - mSuggestionStripView.updateVisibility(shouldShowSuggestionsStrip, mInputMethodService.isFullscreenMode()); + mSuggestionStripController.updateVisibility(shouldShowSuggestionsStrip, mInputMethodService.isFullscreenMode()); if (!shouldShowSuggestionsStrip) { return; } @@ -1536,7 +1541,7 @@ public class LatinIMELegacy implements KeyboardActionListener, final boolean noSuggestionsToOverrideImportantNotice = noSuggestionsFromDictionaries || isBeginningOfSentencePrediction; if (shouldShowImportantNotice && noSuggestionsToOverrideImportantNotice) { - if (mSuggestionStripView.maybeShowImportantNoticeTitle()) { + if (mSuggestionStripController.maybeShowImportantNoticeTitle()) { return; } } @@ -1545,7 +1550,7 @@ public class LatinIMELegacy implements KeyboardActionListener, || currentSettingsValues.isApplicationSpecifiedCompletionsOn() // We should clear the contextual strip if there is no suggestion from dictionaries. || noSuggestionsFromDictionaries) { - mSuggestionStripView.setSuggestions(suggestedWords, + mSuggestionStripController.setSuggestions(suggestedWords, mRichImm.getCurrentSubtype().isRtlSubtype()); } } diff --git a/java/src/org/futo/inputmethod/latin/uix/ActionBar.kt b/java/src/org/futo/inputmethod/latin/uix/ActionBar.kt new file mode 100644 index 000000000..36b0d7b30 --- /dev/null +++ b/java/src/org/futo/inputmethod/latin/uix/ActionBar.kt @@ -0,0 +1,402 @@ +package org.futo.inputmethod.latin.uix + +import androidx.compose.foundation.Canvas +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.RowScope +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxHeight +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.width +import androidx.compose.material3.ButtonColors +import androidx.compose.material3.ButtonDefaults +import androidx.compose.material3.Divider +import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton +import androidx.compose.material3.LocalTextStyle +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Surface +import androidx.compose.material3.Text +import androidx.compose.material3.TextButton +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.ui.Alignment +import androidx.compose.ui.Alignment.Companion.CenterVertically +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.alpha +import androidx.compose.ui.draw.drawBehind +import androidx.compose.ui.draw.rotate +import androidx.compose.ui.geometry.CornerRadius +import androidx.compose.ui.geometry.Offset +import androidx.compose.ui.geometry.Size +import androidx.compose.ui.graphics.RectangleShape +import androidx.compose.ui.graphics.drawscope.scale +import androidx.compose.ui.graphics.drawscope.translate +import androidx.compose.ui.platform.LocalDensity +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.text.AnnotatedString +import androidx.compose.ui.text.ExperimentalTextApi +import androidx.compose.ui.text.TextStyle +import androidx.compose.ui.text.drawText +import androidx.compose.ui.text.font.FontFamily +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.rememberTextMeasurer +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.text.style.TextOverflow +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.Constraints +import androidx.compose.ui.unit.Dp +import androidx.compose.ui.unit.IntSize +import androidx.compose.ui.unit.LayoutDirection +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import org.futo.inputmethod.latin.R +import org.futo.inputmethod.latin.SuggestedWords +import org.futo.inputmethod.latin.SuggestedWords.SuggestedWordInfo +import org.futo.inputmethod.latin.common.Constants +import org.futo.inputmethod.latin.suggestions.SuggestionStripView +import org.futo.inputmethod.latin.uix.theme.Typography +import org.futo.inputmethod.latin.uix.theme.WhisperVoiceInputTheme +import java.lang.Float.max +import java.lang.IndexOutOfBoundsException +import java.lang.Integer.min +import kotlin.math.ceil +import kotlin.math.roundToInt + +/* + * The UIX Action Bar is intended to replace the previous top bar of the AOSP keyboard. + * Its goal is to function similar to the old top bar by showing predictions, but also modernize + * it with actions and new features. + * + * Example bar: + * [>] word1 | word2 | word3 [mic] + * + * The [>] button expands the action bar, replacing word predictions with actions the user can take. + * Actions have little icons which perform an action. Some examples: + * - Microphone: opens the voice input menu + * - Undo/Redo + * - Text editing: switches to the text editing menu + * - Settings: opens the keyboard settings menu + * - Report problem: opens the report menu + * + * Generally there are a few kinds of actions: + * - Take an action on the text being typed (undo/redo) + * - Switch from the keyboard UI to something else (voice input, text editing) + * - Open an app (settings, report) + * + * The UIX effort is to modernize the AOSP Keyboard by replacing and extending + * parts of it with UI written in Android Compose, while keeping most of the + * battle-tested original keyboard code the same + * + * TODO: Will need to make RTL languages work + */ + +val exampleSuggestionsList = arrayListOf( + SuggestedWordInfo("verylongword123", "", 100, 1, null, 0, 0), + SuggestedWordInfo("world understanding of patience", "", 99, 1, null, 0, 0), + SuggestedWordInfo("b", "", 98, 1, null, 0, 0), + SuggestedWordInfo("extra1", "", 97, 1, null, 0, 0), + SuggestedWordInfo("extra2", "", 96, 1, null, 0, 0), + SuggestedWordInfo("extra3", "", 95, 1, null, 0, 0) +) + +val exampleSuggestedWords = SuggestedWords( + exampleSuggestionsList, + exampleSuggestionsList, + exampleSuggestionsList[0], + true, + true, + false, + 0, + 0 +) + +val exampleSuggestedWordsEmpty = SuggestedWords( + arrayListOf(), + arrayListOf(), + exampleSuggestionsList[0], + true, + true, + false, + 0, + 0 +) + +val suggestionStylePrimary = TextStyle( + fontFamily = FontFamily.SansSerif, + fontWeight = FontWeight.Medium, + fontSize = 18.sp, + lineHeight = 26.sp, + letterSpacing = 0.5.sp, + //textAlign = TextAlign.Center +) + +val suggestionStyleAlternative = TextStyle( + fontFamily = FontFamily.SansSerif, + fontWeight = FontWeight.Normal, + fontSize = 18.sp, + lineHeight = 26.sp, + letterSpacing = 0.5.sp, + //textAlign = TextAlign.Center +) + + +@OptIn(ExperimentalTextApi::class) +@Composable fun RowScope.SuggestionItem(words: SuggestedWords, idx: Int, onClick: () -> Unit) { + val word = try { + words.getWord(idx) + } catch(e: IndexOutOfBoundsException) { + null + } + + val topSuggestionIcon = painterResource(id = R.drawable.top_suggestion) + val textButtonModifier = when (idx) { + 0 -> Modifier.drawBehind { + with(topSuggestionIcon) { + val iconSize = topSuggestionIcon.intrinsicSize + translate( + left = (size.width - iconSize.width) / 2.0f, + top = size.height - iconSize.height * 2.0f + ) { + draw(topSuggestionIcon.intrinsicSize) + } + } + } + + else -> Modifier + } + + val textModifier = when (idx) { + 0 -> Modifier + else -> Modifier.alpha(0.75f) + } + + val textStyle = when (idx) { + 0 -> suggestionStylePrimary + else -> suggestionStyleAlternative + }.copy(color = MaterialTheme.colorScheme.onPrimary) + + TextButton( + onClick = onClick, + modifier = textButtonModifier + .weight(1.0f) + .fillMaxHeight(), + shape = RectangleShape, + colors = ButtonDefaults.textButtonColors(contentColor = MaterialTheme.colorScheme.onSurface), + enabled = word != null + ) { + if(word != null) { + val measurer = rememberTextMeasurer() + + Canvas(modifier = textModifier.fillMaxSize()) { + val measurement = measurer.measure( + text = AnnotatedString(word), + style = textStyle, + overflow = TextOverflow.Visible, + softWrap = false, + maxLines = 1, + constraints = Constraints( + maxWidth = Int.MAX_VALUE, + maxHeight = ceil(this.size.height).roundToInt() + ), + layoutDirection = LayoutDirection.Ltr, + density = this + ) + + val scale = Math.min(1.0f, size.width / measurement.size.width) + + translate(left = (scale * (size.width - measurement.size.width)) / 2.0f) { + scale(scaleX = scale, scaleY = 1.0f) { + drawText( + measurement + ) + } + } + } + /* + Text( + word, + modifier = textModifier, + style = textStyle, + overflow = TextOverflow.Visible, + softWrap = false, + maxLines = 1 + ) + */ + } + } +} + +@Composable fun RowScope.SuggestionSeparator() { + Box( + modifier = Modifier + .fillMaxHeight(0.66f) + .align(CenterVertically) + .background(color = MaterialTheme.colorScheme.outline) + .width((1f / LocalDensity.current.density).dp) + ) +} + + + +// Show the most probable in the middle, then left, then right +val ORDER_OF_SUGGESTIONS = listOf(1, 0, 2) + +@Composable +fun RowScope.SuggestionItems(words: SuggestedWords, onClick: (i: Int) -> Unit) { + val maxSuggestions = min(ORDER_OF_SUGGESTIONS.size, words.size()) + + if(maxSuggestions == 0) { + Spacer(modifier = Modifier.weight(1.0f)) + return + } + + for (i in 0 until maxSuggestions) { + val remapped = ORDER_OF_SUGGESTIONS[i] + + SuggestionItem(words, remapped) { onClick(remapped) } + + if (i < maxSuggestions - 1) SuggestionSeparator() + } +} + + +@Composable +fun RowScope.ActionItem() { + val col = MaterialTheme.colorScheme.secondary + IconButton(onClick = { /*TODO*/ }, modifier = Modifier + .drawBehind { + val radius = size.height / 4.0f + drawRoundRect( + col, + topLeft = Offset(size.width * 0.1f, size.height * 0.1f), + size = Size(size.width * 0.8f, size.height * 0.8f), + cornerRadius = CornerRadius(radius, radius) + ) + } + .width(50.dp) + .fillMaxHeight()) { + Icon( + painter = painterResource(id = R.drawable.mic_fill), + contentDescription = "Voice Input" + ) + } + +} + +@Composable +fun RowScope.ActionItems() { + + ActionItem() + ActionItem() + ActionItem() + + + Spacer(modifier = Modifier.weight(1.0f)) +} + +class ExampleListener : SuggestionStripView.Listener { + override fun showImportantNoticeContents() { + } + + override fun pickSuggestionManually(word: SuggestedWordInfo?) { + } + + override fun onCodeInput(primaryCode: Int, x: Int, y: Int, isKeyRepeat: Boolean) { + } + +} + +@Composable +@Preview +fun ActionBar( + words: SuggestedWords? = exampleSuggestedWords, + suggestionStripListener: SuggestionStripView.Listener = ExampleListener() +) { + val isActionsOpen = remember { mutableStateOf(false) } + val actionsForcedOpenByUser = remember { mutableStateOf(false) } + + //LaunchedEffect(words?.isEmpty) { + // isActionsOpen.value = actionsForcedOpenByUser.value || words == null || words.isEmpty + //} + + LaunchedEffect(actionsForcedOpenByUser.value) { + isActionsOpen.value = actionsForcedOpenByUser.value //|| (words == null || words.isEmpty) + } + + WhisperVoiceInputTheme { + Surface(modifier = Modifier + .fillMaxWidth() + .height(40.dp), color = MaterialTheme.colorScheme.surface) + { + Row { + val moreActionsColor = MaterialTheme.colorScheme.primary + + val moreActionsFill = if(isActionsOpen.value) { + MaterialTheme.colorScheme.primary + } else { + MaterialTheme.colorScheme.surface + } + IconButton( + onClick = { actionsForcedOpenByUser.value = !actionsForcedOpenByUser.value }, + modifier = Modifier + .width(42.dp) + .rotate( + if (isActionsOpen.value) { + 180.0f + } else { + 0.0f + } + ) + .fillMaxHeight() + .drawBehind { + drawCircle(color = moreActionsColor, radius = size.width / 3.0f + 1.0f) + drawCircle(color = moreActionsFill, radius = size.width / 3.0f - 2.0f) + } + ) { + Icon( + painter = painterResource(id = R.drawable.chevron_right), + contentDescription = "Open Actions" + ) + } + + if(isActionsOpen.value) { + ActionItems() + } else if(words != null) { + SuggestionItems(words) { + suggestionStripListener.pickSuggestionManually( + words.getInfo(it) + ) + } + } else { + Spacer(modifier = Modifier.weight(1.0f)) + } + + + // TODO: For now, this calls CODE_SHORTCUT. In the future, we will want to + // ask the main UI to hide the keyboard and show our own voice input menu + IconButton(onClick = { + suggestionStripListener.onCodeInput( + Constants.CODE_SHORTCUT, + Constants.SUGGESTION_STRIP_COORDINATE, + Constants.SUGGESTION_STRIP_COORDINATE, + false + ); + }, modifier = Modifier + .width(42.dp) + .fillMaxHeight()) { + Icon( + painter = painterResource(id = R.drawable.mic_fill), + contentDescription = "Voice Input" + ) + } + } + } + } +} \ No newline at end of file diff --git a/java/src/org/futo/inputmethod/latin/uix/theme/Color.kt b/java/src/org/futo/inputmethod/latin/uix/theme/Color.kt new file mode 100644 index 000000000..37cd0f123 --- /dev/null +++ b/java/src/org/futo/inputmethod/latin/uix/theme/Color.kt @@ -0,0 +1,292 @@ +@file:Suppress("unused") + +package org.futo.inputmethod.latin.uix.theme + +import androidx.compose.ui.graphics.Color + +// Colors from Tailwind - https://tailwindcss.com/docs/customizing-colors + +val Slate50 = Color(0xfff8fafc) +val Slate100 = Color(0xfff1f5f9) +val Slate200 = Color(0xffe2e8f0) +val Slate300 = Color(0xffcbd5e1) +val Slate400 = Color(0xff94a3b8) +val Slate500 = Color(0xff64748b) +val Slate600 = Color(0xff475569) +val Slate700 = Color(0xff334155) +val Slate800 = Color(0xff1e293b) +val Slate900 = Color(0xff0f172a) +val Slate950 = Color(0xff020617) + + +val Gray50 = Color(0xfff9fafb) +val Gray100 = Color(0xfff3f4f6) +val Gray200 = Color(0xffe5e7eb) +val Gray300 = Color(0xffd1d5db) +val Gray400 = Color(0xff9ca3af) +val Gray500 = Color(0xff6b7280) +val Gray600 = Color(0xff4b5563) +val Gray700 = Color(0xff374151) +val Gray800 = Color(0xff1f2937) +val Gray900 = Color(0xff111827) +val Gray950 = Color(0xff030712) + + +val Zinc50 = Color(0xfffafafa) +val Zinc100 = Color(0xfff4f4f5) +val Zinc200 = Color(0xffe4e4e7) +val Zinc300 = Color(0xffd4d4d8) +val Zinc400 = Color(0xffa1a1aa) +val Zinc500 = Color(0xff71717a) +val Zinc600 = Color(0xff52525b) +val Zinc700 = Color(0xff3f3f46) +val Zinc800 = Color(0xff27272a) +val Zinc900 = Color(0xff18181b) +val Zinc950 = Color(0xff09090b) + + +val Neutral50 = Color(0xfffafafa) +val Neutral100 = Color(0xfff5f5f5) +val Neutral200 = Color(0xffe5e5e5) +val Neutral300 = Color(0xffd4d4d4) +val Neutral400 = Color(0xffa3a3a3) +val Neutral500 = Color(0xff737373) +val Neutral600 = Color(0xff525252) +val Neutral700 = Color(0xff404040) +val Neutral800 = Color(0xff262626) +val Neutral900 = Color(0xff171717) +val Neutral950 = Color(0xff0a0a0a) + + +val Stone50 = Color(0xfffafaf9) +val Stone100 = Color(0xfff5f5f4) +val Stone200 = Color(0xffe7e5e4) +val Stone300 = Color(0xffd6d3d1) +val Stone400 = Color(0xffa8a29e) +val Stone500 = Color(0xff78716c) +val Stone600 = Color(0xff57534e) +val Stone700 = Color(0xff44403c) +val Stone800 = Color(0xff292524) +val Stone900 = Color(0xff1c1917) +val Stone950 = Color(0xff0c0a09) + + +val Red50 = Color(0xfffef2f2) +val Red100 = Color(0xfffee2e2) +val Red200 = Color(0xfffecaca) +val Red300 = Color(0xfffca5a5) +val Red400 = Color(0xfff87171) +val Red500 = Color(0xffef4444) +val Red600 = Color(0xffdc2626) +val Red700 = Color(0xffb91c1c) +val Red800 = Color(0xff991b1b) +val Red900 = Color(0xff7f1d1d) +val Red950 = Color(0xff450a0a) + + +val Orange50 = Color(0xfffff7ed) +val Orange100 = Color(0xffffedd5) +val Orange200 = Color(0xfffed7aa) +val Orange300 = Color(0xfffdba74) +val Orange400 = Color(0xfffb923c) +val Orange500 = Color(0xfff97316) +val Orange600 = Color(0xffea580c) +val Orange700 = Color(0xffc2410c) +val Orange800 = Color(0xff9a3412) +val Orange900 = Color(0xff7c2d12) +val Orange950 = Color(0xff431407) + + +val Amber50 = Color(0xfffffbeb) +val Amber100 = Color(0xfffef3c7) +val Amber200 = Color(0xfffde68a) +val Amber300 = Color(0xfffcd34d) +val Amber400 = Color(0xfffbbf24) +val Amber500 = Color(0xfff59e0b) +val Amber600 = Color(0xffd97706) +val Amber700 = Color(0xffb45309) +val Amber800 = Color(0xff92400e) +val Amber900 = Color(0xff78350f) +val Amber950 = Color(0xff451a03) + + +val Yellow50 = Color(0xfffefce8) +val Yellow100 = Color(0xfffef9c3) +val Yellow200 = Color(0xfffef08a) +val Yellow300 = Color(0xfffde047) +val Yellow400 = Color(0xfffacc15) +val Yellow500 = Color(0xffeab308) +val Yellow600 = Color(0xffca8a04) +val Yellow700 = Color(0xffa16207) +val Yellow800 = Color(0xff854d0e) +val Yellow900 = Color(0xff713f12) +val Yellow950 = Color(0xff422006) + + +val Lime50 = Color(0xfff7fee7) +val Lime100 = Color(0xffecfccb) +val Lime200 = Color(0xffd9f99d) +val Lime300 = Color(0xffbef264) +val Lime400 = Color(0xffa3e635) +val Lime500 = Color(0xff84cc16) +val Lime600 = Color(0xff65a30d) +val Lime700 = Color(0xff4d7c0f) +val Lime800 = Color(0xff3f6212) +val Lime900 = Color(0xff365314) +val Lime950 = Color(0xff1a2e05) + + +val Green50 = Color(0xfff0fdf4) +val Green100 = Color(0xffdcfce7) +val Green200 = Color(0xffbbf7d0) +val Green300 = Color(0xff86efac) +val Green400 = Color(0xff4ade80) +val Green500 = Color(0xff22c55e) +val Green600 = Color(0xff16a34a) +val Green700 = Color(0xff15803d) +val Green800 = Color(0xff166534) +val Green900 = Color(0xff14532d) +val Green950 = Color(0xff052e16) + + +val Emerald50 = Color(0xffecfdf5) +val Emerald100 = Color(0xffd1fae5) +val Emerald200 = Color(0xffa7f3d0) +val Emerald300 = Color(0xff6ee7b7) +val Emerald400 = Color(0xff34d399) +val Emerald500 = Color(0xff10b981) +val Emerald600 = Color(0xff059669) +val Emerald700 = Color(0xff047857) +val Emerald800 = Color(0xff065f46) +val Emerald900 = Color(0xff064e3b) +val Emerald950 = Color(0xff022c22) + + +val Teal50 = Color(0xfff0fdfa) +val Teal100 = Color(0xffccfbf1) +val Teal200 = Color(0xff99f6e4) +val Teal300 = Color(0xff5eead4) +val Teal400 = Color(0xff2dd4bf) +val Teal500 = Color(0xff14b8a6) +val Teal600 = Color(0xff0d9488) +val Teal700 = Color(0xff0f766e) +val Teal800 = Color(0xff115e59) +val Teal900 = Color(0xff134e4a) +val Teal950 = Color(0xff042f2e) + + +val Cyan50 = Color(0xffecfeff) +val Cyan100 = Color(0xffcffafe) +val Cyan200 = Color(0xffa5f3fc) +val Cyan300 = Color(0xff67e8f9) +val Cyan400 = Color(0xff22d3ee) +val Cyan500 = Color(0xff06b6d4) +val Cyan600 = Color(0xff0891b2) +val Cyan700 = Color(0xff0e7490) +val Cyan800 = Color(0xff155e75) +val Cyan900 = Color(0xff164e63) +val Cyan950 = Color(0xff083344) + + +val Sky50 = Color(0xfff0f9ff) +val Sky100 = Color(0xffe0f2fe) +val Sky200 = Color(0xffbae6fd) +val Sky300 = Color(0xff7dd3fc) +val Sky400 = Color(0xff38bdf8) +val Sky500 = Color(0xff0ea5e9) +val Sky600 = Color(0xff0284c7) +val Sky700 = Color(0xff0369a1) +val Sky800 = Color(0xff075985) +val Sky900 = Color(0xff0c4a6e) +val Sky950 = Color(0xff082f49) + + +val Blue50 = Color(0xffeff6ff) +val Blue100 = Color(0xffdbeafe) +val Blue200 = Color(0xffbfdbfe) +val Blue300 = Color(0xff93c5fd) +val Blue400 = Color(0xff60a5fa) +val Blue500 = Color(0xff3b82f6) +val Blue600 = Color(0xff2563eb) +val Blue700 = Color(0xff1d4ed8) +val Blue800 = Color(0xff1e40af) +val Blue900 = Color(0xff1e3a8a) +val Blue950 = Color(0xff172554) + + +val Indigo50 = Color(0xffeef2ff) +val Indigo100 = Color(0xffe0e7ff) +val Indigo200 = Color(0xffc7d2fe) +val Indigo300 = Color(0xffa5b4fc) +val Indigo400 = Color(0xff818cf8) +val Indigo500 = Color(0xff6366f1) +val Indigo600 = Color(0xff4f46e5) +val Indigo700 = Color(0xff4338ca) +val Indigo800 = Color(0xff3730a3) +val Indigo900 = Color(0xff312e81) +val Indigo950 = Color(0xff1e1b4b) + + +val Violet50 = Color(0xfff5f3ff) +val Violet100 = Color(0xffede9fe) +val Violet200 = Color(0xffddd6fe) +val Violet300 = Color(0xffc4b5fd) +val Violet400 = Color(0xffa78bfa) +val Violet500 = Color(0xff8b5cf6) +val Violet600 = Color(0xff7c3aed) +val Violet700 = Color(0xff6d28d9) +val Violet800 = Color(0xff5b21b6) +val Violet900 = Color(0xff4c1d95) +val Violet950 = Color(0xff2e1065) + + +val Purple50 = Color(0xfffaf5ff) +val Purple100 = Color(0xfff3e8ff) +val Purple200 = Color(0xffe9d5ff) +val Purple300 = Color(0xffd8b4fe) +val Purple400 = Color(0xffc084fc) +val Purple500 = Color(0xffa855f7) +val Purple600 = Color(0xff9333ea) +val Purple700 = Color(0xff7e22ce) +val Purple800 = Color(0xff6b21a8) +val Purple900 = Color(0xff581c87) +val Purple950 = Color(0xff3b0764) + + +val Fuchsia50 = Color(0xfffdf4ff) +val Fuchsia100 = Color(0xfffae8ff) +val Fuchsia200 = Color(0xfff5d0fe) +val Fuchsia300 = Color(0xfff0abfc) +val Fuchsia400 = Color(0xffe879f9) +val Fuchsia500 = Color(0xffd946ef) +val Fuchsia600 = Color(0xffc026d3) +val Fuchsia700 = Color(0xffa21caf) +val Fuchsia800 = Color(0xff86198f) +val Fuchsia900 = Color(0xff701a75) +val Fuchsia950 = Color(0xff4a044e) + + +val Pink50 = Color(0xfffdf2f8) +val Pink100 = Color(0xfffce7f3) +val Pink200 = Color(0xfffbcfe8) +val Pink300 = Color(0xfff9a8d4) +val Pink400 = Color(0xfff472b6) +val Pink500 = Color(0xffec4899) +val Pink600 = Color(0xffdb2777) +val Pink700 = Color(0xffbe185d) +val Pink800 = Color(0xff9d174d) +val Pink900 = Color(0xff831843) +val Pink950 = Color(0xff500724) + + +val Rose50 = Color(0xfffff1f2) +val Rose100 = Color(0xffffe4e6) +val Rose200 = Color(0xfffecdd3) +val Rose300 = Color(0xfffda4af) +val Rose400 = Color(0xfffb7185) +val Rose500 = Color(0xfff43f5e) +val Rose600 = Color(0xffe11d48) +val Rose700 = Color(0xffbe123c) +val Rose800 = Color(0xff9f1239) +val Rose900 = Color(0xff881337) +val Rose950 = Color(0xff4c0519) diff --git a/java/src/org/futo/inputmethod/latin/uix/theme/Theme.kt b/java/src/org/futo/inputmethod/latin/uix/theme/Theme.kt new file mode 100644 index 000000000..3222a46e6 --- /dev/null +++ b/java/src/org/futo/inputmethod/latin/uix/theme/Theme.kt @@ -0,0 +1,77 @@ +package org.futo.inputmethod.latin.uix.theme + +import android.app.Activity +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.darkColorScheme +import androidx.compose.runtime.Composable +import androidx.compose.runtime.SideEffect +import androidx.compose.ui.graphics.toArgb +import androidx.compose.ui.platform.LocalView +import androidx.core.view.WindowCompat + +private val DarkColorScheme = darkColorScheme( + primary = Slate600, + onPrimary = Slate50, + + primaryContainer = Slate700, + onPrimaryContainer = Slate50, + + secondary = Slate700, + onSecondary = Slate50, + + secondaryContainer = Slate600, + onSecondaryContainer = Slate50, + + tertiary = Stone700, + onTertiary = Stone50, + + tertiaryContainer = Stone600, + onTertiaryContainer = Stone50, + + background = Slate900, + onBackground = Slate50, + + surface = Slate800, + onSurface = Slate50, + + outline = Slate300, + + surfaceVariant = Slate800, + onSurfaceVariant = Slate300 +) + +@Composable +fun WhisperVoiceInputTheme(content: @Composable () -> Unit) { + /* + val colorScheme = when { + dynamicColor && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S -> { + val context = LocalContext.current + if (darkTheme) dynamicDarkColorScheme(context) else dynamicLightColorScheme(context) + } + + darkTheme -> DarkColorScheme + else -> LightColorScheme + } + */ + val colorScheme = DarkColorScheme // TODO: Figure out light/dynamic if it's worth it + + + val view = LocalView.current + if (!view.isInEditMode) { + SideEffect { + if(view.context is Activity) { + val window = (view.context as Activity).window + window.statusBarColor = colorScheme.primary.toArgb() + WindowCompat.getInsetsController(window, view).isAppearanceLightStatusBars = + false + } + } + } + + + MaterialTheme( + colorScheme = colorScheme, + typography = Typography, + content = content + ) +} \ No newline at end of file diff --git a/java/src/org/futo/inputmethod/latin/uix/theme/Type.kt b/java/src/org/futo/inputmethod/latin/uix/theme/Type.kt new file mode 100644 index 000000000..f0da9f77a --- /dev/null +++ b/java/src/org/futo/inputmethod/latin/uix/theme/Type.kt @@ -0,0 +1,34 @@ +package org.futo.inputmethod.latin.uix.theme + +import androidx.compose.material3.Typography +import androidx.compose.ui.text.TextStyle +import androidx.compose.ui.text.font.FontFamily +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.unit.sp + +// Set of Material typography styles to start with +val Typography = Typography( + bodyLarge = TextStyle( + fontFamily = FontFamily.SansSerif, + fontWeight = FontWeight.Light, + fontSize = 20.sp, + lineHeight = 26.sp, + letterSpacing = 0.5.sp + ), + /* Other default text styles to override + titleLarge = TextStyle( + fontFamily = FontFamily.Default, + fontWeight = FontWeight.Normal, + fontSize = 22.sp, + lineHeight = 28.sp, + letterSpacing = 0.sp + ), + */ + labelSmall = TextStyle( + fontFamily = FontFamily.Default, + fontWeight = FontWeight.Medium, + fontSize = 11.sp, + lineHeight = 16.sp, + letterSpacing = 0.5.sp + ) +) \ No newline at end of file diff --git a/tests/src/org/futo/inputmethod/latin/LatinIMELegacyForTests.java b/tests/src/org/futo/inputmethod/latin/LatinIMELegacyForTests.java index 31cee8518..78db36b43 100644 --- a/tests/src/org/futo/inputmethod/latin/LatinIMELegacyForTests.java +++ b/tests/src/org/futo/inputmethod/latin/LatinIMELegacyForTests.java @@ -18,7 +18,7 @@ package org.futo.inputmethod.latin; public class LatinIMELegacyForTests extends LatinIMELegacy { public LatinIMELegacyForTests() { - super(mInputMethodService); + super(mInputMethodService, null); } @Override