Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions app/src/main/java/com/sameerasw/essentials/domain/diy/Action.kt
Original file line number Diff line number Diff line change
Expand Up @@ -336,5 +336,15 @@ sealed interface Action {
override val permissions: List<String> = listOf("SHIZUKU", "ROOT")
override val isConfigurable: Boolean = true
}

@Keep
data class Keyboard(
@SerializedName("packageName") val packageName: String? = null
) : Action {
override val title: Int = R.string.feat_system_keyboard_title
override val icon: Int = R.drawable.rounded_keyboard_24
override val permissions: List<String> = listOf("WRITE_SECURE_SETTINGS")
override val isConfigurable: Boolean = true
}
}

Original file line number Diff line number Diff line change
Expand Up @@ -105,5 +105,8 @@ data class AutomationSuggestion(
val smartPixelsEnabled: Boolean? = null,

@Guide(description = "Dim wallpaper amount (0.0 to 1.0) for DimWallpaper action")
val dimWallpaperAmount: Float? = null
val dimWallpaperAmount: Float? = null,

@Guide(description = "Select keyboard from the list to switch")
val keyboard: String? = null,
)
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,15 @@ import android.hardware.camera2.CameraManager
import android.media.AudioManager
import android.net.wifi.WifiManager
import android.os.Build
import android.provider.Settings
import android.util.Log
import android.view.KeyEvent
import android.widget.Toast
import com.sameerasw.essentials.domain.HapticFeedbackType
import com.sameerasw.essentials.domain.diy.Action
import com.sameerasw.essentials.services.tiles.ScreenOffAccessibilityService
import com.sameerasw.essentials.utils.DeviceLockUtils
import com.sameerasw.essentials.utils.PermissionUtils
import com.sameerasw.essentials.utils.performHapticFeedback
import rikka.shizuku.ShizukuBinderWrapper
import rikka.shizuku.SystemServiceHelper
Expand Down Expand Up @@ -650,6 +653,18 @@ object CombinedActionExecutor {
com.sameerasw.essentials.utils.FreezeManager.unfreezeApp(context, pkg)
}
}

