compose welcome wizard (could look better, but good enough for now)

This commit is contained in:
Helium314 2025-02-28 20:26:38 +01:00
parent b65d00e142
commit 247ec2b7f3
3 changed files with 233 additions and 3 deletions

View file

@ -20,9 +20,9 @@ public final class SetupActivity extends Activity {
super.onCreate(savedInstanceState);
final Intent intent = new Intent();
final InputMethodManager imm = (InputMethodManager)getSystemService(INPUT_METHOD_SERVICE);
if (UncachedInputMethodManagerUtils.isThisImeCurrent(this, imm))
// if (UncachedInputMethodManagerUtils.isThisImeCurrent(this, imm))
intent.setClass(this, SettingsActivity.class);
else intent.setClass(this, SetupWizardActivity.class);
// else intent.setClass(this, SetupWizardActivity.class);
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP
| Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(intent);

View file

@ -1,6 +1,7 @@
// SPDX-License-Identifier: GPL-3.0-only
package helium314.keyboard.settings
import android.content.Context
import android.content.Intent
import android.content.SharedPreferences
import android.net.Uri
@ -8,6 +9,7 @@ import android.os.Build
import android.os.Bundle
import android.view.WindowInsets.Type
import android.view.inputmethod.EditorInfo
import android.view.inputmethod.InputMethodManager
import android.widget.RelativeLayout
import androidx.appcompat.app.AppCompatActivity
import androidx.compose.foundation.layout.Column
@ -15,6 +17,9 @@ import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue
import androidx.compose.ui.platform.ComposeView
import androidx.core.view.ViewCompat
import androidx.core.view.isGone
@ -26,6 +31,7 @@ import helium314.keyboard.latin.common.FileUtils
import helium314.keyboard.latin.define.DebugFlags
import helium314.keyboard.latin.settings.Settings
import helium314.keyboard.latin.utils.ExecutorUtils
import helium314.keyboard.latin.utils.UncachedInputMethodManagerUtils
import helium314.keyboard.latin.utils.cleanUnusedMainDicts
import helium314.keyboard.latin.utils.prefs
import helium314.keyboard.settings.dialogs.ConfirmationDialog
@ -50,6 +56,7 @@ class SettingsActivity : AppCompatActivity(), SharedPreferences.OnSharedPreferen
private val dictUriFlow = MutableStateFlow<Uri?>(null)
private val cachedDictionaryFile by lazy { File(this.cacheDir.path + File.separator + "temp_dict") }
private val crashReportFiles = MutableStateFlow<List<File>>(emptyList())
private var paused = true
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
@ -60,6 +67,7 @@ class SettingsActivity : AppCompatActivity(), SharedPreferences.OnSharedPreferen
ExecutorUtils.getBackgroundExecutor(ExecutorUtils.KEYBOARD).execute { cleanUnusedMainDicts(this) }
if (BuildConfig.DEBUG || DebugFlags.DEBUG_ENABLED)
crashReportFiles.value = findCrashReports()
val imm = getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
// with this the layout edit dialog is not covered by the keyboard
// alternative of Modifier.imePadding() and properties = DialogProperties(decorFitsSystemWindows = false) has other weird side effects
@ -89,6 +97,10 @@ class SettingsActivity : AppCompatActivity(), SharedPreferences.OnSharedPreferen
val dictUri by dictUriFlow.collectAsState()
val crashReports by crashReportFiles.collectAsState()
val crashFilePicker = filePicker { saveCrashReports(it) }
var showWelcomeWizard by rememberSaveable { mutableStateOf(
!UncachedInputMethodManagerUtils.isThisImeCurrent(this, imm)
|| !UncachedInputMethodManagerUtils.isThisImeEnabled(this, imm)
) }
if (spellchecker)
Column { // lazy way of implementing spell checker settings
settingsContainer[Settings.PREF_USE_CONTACTS]!!.Preference()
@ -127,6 +139,9 @@ class SettingsActivity : AppCompatActivity(), SharedPreferences.OnSharedPreferen
content = { Text("Crash report files found") },
)
}
if (showWelcomeWizard) {
WelcomeWizard(close = { showWelcomeWizard = false }, finish = this::finish)
}
}
}
}
@ -168,7 +183,6 @@ class SettingsActivity : AppCompatActivity(), SharedPreferences.OnSharedPreferen
paused = false
}
private var paused = true
fun setForceTheme(theme: String?, night: Boolean?) {
if (paused) return
if (forceTheme != theme || forceNight != night) {

View file

@ -0,0 +1,216 @@
package helium314.keyboard.settings
import android.content.Context
import android.content.Intent
import android.provider.Settings
import android.view.inputmethod.InputMethodManager
import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.activity.result.contract.ActivityResultContracts
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.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.material3.Icon
import androidx.compose.material3.LocalContentColor
import androidx.compose.material3.LocalTextStyle
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableIntStateOf
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.painter.Painter
import androidx.compose.ui.platform.LocalConfiguration
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import helium314.keyboard.latin.R
import helium314.keyboard.latin.utils.UncachedInputMethodManagerUtils
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
@Composable
fun WelcomeWizard(
close: () -> Unit,
finish: () -> Unit
) {
val ctx = LocalContext.current
val width = LocalConfiguration.current.screenWidthDp
val height = LocalConfiguration.current.screenHeightDp
val imm = ctx.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
fun determineStep(): Int = when {
!UncachedInputMethodManagerUtils.isThisImeEnabled(ctx, imm) -> 0
!UncachedInputMethodManagerUtils.isThisImeCurrent(ctx, imm) -> 2
else -> 3
}
var step by rememberSaveable { mutableIntStateOf(determineStep()) }
val scope = rememberCoroutineScope()
LaunchedEffect(step) {
if (step == 2)
scope.launch {
while (step == 2 && !UncachedInputMethodManagerUtils.isThisImeCurrent(ctx, imm)) {
delay(200)
}
step = 3
}
}
val useWideLayout = height < 500 && width > height
val appName = stringResource(ctx.applicationInfo.labelRes)
@Composable fun bigText() {
val resource = if (step == 0) R.string.setup_welcome_title else R.string.setup_steps_title
Text(
stringResource(resource, appName),
style = MaterialTheme.typography.displayMedium,
textAlign = TextAlign.Center,
modifier = Modifier.padding(bottom = 36.dp)
)
}
@Composable fun steps() {
if (step == 0)
Column(horizontalAlignment = Alignment.CenterHorizontally) {
Image(painterResource(R.drawable.setup_welcome_image), null)
Row(Modifier.clickable { step = 1 }
.padding(top = 4.dp, start = 4.dp, end = 4.dp)
.background(color = MaterialTheme.colorScheme.primary)
) {
Spacer(Modifier.weight(1f))
Text(
stringResource(R.string.setup_start_action),
modifier = Modifier.padding(horizontal = 16.dp)
)
}
}
else
Column {
val title: String
val instruction: String
val icon: Painter
val actionText: String
val action: () -> Unit
val launcher = rememberLauncherForActivityResult(ActivityResultContracts.StartActivityForResult()) {
step = determineStep()
}
if (step == 1) {
title = stringResource(R.string.setup_step1_title, appName)
instruction = stringResource(R.string.setup_step1_instruction, appName)
icon = painterResource(R.drawable.ic_setup_key)
actionText = stringResource(R.string.setup_step1_action)
action = {
val intent = Intent()
intent.setAction(Settings.ACTION_INPUT_METHOD_SETTINGS)
intent.addCategory(Intent.CATEGORY_DEFAULT)
launcher.launch(intent)
}
} else if (step == 2) {
title = stringResource(R.string.setup_step2_title, appName)
instruction = stringResource(R.string.setup_step2_instruction, appName)
icon = painterResource(R.drawable.ic_setup_select)
actionText = stringResource(R.string.setup_step2_action)
action = imm::showInputMethodPicker
} else { // step 3
title = stringResource(R.string.setup_step3_title)
instruction = stringResource(R.string.setup_step3_instruction, appName)
icon = painterResource(R.drawable.sym_keyboard_language_switch)
actionText = stringResource(R.string.setup_step3_action)
action = close
}
Row(Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween) {
Text("1", color = if (step == 1) MaterialTheme.colorScheme.primary else MaterialTheme.colorScheme.onPrimary)
Text("2", color = if (step == 2) MaterialTheme.colorScheme.primary else MaterialTheme.colorScheme.onPrimary)
Text("3", color = if (step == 3) MaterialTheme.colorScheme.primary else MaterialTheme.colorScheme.onPrimary)
}
Column(Modifier
.background(color = MaterialTheme.colorScheme.primary)
.padding(8.dp)
) {
Text(title)
Text(instruction, style = MaterialTheme.typography.bodyLarge.merge(color = MaterialTheme.colorScheme.onPrimary))
}
Spacer(Modifier.height(4.dp))
Row(
Modifier.clickable { action() }
.background(color = MaterialTheme.colorScheme.primary)
.padding(8.dp),
verticalAlignment = Alignment.CenterVertically
) {
Icon(icon, null, Modifier.padding(end = 6.dp).size(32.dp), tint = MaterialTheme.colorScheme.onPrimary)
Text(actionText, Modifier.weight(1f))
}
if (step == 3) {
Spacer(Modifier.height(4.dp))
Row(
Modifier.clickable { finish() }
.background(color = MaterialTheme.colorScheme.primary)
.padding(8.dp),
verticalAlignment = Alignment.CenterVertically
) {
Icon(
painterResource(R.drawable.ic_setup_check),
null,
Modifier.padding(end = 6.dp).size(32.dp),
tint = MaterialTheme.colorScheme.onPrimary
)
Text(stringResource(R.string.setup_finish_action), Modifier.weight(1f))
}
}
}
}
Surface {
CompositionLocalProvider(
LocalContentColor provides MaterialTheme.colorScheme.primary,
LocalTextStyle provides MaterialTheme.typography.titleLarge.merge(color = MaterialTheme.colorScheme.onPrimary),
) {
Box(
modifier = Modifier.fillMaxSize().padding(32.dp),
contentAlignment = Alignment.Center
) {
if (useWideLayout)
Row {
Box(Modifier.weight(0.4f)) {
bigText()
}
Box(Modifier.weight(0.6f)) {
steps()
}
}
else
Column {
bigText()
steps()
}
}
}
}
}
@Preview(
// content cut off on real device, but not here... great?
device = "spec:orientation=landscape,width=400dp,height=780dp"
)
@Composable
private fun Preview() {
Theme(true) {
Surface {
WelcomeWizard({}) { }
}
}
}