Skip to content

Commit

Permalink
Dynamic color scheme
Browse files Browse the repository at this point in the history
Add sorting when selecting apps
  • Loading branch information
wenxuanjun committed May 14, 2022
1 parent 1f28f51 commit 57ee5c6
Show file tree
Hide file tree
Showing 24 changed files with 458 additions and 271 deletions.
25 changes: 13 additions & 12 deletions app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,13 @@ plugins {
android {
compileSdk 32
buildFeatures.compose true
composeOptions.kotlinCompilerExtensionVersion "1.2.0-beta01"
composeOptions.kotlinCompilerExtensionVersion compose_version

defaultConfig {
applicationId "lantian.nolitter"
minSdk 27
targetSdk 32
versionCode 15
versionCode 16
versionName "1.5.0"
}
buildTypes {
Expand All @@ -29,15 +29,16 @@ android {
}

dependencies {
compileOnly 'de.robv.android.xposed:api:82'
implementation 'androidx.core:core-ktx:1.7.0'
implementation "androidx.compose.ui:ui:1.2.0-alpha08"
implementation 'androidx.compose.ui:ui-tooling:1.2.0-alpha08'
implementation 'androidx.compose.animation:animation:1.1.1'
implementation "androidx.compose.material:material:1.2.0-alpha08"
compileOnly "de.robv.android.xposed:api:82"
implementation "androidx.core:core-ktx:1.7.0"
implementation "androidx.compose.ui:ui:$compose_version"
implementation "androidx.compose.ui:ui-tooling:$compose_version"
implementation "androidx.compose.ui:ui-tooling-preview:$compose_version"
implementation "androidx.compose.animation:animation:1.1.1"
implementation "androidx.compose.material3:material3:1.0.0-alpha11"
implementation "androidx.compose.material:material-icons-extended:1.2.0-alpha08"
implementation 'androidx.activity:activity-compose:1.6.0-alpha03'
implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.5.0-beta01'
implementation 'androidx.navigation:navigation-compose:2.5.0-beta01'
implementation 'com.google.accompanist:accompanist-drawablepainter:0.24.7-alpha'
implementation 'androidx.activity:activity-compose:1.5.0-beta01'
implementation "androidx.lifecycle:lifecycle-runtime-ktx:2.5.0-beta01"
implementation "androidx.navigation:navigation-compose:2.5.0-beta01"
implementation "com.google.accompanist:accompanist-drawablepainter:0.24.8-beta"
}
2 changes: 1 addition & 1 deletion app/src/main/java/lantian/nolitter/MainActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
registerForActivityResult(ActivityResultContracts.RequestPermission()) { isGranted: Boolean ->
if (!isGranted) Toast.makeText(this, R.string.ui_failPermission, Toast.LENGTH_SHORT).show()
if (!isGranted) Toast.makeText(this, R.string.ui_cleanFolder_failPermission, Toast.LENGTH_SHORT).show()
}.launch(Manifest.permission.WRITE_EXTERNAL_STORAGE)
setContent { AppUi(ViewModelProvider(this)[MainViewModel::class.java]) }
}
Expand Down
9 changes: 6 additions & 3 deletions app/src/main/java/lantian/nolitter/XposedHook.kt
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ class XposedHook : IXposedHookZygoteInit, IXposedHookLoadPackage {
val hookFileWithString: XC_MethodHook = object : XC_MethodHook() {
@Throws(Throwable::class)
override fun beforeHookedMethod(param: MethodHookParam) {
if (param.args[0] == null) return
val oldPath = param.args[0].toString()
val newPath = doReplace(param.args[0].toString(), lpparam.packageName)
if (oldPath != newPath) {
Expand All @@ -34,8 +35,9 @@ class XposedHook : IXposedHookZygoteInit, IXposedHookLoadPackage {
val hookFileWithStringAndString: XC_MethodHook = object : XC_MethodHook() {
@Throws(Throwable::class)
override fun beforeHookedMethod(param: MethodHookParam) {
if (param.args[1] == null) return
if (param.args[0] == null) param.args[0] = ""
val oldPath = param.args[0].toString() + "/" + param.args[1].toString().replace("//", "/")
val oldPath = (param.args[0].toString() + "/" + param.args[1].toString()).replace("//", "/")
val newPath = doReplace(oldPath, lpparam.packageName)
if (oldPath != newPath) {
param.args[0] = null
Expand All @@ -47,6 +49,7 @@ class XposedHook : IXposedHookZygoteInit, IXposedHookLoadPackage {
val hookFileWithFileAndString: XC_MethodHook = object : XC_MethodHook() {
@Throws(Throwable::class)
override fun beforeHookedMethod(param: MethodHookParam) {
if (param.args[1] == null) return
if (param.args[0] == null) param.args[0] = File("")
val oldPath = ((param.args[0] as File).absolutePath + "/" + param.args[1].toString()).replace("//", "/")
val newPath = doReplace(oldPath, lpparam.packageName)
Expand Down Expand Up @@ -86,7 +89,7 @@ class XposedHook : IXposedHookZygoteInit, IXposedHookLoadPackage {
XposedHelpers.findAndHookConstructor(File::class.java, String::class.java, hookFileWithString)
XposedHelpers.findAndHookConstructor(File::class.java, String::class.java, String::class.java, hookFileWithStringAndString)
XposedHelpers.findAndHookConstructor(File::class.java, File::class.java, String::class.java, hookFileWithFileAndString)
if (prefs!!.getString("forced", Constants.defaultForcedList)!!.split(",").contains(lpparam.packageName)) {
if (prefs!!.getString("forced_apps", Constants.defaultForcedList)!!.split(",").contains(lpparam.packageName)) {
printDebugLogs(lpparam.packageName, "Forced", "in forcelist")
XposedHelpers.findAndHookMethod(Environment::class.java, "getExternalStorageDirectory", changeDirHook)
XposedHelpers.findAndHookMethod(Environment::class.java, "getExternalStoragePublicDirectory", String::class.java, changeDirHook)
Expand All @@ -112,7 +115,7 @@ class XposedHook : IXposedHookZygoteInit, IXposedHookLoadPackage {

val storageDirs = Constants.defaultStorageDirs.split(",")
val redirectDir = prefs!!.getString("redirect_dir", Constants.defaultRedirectDir)
val forceMode = prefs!!.getString("forced", Constants.defaultForcedList)!!.split(",").contains(packageName)
val forceMode = prefs!!.getString("forced_apps", Constants.defaultForcedList)!!.split(",").contains(packageName)
val separateApp = prefs!!.getBoolean("separate_app", true)

for (storageDir in storageDirs) {
Expand Down
67 changes: 49 additions & 18 deletions app/src/main/java/lantian/nolitter/models/MainViewModel.kt
Original file line number Diff line number Diff line change
Expand Up @@ -3,79 +3,110 @@ package lantian.nolitter.models
import android.app.Application
import android.content.ComponentName
import android.content.Context
import android.content.Intent
import android.content.SharedPreferences
import android.content.pm.ApplicationInfo
import android.content.pm.PackageInfo
import android.content.pm.PackageManager
import android.graphics.drawable.Drawable
import android.net.Uri
import androidx.compose.foundation.layout.RowScope
import androidx.compose.runtime.Composable
import androidx.compose.runtime.MutableState
import androidx.compose.runtime.mutableStateOf
import androidx.core.content.ContextCompat.startActivity
import androidx.lifecycle.AndroidViewModel
import androidx.lifecycle.viewModelScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import lantian.nolitter.BuildConfig
import lantian.nolitter.Constants
import lantian.nolitter.MainActivity
import lantian.nolitter.R

data class TopAppBarStatus (var title: String, var actions: @Composable RowScope.() -> Unit)
data class InstalledPackageInfo (val appName: String, val appIcon: Drawable, val isSystem: Boolean, val isModule: Boolean, val isForced: Boolean, val packageName: String)
data class InstalledPackageInfo (
val appName: String, val appIcon: Drawable,
val isSystem: Boolean, val isModule: Boolean,
val isForced: Boolean, val packageName: String,
val firstInstallTime: Long
)

class MainViewModel(application: Application) : AndroidViewModel(application) {
private var activity: Application = application
private var sharedPreferences: SharedPreferences? = try {
activity.getSharedPreferences(BuildConfig.APPLICATION_ID + "_preferences", Context.MODE_WORLD_READABLE)
} catch (e: SecurityException) { null }

// Several states of the view
var appTheme: MutableState<String> = mutableStateOf(getStringPreference("theme", "default"))
var topAppBarTitle: MutableState<String> = mutableStateOf(activity.resources.getString(R.string.app_name))
var topAppBarActions: MutableState<@Composable RowScope.() -> Unit> = mutableStateOf({})
fun isAvailable(): Boolean { return sharedPreferences != null }
val installedPackages: MutableState<List<InstalledPackageInfo>> = mutableStateOf(listOf())

// Return if the module is enabld
fun isModuleEnabled(): Boolean { return sharedPreferences != null }

// Intent to some webpages
fun intentToWebsite(link: String) { startActivity(activity, Intent(Intent.ACTION_VIEW, Uri.parse(link)).setFlags(Intent.FLAG_ACTIVITY_NEW_TASK), null) }

// Functions of handling preferences
fun getBooleanPreference(key: String, defaultValue: Boolean): Boolean { return sharedPreferences?.getBoolean(key, defaultValue) ?: defaultValue }
fun getStringPreference(key: String, defaultValue: String): String { return sharedPreferences?.getString(key, defaultValue) ?: defaultValue }
fun setBooleanPreference(key: String, value: Boolean) { sharedPreferences!!.edit().putBoolean(key, value).apply() }
fun setStringPreference(key: String, value: String) { sharedPreferences!!.edit().putString(key, value).apply() }

// Other useful utils
fun hideAppIcon(value: Boolean) {
activity.packageManager.setComponentEnabledSetting(
ComponentName(activity, MainActivity::class.java),
if (value) PackageManager.COMPONENT_ENABLED_STATE_DISABLED else PackageManager.COMPONENT_ENABLED_STATE_ENABLED,
PackageManager.DONT_KILL_APP
)
}
fun initAllInstalledPackages() {
viewModelScope.launch {
installedPackages.value = getAllInstalledPackages()
}
}
fun getNavigationTitle(key: String?): String {
return when (key) {
"PreferenceGeneral" -> activity.resources.getString(R.string.ui_settings_general)
"PreferenceInterface" -> activity.resources.getString(R.string.ui_settings_interface)
"PreferenceAdvanced" -> activity.resources.getString(R.string.ui_settings_advanced)
"PreferenceMiscellaneous" -> activity.resources.getString(R.string.ui_settings_miscellaneous)
"PreferenceSelectApps" -> activity.resources.getString(R.string.ui_settings_forceMode)
else -> activity.resources.getString(R.string.app_name)
}
}
fun getAllInstalledPackages(): ArrayList<InstalledPackageInfo> {
fun onChangeForcedApps(packageName: String, newValue: Boolean) {
val forcedApps = getStringPreference("forced_apps", Constants.defaultForcedList).split(",").toMutableList()
if (newValue) forcedApps.add(packageName) else forcedApps.remove(packageName)
var newForcedApps = ""
for (forcedApp in forcedApps) {
if (forcedApp.trim().isNotEmpty()) newForcedApps += forcedApp.trim() + ","
}
setStringPreference("forced_apps", newForcedApps)
}

// Private functions
private suspend fun getAllInstalledPackages(): ArrayList<InstalledPackageInfo> = withContext(Dispatchers.IO) {
val packageManager = activity.packageManager
val installedPackages: List<PackageInfo> = packageManager.getInstalledPackages(PackageManager.GET_META_DATA)
val allPackageInfo: ArrayList<InstalledPackageInfo> = ArrayList()
val forcedApps = getStringPreference("forced", Constants.defaultForcedList).split(",")
val forcedApps = getStringPreference("forced_apps", Constants.defaultForcedList).split(",")
val installedPackages: List<PackageInfo> = packageManager.getInstalledPackages(PackageManager.GET_META_DATA)
for (installedPackage in installedPackages) {
val applicationInfo = installedPackage.applicationInfo
val applicationFlag = installedPackage.applicationInfo.flags
allPackageInfo.add(InstalledPackageInfo(
appName = applicationInfo.loadLabel(packageManager).toString(),
appIcon = applicationInfo.loadIcon(packageManager),
isSystem = (applicationFlag and ApplicationInfo.FLAG_SYSTEM != 0) || (applicationFlag and ApplicationInfo.FLAG_UPDATED_SYSTEM_APP != 0),
isModule = applicationInfo.metaData != null && applicationInfo.metaData.containsKey("xposedmodule"),
isModule = applicationInfo.metaData != null && applicationInfo.metaData.containsKey("xposedminversion"),
isForced = forcedApps.contains(applicationInfo.packageName),
packageName = applicationInfo.packageName,
firstInstallTime = installedPackage.firstInstallTime
))
}
return allPackageInfo
}
fun onChangeForcedApps(packageName: String, newValue: Boolean) {
val forcedApps = getStringPreference("forced", Constants.defaultForcedList).split(",").toMutableList()
if (newValue) forcedApps.add(packageName) else forcedApps.remove(packageName)
var newForcedApps = ""
for (forcedApp in forcedApps) {
if (forcedApp.trim().isNotEmpty()) newForcedApps += forcedApp.trim() + ","
}
setStringPreference("forced", newForcedApps)
return@withContext allPackageInfo
}
}
8 changes: 4 additions & 4 deletions app/src/main/java/lantian/nolitter/receiver/CleanFolder.kt
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ class CleanFolder: BroadcastReceiver() {

// Check if have sdcard access permission
if (ContextCompat.checkSelfPermission(context, Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
Toast.makeText(context, packageName + context.getString(R.string.ui_failClear), Toast.LENGTH_SHORT).show()
Toast.makeText(context, packageName + context.getString(R.string.ui_cleanFolder_failClear), Toast.LENGTH_SHORT).show()
return
}

Expand All @@ -43,11 +43,11 @@ class CleanFolder: BroadcastReceiver() {
val directoryPath = "/sdcard" + prefs.getString("redirect_dir", Constants.defaultRedirectDir) + "/" + packageName
if (File(URI.create("file://$directoryPath")).exists()) {
try {
Toast.makeText(context, packageName + context.getString(R.string.ui_isClearing), Toast.LENGTH_SHORT).show()
Toast.makeText(context, packageName + context.getString(R.string.ui_cleanFolder_isClearing), Toast.LENGTH_SHORT).show()
Runtime.getRuntime().exec("rm -r $directoryPath")
Toast.makeText(context, packageName + context.getString(R.string.ui_isCleared), Toast.LENGTH_SHORT).show()
Toast.makeText(context, packageName + context.getString(R.string.ui_cleanFolder_isCleared), Toast.LENGTH_SHORT).show()
} catch (e: IOException) {
Toast.makeText(context, packageName + context.getString(R.string.ui_failClear), Toast.LENGTH_SHORT).show()
Toast.makeText(context, packageName + context.getString(R.string.ui_cleanFolder_failClear), Toast.LENGTH_SHORT).show()
}
}
}
Expand Down
17 changes: 7 additions & 10 deletions app/src/main/java/lantian/nolitter/ui/AppUI.kt
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
package lantian.nolitter.ui

import androidx.compose.foundation.layout.*
import androidx.compose.material.*
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.ArrowBack
import androidx.compose.material3.*
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
Expand All @@ -15,10 +15,10 @@ import lantian.nolitter.models.MainViewModel
import lantian.nolitter.ui.theme.ApplicationTheme

@Composable
@OptIn(ExperimentalMaterial3Api::class)
fun AppUi(viewModel: MainViewModel) {
ApplicationTheme(viewModel.appTheme.value) {
val navController = rememberNavController()
val scaffoldState = rememberScaffoldState()
var canNavigationPop by remember { mutableStateOf(false) }
val navigateUpIcon: @Composable () -> Unit = { Icon(imageVector = Icons.Default.ArrowBack, contentDescription = null) }
val toolbarController = object {
Expand All @@ -41,27 +41,24 @@ fun AppUi(viewModel: MainViewModel) {

Scaffold(
topBar = {
TopAppBar(
SmallTopAppBar(
title = { Text(viewModel.topAppBarTitle.value) },
navigationIcon = if (canNavigationPop) {{ IconButton(content = navigateUpIcon, onClick = { navController.navigateUp() }) }} else null,
navigationIcon = { if (canNavigationPop) { IconButton(content = navigateUpIcon, onClick = { navController.navigateUp() }) } else null },
actions = viewModel.topAppBarActions.value
)
},
content = { innerPadding ->
if (viewModel.isAvailable()) Router(innerPadding, viewModel, navController)
if (viewModel.isModuleEnabled()) Router(innerPadding, viewModel, navController)
else ModuleNotEnabled(innerPadding)
},
scaffoldState = scaffoldState
}
)
}
}

@Composable
fun ModuleNotEnabled(innerPadding: PaddingValues) {
Column(
modifier = Modifier
.fillMaxSize()
.padding(innerPadding),
modifier = Modifier.fillMaxSize().padding(innerPadding),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center,
content = { Text(stringResource(R.string.ui_moduleNotEnabled)) }
Expand Down
2 changes: 1 addition & 1 deletion app/src/main/java/lantian/nolitter/ui/Router.kt
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ fun Router(innerPadding: PaddingValues, viewModel: MainViewModel, navController:
composable("PreferenceHome") { PreferenceHome(viewModel, navController) }
composable("PreferenceGeneral") { PreferenceGeneral(viewModel, navController) }
composable("PreferenceInterface") { PreferenceInterface(viewModel) }
composable("PreferenceAdvanced") { PreferenceAdvanced(viewModel) }
composable("PreferenceMiscellaneous") { PreferenceMiscellaneous(viewModel) }
composable("PreferenceSelectApps") { PreferenceSelectApps(viewModel) }
}
}

This file was deleted.

Loading

0 comments on commit 57ee5c6

Please sign in to comment.