diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 4763cdee..80bed2c3 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -1,6 +1,7 @@ plugins { alias(libs.plugins.android.application) alias(libs.plugins.kotlin.android) + alias(libs.plugins.compose.compiler) } android { @@ -30,6 +31,7 @@ android { release { isMinifyEnabled = true isShrinkResources = true + versionNameSuffix = ".$commitShort" signingConfig = if (signingProps.exists()) { val props = `java.util`.Properties().apply { load(signingProps.reader()) } signingConfigs.create("release") { @@ -66,6 +68,7 @@ android { buildFeatures { viewBinding = true buildConfig = true + compose = true } dependenciesInfo { includeInApk = false @@ -74,6 +77,12 @@ android { } dependencies { + implementation(platform(libs.androidx.compose.bom)) + implementation(libs.androidx.activity.compose) + implementation(libs.androidx.material3) + implementation(libs.androidx.material.icons.extended) + implementation(libs.androidx.ui.tooling.preview) + debugImplementation(libs.androidx.ui.tooling) implementation(libs.androidx.appcompat) implementation(libs.androidx.biometric.ktx) implementation(libs.androidx.constraintlayout) @@ -87,11 +96,11 @@ dependencies { implementation(libs.pinyin4j) implementation(libs.material) implementation(libs.insetter) - implementation(libs.simplemenu.preference) implementation(libs.shizuku.api) implementation(libs.shizuku.provider) implementation(libs.dhizuku.api) implementation(libs.appiconloader) + implementation(libs.compose.preference) implementation(libs.commons.text) implementation(libs.kotlinx.coroutines.android) implementation(libs.hiddenapibypass) diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 5a19a277..bdd7ad3f 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -57,7 +57,7 @@ android:excludeFromRecents="true" android:exported="true" android:launchMode="singleInstance" - android:theme="@style/Theme.Hail.Dialog"> + android:theme="@style/Theme.Hail.Translucent"> diff --git a/app/src/main/kotlin/com/aistra/hail/app/HailData.kt b/app/src/main/kotlin/com/aistra/hail/app/HailData.kt index b7bf2375..07b487e2 100644 --- a/app/src/main/kotlin/com/aistra/hail/app/HailData.kt +++ b/app/src/main/kotlin/com/aistra/hail/app/HailData.kt @@ -23,103 +23,127 @@ object HailData { const val URL_PAYPAL = "https://www.paypal.me/aistra0528" const val URL_TRANSLATE = "https://hosted.weblate.org/engage/hail/" const val VERSION = "${BuildConfig.VERSION_NAME} (${BuildConfig.VERSION_CODE})" + private const val KEY_ID = "id" + const val KEY_TAG = "tag" + private const val KEY_PINNED = "pinned" + private const val KEY_WHITELISTED = "whitelisted" const val KEY_PACKAGE = "package" const val KEY_FROZEN = "frozen" - const val WORKING_MODE = "working_mode" - const val MODE_DEFAULT = "default" - + private const val SORT_BY = "sort_by" + const val SORT_NAME = "name" + const val SORT_INSTALL = "install" + const val SORT_UPDATE = "update" + const val FILTER_USER_APPS = "filter_user_apps" + const val FILTER_SYSTEM_APPS = "filter_system_apps" + const val FILTER_FROZEN_APPS = "filter_frozen_apps" + const val FILTER_UNFROZEN_APPS = "filter_unfrozen_apps" const val OWNER = "owner_" const val DHIZUKU = "dhizuku_" const val SU = "su_" const val SHIZUKU = "shizuku_" const val ISLAND = "island_" const val PRIVAPP = "privapp_" - const val STOP = "stop" const val DISABLE = "disable" const val HIDE = "hide" const val SUSPEND = "suspend" - - const val MODE_OWNER_HIDE = OWNER + HIDE - const val MODE_OWNER_SUSPEND = OWNER + SUSPEND - const val MODE_DHIZUKU_HIDE = DHIZUKU + HIDE - const val MODE_DHIZUKU_SUSPEND = DHIZUKU + SUSPEND - const val MODE_SU_STOP = SU + STOP - const val MODE_SU_DISABLE = SU + DISABLE - const val MODE_SU_HIDE = SU + HIDE - const val MODE_SU_SUSPEND = SU + SUSPEND + const val WORKING_MODE = "working_mode" + const val MODE_DEFAULT = "default" const val MODE_SHIZUKU_STOP = SHIZUKU + STOP const val MODE_SHIZUKU_DISABLE = SHIZUKU + DISABLE const val MODE_SHIZUKU_HIDE = SHIZUKU + HIDE const val MODE_SHIZUKU_SUSPEND = SHIZUKU + SUSPEND + const val MODE_SU_STOP = SU + STOP + const val MODE_SU_DISABLE = SU + DISABLE + const val MODE_SU_HIDE = SU + HIDE + const val MODE_SU_SUSPEND = SU + SUSPEND + const val MODE_DHIZUKU_HIDE = DHIZUKU + HIDE + const val MODE_DHIZUKU_SUSPEND = DHIZUKU + SUSPEND + const val MODE_OWNER_HIDE = OWNER + HIDE + const val MODE_OWNER_SUSPEND = OWNER + SUSPEND const val MODE_ISLAND_HIDE = ISLAND + HIDE const val MODE_ISLAND_SUSPEND = ISLAND + SUSPEND const val MODE_PRIVAPP_STOP = PRIVAPP + STOP const val MODE_PRIVAPP_DISABLE = PRIVAPP + DISABLE - - private const val TILE_ACTION = "tile_action" - private const val HOME_FONT_SIZE = "home_font_size" - const val DYNAMIC_SHORTCUT_ACTION = "dynamic_shortcut_action" + val WORKING_MODE_VALUES = listOf( + MODE_DEFAULT, + MODE_SHIZUKU_STOP, + MODE_SHIZUKU_DISABLE, + MODE_SHIZUKU_HIDE, + MODE_SHIZUKU_SUSPEND, + MODE_SU_STOP, + MODE_SU_DISABLE, + MODE_SU_HIDE, + MODE_SU_SUSPEND, + MODE_DHIZUKU_HIDE, + MODE_DHIZUKU_SUSPEND, + MODE_OWNER_HIDE, + MODE_OWNER_SUSPEND, + MODE_ISLAND_HIDE, + MODE_ISLAND_SUSPEND, + MODE_PRIVAPP_STOP, + MODE_PRIVAPP_DISABLE + ) + const val BIOMETRIC_LOGIN = "biometric_login" + const val APP_THEME = "app_theme" + const val FOLLOW_SYSTEM = "follow_system" + const val THEME_LIGHT = "theme_light" + const val THEME_DARK = "theme_dark" + val APP_THEME_VALUES = listOf(FOLLOW_SYSTEM, THEME_LIGHT, THEME_DARK) + const val ICON_PACK = "icon_pack" + const val GRAYSCALE_ICON = "grayscale_icon" + const val COMPACT_ICON = "compact_icon" + const val SYNTHESIZE_ADAPTIVE_ICONS = "synthesize_adaptive_icons" + const val HOME_FONT_SIZE = "home_font_size_f" + const val FUZZY_SEARCH = "fuzzy_search" + const val NINE_KEY_SEARCH = "nine_key" + const val TILE_ACTION = "tile_action" const val ACTION_NONE = "none" const val ACTION_FREEZE_ALL = "freeze_all" + const val ACTION_UNFREEZE_ALL = "unfreeze_all" const val ACTION_FREEZE_NON_WHITELISTED = "freeze_non_whitelisted" const val ACTION_LOCK = "lock" const val ACTION_LOCK_FREEZE = "lock_freeze" - private const val SORT_BY = "sort_by" - const val SORT_NAME = "name" - const val SORT_INSTALL = "install" - const val SORT_UPDATE = "update" - private const val KEY_ID = "id" - const val KEY_TAG = "tag" - private const val KEY_PINNED = "pinned" - private const val KEY_WHITELISTED = "whitelisted" - const val FILTER_USER_APPS = "filter_user_apps" - const val FILTER_SYSTEM_APPS = "filter_system_apps" - const val FILTER_FROZEN_APPS = "filter_frozen_apps" - const val FILTER_UNFROZEN_APPS = "filter_unfrozen_apps" - private const val BIOMETRIC_LOGIN = "biometric_login" - private const val FUZZY_SEARCH = "fuzzy_search" - private const val NINE_KEY_SEARCH = "nine_key" - const val APP_THEME = "app_theme" - private const val FOLLOW_SYSTEM = "follow_system" - const val THEME_LIGHT = "theme_light" - const val THEME_DARK = "theme_dark" - const val ICON_PACK = "icon_pack" - private const val GRAYSCALE_ICON = "grayscale_icon" - private const val COMPACT_ICON = "compact_icon" - private const val SYNTHESIZE_ADAPTIVE_ICONS = "synthesize_adaptive_icons" + val TILE_ACTION_VALUES = + listOf(ACTION_FREEZE_ALL, ACTION_UNFREEZE_ALL, ACTION_FREEZE_NON_WHITELISTED, ACTION_LOCK, ACTION_LOCK_FREEZE) const val AUTO_FREEZE_AFTER_LOCK = "auto_freeze_after_lock" - private const val SKIP_WHILE_CHARGING = "skip_while_charging" + const val AUTO_FREEZE_DELAY = "auto_freeze_delay_f" + const val SKIP_WHILE_CHARGING = "skip_while_charging" const val SKIP_FOREGROUND_APP = "skip_foreground_app" const val SKIP_NOTIFYING_APP = "skip_notifying_app" - private const val AUTO_FREEZE_DELAY = "auto_freeze_delay" + const val DYNAMIC_SHORTCUT_ACTION = "dynamic_shortcut_action" + val DYNAMIC_SHORTCUT_ACTIONS = listOf( + ACTION_NONE, + ACTION_FREEZE_ALL, + ACTION_UNFREEZE_ALL, + ACTION_FREEZE_NON_WHITELISTED, + ACTION_LOCK, + ACTION_LOCK_FREEZE + ) private val sp = PreferenceManager.getDefaultSharedPreferences(app) - val workingMode get() = sp.getString(WORKING_MODE, MODE_DEFAULT)!! val sortBy get() = sp.getString(SORT_BY, SORT_NAME) val filterUserApps get() = sp.getBoolean(FILTER_USER_APPS, true) val filterSystemApps get() = sp.getBoolean(FILTER_SYSTEM_APPS, false) val filterFrozenApps get() = sp.getBoolean(FILTER_FROZEN_APPS, true) val filterUnfrozenApps get() = sp.getBoolean(FILTER_UNFROZEN_APPS, true) + val workingMode get() = sp.getString(WORKING_MODE, MODE_DEFAULT)!! val biometricLogin get() = sp.getBoolean(BIOMETRIC_LOGIN, false) + val appTheme get() = sp.getString(APP_THEME, FOLLOW_SYSTEM)!! + val iconPack get() = sp.getString(ICON_PACK, ACTION_NONE)!! val grayscaleIcon get() = sp.getBoolean(GRAYSCALE_ICON, true) val compactIcon get() = sp.getBoolean(COMPACT_ICON, false) - val tileAction get() = sp.getString(TILE_ACTION, ACTION_FREEZE_ALL) - val homeFontSize get() = sp.getInt(HOME_FONT_SIZE, 14) - val dynamicShortcutAction get() = sp.getString(DYNAMIC_SHORTCUT_ACTION, ACTION_NONE)!! val synthesizeAdaptiveIcons get() = sp.getBoolean(SYNTHESIZE_ADAPTIVE_ICONS, false) + val homeFontSize get() = sp.getFloat(HOME_FONT_SIZE, 14f) + val fuzzySearch get() = sp.getBoolean(FUZZY_SEARCH, false) + val nineKeySearch get() = sp.getBoolean(NINE_KEY_SEARCH, false) + val tileAction get() = sp.getString(TILE_ACTION, ACTION_FREEZE_ALL) val autoFreezeAfterLock get() = sp.getBoolean(AUTO_FREEZE_AFTER_LOCK, false) + val autoFreezeDelay get() = sp.getFloat(AUTO_FREEZE_DELAY, 0f).toLong() val skipWhileCharging get() = sp.getBoolean(SKIP_WHILE_CHARGING, false) val skipForegroundApp get() = sp.getBoolean(SKIP_FOREGROUND_APP, false) val skipNotifyingApp get() = sp.getBoolean(SKIP_NOTIFYING_APP, false) - val autoFreezeDelay get() = sp.getInt(AUTO_FREEZE_DELAY, 0).toLong() - - val fuzzySearch get() = sp.getBoolean(FUZZY_SEARCH, false) - val nineKeySearch get() = sp.getBoolean(NINE_KEY_SEARCH, false) - - val appTheme get() = sp.getString(APP_THEME, FOLLOW_SYSTEM)!! - val iconPack get() = sp.getString(ICON_PACK, ACTION_NONE)!! - fun setIconPack(packageName: String) = sp.edit().putString(ICON_PACK, packageName).apply() + val dynamicShortcutAction get() = sp.getString(DYNAMIC_SHORTCUT_ACTION, ACTION_NONE)!! private val dir = "${app.filesDir.path}/v1" private val appsPath = "$dir/apps.json" diff --git a/app/src/main/kotlin/com/aistra/hail/ui/about/AboutFragment.kt b/app/src/main/kotlin/com/aistra/hail/ui/about/AboutFragment.kt index e4469744..d7509c6e 100644 --- a/app/src/main/kotlin/com/aistra/hail/ui/about/AboutFragment.kt +++ b/app/src/main/kotlin/com/aistra/hail/ui/about/AboutFragment.kt @@ -1,82 +1,179 @@ package com.aistra.hail.ui.about import android.os.Bundle -import android.text.util.Linkify import android.view.LayoutInflater import android.view.View import android.view.ViewGroup -import androidx.lifecycle.ViewModelProvider +import androidx.annotation.StringRes +import androidx.compose.foundation.* +import androidx.compose.foundation.layout.* +import androidx.compose.foundation.shape.CircleShape +import androidx.compose.foundation.text.selection.SelectionContainer +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.automirrored.filled.Send +import androidx.compose.material.icons.outlined.* +import androidx.compose.material3.* +import androidx.compose.runtime.* +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.vector.ImageVector +import androidx.compose.ui.layout.ContentScale +import androidx.compose.ui.platform.ComposeView +import androidx.compose.ui.platform.ViewCompositionStrategy +import androidx.compose.ui.res.dimensionResource +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.* +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp import com.aistra.hail.HailApp.Companion.app import com.aistra.hail.R import com.aistra.hail.app.HailData -import com.aistra.hail.databinding.FragmentAboutBinding -import com.aistra.hail.extensions.* import com.aistra.hail.ui.main.MainFragment +import com.aistra.hail.ui.theme.AppTheme +import com.aistra.hail.utils.HPackages import com.aistra.hail.utils.HUI import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.google.android.material.imageview.ShapeableImageView -import com.google.android.material.textview.MaterialTextView +import java.text.SimpleDateFormat -class AboutFragment : MainFragment(), View.OnClickListener { - private lateinit var aboutViewModel: AboutViewModel - private var _binding: FragmentAboutBinding? = null - private val binding get() = _binding!! +class AboutFragment : MainFragment() { - override fun onCreateView( - inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? - ): View { - _binding = FragmentAboutBinding.inflate(inflater, container, false) - activity.appbar.setLiftOnScrollTargetView(binding.root) - - binding.descVersion.text = HailData.VERSION - aboutViewModel = ViewModelProvider(this)[AboutViewModel::class.java] - aboutViewModel.time.observe(viewLifecycleOwner) { - binding.descTime.text = it + override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View = + ComposeView(requireContext()).apply { + setViewCompositionStrategy(ViewCompositionStrategy.DisposeOnViewTreeLifecycleDestroyed) + setContent { + AppTheme { + AboutScreen(HPackages.getUnhiddenPackageInfoOrNull(app.packageName)!!.firstInstallTime) + } + } } - binding.actionLibre.setOnClickListener(this) - binding.actionVersion.setOnClickListener(this) - binding.actionTime.setOnClickListener(this) - binding.actionTelegram.setOnClickListener(this) - binding.actionQq.setOnClickListener(this) - binding.actionFdroid.setOnClickListener(this) - binding.actionDonate.setOnClickListener(this) - binding.actionGithub.setOnClickListener(this) - binding.actionTranslate.setOnClickListener(this) - binding.actionLicenses.setOnClickListener(this) - binding.scrollView.applyDefaultInsetter { - paddingRelative(isRtl, bottom = isLandscape) - marginRelative(isRtl, start = !isLandscape, end = true) - } + @Preview(showBackground = true) + @Composable + fun PreviewAboutScreen() = AppTheme { AboutScreen(System.currentTimeMillis()) } - return binding.root + @Composable + private fun AboutScreen(installTime: Long) { + var openLicenseDialog by remember { mutableStateOf(false) } + if (openLicenseDialog) LicenseDialog { openLicenseDialog = false } + Column( + modifier = Modifier.verticalScroll(state = rememberScrollState()) + ) { + Spacer(modifier = Modifier.height(dimensionResource(R.dimen.padding_medium))) + Card( + onClick = { HUI.openLink(HailData.URL_WHY_FREE_SOFTWARE) }, + modifier = Modifier.height(dimensionResource(R.dimen.header_height)) + .padding(horizontal = dimensionResource(R.dimen.padding_medium)) + ) { + Column( + modifier = Modifier.fillMaxSize(), verticalArrangement = Arrangement.spacedBy( + dimensionResource(R.dimen.padding_extra_small), Alignment.CenterVertically + ), horizontalAlignment = Alignment.CenterHorizontally + ) { + Image( + painter = painterResource(R.drawable.ic_launcher_foreground), + contentDescription = null, + modifier = Modifier.size(72.dp).background(Color.White, CircleShape), + contentScale = ContentScale.None + ) + Text( + text = stringResource(R.string.app_name), style = MaterialTheme.typography.bodyLarge + ) + Text( + text = stringResource(R.string.app_slogan), style = MaterialTheme.typography.bodyMedium + ) + } + } + Spacer(modifier = Modifier.height(dimensionResource(R.dimen.padding_medium))) + OutlinedCard(modifier = Modifier.padding(horizontal = dimensionResource(R.dimen.padding_medium))) { + ClickableItem( + icon = Icons.Outlined.Update, title = R.string.label_version, desc = HailData.VERSION + ) { HUI.openLink(HailData.URL_RELEASES) } + ClickableItem( + icon = Icons.Outlined.InstallMobile, + title = R.string.label_time, + desc = SimpleDateFormat.getDateInstance().format(installTime) + ) { HUI.showToast("\uD83E\uDD76\uD83D\uDCA8\uD83D\uDC09") } + } + Spacer(modifier = Modifier.height(dimensionResource(R.dimen.padding_medium))) + OutlinedCard(modifier = Modifier.padding(horizontal = dimensionResource(R.dimen.padding_medium))) { + ClickableItem( + icon = Icons.AutoMirrored.Filled.Send, title = R.string.action_telegram + ) { HUI.openLink(HailData.URL_TELEGRAM) } + ClickableItem( + icon = Icons.Outlined.Group, title = R.string.action_qq + ) { HUI.openLink(HailData.URL_QQ) } + ClickableItem( + icon = Icons.Outlined.LocalMall, title = R.string.action_fdroid + ) { HUI.openLink(HailData.URL_FDROID) } + ClickableItem( + icon = Icons.Outlined.CardGiftcard, title = R.string.action_donate, onClick = ::openDonateDialog + ) + } + Spacer(modifier = Modifier.height(dimensionResource(R.dimen.padding_medium))) + OutlinedCard(modifier = Modifier.padding(horizontal = dimensionResource(R.dimen.padding_medium))) { + ClickableItem( + icon = Icons.Outlined.Code, title = R.string.action_github + ) { HUI.openLink(HailData.URL_GITHUB) } + ClickableItem( + icon = Icons.Outlined.Translate, title = R.string.action_translate + ) { HUI.openLink(HailData.URL_TRANSLATE) } + ClickableItem( + icon = Icons.Outlined.Description, title = R.string.action_licenses + ) { openLicenseDialog = true } + } + Spacer(modifier = Modifier.height(dimensionResource(R.dimen.padding_medium))) + } } - override fun onClick(view: View) { - when (view) { - binding.actionLibre -> HUI.openLink(HailData.URL_WHY_FREE_SOFTWARE) - binding.actionVersion -> HUI.openLink(HailData.URL_RELEASES) - binding.actionTime -> HUI.showToast("\uD83E\uDD76\uD83D\uDCA8\uD83D\uDC09") - binding.actionTelegram -> HUI.openLink(HailData.URL_TELEGRAM) - binding.actionQq -> HUI.openLink(HailData.URL_QQ) - binding.actionFdroid -> HUI.openLink(HailData.URL_FDROID) - binding.actionDonate -> onDonate() - binding.actionGithub -> HUI.openLink(HailData.URL_GITHUB) - binding.actionTranslate -> HUI.openLink(HailData.URL_TRANSLATE) - binding.actionLicenses -> MaterialAlertDialogBuilder(activity).setTitle(R.string.action_licenses) - .setMessage(resources.openRawResource(R.raw.licenses).bufferedReader().readText()) - .setPositiveButton(android.R.string.ok, null).show() - .findViewById(android.R.id.message)?.apply { - setTextIsSelectable(true) - Linkify.addLinks(this, Linkify.EMAIL_ADDRESSES or Linkify.WEB_URLS) - // The first time the link is clicked the background does not change color and - // the view needs to get focus once. - requestFocus() - } + @Composable + private fun ClickableItem( + icon: ImageVector, @StringRes title: Int, desc: String? = null, onClick: () -> Unit + ) = Row( + modifier = Modifier.fillMaxWidth().clickable(onClick = onClick), + verticalAlignment = Alignment.CenterVertically + ) { + Icon( + imageVector = icon, contentDescription = null, modifier = Modifier.padding( + horizontal = dimensionResource(R.dimen.padding_medium), + vertical = dimensionResource(if (desc == null) R.dimen.padding_medium else R.dimen.padding_large) + ) + ) + Column { + Text(text = stringResource(title), style = MaterialTheme.typography.bodyLarge) + if (desc != null) Text(text = desc, style = MaterialTheme.typography.bodyMedium) } } - private fun onDonate() { + @Composable + private fun LicenseDialog(onDismiss: () -> Unit) = AlertDialog( + title = { Text(text = stringResource(R.string.action_licenses)) }, + text = { + SelectionContainer { + Text( + text = buildAnnotatedString { + val lines = resources.openRawResource(R.raw.licenses).bufferedReader().readLines() + lines.forEach { + if (it.isNotBlank()) withLink( + LinkAnnotation.Url( + it.substringAfter(": "), + TextLinkStyles(style = SpanStyle(color = MaterialTheme.colorScheme.primary)) + ) + ) { + append(it.substringBefore(": ")) + } + if (it != lines.last()) append("\n\n") + } + }, modifier = Modifier.verticalScroll(state = rememberScrollState()) + ) + } + }, + onDismissRequest = onDismiss, + confirmButton = { TextButton(onClick = onDismiss) { Text(text = stringResource(android.R.string.ok)) } }) + + private fun openDonateDialog() { MaterialAlertDialogBuilder(activity).setTitle(R.string.title_donate) .setSingleChoiceItems(R.array.donate_payment_entries, 0) { dialog, which -> dialog.dismiss() @@ -87,7 +184,7 @@ class AboutFragment : MainFragment(), View.OnClickListener { 1 -> MaterialAlertDialogBuilder(activity).setTitle(R.string.title_donate) .setView(ShapeableImageView(activity).apply { - val padding = resources.getDimensionPixelOffset(R.dimen.dialog_padding) + val padding = resources.getDimensionPixelOffset(R.dimen.padding_large) setPadding(0, padding, 0, padding) setImageResource(R.mipmap.qr_wechat) }).setPositiveButton(R.string.donate_wechat_scan) { _, _ -> @@ -108,9 +205,4 @@ class AboutFragment : MainFragment(), View.OnClickListener { } }.setNegativeButton(android.R.string.cancel, null).show() } - - override fun onDestroyView() { - super.onDestroyView() - _binding = null - } } \ No newline at end of file diff --git a/app/src/main/kotlin/com/aistra/hail/ui/about/AboutViewModel.kt b/app/src/main/kotlin/com/aistra/hail/ui/about/AboutViewModel.kt deleted file mode 100644 index 05f489b1..00000000 --- a/app/src/main/kotlin/com/aistra/hail/ui/about/AboutViewModel.kt +++ /dev/null @@ -1,14 +0,0 @@ -package com.aistra.hail.ui.about - -import android.app.Application -import androidx.lifecycle.AndroidViewModel -import androidx.lifecycle.MutableLiveData -import com.aistra.hail.utils.HPackages -import java.text.SimpleDateFormat - -class AboutViewModel(val app: Application) : AndroidViewModel(app) { - val time = MutableLiveData().apply { - value = SimpleDateFormat.getDateInstance() - .format(HPackages.getUnhiddenPackageInfoOrNull(app.packageName)!!.firstInstallTime) - } -} \ No newline at end of file diff --git a/app/src/main/kotlin/com/aistra/hail/ui/api/ApiActivity.kt b/app/src/main/kotlin/com/aistra/hail/ui/api/ApiActivity.kt index 48bf22f4..ae86a1f0 100644 --- a/app/src/main/kotlin/com/aistra/hail/ui/api/ApiActivity.kt +++ b/app/src/main/kotlin/com/aistra/hail/ui/api/ApiActivity.kt @@ -4,33 +4,51 @@ import android.content.ActivityNotFoundException import android.content.Intent import android.content.pm.PackageManager.NameNotFoundException import android.os.Bundle -import androidx.appcompat.app.AppCompatActivity +import androidx.activity.ComponentActivity +import androidx.activity.compose.setContent +import androidx.annotation.StringRes +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.automirrored.outlined.Launch +import androidx.compose.material.icons.rounded.AcUnit +import androidx.compose.material.icons.rounded.BrightnessLow +import androidx.compose.material3.* +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.vector.ImageVector +import androidx.compose.ui.res.dimensionResource +import androidx.compose.ui.res.stringResource import com.aistra.hail.HailApp.Companion.app import com.aistra.hail.R import com.aistra.hail.app.AppInfo import com.aistra.hail.app.AppManager import com.aistra.hail.app.HailApi import com.aistra.hail.app.HailData +import com.aistra.hail.ui.theme.AppTheme import com.aistra.hail.utils.HPackages import com.aistra.hail.utils.HShortcuts import com.aistra.hail.utils.HTarget import com.aistra.hail.utils.HUI import com.aistra.hail.work.HWork.setAutoFreeze -import com.google.android.material.dialog.MaterialAlertDialogBuilder -class ApiActivity : AppCompatActivity() { +class ApiActivity : ComponentActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) runCatching { when (intent.action) { Intent.ACTION_SHOW_APP_INFO -> { - redirect(requirePackage(if (HTarget.N) Intent.EXTRA_PACKAGE_NAME else "android.intent.extra.PACKAGE_NAME")) + val pkg = + requirePackage(if (HTarget.N) Intent.EXTRA_PACKAGE_NAME else "android.intent.extra.PACKAGE_NAME") + setContent { AppTheme { RedirectBottomSheet(pkg) } } return } - HailApi.ACTION_LAUNCH -> launchApp( - requirePackage(), runCatching { requireTagId }.getOrNull() - ) + HailApi.ACTION_LAUNCH -> launchApp(requirePackage(), runCatching { requireTagId }.getOrNull()) HailApi.ACTION_FREEZE -> setAppFrozen(requirePackage(), true) HailApi.ACTION_UNFREEZE -> setAppFrozen(requirePackage(), false) @@ -39,8 +57,7 @@ class ApiActivity : AppCompatActivity() { ) HailApi.ACTION_UNFREEZE_TAG -> setListFrozen( - false, - HailData.checkedList.filter { it.tagId == requireTagId }) + false, HailData.checkedList.filter { it.tagId == requireTagId }) HailApi.ACTION_FREEZE_ALL -> setListFrozen(true) HailApi.ACTION_UNFREEZE_ALL -> setListFrozen(false) @@ -51,20 +68,70 @@ class ApiActivity : AppCompatActivity() { else -> throw IllegalArgumentException("unknown action:\n${intent.action}") } finish() - }.onFailure { - showErrorDialog(it) + }.onFailure(::setErrorDialog) + } + + private fun setErrorDialog(t: Throwable) = setContent { AppTheme { ErrorDialog(t) } } + + @OptIn(ExperimentalMaterial3Api::class) + @Composable + private fun RedirectBottomSheet(pkg: String) = ModalBottomSheet( + onDismissRequest = ::finish, sheetState = rememberModalBottomSheetState(skipPartiallyExpanded = true) + ) { + Column { + Text( + text = HPackages.getApplicationInfoOrNull(pkg)?.loadLabel(packageManager)?.toString() ?: pkg, + modifier = Modifier.padding( + horizontal = dimensionResource(R.dimen.padding_medium), + vertical = dimensionResource(R.dimen.padding_small) + ), + style = MaterialTheme.typography.headlineSmall + ) + ClickableItem( + icon = Icons.AutoMirrored.Outlined.Launch, title = R.string.action_launch + ) { launchApp(pkg) } + ClickableItem( + icon = Icons.Rounded.AcUnit, title = R.string.action_freeze + ) { + if (!HailData.isChecked(pkg)) HailData.addCheckedApp(pkg) + setAppFrozen(pkg, true) + } + ClickableItem( + icon = Icons.Rounded.BrightnessLow, title = R.string.action_unfreeze + ) { setAppFrozen(pkg, false) } } } - private fun showErrorDialog(t: Throwable) { - MaterialAlertDialogBuilder(this).setMessage(t.message ?: t.stackTraceToString()) - .setPositiveButton(android.R.string.ok, null).setOnDismissListener { finish() }.show() + @Composable + private fun ClickableItem(icon: ImageVector, @StringRes title: Int, onClick: () -> Unit) = Row( + modifier = Modifier.fillMaxWidth().clickable(onClick = { + runCatching { + onClick() + finish() + }.onFailure(::setErrorDialog) + }), verticalAlignment = Alignment.CenterVertically + ) { + Icon( + imageVector = icon, + contentDescription = null, + modifier = Modifier.padding(dimensionResource(R.dimen.padding_medium)) + ) + Text(text = stringResource(title), style = MaterialTheme.typography.bodyLarge) } + @Composable + private fun ErrorDialog(t: Throwable) = AlertDialog( + text = { Text(text = t.message ?: t.stackTraceToString()) }, + onDismissRequest = ::finish, + confirmButton = { + TextButton(onClick = ::finish) { + Text(text = stringResource(android.R.string.ok)) + } + }) + private fun requirePackage(extraName: String = HailData.KEY_PACKAGE): String = intent?.getStringExtra(extraName)?.also { - HPackages.getApplicationInfoOrNull(it) - ?: throw NameNotFoundException(getString(R.string.app_not_installed)) + HPackages.getApplicationInfoOrNull(it) ?: throw NameNotFoundException(getString(R.string.app_not_installed)) } ?: throw IllegalArgumentException("package must not be null") private val requireTagId: Int @@ -73,29 +140,6 @@ class ApiActivity : AppCompatActivity() { ?: throw IllegalStateException("tag unavailable:\n$it") } ?: throw IllegalArgumentException("tag must not be null") - private fun redirect(pkg: String) { - var shouldFinished = true - MaterialAlertDialogBuilder(this).setTitle( - HPackages.getApplicationInfoOrNull(pkg)?.loadLabel(packageManager) ?: pkg - ).setItems(R.array.api_redirect_action_entries) { _, which -> - runCatching { - when (which) { - 0 -> launchApp(pkg) - 1 -> { - if (!HailData.isChecked(pkg)) HailData.addCheckedApp(pkg) - setAppFrozen(pkg, true) - } - - 2 -> setAppFrozen(pkg, false) - } - }.onFailure { - shouldFinished = false - showErrorDialog(it) - } - }.setNegativeButton(android.R.string.cancel, null) - .setOnDismissListener { if (shouldFinished) finish() }.show() - } - private fun launchApp(pkg: String, tagId: Int? = null) { if (tagId != null) setListFrozen(false, HailData.checkedList.filter { it.tagId == tagId }) if (AppManager.isAppFrozen(pkg) && AppManager.setAppFrozen(pkg, false)) { @@ -123,9 +167,7 @@ class ApiActivity : AppCompatActivity() { } private fun setListFrozen( - frozen: Boolean, - list: List = HailData.checkedList, - skipWhitelisted: Boolean = false + frozen: Boolean, list: List = HailData.checkedList, skipWhitelisted: Boolean = false ) { val filtered = list.filter { AppManager.isAppFrozen(it.packageName) != frozen && !(skipWhitelisted && it.whitelisted) } @@ -133,8 +175,7 @@ class ApiActivity : AppCompatActivity() { null -> throw IllegalStateException(getString(R.string.permission_denied)) else -> { HUI.showToast( - if (frozen) R.string.msg_freeze else R.string.msg_unfreeze, - result + if (frozen) R.string.msg_freeze else R.string.msg_unfreeze, result ) app.setAutoFreezeService() } diff --git a/app/src/main/kotlin/com/aistra/hail/ui/apps/AppsAdapter.kt b/app/src/main/kotlin/com/aistra/hail/ui/apps/AppsAdapter.kt index ee6b015b..b059b023 100644 --- a/app/src/main/kotlin/com/aistra/hail/ui/apps/AppsAdapter.kt +++ b/app/src/main/kotlin/com/aistra/hail/ui/apps/AppsAdapter.kt @@ -7,12 +7,12 @@ import android.widget.CompoundButton import androidx.recyclerview.widget.DiffUtil import androidx.recyclerview.widget.ListAdapter import androidx.recyclerview.widget.RecyclerView -import com.aistra.hail.R import com.aistra.hail.app.AppManager import com.aistra.hail.app.HailData import com.aistra.hail.databinding.ItemAppsBinding import com.aistra.hail.utils.AppIconCache import com.aistra.hail.utils.HPackages +import com.google.android.material.color.MaterialColors import kotlinx.coroutines.Job class AppsAdapter : ListAdapter(DIFF) { @@ -78,7 +78,11 @@ class AppsAdapter : ListAdapter(DIFF) { val name = info.loadLabel(context.packageManager) text = if (!HailData.grayscaleIcon && frozen) "❄️$name" else name isEnabled = !HailData.grayscaleIcon || !frozen - if (HPackages.isAppUninstalled(pkg)) setTextColor(context.getColorStateList(R.color.color_warn)) + if (HPackages.isAppUninstalled(pkg)) setTextColor( + MaterialColors.getColor( + this, com.google.android.material.R.attr.colorError + ) + ) else setTextAppearance(com.google.android.material.R.style.TextAppearance_Material3_BodyMedium) } binding.appDesc.apply { diff --git a/app/src/main/kotlin/com/aistra/hail/ui/apps/AppsFragment.kt b/app/src/main/kotlin/com/aistra/hail/ui/apps/AppsFragment.kt index 0413645f..4536df3a 100644 --- a/app/src/main/kotlin/com/aistra/hail/ui/apps/AppsFragment.kt +++ b/app/src/main/kotlin/com/aistra/hail/ui/apps/AppsFragment.kt @@ -207,7 +207,7 @@ class AppsFragment : MainFragment(), AppsAdapter.OnItemClickListener, AppsAdapte inflater.inflate(R.menu.menu_apps, menu) val searchView = menu.findItem(R.id.action_search).actionView as SearchView if (HailData.nineKeySearch) { - val editText = searchView.findViewById(androidx.appcompat.R.id.search_src_text) + val editText = searchView.findViewById(com.google.android.material.R.id.search_src_text) editText.inputType = InputType.TYPE_CLASS_PHONE } searchView.setOnQueryTextListener(object : SearchView.OnQueryTextListener { diff --git a/app/src/main/kotlin/com/aistra/hail/ui/home/PagerAdapter.kt b/app/src/main/kotlin/com/aistra/hail/ui/home/PagerAdapter.kt index ad96d890..612868bf 100644 --- a/app/src/main/kotlin/com/aistra/hail/ui/home/PagerAdapter.kt +++ b/app/src/main/kotlin/com/aistra/hail/ui/home/PagerAdapter.kt @@ -57,13 +57,19 @@ class PagerAdapter(private val selectedList: List) : isEnabled = !HailData.grayscaleIcon || info.state != AppInfo.STATE_FROZEN when { info.selected -> setTextColor( - MaterialColors.getColor(this, androidx.appcompat.R.attr.colorPrimary) + MaterialColors.getColor(this, com.google.android.material.R.attr.colorPrimary) + ) + + info.state == AppInfo.STATE_NOT_FOUND -> setTextColor( + MaterialColors.getColor( + this, + com.google.android.material.R.attr.colorError + ) ) - info.state == AppInfo.STATE_NOT_FOUND -> setTextColor(context.getColorStateList(R.color.color_warn)) else -> setTextAppearance(com.google.android.material.R.style.TextAppearance_Material3_BodyMedium) } - setTextSize(TypedValue.COMPLEX_UNIT_SP, HailData.homeFontSize.toFloat()) + setTextSize(TypedValue.COMPLEX_UNIT_SP, HailData.homeFontSize) } } } diff --git a/app/src/main/kotlin/com/aistra/hail/ui/home/PagerFragment.kt b/app/src/main/kotlin/com/aistra/hail/ui/home/PagerFragment.kt index 8799069a..d337809c 100644 --- a/app/src/main/kotlin/com/aistra/hail/ui/home/PagerFragment.kt +++ b/app/src/main/kotlin/com/aistra/hail/ui/home/PagerFragment.kt @@ -450,7 +450,7 @@ class PagerFragment : MainFragment(), PagerAdapter.OnItemClickListener, PagerAda private fun MenuItem.updateIcon() = icon?.setTint( MaterialColors.getColor( activity.findViewById(R.id.toolbar), - if (multiselect) androidx.appcompat.R.attr.colorPrimary else com.google.android.material.R.attr.colorOnSurface + if (multiselect) com.google.android.material.R.attr.colorPrimary else com.google.android.material.R.attr.colorOnSurface ) ) @@ -492,7 +492,7 @@ class PagerFragment : MainFragment(), PagerAdapter.OnItemClickListener, PagerAda inflater.inflate(R.menu.menu_home, menu) val searchView = menu.findItem(R.id.action_search).actionView as SearchView if (HailData.nineKeySearch) { - val editText = searchView.findViewById(androidx.appcompat.R.id.search_src_text) + val editText = searchView.findViewById(com.google.android.material.R.id.search_src_text) editText.inputType = InputType.TYPE_CLASS_PHONE } searchView.setOnQueryTextListener(object : SearchView.OnQueryTextListener { diff --git a/app/src/main/kotlin/com/aistra/hail/ui/settings/SettingsFragment.kt b/app/src/main/kotlin/com/aistra/hail/ui/settings/SettingsFragment.kt index 8f1c5925..6b31db8e 100644 --- a/app/src/main/kotlin/com/aistra/hail/ui/settings/SettingsFragment.kt +++ b/app/src/main/kotlin/com/aistra/hail/ui/settings/SettingsFragment.kt @@ -6,24 +6,41 @@ import android.os.Bundle import android.provider.Settings import android.view.* import androidx.activity.result.contract.ActivityResultContracts +import androidx.annotation.ArrayRes +import androidx.annotation.StringRes import androidx.appcompat.content.res.AppCompatResources +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.LazyListScope +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.automirrored.outlined.ManageSearch +import androidx.compose.material.icons.automirrored.outlined.Shortcut +import androidx.compose.material.icons.outlined.* +import androidx.compose.material3.HorizontalDivider +import androidx.compose.material3.Icon +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.MutableState +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.vector.ImageVector +import androidx.compose.ui.platform.ComposeView +import androidx.compose.ui.platform.ViewCompositionStrategy +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.AnnotatedString import androidx.core.app.NotificationManagerCompat import androidx.core.view.MenuHost import androidx.core.view.MenuProvider import androidx.lifecycle.Lifecycle import androidx.lifecycle.lifecycleScope -import androidx.preference.ListPreference -import androidx.preference.Preference -import androidx.preference.PreferenceFragmentCompat -import androidx.recyclerview.widget.RecyclerView import com.aistra.hail.HailApp.Companion.app import com.aistra.hail.R import com.aistra.hail.app.AppManager import com.aistra.hail.app.HailApi import com.aistra.hail.app.HailData import com.aistra.hail.databinding.DialogInputBinding -import com.aistra.hail.extensions.* import com.aistra.hail.ui.main.MainActivity +import com.aistra.hail.ui.main.MainFragment +import com.aistra.hail.ui.theme.AppTheme import com.aistra.hail.utils.* import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.google.android.material.textview.MaterialTextView @@ -36,96 +53,296 @@ import kotlinx.coroutines.flow.callbackFlow import kotlinx.coroutines.flow.first import kotlinx.coroutines.launch import kotlinx.coroutines.withContext +import me.zhanghai.compose.preference.* import rikka.shizuku.Shizuku - -class SettingsFragment : PreferenceFragmentCompat(), Preference.OnPreferenceChangeListener, MenuProvider { +class SettingsFragment : MainFragment(), MenuProvider { private val requestPermissionLauncher = registerForActivityResult(ActivityResultContracts.RequestPermission()) {} - override fun onCreateView( - inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? - ): View { + + override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View { val menuHost = requireActivity() as MenuHost menuHost.addMenuProvider(this, viewLifecycleOwner, Lifecycle.State.RESUMED) - val activity = (requireActivity() as MainActivity) - activity.appbar.liftOnScrollTargetViewId = R.id.recycler_view - val view = super.onCreateView(inflater, container, savedInstanceState) - val recyclerView = view.findViewById(R.id.recycler_view) - - recyclerView.applyDefaultInsetter { - paddingRelative(isRtl, bottom = isLandscape) - marginRelative(isRtl, start = !isLandscape, end = true) + return ComposeView(requireContext()).apply { + setViewCompositionStrategy(ViewCompositionStrategy.DisposeOnViewTreeLifecycleDestroyed) + setContent { + AppTheme { + ProvidePreferenceLocals { + SettingsScreen() + } + } + } } - return view } - override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) { - setPreferencesFromResource(R.xml.root_preferences, rootKey) - findPreference(HailData.WORKING_MODE)?.onPreferenceChangeListener = this - findPreference(HailData.SKIP_FOREGROUND_APP)?.setOnPreferenceChangeListener { _, value -> - if (value == true && !HSystem.checkOpUsageStats(requireContext())) { - startActivity(Intent(Settings.ACTION_USAGE_ACCESS_SETTINGS).setFlags(Intent.FLAG_ACTIVITY_NEW_TASK)) - false - } else true - } - findPreference(HailData.SKIP_NOTIFYING_APP)?.setOnPreferenceChangeListener { _, value -> - val isGranted = NotificationManagerCompat.getEnabledListenerPackages(requireContext()) - .contains(requireContext().packageName) - if (value == true && !isGranted) { - app.setAutoFreezeServiceEnabled(true) - startActivity(Intent(Settings.ACTION_NOTIFICATION_LISTENER_SETTINGS).setFlags(Intent.FLAG_ACTIVITY_NEW_TASK)) - false - } else true - } - findPreference(HailData.AUTO_FREEZE_AFTER_LOCK)?.setOnPreferenceChangeListener { _, autoFreezeAfterLock -> - app.setAutoFreezeService(autoFreezeAfterLock as Boolean) - true - } - findPreference(HailData.APP_THEME)?.setOnPreferenceChangeListener { _, value -> - app.setAppTheme(value as String) - true - } - findPreference(HailData.ICON_PACK)?.setOnPreferenceClickListener { - iconPackDialog() - true - } - findPreference("add_pin_shortcut")?.setOnPreferenceClickListener { - addPinShortcut() - true - } - findPreference(HailData.DYNAMIC_SHORTCUT_ACTION)?.setOnPreferenceChangeListener { _, action -> - HShortcuts.removeAllDynamicShortcuts() - HShortcuts.addDynamicShortcutAction(action as String) - true - } - findPreference("clear_dynamic_shortcuts")?.setOnPreferenceClickListener { - HShortcuts.removeAllDynamicShortcuts() - HShortcuts.addDynamicShortcutAction(HailData.dynamicShortcutAction) - true + @Composable + private fun SettingsScreen() { + val autoFreezeAfterLock = rememberPreferenceState(HailData.AUTO_FREEZE_AFTER_LOCK, false) + LazyColumn(modifier = Modifier.fillMaxSize()) { + listPreference( + key = HailData.WORKING_MODE, + defaultValue = HailData.MODE_DEFAULT, + onValueChange = ::onWorkingModeChange, + values = HailData.WORKING_MODE_VALUES, + entriesId = R.array.working_mode_entries, + titleId = R.string.working_mode, + icon = Icons.Outlined.Adb + ) + switchPreference( + key = HailData.BIOMETRIC_LOGIN, + defaultValue = false, + titleId = R.string.action_biometric, + icon = Icons.Outlined.Fingerprint + ) + horizontalDivider() + preferenceCategory(key = "customize", title = { Text(text = stringResource(R.string.title_customize)) }) + listPreference( + key = HailData.APP_THEME, + defaultValue = HailData.FOLLOW_SYSTEM, + onValueChange = { _, value -> + app.setAppTheme(value) + true + }, + values = HailData.APP_THEME_VALUES, + entriesId = R.array.app_theme_entries, + titleId = R.string.app_theme, + icon = Icons.Outlined.DarkMode + ) + listPreference( + key = HailData.ICON_PACK, + defaultValue = HailData.ACTION_NONE, + onValueChange = { _, value -> + AppIconCache.clear() + true + }, + values = mutableListOf(HailData.ACTION_NONE).apply { + addAll(Intent(Intent.ACTION_MAIN).addCategory("com.anddoes.launcher.THEME").let { + if (HTarget.T) app.packageManager.queryIntentActivities( + it, PackageManager.ResolveInfoFlags.of(0) + ) else app.packageManager.queryIntentActivities(it, 0) + }.map { it.activityInfo.packageName }) + }, + titleId = R.string.icon_pack, + icon = Icons.Outlined.Palette, + summary = { iconPackName(it) }, + valueToText = ::iconPackName + ) + switchPreference( + key = HailData.GRAYSCALE_ICON, + defaultValue = true, + titleId = R.string.grayscale_icon, + icon = Icons.Outlined.FilterBAndW + ) + switchPreference( + key = HailData.COMPACT_ICON, + defaultValue = false, + titleId = R.string.compact_icon, + icon = Icons.Outlined.Apps + ) + switchPreference( + key = HailData.SYNTHESIZE_ADAPTIVE_ICONS, + defaultValue = false, + titleId = R.string.synthesize_adaptive_icons, + icon = Icons.Outlined.Layers + ) + sliderPreference( + key = HailData.HOME_FONT_SIZE, + defaultValue = 14f, + title = { Text(text = stringResource(R.string.home_font_size)) }, + valueRange = 11f..16f, + valueSteps = 4, + icon = { Icon(imageVector = Icons.Outlined.TextFields, contentDescription = null) }, + valueText = { Text(text = "%.0f".format(it)) }, + ) + switchPreference( + key = HailData.FUZZY_SEARCH, + defaultValue = false, + titleId = R.string.fuzzy_search, + icon = Icons.AutoMirrored.Outlined.ManageSearch + ) + switchPreference( + key = HailData.NINE_KEY_SEARCH, + defaultValue = false, + titleId = R.string.nine_key, + icon = Icons.Outlined.Dialpad + ) + listPreference( + key = HailData.TILE_ACTION, + defaultValue = HailData.ACTION_FREEZE_ALL, + values = HailData.TILE_ACTION_VALUES, + entriesId = R.array.tile_action_entries, + titleId = R.string.tile_action, + icon = Icons.Outlined.DashboardCustomize + ) + horizontalDivider() + preferenceCategory(key = "auto_freeze", title = { Text(text = stringResource(R.string.auto_freeze)) }) + switchPreference( + rememberState = { autoFreezeAfterLock }, + onValueChange = { _, value -> + app.setAutoFreezeService(value) + true + }, + titleId = R.string.auto_freeze_after_lock, + icon = Icons.Outlined.ScreenLockPortrait + ) + sliderPreference( + key = HailData.AUTO_FREEZE_DELAY, + defaultValue = 0f, + title = { Text(text = stringResource(R.string.auto_freeze_delay)) }, + valueRange = 0f..30f, + valueSteps = 29, + enabled = { autoFreezeAfterLock.value }, + icon = { Icon(imageVector = Icons.Outlined.LockClock, contentDescription = null) }, + valueText = { Text(text = "%.0f".format(it)) }, + ) + switchPreference( + key = HailData.SKIP_WHILE_CHARGING, + defaultValue = false, + titleId = R.string.skip_while_charging, + enabled = autoFreezeAfterLock.value, + icon = Icons.Outlined.BatteryChargingFull + ) + switchPreference( + key = HailData.SKIP_FOREGROUND_APP, + defaultValue = false, + onValueChange = { _, value -> + if (value && !HSystem.checkOpUsageStats(requireContext())) { + startActivity(Intent(Settings.ACTION_USAGE_ACCESS_SETTINGS).setFlags(Intent.FLAG_ACTIVITY_NEW_TASK)) + false + } else true + }, + titleId = R.string.skip_foreground_app, + enabled = autoFreezeAfterLock.value, + icon = Icons.Outlined.Android + ) + switchPreference( + key = HailData.SKIP_NOTIFYING_APP, + defaultValue = false, + onValueChange = { _, value -> + val isGranted = NotificationManagerCompat.getEnabledListenerPackages(requireContext()) + .contains(requireContext().packageName) + if (value && !isGranted) { + app.setAutoFreezeServiceEnabled(true) + startActivity(Intent(Settings.ACTION_NOTIFICATION_LISTENER_SETTINGS).setFlags(Intent.FLAG_ACTIVITY_NEW_TASK)) + false + } else true + }, + titleId = R.string.skip_notifying_app, + enabled = autoFreezeAfterLock.value, + icon = Icons.Outlined.NotificationsActive + ) + horizontalDivider() + preferenceCategory(key = "shortcuts", title = { Text(text = stringResource(R.string.title_shortcuts)) }) + preference( + key = "add_pin_shortcut", + title = { Text(text = stringResource(R.string.action_add_pin_shortcut)) }, + icon = { Icon(imageVector = Icons.AutoMirrored.Outlined.Shortcut, contentDescription = null) }, + onClick = ::addPinShortcut + ) + listPreference( + key = HailData.DYNAMIC_SHORTCUT_ACTION, + defaultValue = HailData.ACTION_NONE, + onValueChange = { _, action -> + HShortcuts.removeAllDynamicShortcuts() + HShortcuts.addDynamicShortcutAction(action) + true + }, + values = HailData.DYNAMIC_SHORTCUT_ACTIONS, + entriesId = R.array.dynamic_shortcut_entries, + titleId = R.string.dynamic_shortcut_action, + icon = Icons.Outlined.AppShortcut + ) + preference( + key = "clear_dynamic_shortcuts", + title = { Text(text = stringResource(R.string.action_clear_dynamic_shortcuts)) }, + icon = { Icon(imageVector = Icons.Outlined.CleaningServices, contentDescription = null) }) { + HShortcuts.removeAllDynamicShortcuts() + HShortcuts.addDynamicShortcutAction(HailData.dynamicShortcutAction) + } } } - private fun iconPackDialog() { - val list = Intent(Intent.ACTION_MAIN).addCategory("com.anddoes.launcher.THEME").let { - if (HTarget.T) app.packageManager.queryIntentActivities( - it, PackageManager.ResolveInfoFlags.of(0) - ) else app.packageManager.queryIntentActivities(it, 0) - }.map { it.activityInfo } - if (list.isEmpty()) { - HUI.showToast(R.string.app_not_installed) - return - } - MaterialAlertDialogBuilder(requireActivity()).setTitle(R.string.icon_pack) - .setItems(list.map { it.loadLabel(app.packageManager) }.toTypedArray()) { _, which -> - if (HailData.iconPack == list[which].packageName) return@setItems - HailData.setIconPack(list[which].packageName) - AppIconCache.clear() - }.setNeutralButton(R.string.label_default) { _, _ -> - if (HailData.iconPack == HailData.ACTION_NONE) return@setNeutralButton - HailData.setIconPack(HailData.ACTION_NONE) - AppIconCache.clear() - }.setNegativeButton(android.R.string.cancel, null).show() + private fun LazyListScope.horizontalDivider() = item { HorizontalDivider() } + + private fun LazyListScope.switchPreference( + rememberState: @Composable () -> MutableState, + onValueChange: (MutableState, Boolean) -> Boolean = { rememberState, value -> true }, + @StringRes titleId: Int, + enabled: Boolean = true, + icon: ImageVector, + ) = item(key = titleId, contentType = "SwitchPreference") { + val state = rememberState() + SwitchPreference( + value = state.value, + onValueChange = { if (onValueChange(state, it)) state.value = it }, + title = { Text(text = stringResource(titleId)) }, + enabled = enabled, + icon = { Icon(imageVector = icon, contentDescription = null) }) } + private fun LazyListScope.switchPreference( + key: String, + defaultValue: Boolean, + onValueChange: (MutableState, Boolean) -> Boolean = { rememberState, value -> true }, + @StringRes titleId: Int, + enabled: Boolean = true, + icon: ImageVector, + ) = switchPreference( + rememberState = { rememberPreferenceState(key, defaultValue) }, + onValueChange = onValueChange, + titleId = titleId, + enabled = enabled, + icon = icon + ) + + private fun LazyListScope.listPreference( + key: String, + defaultValue: String, + onValueChange: (MutableState, String) -> Boolean = { rememberState, value -> true }, + values: List, + @StringRes titleId: Int, + icon: ImageVector, + summary: @Composable (String) -> String, + type: ListPreferenceType = ListPreferenceType.DROPDOWN_MENU, + valueToText: (String) -> String + ) = item(key = key, contentType = "ListPreference") { + val state = rememberPreferenceState(key, defaultValue) + ListPreference( + value = state.value, + onValueChange = { if (onValueChange(state, it)) state.value = it }, + values = values, + title = { Text(text = stringResource(titleId)) }, + icon = { Icon(imageVector = icon, contentDescription = null) }, + summary = { Text(text = summary(state.value)) }, + type = type, + valueToText = { AnnotatedString(valueToText(it)) }) + } + + private fun LazyListScope.listPreference( + key: String, + defaultValue: String, + onValueChange: (MutableState, String) -> Boolean = { rememberState, value -> true }, + values: List, + @ArrayRes entriesId: Int, + @StringRes titleId: Int, + icon: ImageVector, + type: ListPreferenceType = ListPreferenceType.DROPDOWN_MENU + ) = listPreference( + key = key, + defaultValue = defaultValue, + onValueChange = onValueChange, + values = values, + titleId = titleId, + icon = icon, + summary = { it.toEntry(values, entriesId) }, + type = type, + valueToText = { it.toEntry(values, entriesId) }) + + private fun String.toEntry(values: List, @ArrayRes entriesId: Int): String = + resources.getStringArray(entriesId)[values.indexOf(this)] + + private fun iconPackName(pack: String): String = if (pack == HailData.ACTION_NONE) getString(R.string.action_none) + else HPackages.getApplicationInfoOrNull(pack)?.loadLabel(app.packageManager)?.toString() ?: pack + private fun addPinShortcut() { MaterialAlertDialogBuilder(requireActivity()).setTitle(R.string.action_add_pin_shortcut) .setItems(R.array.pin_shortcut_entries) { _, which -> @@ -201,10 +418,9 @@ class SettingsFragment : PreferenceFragmentCompat(), Preference.OnPreferenceChan }.setNegativeButton(android.R.string.cancel, null).show() } - override fun onPreferenceChange(preference: Preference, newValue: Any): Boolean { + fun onWorkingModeChange(rememberState: MutableState, mode: String): Boolean { // Show/hide terminal menu. - activity?.invalidateOptionsMenu() - val mode = newValue as String + activity.invalidateOptionsMenu() when { mode.startsWith(HailData.OWNER) -> if (!HPolicy.isDeviceOwnerActive) { MaterialAlertDialogBuilder(requireActivity()).setTitle(R.string.title_set_owner) @@ -229,8 +445,8 @@ class SettingsFragment : PreferenceFragmentCompat(), Preference.OnPreferenceChan }) awaitClose() }.first() - if (result && preference is ListPreference) { - preference.value = mode + if (result) { + rememberState.value = mode if (HTarget.O) HDhizuku.setDelegatedScopes() } } @@ -271,7 +487,7 @@ class SettingsFragment : PreferenceFragmentCompat(), Preference.OnPreferenceChan Shizuku.removeRequestPermissionResultListener(listener) } }.first() - if (result && preference is ListPreference) preference.value = mode + if (result) rememberState.value = mode } false } diff --git a/app/src/main/kotlin/com/aistra/hail/ui/theme/Color.kt b/app/src/main/kotlin/com/aistra/hail/ui/theme/Color.kt new file mode 100644 index 00000000..795380b4 --- /dev/null +++ b/app/src/main/kotlin/com/aistra/hail/ui/theme/Color.kt @@ -0,0 +1,75 @@ +package com.aistra.hail.ui.theme + +import androidx.compose.ui.graphics.Color + +val primaryLight = Color(0xFF32628D) +val onPrimaryLight = Color(0xFFFFFFFF) +val primaryContainerLight = Color(0xFFCFE5FF) +val onPrimaryContainerLight = Color(0xFF001D34) +val secondaryLight = Color(0xFF526070) +val onSecondaryLight = Color(0xFFFFFFFF) +val secondaryContainerLight = Color(0xFFD6E4F7) +val onSecondaryContainerLight = Color(0xFF0F1D2A) +val tertiaryLight = Color(0xFF695779) +val onTertiaryLight = Color(0xFFFFFFFF) +val tertiaryContainerLight = Color(0xFFF0DBFF) +val onTertiaryContainerLight = Color(0xFF241532) +val errorLight = Color(0xFFBA1A1A) +val onErrorLight = Color(0xFFFFFFFF) +val errorContainerLight = Color(0xFFFFDAD6) +val onErrorContainerLight = Color(0xFF410002) +val backgroundLight = Color(0xFFF7F9FF) +val onBackgroundLight = Color(0xFF191C20) +val surfaceLight = Color(0xFFF7F9FF) +val onSurfaceLight = Color(0xFF191C20) +val surfaceVariantLight = Color(0xFFDEE3EB) +val onSurfaceVariantLight = Color(0xFF42474E) +val outlineLight = Color(0xFF73777F) +val outlineVariantLight = Color(0xFFC2C7CF) +val scrimLight = Color(0xFF000000) +val inverseSurfaceLight = Color(0xFF2D3135) +val inverseOnSurfaceLight = Color(0xFFEFF1F6) +val inversePrimaryLight = Color(0xFF9DCBFC) +val surfaceDimLight = Color(0xFFD8DAE0) +val surfaceBrightLight = Color(0xFFF7F9FF) +val surfaceContainerLowestLight = Color(0xFFFFFFFF) +val surfaceContainerLowLight = Color(0xFFF2F3F9) +val surfaceContainerLight = Color(0xFFECEEF4) +val surfaceContainerHighLight = Color(0xFFE6E8EE) +val surfaceContainerHighestLight = Color(0xFFE0E2E8) + +val primaryDark = Color(0xFF9DCBFC) +val onPrimaryDark = Color(0xFF003355) +val primaryContainerDark = Color(0xFF134A74) +val onPrimaryContainerDark = Color(0xFFCFE5FF) +val secondaryDark = Color(0xFFBAC8DA) +val onSecondaryDark = Color(0xFF243240) +val secondaryContainerDark = Color(0xFF3A4857) +val onSecondaryContainerDark = Color(0xFFD6E4F7) +val tertiaryDark = Color(0xFFD4BEE6) +val onTertiaryDark = Color(0xFF392A49) +val tertiaryContainerDark = Color(0xFF514060) +val onTertiaryContainerDark = Color(0xFFF0DBFF) +val errorDark = Color(0xFFFFB4AB) +val onErrorDark = Color(0xFF690005) +val errorContainerDark = Color(0xFF93000A) +val onErrorContainerDark = Color(0xFFFFDAD6) +val backgroundDark = Color(0xFF101418) +val onBackgroundDark = Color(0xFFE0E2E8) +val surfaceDark = Color(0xFF101418) +val onSurfaceDark = Color(0xFFE0E2E8) +val surfaceVariantDark = Color(0xFF42474E) +val onSurfaceVariantDark = Color(0xFFC2C7CF) +val outlineDark = Color(0xFF8C9199) +val outlineVariantDark = Color(0xFF42474E) +val scrimDark = Color(0xFF000000) +val inverseSurfaceDark = Color(0xFFE0E2E8) +val inverseOnSurfaceDark = Color(0xFF2D3135) +val inversePrimaryDark = Color(0xFF32628D) +val surfaceDimDark = Color(0xFF101418) +val surfaceBrightDark = Color(0xFF36393E) +val surfaceContainerLowestDark = Color(0xFF0B0E12) +val surfaceContainerLowDark = Color(0xFF191C20) +val surfaceContainerDark = Color(0xFF1D2024) +val surfaceContainerHighDark = Color(0xFF272A2F) +val surfaceContainerHighestDark = Color(0xFF32353A) diff --git a/app/src/main/kotlin/com/aistra/hail/ui/theme/Theme.kt b/app/src/main/kotlin/com/aistra/hail/ui/theme/Theme.kt new file mode 100644 index 00000000..a9965c7d --- /dev/null +++ b/app/src/main/kotlin/com/aistra/hail/ui/theme/Theme.kt @@ -0,0 +1,104 @@ +package com.aistra.hail.ui.theme + +import androidx.compose.foundation.isSystemInDarkTheme +import androidx.compose.material3.* +import androidx.compose.runtime.Composable +import androidx.compose.ui.platform.LocalContext +import com.aistra.hail.utils.HTarget + +private val lightScheme = lightColorScheme( + primary = primaryLight, + onPrimary = onPrimaryLight, + primaryContainer = primaryContainerLight, + onPrimaryContainer = onPrimaryContainerLight, + secondary = secondaryLight, + onSecondary = onSecondaryLight, + secondaryContainer = secondaryContainerLight, + onSecondaryContainer = onSecondaryContainerLight, + tertiary = tertiaryLight, + onTertiary = onTertiaryLight, + tertiaryContainer = tertiaryContainerLight, + onTertiaryContainer = onTertiaryContainerLight, + error = errorLight, + onError = onErrorLight, + errorContainer = errorContainerLight, + onErrorContainer = onErrorContainerLight, + background = backgroundLight, + onBackground = onBackgroundLight, + surface = surfaceLight, + onSurface = onSurfaceLight, + surfaceVariant = surfaceVariantLight, + onSurfaceVariant = onSurfaceVariantLight, + outline = outlineLight, + outlineVariant = outlineVariantLight, + scrim = scrimLight, + inverseSurface = inverseSurfaceLight, + inverseOnSurface = inverseOnSurfaceLight, + inversePrimary = inversePrimaryLight, + surfaceDim = surfaceDimLight, + surfaceBright = surfaceBrightLight, + surfaceContainerLowest = surfaceContainerLowestLight, + surfaceContainerLow = surfaceContainerLowLight, + surfaceContainer = surfaceContainerLight, + surfaceContainerHigh = surfaceContainerHighLight, + surfaceContainerHighest = surfaceContainerHighestLight, +) + +private val darkScheme = darkColorScheme( + primary = primaryDark, + onPrimary = onPrimaryDark, + primaryContainer = primaryContainerDark, + onPrimaryContainer = onPrimaryContainerDark, + secondary = secondaryDark, + onSecondary = onSecondaryDark, + secondaryContainer = secondaryContainerDark, + onSecondaryContainer = onSecondaryContainerDark, + tertiary = tertiaryDark, + onTertiary = onTertiaryDark, + tertiaryContainer = tertiaryContainerDark, + onTertiaryContainer = onTertiaryContainerDark, + error = errorDark, + onError = onErrorDark, + errorContainer = errorContainerDark, + onErrorContainer = onErrorContainerDark, + background = backgroundDark, + onBackground = onBackgroundDark, + surface = surfaceDark, + onSurface = onSurfaceDark, + surfaceVariant = surfaceVariantDark, + onSurfaceVariant = onSurfaceVariantDark, + outline = outlineDark, + outlineVariant = outlineVariantDark, + scrim = scrimDark, + inverseSurface = inverseSurfaceDark, + inverseOnSurface = inverseOnSurfaceDark, + inversePrimary = inversePrimaryDark, + surfaceDim = surfaceDimDark, + surfaceBright = surfaceBrightDark, + surfaceContainerLowest = surfaceContainerLowestDark, + surfaceContainerLow = surfaceContainerLowDark, + surfaceContainer = surfaceContainerDark, + surfaceContainerHigh = surfaceContainerHighDark, + surfaceContainerHighest = surfaceContainerHighestDark, +) + +@Composable +fun AppTheme( + darkTheme: Boolean = isSystemInDarkTheme(), + // Dynamic color is available on Android 12+ + dynamicColor: Boolean = true, content: @Composable() () -> Unit +) { + val colorScheme = when { + dynamicColor && HTarget.S -> { + val context = LocalContext.current + if (darkTheme) dynamicDarkColorScheme(context) else dynamicLightColorScheme(context) + } + + darkTheme -> darkScheme + else -> lightScheme + } + + MaterialTheme( + colorScheme = colorScheme, typography = AppTypography, content = content + ) +} diff --git a/app/src/main/kotlin/com/aistra/hail/ui/theme/Type.kt b/app/src/main/kotlin/com/aistra/hail/ui/theme/Type.kt new file mode 100644 index 00000000..a393a20f --- /dev/null +++ b/app/src/main/kotlin/com/aistra/hail/ui/theme/Type.kt @@ -0,0 +1,5 @@ +package com.aistra.hail.ui.theme + +import androidx.compose.material3.Typography + +val AppTypography = Typography() diff --git a/app/src/main/res/drawable/ic_baseline_adb.xml b/app/src/main/res/drawable/ic_baseline_adb.xml deleted file mode 100644 index d968f2d0..00000000 --- a/app/src/main/res/drawable/ic_baseline_adb.xml +++ /dev/null @@ -1,10 +0,0 @@ - - - diff --git a/app/src/main/res/drawable/ic_baseline_apps.xml b/app/src/main/res/drawable/ic_baseline_apps.xml deleted file mode 100644 index f9f8143c..00000000 --- a/app/src/main/res/drawable/ic_baseline_apps.xml +++ /dev/null @@ -1,10 +0,0 @@ - - - diff --git a/app/src/main/res/drawable/ic_baseline_battery_charging.xml b/app/src/main/res/drawable/ic_baseline_battery_charging.xml deleted file mode 100644 index 15165acb..00000000 --- a/app/src/main/res/drawable/ic_baseline_battery_charging.xml +++ /dev/null @@ -1,10 +0,0 @@ - - - diff --git a/app/src/main/res/drawable/ic_baseline_crop_square.xml b/app/src/main/res/drawable/ic_baseline_crop_square.xml deleted file mode 100644 index c9d9497b..00000000 --- a/app/src/main/res/drawable/ic_baseline_crop_square.xml +++ /dev/null @@ -1,10 +0,0 @@ - - - diff --git a/app/src/main/res/drawable/ic_baseline_dark_mode.xml b/app/src/main/res/drawable/ic_baseline_dark_mode.xml deleted file mode 100644 index 0022e962..00000000 --- a/app/src/main/res/drawable/ic_baseline_dark_mode.xml +++ /dev/null @@ -1,10 +0,0 @@ - - - diff --git a/app/src/main/res/drawable/ic_baseline_dialpad.xml b/app/src/main/res/drawable/ic_baseline_dialpad.xml deleted file mode 100644 index a4fc869e..00000000 --- a/app/src/main/res/drawable/ic_baseline_dialpad.xml +++ /dev/null @@ -1,10 +0,0 @@ - - - diff --git a/app/src/main/res/drawable/ic_baseline_filter_bw.xml b/app/src/main/res/drawable/ic_baseline_filter_bw.xml deleted file mode 100644 index 2c5cee00..00000000 --- a/app/src/main/res/drawable/ic_baseline_filter_bw.xml +++ /dev/null @@ -1,10 +0,0 @@ - - - diff --git a/app/src/main/res/drawable/ic_baseline_fingerprint.xml b/app/src/main/res/drawable/ic_baseline_fingerprint.xml deleted file mode 100644 index 168724ef..00000000 --- a/app/src/main/res/drawable/ic_baseline_fingerprint.xml +++ /dev/null @@ -1,10 +0,0 @@ - - - diff --git a/app/src/main/res/drawable/ic_baseline_screen_lock.xml b/app/src/main/res/drawable/ic_baseline_screen_lock.xml deleted file mode 100644 index 017e29aa..00000000 --- a/app/src/main/res/drawable/ic_baseline_screen_lock.xml +++ /dev/null @@ -1,10 +0,0 @@ - - - diff --git a/app/src/main/res/drawable/ic_baseline_send.xml b/app/src/main/res/drawable/ic_baseline_send.xml deleted file mode 100644 index c54d3aa8..00000000 --- a/app/src/main/res/drawable/ic_baseline_send.xml +++ /dev/null @@ -1,11 +0,0 @@ - - - diff --git a/app/src/main/res/drawable/ic_baseline_text_fields.xml b/app/src/main/res/drawable/ic_baseline_text_fields.xml deleted file mode 100644 index 2d685ae0..00000000 --- a/app/src/main/res/drawable/ic_baseline_text_fields.xml +++ /dev/null @@ -1,10 +0,0 @@ - - - diff --git a/app/src/main/res/drawable/ic_outline_app_shortcut.xml b/app/src/main/res/drawable/ic_outline_app_shortcut.xml deleted file mode 100644 index b54b5450..00000000 --- a/app/src/main/res/drawable/ic_outline_app_shortcut.xml +++ /dev/null @@ -1,19 +0,0 @@ - - - - - - diff --git a/app/src/main/res/drawable/ic_outline_cleaning.xml b/app/src/main/res/drawable/ic_outline_cleaning.xml deleted file mode 100644 index 0dcc712b..00000000 --- a/app/src/main/res/drawable/ic_outline_cleaning.xml +++ /dev/null @@ -1,10 +0,0 @@ - - - diff --git a/app/src/main/res/drawable/ic_outline_code.xml b/app/src/main/res/drawable/ic_outline_code.xml deleted file mode 100644 index 84b37a48..00000000 --- a/app/src/main/res/drawable/ic_outline_code.xml +++ /dev/null @@ -1,10 +0,0 @@ - - - diff --git a/app/src/main/res/drawable/ic_outline_description.xml b/app/src/main/res/drawable/ic_outline_description.xml deleted file mode 100644 index 1be0240e..00000000 --- a/app/src/main/res/drawable/ic_outline_description.xml +++ /dev/null @@ -1,10 +0,0 @@ - - - diff --git a/app/src/main/res/drawable/ic_outline_download.xml b/app/src/main/res/drawable/ic_outline_download.xml deleted file mode 100644 index 20369830..00000000 --- a/app/src/main/res/drawable/ic_outline_download.xml +++ /dev/null @@ -1,10 +0,0 @@ - - - diff --git a/app/src/main/res/drawable/ic_outline_giftcard.xml b/app/src/main/res/drawable/ic_outline_giftcard.xml deleted file mode 100644 index 8a43e51a..00000000 --- a/app/src/main/res/drawable/ic_outline_giftcard.xml +++ /dev/null @@ -1,10 +0,0 @@ - - - diff --git a/app/src/main/res/drawable/ic_outline_group.xml b/app/src/main/res/drawable/ic_outline_group.xml deleted file mode 100644 index 20f2f0ca..00000000 --- a/app/src/main/res/drawable/ic_outline_group.xml +++ /dev/null @@ -1,10 +0,0 @@ - - - diff --git a/app/src/main/res/drawable/ic_outline_layers.xml b/app/src/main/res/drawable/ic_outline_layers.xml deleted file mode 100644 index ec27aa14..00000000 --- a/app/src/main/res/drawable/ic_outline_layers.xml +++ /dev/null @@ -1,10 +0,0 @@ - - - diff --git a/app/src/main/res/drawable/ic_outline_local_mall.xml b/app/src/main/res/drawable/ic_outline_local_mall.xml deleted file mode 100644 index 11cd48c0..00000000 --- a/app/src/main/res/drawable/ic_outline_local_mall.xml +++ /dev/null @@ -1,10 +0,0 @@ - - - diff --git a/app/src/main/res/drawable/ic_outline_notifications_active.xml b/app/src/main/res/drawable/ic_outline_notifications_active.xml deleted file mode 100644 index 94c626de..00000000 --- a/app/src/main/res/drawable/ic_outline_notifications_active.xml +++ /dev/null @@ -1,10 +0,0 @@ - - - diff --git a/app/src/main/res/drawable/ic_outline_palette.xml b/app/src/main/res/drawable/ic_outline_palette.xml deleted file mode 100644 index c17d630b..00000000 --- a/app/src/main/res/drawable/ic_outline_palette.xml +++ /dev/null @@ -1,22 +0,0 @@ - - - - - - - diff --git a/app/src/main/res/drawable/ic_outline_shortcut.xml b/app/src/main/res/drawable/ic_outline_shortcut.xml deleted file mode 100644 index f5c0f241..00000000 --- a/app/src/main/res/drawable/ic_outline_shortcut.xml +++ /dev/null @@ -1,10 +0,0 @@ - - - diff --git a/app/src/main/res/drawable/ic_outline_timer.xml b/app/src/main/res/drawable/ic_outline_timer.xml deleted file mode 100644 index 48095e6d..00000000 --- a/app/src/main/res/drawable/ic_outline_timer.xml +++ /dev/null @@ -1,10 +0,0 @@ - - - diff --git a/app/src/main/res/drawable/ic_outline_translate.xml b/app/src/main/res/drawable/ic_outline_translate.xml deleted file mode 100644 index 786e1728..00000000 --- a/app/src/main/res/drawable/ic_outline_translate.xml +++ /dev/null @@ -1,10 +0,0 @@ - - - diff --git a/app/src/main/res/drawable/ic_outline_update.xml b/app/src/main/res/drawable/ic_outline_update.xml deleted file mode 100644 index a2718808..00000000 --- a/app/src/main/res/drawable/ic_outline_update.xml +++ /dev/null @@ -1,10 +0,0 @@ - - - diff --git a/app/src/main/res/drawable/m3_simple_menu_background.xml b/app/src/main/res/drawable/m3_simple_menu_background.xml deleted file mode 100644 index af43f17a..00000000 --- a/app/src/main/res/drawable/m3_simple_menu_background.xml +++ /dev/null @@ -1,18 +0,0 @@ - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/app/src/main/res/layout/dialog_input.xml b/app/src/main/res/layout/dialog_input.xml index 7220f3c0..00e8524d 100644 --- a/app/src/main/res/layout/dialog_input.xml +++ b/app/src/main/res/layout/dialog_input.xml @@ -7,9 +7,9 @@ android:id="@+id/input_layout" android:layout_width="match_parent" android:layout_height="wrap_content" - android:layout_marginStart="@dimen/dialog_padding" - android:layout_marginTop="@dimen/container_margin" - android:layout_marginEnd="@dimen/dialog_padding"> + android:layout_marginStart="@dimen/padding_large" + android:layout_marginTop="@dimen/padding_medium" + android:layout_marginEnd="@dimen/padding_large"> + xmlns:tools="http://schemas.android.com/tools" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:gravity="center_vertical" + android:orientation="horizontal" + tools:viewBindingIgnore="true"> + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:padding="@dimen/padding_large"/> + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:text="@string/app_slogan"/> \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_about.xml b/app/src/main/res/layout/fragment_about.xml deleted file mode 100644 index c730518b..00000000 --- a/app/src/main/res/layout/fragment_about.xml +++ /dev/null @@ -1,349 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/app/src/main/res/layout/item_apps.xml b/app/src/main/res/layout/item_apps.xml index d9a0dc33..a437a673 100644 --- a/app/src/main/res/layout/item_apps.xml +++ b/app/src/main/res/layout/item_apps.xml @@ -1,46 +1,46 @@ + android:layout_width="match_parent" + android:layout_height="wrap_content" + xmlns:tools="http://schemas.android.com/tools" + android:background="?selectableItemBackground" + android:gravity="center_vertical"> + android:id="@+id/app_icon" + android:layout_width="@dimen/app_icon_size" + android:layout_height="@dimen/app_icon_size" + android:contentDescription="@null" + android:padding="@dimen/padding_small" + tools:src="@mipmap/ic_launcher"/> + android:layout_width="0dp" + android:layout_height="wrap_content" + android:layout_weight="1" + android:orientation="vertical"> + android:id="@+id/app_name" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:ellipsize="end" + android:maxLines="1" + android:textAppearance="@style/TextAppearance.Material3.BodyMedium" + tools:text="@string/app_name"/> + android:id="@+id/app_desc" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:ellipsize="end" + android:maxLines="2" + android:textAppearance="@style/TextAppearance.Material3.BodySmall" + tools:text="com.aistra.hail"/> + android:id="@+id/app_star" + android:layout_width="wrap_content" + android:layout_height="wrap_content"/> \ No newline at end of file diff --git a/app/src/main/res/layout/item_home.xml b/app/src/main/res/layout/item_home.xml index 7fd1bd19..edcf2047 100644 --- a/app/src/main/res/layout/item_home.xml +++ b/app/src/main/res/layout/item_home.xml @@ -3,7 +3,7 @@ xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="wrap_content" - android:layout_margin="@dimen/control_margin" + android:layout_margin="@dimen/padding_extra_small" android:background="?selectableItemBackgroundBorderless" android:gravity="center_horizontal" android:orientation="vertical" @@ -14,7 +14,7 @@ android:layout_width="@dimen/app_icon_size" android:layout_height="@dimen/app_icon_size" android:contentDescription="@null" - android:padding="@dimen/control_padding"/> + android:padding="@dimen/padding_small"/> - - - diff --git a/app/src/main/res/raw/licenses b/app/src/main/res/raw/licenses index f32eafd9..8b423f20 100644 --- a/app/src/main/res/raw/licenses +++ b/app/src/main/res/raw/licenses @@ -1,23 +1,12 @@ Android Jetpack (Apache 2.0): https://developer.android.com/jetpack - Chinese to Pinyin (BSD): https://github.com/belerweb/pinyin4j - Material Components for Android (Apache 2.0): https://github.com/material-components/material-components-android - Insetter (Apache 2.0): https://github.com/chrisbanes/insetter - -RikkaX (MIT): https://github.com/RikkaApps/RikkaX - Shizuku (Apache 2.0): https://github.com/RikkaApps/Shizuku - Shizuku-API (MIT): https://github.com/RikkaApps/Shizuku-API - Dhizuku-API (MIT): https://github.com/iamr0s/Dhizuku-API - AppIconLoader (Apache 2.0): https://github.com/zhanghai/AppIconLoader - +Compose Preference (Apache 2.0): https://github.com/zhanghai/ComposePreference Apache Commons Text (Apache 2.0): https://github.com/apache/commons-text - Kotlin (Apache 2.0): https://kotlinlang.org - AndroidHiddenApiBypass (Apache 2.0): https://github.com/LSPosed/AndroidHiddenApiBypass \ No newline at end of file diff --git a/app/src/main/res/values-night/colors.xml b/app/src/main/res/values-night/colors.xml index b0c56a02..3507e6c3 100644 --- a/app/src/main/res/values-night/colors.xml +++ b/app/src/main/res/values-night/colors.xml @@ -1,8 +1,50 @@ - - @color/error_color_material_dark + #9DCBFC + #003355 + #134A74 + #CFE5FF + #BAC8DA + #243240 + #3A4857 + #D6E4F7 + #D4BEE6 + #392A49 + #514060 + #F0DBFF + #FFB4AB + #690005 + #93000A + #FFDAD6 + #101418 + #E0E2E8 + #101418 + #E0E2E8 + #42474E + #C2C7CF + #8C9199 + #42474E + #000000 + #E0E2E8 + #2D3135 + #32628D + #CFE5FF + #001D34 + #9DCBFC + #134A74 + #D6E4F7 + #0F1D2A + #BAC8DA + #3A4857 + #F0DBFF + #241532 + #D4BEE6 + #514060 + #101418 + #36393E + #0B0E12 + #191C20 + #1D2024 + #272A2F + #32353A \ No newline at end of file diff --git a/app/src/main/res/values-v31/themes.xml b/app/src/main/res/values-v31/themes.xml new file mode 100644 index 00000000..ac0445a1 --- /dev/null +++ b/app/src/main/res/values-v31/themes.xml @@ -0,0 +1,4 @@ + + + - - - - \ No newline at end of file diff --git a/app/src/main/res/values/themes.xml b/app/src/main/res/values/themes.xml index 13a10eef..fd8435de 100644 --- a/app/src/main/res/values/themes.xml +++ b/app/src/main/res/values/themes.xml @@ -1,47 +1,75 @@ - - - +