is Action.Keyboard -> {
try {
if (PermissionUtils.canWriteSecureSettings(context)) {
Settings.Secure.putString(context.contentResolver, Settings.Secure.DEFAULT_INPUT_METHOD, action.packageName)
return@withContext
}
Toast.makeText(context, "give me Write Secure Settings permission to change your keyboard.", Toast.LENGTH_SHORT).show()
} catch (e: Exception) {
Toast.makeText(context, "Keyboard Switching Failed: ${e.message ?: ""}", Toast.LENGTH_SHORT).show()
}
}
}

}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ package com.sameerasw.essentials.ui.activities
import android.content.Context
import android.content.Intent
import android.os.Bundle
import android.view.inputmethod.InputMethodInfo
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.activity.enableEdgeToEdge
Expand Down Expand Up @@ -101,6 +102,7 @@ import com.sameerasw.essentials.ui.core.sheets.ScreenOffSettingsSheet
import com.sameerasw.essentials.ui.core.sheets.SingleAppSelectionSheet
import com.sameerasw.essentials.ui.core.sheets.SoundModeSettingsSheet
import com.sameerasw.essentials.ui.core.sheets.WifiNetworkSelectionSheet
import com.sameerasw.essentials.ui.features.apps.sheets.KeyboardSelectionSheet
import com.sameerasw.essentials.ui.theme.EssentialsTheme
import com.sameerasw.essentials.utils.AppUtil
import com.sameerasw.essentials.utils.HapticUtil
Expand Down Expand Up @@ -265,6 +267,8 @@ class AutomationEditorActivity : ComponentActivity() {
var showTimeSettings by remember { mutableStateOf(false) }
var showBluetoothSettings by remember { mutableStateOf(false) }
var showWifiSettings by remember { mutableStateOf(false) }
var showSetKeyboardSheet by remember { mutableStateOf(false) }
var selectedIme by remember { mutableStateOf<String?>(null) }
var configAction by remember { mutableStateOf<Action?>(null) } // Generic config action

val isTriggerConfigured = when (val trigger = selectedTrigger) {
Expand Down Expand Up @@ -902,7 +906,8 @@ class AutomationEditorActivity : ComponentActivity() {
Action.FreezeApps(),
Action.UnfreezeApps(),
Action.FreezeTag(),
Action.PinApp
Action.PinApp,
Action.Keyboard()
)
val systemActions = listOf(
Action.TurnOnFlashlight,
Expand Down Expand Up @@ -957,6 +962,13 @@ class AutomationEditorActivity : ComponentActivity() {
) {
actions.forEach { action ->
val resolvedAction = if (currentSelection != null && currentSelection::class == action::class) currentSelection else action
val missing = getMissingPermissionsHelper(resolvedAction)
fun showPermissionSheet() {
permissionKeysToShow = missing
permissionFeatureTitle = resolvedAction.title
showPermissionSheet = true
}

EditorActionItem(
title = stringResource(resolvedAction.title),
iconRes = resolvedAction.icon,
Expand All @@ -971,14 +983,14 @@ class AutomationEditorActivity : ComponentActivity() {
else selectedOutAction = resolvedAction
}
}
val missing = getMissingPermissionsHelper(resolvedAction)
if (missing.isNotEmpty()) {
permissionKeysToShow = missing
permissionFeatureTitle = resolvedAction.title
showPermissionSheet = true
}
if(missing.isNotEmpty()) showPermissionSheet()
},
onSettingsClick = {
if(missing.isNotEmpty()) {
showPermissionSheet()
return@EditorActionItem
}

configAction = resolvedAction
when (resolvedAction) {
is Action.DimWallpaper -> showDimSettings = true
Expand All @@ -996,6 +1008,10 @@ class AutomationEditorActivity : ComponentActivity() {
temporarySelectedAppsForAction = resolvedAction.packageNames
showFreezeAppsSettings = true
}
is Action.Keyboard -> {
showSetKeyboardSheet = true
selectedIme = resolvedAction.packageName
}
else -> {}
}
}
Expand Down Expand Up @@ -1279,6 +1295,26 @@ class AutomationEditorActivity : ComponentActivity() {
excludePackages = if (automationType == Automation.Type.APP) selectedApps else emptyList()
)
}

if (showSetKeyboardSheet && configAction is Action.Keyboard) {
KeyboardSelectionSheet(
onDismissRequest = { newIme ->
showSetKeyboardSheet = false
when (automationType) {
Automation.Type.TRIGGER -> selectedAction = Action.Keyboard(newIme)
Automation.Type.ACTION_SHORTCUT, Automation.Type.PIXEL_SEARCHBAR -> selectedAction =
Action.Keyboard(newIme)

Automation.Type.STATE, Automation.Type.APP -> {
if (selectedActionTab == 0) selectedInAction = Action.Keyboard(newIme)
else selectedOutAction = Action.Keyboard(newIme)
}
}
configAction = null
},
selectedIme
)
}
}

if (showPermissionSheet) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,194 @@
package com.sameerasw.essentials.ui.features.apps.sheets

import android.content.Context
import android.os.Build
import android.provider.Settings
import android.util.Log
import android.view.inputmethod.InputMethodInfo
import android.view.inputmethod.InputMethodManager
import android.widget.Toast
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.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.itemsIndexed
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.Icon
import androidx.compose.material3.LoadingIndicator
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.RadioButton
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.asImageBitmap
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalView
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
import androidx.core.graphics.drawable.toBitmap
import com.sameerasw.essentials.R
import com.sameerasw.essentials.ui.core.sheets.EssentialsBottomSheet
import com.sameerasw.essentials.utils.HapticUtil
import com.sameerasw.essentials.utils.PermissionUtils
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext

@Composable
fun KeyboardSelectionSheet(
onDismissRequest: (ime: String?) -> Unit,
selectedIme: String?,
context: Context = LocalContext.current
) {
var isLoadingKeyboards by remember { mutableStateOf(true) }
var defaultInputMethod by remember { mutableStateOf<String?>(null) }
var imesList by remember { mutableStateOf<List<InputMethodInfo>>(emptyList()) }
val view = LocalView.current
val isDeprecated = Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE

LaunchedEffect(Unit) {
isLoadingKeyboards = true
try {
val list = withContext(Dispatchers.IO) {
val imes =
context.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
val defaultIme =
if (isDeprecated)
imes.currentInputMethodInfo?.id
else
Settings.Secure.getString(
context.contentResolver,
Settings.Secure.DEFAULT_INPUT_METHOD
)
defaultIme to imes
}
defaultInputMethod = selectedIme ?: (list.first ?: "")
imesList = list.second.inputMethodList
} catch (e: Exception) {
Log.e(
"KeyboardSelectionSheet", "Error loading keyboards list: ${e.message ?: ""}"
)
} finally {
isLoadingKeyboards = false
}
}

EssentialsBottomSheet(
onDismissRequest = { onDismissRequest(defaultInputMethod) },
) {
Column(
modifier = Modifier
.fillMaxSize()
.padding(horizontal = 16.dp),
verticalArrangement = Arrangement.spacedBy(16.dp)
) {
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically
) {
Text(
text = "Select Keyboard",
style = MaterialTheme.typography.headlineSmall,
fontWeight = FontWeight.SemiBold
)

if (PermissionUtils.canWriteSecureSettings(context)) {
Icon(
painter = painterResource(R.drawable.rounded_shield_lock_24),
contentDescription = "Permission",
tint = MaterialTheme.colorScheme.primary
)
}
}

if (isLoadingKeyboards) {
Row(
modifier = Modifier
.fillMaxWidth()
.padding(vertical = 32.dp),
horizontalArrangement = Arrangement.Center
) {
LoadingIndicator()
}
} else {
LazyColumn(
modifier = Modifier
.weight(1f)
.clip(RoundedCornerShape(24.dp)),
verticalArrangement = Arrangement.spacedBy(2.dp)
) {
itemsIndexed(imesList, key = { _, ime -> ime.id }) { index, ime ->
val isEnabled = ime.serviceInfo.enabled
val isSelected = defaultInputMethod == ime.id

val currentIndex =
if (imesList.size == 1) -2 else if (index == (imesList.size - 1)) -1 else index
val shape = when (currentIndex) {
-2 -> RoundedCornerShape(24.dp)
-1 -> RoundedCornerShape(4.dp, 4.dp, 24.dp, 24.dp)
0 -> RoundedCornerShape(24.dp, 24.dp, 4.dp, 4.dp)
else -> RoundedCornerShape(4.dp)
}

Row(
modifier = Modifier
.fillMaxWidth()
.clip(shape)
.clickable {
HapticUtil.performUIHaptic(view)
if (isEnabled) {
defaultInputMethod = ime.id
return@clickable
}
Toast.makeText(
context,
"Enable in settings",
Toast.LENGTH_SHORT
).show()
}
.background(MaterialTheme.colorScheme.surfaceBright)
.padding(16.dp),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(12.dp)
) {
Image(
bitmap = ime.loadIcon(context.packageManager).toBitmap()
.asImageBitmap(),
contentDescription = "IME Logo",
modifier = Modifier.size(24.dp),
contentScale = ContentScale.Fit
)
Text(
text = ime.loadLabel(context.packageManager).toString(),
style = MaterialTheme.typography.bodyLarge,
modifier = Modifier.weight(1f),
color = MaterialTheme.colorScheme.onSurface
)
RadioButton(
selected = isSelected,
onClick = { defaultInputMethod = ime.id },
enabled = isEnabled
)
}
}
}
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -226,6 +226,10 @@ class DIYViewModel(application: Application) : AndroidViewModel(application) {
tagIds = suggestion.freezeTagIds
)

"Keyboard" -> Action.Keyboard(
packageName = suggestion.keyboard
)

else -> null
}
}
Expand Down
Loading