Replace old design with redesign (p2)
This commit is contained in:
parent
df0a5b59f8
commit
9094cf7ce3
@ -2,11 +2,11 @@ package a;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
import com.topjohnwu.magisk.model.update.UpdateCheckService;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.work.WorkerParameters;
|
||||
|
||||
import com.topjohnwu.magisk.model.update.UpdateCheckService;
|
||||
|
||||
public class g extends w<UpdateCheckService> {
|
||||
/* Stub */
|
||||
public g(@NonNull Context context, @NonNull WorkerParameters workerParams) {
|
||||
|
@ -18,13 +18,12 @@ import com.topjohnwu.magisk.extensions.forceGetDeclaredField
|
||||
import com.topjohnwu.magisk.model.download.DownloadService
|
||||
import com.topjohnwu.magisk.model.receiver.GeneralReceiver
|
||||
import com.topjohnwu.magisk.model.update.UpdateCheckService
|
||||
import com.topjohnwu.magisk.ui.MainActivity
|
||||
import com.topjohnwu.magisk.redesign.MainActivity
|
||||
import com.topjohnwu.magisk.ui.SplashActivity
|
||||
import com.topjohnwu.magisk.ui.flash.FlashActivity
|
||||
import com.topjohnwu.magisk.ui.surequest.SuRequestActivity
|
||||
import com.topjohnwu.magisk.utils.refreshLocale
|
||||
import com.topjohnwu.magisk.utils.updateConfig
|
||||
import com.topjohnwu.magisk.redesign.MainActivity as RedesignActivity
|
||||
|
||||
fun AssetManager.addAssetPath(path: String) {
|
||||
DynAPK.addAssetPath(this, path)
|
||||
@ -150,8 +149,7 @@ object ClassMap {
|
||||
GeneralReceiver::class.java to a.h::class.java,
|
||||
DownloadService::class.java to a.j::class.java,
|
||||
SuRequestActivity::class.java to a.m::class.java,
|
||||
ProcessPhoenix::class.java to a.r::class.java,
|
||||
RedesignActivity::class.java to a.b::class.java
|
||||
ProcessPhoenix::class.java to a.r::class.java
|
||||
)
|
||||
|
||||
operator fun get(c: Class<*>) = map.getOrElse(c) { c }
|
||||
|
@ -1,25 +1,13 @@
|
||||
package com.topjohnwu.magisk.di
|
||||
|
||||
import android.net.Uri
|
||||
import com.topjohnwu.magisk.ui.MainViewModel
|
||||
import com.topjohnwu.magisk.ui.flash.FlashViewModel
|
||||
import com.topjohnwu.magisk.ui.hide.HideViewModel
|
||||
import com.topjohnwu.magisk.ui.home.HomeViewModel
|
||||
import com.topjohnwu.magisk.ui.log.LogViewModel
|
||||
import com.topjohnwu.magisk.ui.module.ModuleViewModel
|
||||
import com.topjohnwu.magisk.ui.superuser.SuperuserViewModel
|
||||
import com.topjohnwu.magisk.ui.surequest.SuRequestViewModel
|
||||
import org.koin.androidx.viewmodel.dsl.viewModel
|
||||
import org.koin.dsl.module
|
||||
|
||||
|
||||
val viewModelModules = module {
|
||||
viewModel { MainViewModel() }
|
||||
viewModel { HomeViewModel(get()) }
|
||||
viewModel { SuperuserViewModel(get(), get(), get(), get()) }
|
||||
viewModel { HideViewModel(get(), get()) }
|
||||
viewModel { ModuleViewModel(get(), get(), get()) }
|
||||
viewModel { LogViewModel(get(), get()) }
|
||||
viewModel { (action: String, file: Uri, additional: Uri) ->
|
||||
FlashViewModel(action, file, additional, get())
|
||||
}
|
||||
|
@ -5,20 +5,13 @@ import android.view.ViewGroup
|
||||
import com.topjohnwu.magisk.R
|
||||
import com.topjohnwu.magisk.databinding.ComparableRvItem
|
||||
import com.topjohnwu.magisk.extensions.addOnPropertyChangedCallback
|
||||
import com.topjohnwu.magisk.extensions.inject
|
||||
import com.topjohnwu.magisk.extensions.startAnimations
|
||||
import com.topjohnwu.magisk.extensions.toggle
|
||||
import com.topjohnwu.magisk.model.entity.HideAppInfo
|
||||
import com.topjohnwu.magisk.model.entity.HideTarget
|
||||
import com.topjohnwu.magisk.model.entity.ProcessHideApp
|
||||
import com.topjohnwu.magisk.model.entity.StatefulProcess
|
||||
import com.topjohnwu.magisk.model.entity.state.IndeterminateState
|
||||
import com.topjohnwu.magisk.model.events.HideProcessEvent
|
||||
import com.topjohnwu.magisk.model.observer.Observer
|
||||
import com.topjohnwu.magisk.redesign.hide.HideViewModel
|
||||
import com.topjohnwu.magisk.utils.DiffObservableList
|
||||
import com.topjohnwu.magisk.utils.KObservableField
|
||||
import com.topjohnwu.magisk.utils.RxBus
|
||||
import kotlin.math.roundToInt
|
||||
|
||||
class HideItem(val item: ProcessHideApp) : ComparableRvItem<HideItem>() {
|
||||
@ -86,81 +79,3 @@ class HideProcessItem(val item: StatefulProcess) : ComparableRvItem<HideProcessI
|
||||
override fun itemSameAs(other: HideProcessItem) = item.name == other.item.name
|
||||
|
||||
}
|
||||
|
||||
class HideRvItem(val item: HideAppInfo, targets: List<HideTarget>) :
|
||||
ComparableRvItem<HideRvItem>() {
|
||||
|
||||
override val layoutRes: Int = R.layout.item_hide_app
|
||||
|
||||
val packageName = item.info.packageName.orEmpty()
|
||||
val items = DiffObservableList(callback).also {
|
||||
val items = item.processes.map {
|
||||
val isHidden = targets.any { target ->
|
||||
packageName == target.packageName && it == target.process
|
||||
}
|
||||
HideProcessRvItem(packageName, it, isHidden)
|
||||
}
|
||||
it.update(items)
|
||||
}
|
||||
val isHiddenState = KObservableField(currentState)
|
||||
val isExpanded = KObservableField(false)
|
||||
|
||||
private val itemsProcess get() = items.filterIsInstance<HideProcessRvItem>()
|
||||
|
||||
private val currentState
|
||||
get() = when (itemsProcess.count { it.isHidden.value }) {
|
||||
items.size -> IndeterminateState.CHECKED
|
||||
in 1 until items.size -> IndeterminateState.INDETERMINATE
|
||||
else -> IndeterminateState.UNCHECKED
|
||||
}
|
||||
|
||||
init {
|
||||
itemsProcess.forEach {
|
||||
it.isHidden.addOnPropertyChangedCallback { isHiddenState.value = currentState }
|
||||
}
|
||||
}
|
||||
|
||||
fun toggle() {
|
||||
val desiredState = when (isHiddenState.value) {
|
||||
IndeterminateState.INDETERMINATE,
|
||||
IndeterminateState.UNCHECKED -> true
|
||||
IndeterminateState.CHECKED -> false
|
||||
}
|
||||
itemsProcess.forEach { it.isHidden.value = desiredState }
|
||||
isHiddenState.value = currentState
|
||||
}
|
||||
|
||||
fun toggleExpansion() {
|
||||
if (items.size <= 1) return
|
||||
isExpanded.toggle()
|
||||
}
|
||||
|
||||
override fun contentSameAs(other: HideRvItem): Boolean = items.all { other.items.contains(it) }
|
||||
override fun itemSameAs(other: HideRvItem): Boolean = item.info == other.item.info
|
||||
|
||||
}
|
||||
|
||||
class HideProcessRvItem(
|
||||
val packageName: String,
|
||||
val process: String,
|
||||
isHidden: Boolean
|
||||
) : ComparableRvItem<HideProcessRvItem>() {
|
||||
|
||||
override val layoutRes: Int = R.layout.item_hide_process
|
||||
|
||||
val isHidden = KObservableField(isHidden)
|
||||
|
||||
private val rxBus: RxBus by inject()
|
||||
|
||||
init {
|
||||
this.isHidden.addOnPropertyChangedCallback {
|
||||
rxBus.post(HideProcessEvent(this@HideProcessRvItem))
|
||||
}
|
||||
}
|
||||
|
||||
fun toggle() = isHidden.toggle()
|
||||
|
||||
override fun contentSameAs(other: HideProcessRvItem): Boolean = itemSameAs(other)
|
||||
override fun itemSameAs(other: HideProcessRvItem): Boolean =
|
||||
packageName == other.packageName && process == other.process
|
||||
}
|
@ -3,79 +3,9 @@ package com.topjohnwu.magisk.model.entity.recycler
|
||||
import androidx.databinding.Bindable
|
||||
import com.topjohnwu.magisk.BR
|
||||
import com.topjohnwu.magisk.R
|
||||
import com.topjohnwu.magisk.databinding.ComparableRvItem
|
||||
import com.topjohnwu.magisk.extensions.timeDateFormat
|
||||
import com.topjohnwu.magisk.extensions.timeFormatMedium
|
||||
import com.topjohnwu.magisk.extensions.toTime
|
||||
import com.topjohnwu.magisk.extensions.toggle
|
||||
import com.topjohnwu.magisk.model.entity.MagiskLog
|
||||
import com.topjohnwu.magisk.model.entity.WrappedMagiskLog
|
||||
import com.topjohnwu.magisk.utils.DiffObservableList
|
||||
import com.topjohnwu.magisk.utils.KObservableField
|
||||
|
||||
class LogRvItem : ComparableRvItem<LogRvItem>() {
|
||||
override val layoutRes: Int = R.layout.item_page_log
|
||||
|
||||
val items = DiffObservableList(callback)
|
||||
|
||||
fun update(list: List<LogItemRvItem>) {
|
||||
list.firstOrNull()?.isExpanded?.value = true
|
||||
items.update(list)
|
||||
}
|
||||
|
||||
//two of these will never be present, safe to assume it's unique
|
||||
override fun contentSameAs(other: LogRvItem): Boolean = false
|
||||
|
||||
override fun itemSameAs(other: LogRvItem): Boolean = false
|
||||
}
|
||||
|
||||
class LogItemRvItem(
|
||||
item: WrappedMagiskLog
|
||||
) : ComparableRvItem<LogItemRvItem>() {
|
||||
override val layoutRes: Int = R.layout.item_superuser_log
|
||||
|
||||
val date = item.time.toTime(timeFormatMedium)
|
||||
val items: List<ComparableRvItem<*>> = item.items.map { LogItemEntryRvItem(it) }
|
||||
val isExpanded = KObservableField(false)
|
||||
|
||||
fun toggle() = isExpanded.toggle()
|
||||
|
||||
override fun contentSameAs(other: LogItemRvItem): Boolean {
|
||||
if (items.size != other.items.size) return false
|
||||
return items.all { it in other.items }
|
||||
}
|
||||
|
||||
override fun itemSameAs(other: LogItemRvItem): Boolean = date == other.date
|
||||
}
|
||||
|
||||
class LogItemEntryRvItem(val item: MagiskLog) : ComparableRvItem<LogItemEntryRvItem>() {
|
||||
override val layoutRes: Int = R.layout.item_superuser_log_entry
|
||||
|
||||
val isExpanded = KObservableField(false)
|
||||
|
||||
fun toggle() = isExpanded.toggle()
|
||||
|
||||
override fun contentSameAs(other: LogItemEntryRvItem) = item == other.item
|
||||
|
||||
override fun itemSameAs(other: LogItemEntryRvItem) = item.appName == other.item.appName
|
||||
}
|
||||
|
||||
class MagiskLogRvItem : ComparableRvItem<MagiskLogRvItem>() {
|
||||
override val layoutRes: Int = R.layout.item_page_magisk_log
|
||||
|
||||
val items = DiffObservableList(callback)
|
||||
|
||||
fun update(list: List<ConsoleRvItem>) {
|
||||
items.update(list)
|
||||
}
|
||||
|
||||
//two of these will never be present, safe to assume it's unique
|
||||
override fun contentSameAs(other: MagiskLogRvItem): Boolean = false
|
||||
|
||||
override fun itemSameAs(other: MagiskLogRvItem): Boolean = false
|
||||
}
|
||||
|
||||
// ---
|
||||
|
||||
class LogItem(val item: MagiskLog) : ObservableItem<LogItem>() {
|
||||
|
||||
@ -106,4 +36,4 @@ class LogItem(val item: MagiskLog) : ObservableItem<LogItem>() {
|
||||
item.time == other.item.time &&
|
||||
isTop == other.isTop &&
|
||||
isBottom == other.isBottom
|
||||
}
|
||||
}
|
||||
|
@ -1,7 +1,5 @@
|
||||
package com.topjohnwu.magisk.model.entity.recycler
|
||||
|
||||
import android.content.res.Resources
|
||||
import androidx.annotation.StringRes
|
||||
import androidx.databinding.Bindable
|
||||
import androidx.databinding.Observable
|
||||
import androidx.databinding.PropertyChangeRegistry
|
||||
@ -10,77 +8,11 @@ import androidx.recyclerview.widget.StaggeredGridLayoutManager
|
||||
import com.topjohnwu.magisk.BR
|
||||
import com.topjohnwu.magisk.R
|
||||
import com.topjohnwu.magisk.databinding.ComparableRvItem
|
||||
import com.topjohnwu.magisk.extensions.addOnPropertyChangedCallback
|
||||
import com.topjohnwu.magisk.extensions.get
|
||||
import com.topjohnwu.magisk.extensions.toggle
|
||||
import com.topjohnwu.magisk.model.entity.module.Module
|
||||
import com.topjohnwu.magisk.model.entity.module.Repo
|
||||
import com.topjohnwu.magisk.redesign.module.ModuleViewModel
|
||||
import com.topjohnwu.magisk.utils.KObservableField
|
||||
|
||||
class ModuleRvItem(val item: Module) : ComparableRvItem<ModuleRvItem>() {
|
||||
|
||||
override val layoutRes: Int = R.layout.item_module
|
||||
|
||||
val lastActionNotice = KObservableField("")
|
||||
val isChecked = KObservableField(item.enable)
|
||||
val isDeletable = KObservableField(item.remove)
|
||||
|
||||
init {
|
||||
isChecked.addOnPropertyChangedCallback {
|
||||
when (it) {
|
||||
true -> {
|
||||
item.enable = true
|
||||
notice(R.string.disable_file_removed)
|
||||
}
|
||||
false -> {
|
||||
item.enable = false
|
||||
notice(R.string.disable_file_created)
|
||||
}
|
||||
}
|
||||
}
|
||||
isDeletable.addOnPropertyChangedCallback {
|
||||
when (it) {
|
||||
true -> {
|
||||
item.remove = true
|
||||
notice(R.string.remove_file_created)
|
||||
}
|
||||
false -> {
|
||||
item.remove = false
|
||||
notice(R.string.remove_file_deleted)
|
||||
}
|
||||
}
|
||||
}
|
||||
when {
|
||||
item.updated -> notice(R.string.update_file_created)
|
||||
item.remove -> notice(R.string.remove_file_created)
|
||||
}
|
||||
}
|
||||
|
||||
fun toggle() = isChecked.toggle()
|
||||
fun toggleDelete() = isDeletable.toggle()
|
||||
|
||||
private fun notice(@StringRes info: Int) {
|
||||
lastActionNotice.value = get<Resources>().getString(info)
|
||||
}
|
||||
|
||||
override fun contentSameAs(other: ModuleRvItem): Boolean = item.version == other.item.version
|
||||
&& item.versionCode == other.item.versionCode
|
||||
&& item.description == other.item.description
|
||||
&& item.name == other.item.name
|
||||
|
||||
override fun itemSameAs(other: ModuleRvItem): Boolean = item.id == other.item.id
|
||||
}
|
||||
|
||||
class RepoRvItem(val item: Repo) : ComparableRvItem<RepoRvItem>() {
|
||||
|
||||
override val layoutRes: Int = R.layout.item_repo
|
||||
|
||||
override fun contentSameAs(other: RepoRvItem): Boolean = item == other.item
|
||||
|
||||
override fun itemSameAs(other: RepoRvItem): Boolean = item.id == other.item.id
|
||||
}
|
||||
|
||||
object SafeModeNotice : ComparableRvItem<SafeModeNotice>() {
|
||||
override val layoutRes = R.layout.item_safe_mode_notice
|
||||
|
||||
@ -227,4 +159,4 @@ abstract class ObservableItem<T> : ComparableRvItem<T>(), Observable {
|
||||
|
||||
fun notifyChange(id: Int) = list.notifyChange(this, id)
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -3,54 +3,11 @@ package com.topjohnwu.magisk.model.entity.recycler
|
||||
import android.graphics.drawable.Drawable
|
||||
import com.topjohnwu.magisk.R
|
||||
import com.topjohnwu.magisk.databinding.ComparableRvItem
|
||||
import com.topjohnwu.magisk.extensions.addOnPropertyChangedCallback
|
||||
import com.topjohnwu.magisk.extensions.inject
|
||||
import com.topjohnwu.magisk.extensions.toggle
|
||||
import com.topjohnwu.magisk.model.entity.MagiskPolicy
|
||||
import com.topjohnwu.magisk.model.events.PolicyEnableEvent
|
||||
import com.topjohnwu.magisk.model.events.PolicyUpdateEvent
|
||||
import com.topjohnwu.magisk.redesign.superuser.SuperuserViewModel
|
||||
import com.topjohnwu.magisk.utils.KObservableField
|
||||
import com.topjohnwu.magisk.utils.RxBus
|
||||
|
||||
class PolicyRvItem(val item: MagiskPolicy, val icon: Drawable) : ComparableRvItem<PolicyRvItem>() {
|
||||
|
||||
override val layoutRes = R.layout.item_policy
|
||||
|
||||
val isExpanded = KObservableField(false)
|
||||
val isEnabled = KObservableField(item.policy == MagiskPolicy.ALLOW)
|
||||
val shouldNotify = KObservableField(item.notification)
|
||||
val shouldLog = KObservableField(item.logging)
|
||||
|
||||
fun toggle() = isExpanded.toggle()
|
||||
|
||||
private val rxBus: RxBus by inject()
|
||||
|
||||
private val currentStateItem
|
||||
get() = item.copy(
|
||||
policy = if (isEnabled.value) MagiskPolicy.ALLOW else MagiskPolicy.DENY,
|
||||
notification = shouldNotify.value,
|
||||
logging = shouldLog.value
|
||||
)
|
||||
|
||||
init {
|
||||
isEnabled.addOnPropertyChangedCallback {
|
||||
it ?: return@addOnPropertyChangedCallback
|
||||
rxBus.post(PolicyEnableEvent(this@PolicyRvItem, it))
|
||||
}
|
||||
shouldNotify.addOnPropertyChangedCallback {
|
||||
it ?: return@addOnPropertyChangedCallback
|
||||
rxBus.post(PolicyUpdateEvent.Notification(currentStateItem))
|
||||
}
|
||||
shouldLog.addOnPropertyChangedCallback {
|
||||
it ?: return@addOnPropertyChangedCallback
|
||||
rxBus.post(PolicyUpdateEvent.Log(currentStateItem))
|
||||
}
|
||||
}
|
||||
|
||||
override fun contentSameAs(other: PolicyRvItem): Boolean = itemSameAs(other)
|
||||
override fun itemSameAs(other: PolicyRvItem): Boolean = item.uid == other.item.uid
|
||||
}
|
||||
|
||||
class PolicyItem(val item: MagiskPolicy, val icon: Drawable) : ComparableRvItem<PolicyItem>() {
|
||||
override val layoutRes = R.layout.item_policy_md2
|
||||
@ -93,4 +50,4 @@ class PolicyItem(val item: MagiskPolicy, val icon: Drawable) : ComparableRvItem<
|
||||
override fun contentSameAs(other: PolicyItem) = itemSameAs(other)
|
||||
override fun itemSameAs(other: PolicyItem) = item.uid == other.item.uid
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -1,19 +1,11 @@
|
||||
package com.topjohnwu.magisk.model.events
|
||||
|
||||
import com.topjohnwu.magisk.model.entity.MagiskPolicy
|
||||
import com.topjohnwu.magisk.model.entity.recycler.HideProcessRvItem
|
||||
import com.topjohnwu.magisk.model.entity.recycler.ModuleRvItem
|
||||
import com.topjohnwu.magisk.model.entity.recycler.PolicyRvItem
|
||||
import com.topjohnwu.magisk.utils.RxBus
|
||||
|
||||
class HideProcessEvent(val item: HideProcessRvItem) : RxBus.Event
|
||||
|
||||
class PolicyEnableEvent(val item: PolicyRvItem, val enable: Boolean) : RxBus.Event
|
||||
sealed class PolicyUpdateEvent(val item: MagiskPolicy) : RxBus.Event {
|
||||
class Notification(item: MagiskPolicy) : PolicyUpdateEvent(item)
|
||||
class Log(item: MagiskPolicy) : PolicyUpdateEvent(item)
|
||||
}
|
||||
|
||||
class ModuleUpdatedEvent(val item: ModuleRvItem) : RxBus.Event
|
||||
|
||||
data class SafetyNetResult(val responseCode: Int) : RxBus.Event
|
||||
data class SafetyNetResult(val responseCode: Int) : RxBus.Event
|
||||
|
@ -10,12 +10,11 @@ import com.topjohnwu.magisk.redesign.hide.HideFragment
|
||||
import com.topjohnwu.magisk.redesign.home.HomeFragment
|
||||
import com.topjohnwu.magisk.redesign.install.InstallFragment
|
||||
import com.topjohnwu.magisk.redesign.log.LogFragment
|
||||
import com.topjohnwu.magisk.redesign.module.ModuleFragment
|
||||
import com.topjohnwu.magisk.redesign.safetynet.SafetynetFragment
|
||||
import com.topjohnwu.magisk.redesign.settings.SettingsFragment
|
||||
import com.topjohnwu.magisk.redesign.superuser.SuperuserFragment
|
||||
import com.topjohnwu.magisk.redesign.theme.ThemeFragment
|
||||
import com.topjohnwu.magisk.ui.module.ModulesFragment
|
||||
import com.topjohnwu.magisk.ui.module.ReposFragment
|
||||
|
||||
object Navigation {
|
||||
|
||||
@ -36,14 +35,10 @@ object Navigation {
|
||||
|
||||
fun modules() = MagiskNavigationEvent {
|
||||
navDirections {
|
||||
destination = ModulesFragment::class
|
||||
destination = ModuleFragment::class
|
||||
}
|
||||
}
|
||||
|
||||
fun repos() = MagiskNavigationEvent {
|
||||
navDirections { destination = ReposFragment::class }
|
||||
}
|
||||
|
||||
fun hide() = MagiskNavigationEvent {
|
||||
navDirections {
|
||||
destination = HideFragment::class
|
||||
@ -77,7 +72,6 @@ object Navigation {
|
||||
fun fromSection(section: String) = when (section) {
|
||||
"superuser" -> superuser()
|
||||
"modules" -> modules()
|
||||
"downloads" -> repos()
|
||||
"magiskhide" -> hide()
|
||||
"log" -> log()
|
||||
"settings" -> settings()
|
||||
|
@ -24,12 +24,15 @@ import com.topjohnwu.magisk.model.navigation.Navigation
|
||||
import com.topjohnwu.magisk.model.observer.Observer
|
||||
import com.topjohnwu.magisk.redesign.compat.CompatViewModel
|
||||
import com.topjohnwu.magisk.redesign.compat.itemBindingOf
|
||||
import com.topjohnwu.magisk.ui.home.MagiskState
|
||||
import com.topjohnwu.magisk.utils.KObservableField
|
||||
import com.topjohnwu.superuser.Shell
|
||||
import me.tatarka.bindingcollectionadapter2.BR
|
||||
import kotlin.math.roundToInt
|
||||
|
||||
enum class MagiskState {
|
||||
NOT_INSTALLED, UP_TO_DATE, OBSOLETE, LOADING
|
||||
}
|
||||
|
||||
class HomeViewModel(
|
||||
private val repoMagisk: MagiskRepository
|
||||
) : CompatViewModel() {
|
||||
|
@ -7,11 +7,15 @@ import com.topjohnwu.magisk.extensions.subscribeK
|
||||
import com.topjohnwu.magisk.model.events.SafetyNetResult
|
||||
import com.topjohnwu.magisk.model.events.UpdateSafetyNetEvent
|
||||
import com.topjohnwu.magisk.redesign.compat.CompatViewModel
|
||||
import com.topjohnwu.magisk.ui.home.SafetyNetState.*
|
||||
import com.topjohnwu.magisk.redesign.safetynet.SafetyNetState.*
|
||||
import com.topjohnwu.magisk.utils.KObservableField
|
||||
import com.topjohnwu.magisk.utils.RxBus
|
||||
import com.topjohnwu.magisk.utils.SafetyNetHelper
|
||||
|
||||
enum class SafetyNetState {
|
||||
LOADING, PASS, FAILED, IDLE
|
||||
}
|
||||
|
||||
class SafetynetViewModel(
|
||||
rxBus: RxBus
|
||||
) : CompatViewModel() {
|
||||
@ -89,4 +93,4 @@ class SafetynetViewModel(
|
||||
private var safetyNetResult = -1
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -1,252 +0,0 @@
|
||||
package com.topjohnwu.magisk.ui
|
||||
|
||||
import android.content.Intent
|
||||
import android.os.Bundle
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import androidx.core.view.GravityCompat
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.fragment.app.FragmentTransaction
|
||||
import com.ncapdevi.fragnav.FragNavController
|
||||
import com.ncapdevi.fragnav.FragNavTransactionOptions
|
||||
import com.topjohnwu.magisk.Const
|
||||
import com.topjohnwu.magisk.Const.Key.OPEN_SECTION
|
||||
import com.topjohnwu.magisk.Info
|
||||
import com.topjohnwu.magisk.R
|
||||
import com.topjohnwu.magisk.base.BaseActivity
|
||||
import com.topjohnwu.magisk.base.BaseFragment
|
||||
import com.topjohnwu.magisk.databinding.ActivityMainBinding
|
||||
import com.topjohnwu.magisk.extensions.addOnPropertyChangedCallback
|
||||
import com.topjohnwu.magisk.extensions.snackbar
|
||||
import com.topjohnwu.magisk.intent
|
||||
import com.topjohnwu.magisk.model.events.*
|
||||
import com.topjohnwu.magisk.model.navigation.MagiskAnimBuilder
|
||||
import com.topjohnwu.magisk.model.navigation.MagiskNavigationEvent
|
||||
import com.topjohnwu.magisk.model.navigation.Navigation
|
||||
import com.topjohnwu.magisk.model.navigation.Navigator
|
||||
import com.topjohnwu.magisk.ui.hide.MagiskHideFragment
|
||||
import com.topjohnwu.magisk.ui.home.HomeFragment
|
||||
import com.topjohnwu.magisk.ui.log.LogFragment
|
||||
import com.topjohnwu.magisk.ui.module.ModulesFragment
|
||||
import com.topjohnwu.magisk.ui.module.ReposFragment
|
||||
import com.topjohnwu.magisk.ui.settings.SettingsFragment
|
||||
import com.topjohnwu.magisk.ui.superuser.SuperuserFragment
|
||||
import com.topjohnwu.magisk.utils.Utils
|
||||
import org.koin.androidx.viewmodel.ext.android.viewModel
|
||||
import timber.log.Timber
|
||||
import kotlin.reflect.KClass
|
||||
|
||||
open class MainActivity : BaseActivity<MainViewModel, ActivityMainBinding>(), Navigator,
|
||||
FragNavController.RootFragmentListener, FragNavController.TransactionListener {
|
||||
|
||||
override val layoutRes: Int = R.layout.activity_main
|
||||
override val viewModel: MainViewModel by viewModel()
|
||||
private val navHostId: Int = R.id.main_nav_host
|
||||
private val defaultPosition: Int = 0
|
||||
|
||||
private val navigationController by lazy {
|
||||
FragNavController(supportFragmentManager, navHostId)
|
||||
}
|
||||
private val isRootFragment get() =
|
||||
navigationController.currentStackIndex != defaultPosition
|
||||
|
||||
override val baseFragments: List<KClass<out Fragment>> = listOf(
|
||||
HomeFragment::class,
|
||||
SuperuserFragment::class,
|
||||
MagiskHideFragment::class,
|
||||
ModulesFragment::class,
|
||||
ReposFragment::class,
|
||||
LogFragment::class,
|
||||
SettingsFragment::class
|
||||
)
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
if (!SplashActivity.DONE) {
|
||||
startActivity(intent<SplashActivity>())
|
||||
finish()
|
||||
}
|
||||
|
||||
super.onCreate(savedInstanceState)
|
||||
|
||||
if (Info.env.isUnsupported && !viewModel.shownUnsupportedDialog) {
|
||||
viewModel.shownUnsupportedDialog = true
|
||||
AlertDialog.Builder(this)
|
||||
.setTitle(R.string.unsupport_magisk_title)
|
||||
.setMessage(getString(R.string.unsupport_magisk_msg, Const.Version.MIN_VERSION))
|
||||
.setPositiveButton(android.R.string.ok, null)
|
||||
.show()
|
||||
}
|
||||
|
||||
navigationController.apply {
|
||||
rootFragmentListener = this@MainActivity
|
||||
transactionListener = this@MainActivity
|
||||
initialize(defaultPosition, savedInstanceState)
|
||||
}
|
||||
|
||||
checkHideSection()
|
||||
setSupportActionBar(binding.mainInclude.mainToolbar)
|
||||
|
||||
viewModel.isConnected.addOnPropertyChangedCallback {
|
||||
checkHideSection()
|
||||
}
|
||||
|
||||
if (savedInstanceState == null) {
|
||||
intent.getStringExtra(OPEN_SECTION)?.let {
|
||||
onEventDispatched(Navigation.fromSection(it))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onSaveInstanceState(outState: Bundle) {
|
||||
super.onSaveInstanceState(outState)
|
||||
navigationController.onSaveInstanceState(outState)
|
||||
}
|
||||
|
||||
override fun setTitle(title: CharSequence?) {
|
||||
supportActionBar?.title = title
|
||||
}
|
||||
|
||||
override fun setTitle(titleId: Int) {
|
||||
supportActionBar?.setTitle(titleId)
|
||||
}
|
||||
|
||||
override fun onBackPressed() {
|
||||
if (binding.drawerLayout.isDrawerOpen(binding.navView)) {
|
||||
binding.drawerLayout.closeDrawer(binding.navView)
|
||||
} else {
|
||||
val fragment = navigationController.currentFrag as? BaseFragment<*, *>
|
||||
|
||||
if (fragment?.onBackPressed() == true) {
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
navigationController.popFragment()
|
||||
} catch (e: UnsupportedOperationException) {
|
||||
when {
|
||||
isRootFragment -> {
|
||||
val options = FragNavTransactionOptions.newBuilder()
|
||||
.transition(FragmentTransaction.TRANSIT_FRAGMENT_CLOSE)
|
||||
.build()
|
||||
navigationController.switchTab(defaultPosition, options)
|
||||
}
|
||||
else -> super.onBackPressed()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onEventDispatched(event: ViewEvent) {
|
||||
super.onEventDispatched(event)
|
||||
when (event) {
|
||||
is SnackbarEvent -> snackbar(snackbarView, event.message(this), event.length, event.f)
|
||||
is BackPressEvent -> onBackPressed()
|
||||
is MagiskNavigationEvent -> navigateTo(event)
|
||||
is ViewActionEvent -> event.action(this)
|
||||
is PermissionEvent -> withPermissions(*event.permissions.toTypedArray()) {
|
||||
onSuccess { event.callback.onNext(true) }
|
||||
onFailure {
|
||||
event.callback.onNext(false)
|
||||
event.callback.onError(SecurityException("User refused permissions"))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onSimpleEventDispatched(event: Int) {
|
||||
super.onSimpleEventDispatched(event)
|
||||
when (event) {
|
||||
Navigation.Main.OPEN_NAV -> openNav()
|
||||
}
|
||||
}
|
||||
|
||||
private fun openNav() = binding.drawerLayout.openDrawer(GravityCompat.START)
|
||||
|
||||
private fun checkHideSection() {
|
||||
val menu = binding.navView.menu
|
||||
menu.findItem(R.id.magiskHideFragment).isVisible = Info.env.isActive && Info.env.magiskHide
|
||||
menu.findItem(R.id.modulesFragment).isVisible = Info.env.isActive
|
||||
menu.findItem(R.id.reposFragment).isVisible = Info.isConnected.value && Info.env.isActive
|
||||
menu.findItem(R.id.logFragment).isVisible = Info.env.isActive
|
||||
menu.findItem(R.id.superuserFragment).isVisible = Utils.showSuperUser()
|
||||
}
|
||||
|
||||
private fun FragNavTransactionOptions.Builder.customAnimations(options: MagiskAnimBuilder) =
|
||||
customAnimations(options.enter, options.exit, options.popEnter, options.popExit).apply {
|
||||
if (!options.anySet) {
|
||||
transition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN)
|
||||
}
|
||||
}
|
||||
|
||||
override val numberOfRootFragments: Int get() = baseFragments.size
|
||||
|
||||
override fun getRootFragment(index: Int) = baseFragments[index].java.newInstance()
|
||||
|
||||
override fun onTabTransaction(fragment: Fragment?, index: Int) {
|
||||
val fragmentId = when (fragment) {
|
||||
is HomeFragment -> R.id.magiskFragment
|
||||
is SuperuserFragment -> R.id.superuserFragment
|
||||
is MagiskHideFragment -> R.id.magiskHideFragment
|
||||
is ModulesFragment -> R.id.modulesFragment
|
||||
is ReposFragment -> R.id.reposFragment
|
||||
is LogFragment -> R.id.logFragment
|
||||
is SettingsFragment -> R.id.settings
|
||||
else -> return
|
||||
}
|
||||
binding.navView.setCheckedItem(fragmentId)
|
||||
}
|
||||
|
||||
override fun navigateTo(event: MagiskNavigationEvent) {
|
||||
val directions = event.navDirections
|
||||
|
||||
navigationController.defaultTransactionOptions = FragNavTransactionOptions.newBuilder()
|
||||
.customAnimations(event.animOptions)
|
||||
.build()
|
||||
|
||||
navigationController.currentStack
|
||||
?.indexOfFirst { it.javaClass == event.navOptions.popUpTo }
|
||||
?.let { if (it == -1) null else it } // invalidate if class is not found
|
||||
?.let { if (event.navOptions.inclusive) it + 1 else it }
|
||||
?.let { navigationController.popFragments(it) }
|
||||
|
||||
when (directions.isActivity) {
|
||||
true -> navigateToActivity(event)
|
||||
else -> navigateToFragment(event)
|
||||
}
|
||||
}
|
||||
|
||||
private fun navigateToActivity(event: MagiskNavigationEvent) {
|
||||
val destination = event.navDirections.destination?.java ?: let {
|
||||
Timber.e("Cannot navigate to null destination")
|
||||
return
|
||||
}
|
||||
val options = event.navOptions
|
||||
|
||||
Intent(this, destination)
|
||||
.putExtras(event.navDirections.args)
|
||||
.apply {
|
||||
if (options.singleTop) addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP)
|
||||
if (options.clearTask) addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK)
|
||||
}
|
||||
.let { startActivity(it) }
|
||||
}
|
||||
|
||||
private fun navigateToFragment(event: MagiskNavigationEvent) {
|
||||
val destination = event.navDirections.destination?.java ?: let {
|
||||
Timber.e("Cannot navigate to null destination")
|
||||
return
|
||||
}
|
||||
|
||||
when (val index = baseFragments.indexOfFirst { it.java.name == destination.name }) {
|
||||
-1 -> destination.newInstance()
|
||||
.apply { arguments = event.navDirections.args }
|
||||
.let { navigationController.pushFragment(it) }
|
||||
// When it's desired that fragments of same class are put on top of one another edit this
|
||||
else -> navigationController.switchTab(index)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onFragmentTransaction(
|
||||
fragment: Fragment?,
|
||||
transactionType: FragNavController.TransactionType
|
||||
) = Unit
|
||||
}
|
@ -1,29 +0,0 @@
|
||||
package com.topjohnwu.magisk.ui
|
||||
|
||||
import android.view.MenuItem
|
||||
import com.topjohnwu.magisk.R
|
||||
import com.topjohnwu.magisk.base.viewmodel.BaseViewModel
|
||||
import com.topjohnwu.magisk.model.navigation.Navigation
|
||||
|
||||
|
||||
class MainViewModel : BaseViewModel() {
|
||||
|
||||
var shownUnsupportedDialog = false
|
||||
|
||||
fun navPressed() = Navigation.Main.OPEN_NAV.publish()
|
||||
|
||||
fun navigationItemPressed(item: MenuItem): Boolean {
|
||||
when (item.itemId) {
|
||||
R.id.magiskFragment -> Navigation.home()
|
||||
R.id.superuserFragment -> Navigation.superuser()
|
||||
R.id.magiskHideFragment -> Navigation.hide()
|
||||
R.id.modulesFragment -> Navigation.modules()
|
||||
R.id.reposFragment -> Navigation.repos()
|
||||
R.id.logFragment -> Navigation.log()
|
||||
R.id.settings -> Navigation.settings()
|
||||
else -> null
|
||||
}?.publish()?.let { return@navigationItemPressed true }
|
||||
return false
|
||||
}
|
||||
|
||||
}
|
@ -1,96 +0,0 @@
|
||||
package com.topjohnwu.magisk.ui.hide
|
||||
|
||||
import android.content.pm.ApplicationInfo
|
||||
import com.topjohnwu.magisk.BR
|
||||
import com.topjohnwu.magisk.base.viewmodel.BaseViewModel
|
||||
import com.topjohnwu.magisk.data.repository.MagiskRepository
|
||||
import com.topjohnwu.magisk.databinding.ComparableRvItem
|
||||
import com.topjohnwu.magisk.extensions.addOnPropertyChangedCallback
|
||||
import com.topjohnwu.magisk.extensions.subscribeK
|
||||
import com.topjohnwu.magisk.extensions.toSingle
|
||||
import com.topjohnwu.magisk.extensions.update
|
||||
import com.topjohnwu.magisk.model.entity.recycler.HideProcessRvItem
|
||||
import com.topjohnwu.magisk.model.entity.recycler.HideRvItem
|
||||
import com.topjohnwu.magisk.model.entity.state.IndeterminateState
|
||||
import com.topjohnwu.magisk.model.events.HideProcessEvent
|
||||
import com.topjohnwu.magisk.utils.DiffObservableList
|
||||
import com.topjohnwu.magisk.utils.KObservableField
|
||||
import com.topjohnwu.magisk.utils.RxBus
|
||||
import me.tatarka.bindingcollectionadapter2.OnItemBind
|
||||
import timber.log.Timber
|
||||
|
||||
class HideViewModel(
|
||||
private val magiskRepo: MagiskRepository,
|
||||
rxBus: RxBus
|
||||
) : BaseViewModel() {
|
||||
|
||||
val query = KObservableField("")
|
||||
val isShowSystem = KObservableField(false)
|
||||
|
||||
private val allItems = mutableListOf<ComparableRvItem<*>>()
|
||||
val items = DiffObservableList(ComparableRvItem.callback)
|
||||
val itemBinding = OnItemBind<ComparableRvItem<*>> { itemBinding, _, item ->
|
||||
item.bind(itemBinding)
|
||||
itemBinding.bindExtra(BR.viewModel, this@HideViewModel)
|
||||
}
|
||||
|
||||
init {
|
||||
rxBus.register<HideProcessEvent>()
|
||||
.subscribeK { toggleItem(it.item) }
|
||||
.add()
|
||||
|
||||
isShowSystem.addOnPropertyChangedCallback { query() }
|
||||
query.addOnPropertyChangedCallback { query() }
|
||||
|
||||
refresh()
|
||||
}
|
||||
|
||||
fun refresh() {
|
||||
// fetching this for every item is nonsensical, so we add .cache() so the response is all
|
||||
// the same for every single mapped item, it only actually executes the whole thing the
|
||||
// first time around.
|
||||
val hideTargets = magiskRepo.fetchHideTargets().cache()
|
||||
|
||||
magiskRepo.fetchApps()
|
||||
.flattenAsFlowable { it }
|
||||
.map { HideRvItem(it, hideTargets.blockingGet()) }
|
||||
.toList()
|
||||
.map {
|
||||
it.sortedWith(compareBy(
|
||||
{ it.isHiddenState.value },
|
||||
{ it.item.name.toLowerCase() },
|
||||
{ it.packageName }
|
||||
))
|
||||
}
|
||||
.doOnSuccess { allItems.update(it) }
|
||||
.flatMap { queryRaw() }
|
||||
.applyViewModel(this)
|
||||
.subscribeK(onError = Timber::e) { items.update(it.first, it.second) }
|
||||
.add()
|
||||
}
|
||||
|
||||
private fun query() = queryRaw()
|
||||
.subscribeK { items.update(it.first, it.second) }
|
||||
.add()
|
||||
|
||||
private fun queryRaw(
|
||||
showSystem: Boolean = isShowSystem.value,
|
||||
query: String = this.query.value
|
||||
) = allItems.toSingle()
|
||||
.map { it.filterIsInstance<HideRvItem>() }
|
||||
.flattenAsFlowable { it }
|
||||
.filter {
|
||||
it.item.name.contains(query, ignoreCase = true) ||
|
||||
it.item.processes.any { it.contains(query, ignoreCase = true) }
|
||||
}
|
||||
.filter {
|
||||
showSystem || (it.isHiddenState.value != IndeterminateState.UNCHECKED) ||
|
||||
(it.item.info.flags and ApplicationInfo.FLAG_SYSTEM == 0)
|
||||
}
|
||||
.toList()
|
||||
.map { it to items.calculateDiff(it) }
|
||||
|
||||
private fun toggleItem(item: HideProcessRvItem) =
|
||||
magiskRepo.toggleHide(item.isHidden.value, item.packageName, item.process)
|
||||
|
||||
}
|
@ -1,71 +0,0 @@
|
||||
package com.topjohnwu.magisk.ui.hide
|
||||
|
||||
import android.view.Menu
|
||||
import android.view.MenuInflater
|
||||
import android.view.MenuItem
|
||||
import android.widget.SearchView
|
||||
import com.topjohnwu.magisk.Config
|
||||
import com.topjohnwu.magisk.R
|
||||
import com.topjohnwu.magisk.base.BaseFragment
|
||||
import com.topjohnwu.magisk.databinding.FragmentMagiskHideBinding
|
||||
import org.koin.androidx.viewmodel.ext.android.viewModel
|
||||
|
||||
class MagiskHideFragment : BaseFragment<HideViewModel, FragmentMagiskHideBinding>(),
|
||||
SearchView.OnQueryTextListener {
|
||||
|
||||
override val layoutRes: Int = R.layout.fragment_magisk_hide
|
||||
override val viewModel: HideViewModel by viewModel()
|
||||
|
||||
override fun onStart() {
|
||||
super.onStart()
|
||||
setHasOptionsMenu(true)
|
||||
requireActivity().setTitle(R.string.magiskhide)
|
||||
}
|
||||
|
||||
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
|
||||
inflater.inflate(R.menu.menu_magiskhide, menu)
|
||||
menu.apply {
|
||||
val query = viewModel.query.value
|
||||
val searchItem = menu.findItem(R.id.app_search)
|
||||
val searchView = searchItem.actionView as? SearchView
|
||||
|
||||
searchView?.run {
|
||||
setOnQueryTextListener(this@MagiskHideFragment)
|
||||
setQuery(query, false)
|
||||
}
|
||||
|
||||
if (query.isNotBlank()) {
|
||||
searchItem.expandActionView()
|
||||
searchView?.isIconified = false
|
||||
} else {
|
||||
searchItem.collapseActionView()
|
||||
searchView?.isIconified = true
|
||||
}
|
||||
|
||||
val showSystem = Config.showSystemApp
|
||||
|
||||
findItem(R.id.show_system).isChecked = showSystem
|
||||
viewModel.isShowSystem.value = showSystem
|
||||
}
|
||||
}
|
||||
|
||||
override fun onOptionsItemSelected(item: MenuItem): Boolean {
|
||||
if (item.itemId == R.id.show_system) {
|
||||
val showSystem = !item.isChecked
|
||||
item.isChecked = showSystem
|
||||
Config.showSystemApp = showSystem
|
||||
viewModel.isShowSystem.value = showSystem
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
override fun onQueryTextSubmit(query: String?): Boolean {
|
||||
viewModel.query.value = query.orEmpty()
|
||||
return false
|
||||
}
|
||||
|
||||
override fun onQueryTextChange(query: String?): Boolean {
|
||||
viewModel.query.value = query.orEmpty()
|
||||
return false
|
||||
}
|
||||
}
|
@ -1,132 +0,0 @@
|
||||
package com.topjohnwu.magisk.ui.home
|
||||
|
||||
import android.content.Context
|
||||
import com.topjohnwu.magisk.BuildConfig
|
||||
import com.topjohnwu.magisk.Const
|
||||
import com.topjohnwu.magisk.Info
|
||||
import com.topjohnwu.magisk.R
|
||||
import com.topjohnwu.magisk.base.BaseActivity
|
||||
import com.topjohnwu.magisk.base.BaseFragment
|
||||
import com.topjohnwu.magisk.data.repository.MagiskRepository
|
||||
import com.topjohnwu.magisk.databinding.FragmentMagiskBinding
|
||||
import com.topjohnwu.magisk.extensions.DynamicClassLoader
|
||||
import com.topjohnwu.magisk.extensions.openUrl
|
||||
import com.topjohnwu.magisk.extensions.subscribeK
|
||||
import com.topjohnwu.magisk.extensions.writeTo
|
||||
import com.topjohnwu.magisk.model.events.*
|
||||
import com.topjohnwu.magisk.utils.SafetyNetHelper
|
||||
import com.topjohnwu.magisk.view.MarkDownWindow
|
||||
import com.topjohnwu.magisk.view.dialogs.*
|
||||
import com.topjohnwu.superuser.Shell
|
||||
import dalvik.system.DexFile
|
||||
import io.reactivex.Completable
|
||||
import org.koin.android.ext.android.inject
|
||||
import org.koin.androidx.viewmodel.ext.android.viewModel
|
||||
import java.io.File
|
||||
import java.lang.reflect.InvocationHandler
|
||||
|
||||
class HomeFragment : BaseFragment<HomeViewModel, FragmentMagiskBinding>(),
|
||||
SafetyNetHelper.Callback {
|
||||
|
||||
override val layoutRes: Int = R.layout.fragment_magisk
|
||||
override val viewModel: HomeViewModel by viewModel()
|
||||
|
||||
private val magiskRepo: MagiskRepository by inject()
|
||||
private val EXT_APK by lazy { File("${activity.filesDir.parent}/snet", "snet.jar") }
|
||||
private val EXT_DEX by lazy { File(EXT_APK.parent, "snet.dex") }
|
||||
|
||||
override fun onResponse(responseCode: Int) = viewModel.finishSafetyNetCheck(responseCode)
|
||||
|
||||
override fun onEventDispatched(event: ViewEvent) {
|
||||
super.onEventDispatched(event)
|
||||
when (event) {
|
||||
is OpenLinkEvent -> activity.openUrl(event.url)
|
||||
is ManagerInstallEvent -> installManager()
|
||||
is MagiskInstallEvent -> installMagisk()
|
||||
is UninstallEvent -> uninstall()
|
||||
is ManagerChangelogEvent -> changelogManager()
|
||||
is EnvFixEvent -> fixEnv()
|
||||
is UpdateSafetyNetEvent -> updateSafetyNet(false)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onStart() {
|
||||
super.onStart()
|
||||
setHasOptionsMenu(true)
|
||||
requireActivity().setTitle(R.string.magisk)
|
||||
}
|
||||
|
||||
private fun installMagisk() {
|
||||
// Show Manager update first
|
||||
if (Info.remote.app.versionCode > BuildConfig.VERSION_CODE) {
|
||||
installManager()
|
||||
return
|
||||
}
|
||||
|
||||
MagiskInstallDialog(requireActivity() as BaseActivity<*, *>).show()
|
||||
}
|
||||
|
||||
private fun installManager() = ManagerInstallDialog(requireActivity()).show()
|
||||
private fun uninstall() = UninstallDialog(requireActivity()).show()
|
||||
private fun fixEnv() = EnvFixDialog(requireActivity()).show()
|
||||
|
||||
private fun changelogManager() = MarkDownWindow
|
||||
.show(requireActivity(), null, resources.openRawResource(R.raw.changelog))
|
||||
|
||||
private fun downloadSafetyNet(requiresUserInput: Boolean = true) {
|
||||
fun download() = magiskRepo.fetchSafetynet()
|
||||
.map { it.byteStream().writeTo(EXT_APK) }
|
||||
.subscribeK { updateSafetyNet(true) }
|
||||
|
||||
if (!requiresUserInput) {
|
||||
download()
|
||||
return
|
||||
}
|
||||
|
||||
CustomAlertDialog(requireActivity())
|
||||
.setTitle(R.string.proprietary_title)
|
||||
.setMessage(R.string.proprietary_notice)
|
||||
.setCancelable(false)
|
||||
.setPositiveButton(android.R.string.yes) { _, _ -> download() }
|
||||
.setNegativeButton(android.R.string.no) { _, _ -> viewModel.finishSafetyNetCheck(-2) }
|
||||
.show()
|
||||
}
|
||||
|
||||
private fun updateSafetyNet(dieOnError: Boolean) {
|
||||
Completable.fromAction {
|
||||
val loader = DynamicClassLoader(EXT_APK)
|
||||
val dex = DexFile.loadDex(EXT_APK.path, EXT_DEX.path, 0)
|
||||
|
||||
// Scan through the dex and find our helper class
|
||||
var helperClass: Class<*>? = null
|
||||
for (className in dex.entries()) {
|
||||
if (className.startsWith("x.")) {
|
||||
val cls = loader.loadClass(className)
|
||||
if (InvocationHandler::class.java.isAssignableFrom(cls)) {
|
||||
helperClass = cls
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
helperClass ?: throw Exception()
|
||||
|
||||
val helper = helperClass.getMethod("get",
|
||||
Class::class.java, Context::class.java, Any::class.java)
|
||||
.invoke(null, SafetyNetHelper::class.java, activity, this) as SafetyNetHelper
|
||||
|
||||
if (helper.version < Const.SNET_EXT_VER)
|
||||
throw Exception()
|
||||
|
||||
helper.attest()
|
||||
}.subscribeK(onError = {
|
||||
if (dieOnError) {
|
||||
viewModel.finishSafetyNetCheck(-1)
|
||||
} else {
|
||||
Shell.sh("rm -rf " + EXT_APK.parent).exec()
|
||||
EXT_APK.parentFile?.mkdir()
|
||||
downloadSafetyNet(!dieOnError)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -1,259 +0,0 @@
|
||||
package com.topjohnwu.magisk.ui.home
|
||||
|
||||
import android.content.pm.PackageManager
|
||||
import com.topjohnwu.magisk.*
|
||||
import com.topjohnwu.magisk.base.viewmodel.BaseViewModel
|
||||
import com.topjohnwu.magisk.data.repository.MagiskRepository
|
||||
import com.topjohnwu.magisk.extensions.*
|
||||
import com.topjohnwu.magisk.model.events.*
|
||||
import com.topjohnwu.magisk.model.observer.Observer
|
||||
import com.topjohnwu.magisk.utils.KObservableField
|
||||
import com.topjohnwu.magisk.utils.SafetyNetHelper
|
||||
import com.topjohnwu.superuser.Shell
|
||||
import io.reactivex.Completable
|
||||
|
||||
enum class SafetyNetState {
|
||||
LOADING, PASS, FAILED, IDLE
|
||||
}
|
||||
|
||||
enum class MagiskState {
|
||||
NOT_INSTALLED, UP_TO_DATE, OBSOLETE, LOADING
|
||||
}
|
||||
|
||||
enum class MagiskItem {
|
||||
MANAGER, MAGISK
|
||||
}
|
||||
|
||||
class HomeViewModel(
|
||||
private val magiskRepo: MagiskRepository
|
||||
) : BaseViewModel(State.LOADED) {
|
||||
|
||||
val hasGMS = runCatching {
|
||||
get<PackageManager>().getPackageInfo("com.google.android.gms", 0); true
|
||||
}.getOrElse { false }
|
||||
|
||||
val isAdvancedExpanded = KObservableField(false)
|
||||
|
||||
val isForceEncryption = KObservableField(Info.keepEnc)
|
||||
val isKeepVerity = KObservableField(Info.keepVerity)
|
||||
val isRecovery = KObservableField(Info.recovery)
|
||||
|
||||
val magiskState = KObservableField(MagiskState.LOADING)
|
||||
val magiskStateText = Observer(magiskState) {
|
||||
when (magiskState.value) {
|
||||
MagiskState.NOT_INSTALLED -> R.string.magisk_version_error.res()
|
||||
MagiskState.UP_TO_DATE -> R.string.magisk_up_to_date.res()
|
||||
MagiskState.LOADING -> R.string.checking_for_updates.res()
|
||||
MagiskState.OBSOLETE -> R.string.magisk_update_title.res()
|
||||
}
|
||||
}
|
||||
val magiskCurrentVersion = KObservableField("")
|
||||
val magiskLatestVersion = KObservableField("")
|
||||
val magiskAdditionalInfo = Observer(magiskState) {
|
||||
if (Config.coreOnly)
|
||||
R.string.core_only_enabled.res()
|
||||
else
|
||||
""
|
||||
}
|
||||
|
||||
private val _managerState = KObservableField(MagiskState.LOADING)
|
||||
val managerState = Observer(_managerState, isConnected) {
|
||||
if (isConnected.value) _managerState.value else MagiskState.UP_TO_DATE
|
||||
}
|
||||
val managerStateText = Observer(managerState) {
|
||||
when (managerState.value) {
|
||||
MagiskState.NOT_INSTALLED -> R.string.invalid_update_channel.res()
|
||||
MagiskState.UP_TO_DATE -> R.string.manager_up_to_date.res()
|
||||
MagiskState.LOADING -> R.string.checking_for_updates.res()
|
||||
MagiskState.OBSOLETE -> R.string.manager_update_title.res()
|
||||
}
|
||||
}
|
||||
val managerCurrentVersion = KObservableField("")
|
||||
val managerLatestVersion = KObservableField("")
|
||||
val managerAdditionalInfo = Observer(managerState) {
|
||||
if (packageName != BuildConfig.APPLICATION_ID)
|
||||
"($packageName)"
|
||||
else
|
||||
""
|
||||
}
|
||||
|
||||
val safetyNetTitle = KObservableField(R.string.safetyNet_check_text.res())
|
||||
val ctsState = KObservableField(SafetyNetState.IDLE)
|
||||
val basicIntegrityState = KObservableField(SafetyNetState.IDLE)
|
||||
val safetyNetState = Observer(ctsState, basicIntegrityState) {
|
||||
val cts = ctsState.value
|
||||
val basic = basicIntegrityState.value
|
||||
val states = listOf(cts, basic)
|
||||
|
||||
when {
|
||||
states.any { it == SafetyNetState.LOADING } -> State.LOADING
|
||||
states.any { it == SafetyNetState.IDLE } -> State.LOADING
|
||||
else -> State.LOADED
|
||||
}
|
||||
}
|
||||
|
||||
val isActive = KObservableField(false)
|
||||
|
||||
private var shownDialog = false
|
||||
|
||||
init {
|
||||
isForceEncryption.addOnPropertyChangedCallback {
|
||||
Info.keepEnc = it ?: return@addOnPropertyChangedCallback
|
||||
}
|
||||
isKeepVerity.addOnPropertyChangedCallback {
|
||||
Info.keepVerity = it ?: return@addOnPropertyChangedCallback
|
||||
}
|
||||
isRecovery.addOnPropertyChangedCallback {
|
||||
Info.recovery = it ?: return@addOnPropertyChangedCallback
|
||||
}
|
||||
isConnected.addOnPropertyChangedCallback {
|
||||
if (it == true) refresh(false)
|
||||
}
|
||||
|
||||
refresh(false)
|
||||
}
|
||||
|
||||
fun paypalPressed() = OpenLinkEvent(Const.Url.PAYPAL_URL).publish()
|
||||
fun patreonPressed() = OpenLinkEvent(Const.Url.PATREON_URL).publish()
|
||||
fun twitterPressed() = OpenLinkEvent(Const.Url.TWITTER_URL).publish()
|
||||
fun githubPressed() = OpenLinkEvent(Const.Url.SOURCE_CODE_URL).publish()
|
||||
fun xdaPressed() = OpenLinkEvent(Const.Url.XDA_THREAD).publish()
|
||||
fun uninstallPressed() = UninstallEvent().publish()
|
||||
|
||||
fun advancedPressed() = isAdvancedExpanded.toggle()
|
||||
|
||||
fun installPressed(item: MagiskItem) = when (item) {
|
||||
MagiskItem.MANAGER -> ManagerInstallEvent().publish()
|
||||
MagiskItem.MAGISK -> MagiskInstallEvent().publish()
|
||||
}
|
||||
|
||||
fun cardPressed(item: MagiskItem) = when (item) {
|
||||
MagiskItem.MANAGER -> ManagerChangelogEvent().publish()
|
||||
MagiskItem.MAGISK -> MagiskChangelogEvent().publish()
|
||||
}
|
||||
|
||||
fun safetyNetPressed() {
|
||||
ctsState.value = SafetyNetState.LOADING
|
||||
basicIntegrityState.value = SafetyNetState.LOADING
|
||||
safetyNetTitle.value = R.string.checking_safetyNet_status.res()
|
||||
|
||||
UpdateSafetyNetEvent().publish()
|
||||
}
|
||||
|
||||
fun finishSafetyNetCheck(response: Int) = when {
|
||||
response and 0x0F == 0 -> {
|
||||
val hasCtsPassed = response and SafetyNetHelper.CTS_PASS != 0
|
||||
val hasBasicIntegrityPassed = response and SafetyNetHelper.BASIC_PASS != 0
|
||||
safetyNetTitle.value = R.string.safetyNet_check_success.res()
|
||||
ctsState.value = if (hasCtsPassed) {
|
||||
SafetyNetState.PASS
|
||||
} else {
|
||||
SafetyNetState.FAILED
|
||||
}
|
||||
basicIntegrityState.value = if (hasBasicIntegrityPassed) {
|
||||
SafetyNetState.PASS
|
||||
} else {
|
||||
SafetyNetState.FAILED
|
||||
}
|
||||
}
|
||||
response == -2 -> {
|
||||
ctsState.value = SafetyNetState.IDLE
|
||||
basicIntegrityState.value = SafetyNetState.IDLE
|
||||
}
|
||||
else -> {
|
||||
ctsState.value = SafetyNetState.IDLE
|
||||
basicIntegrityState.value = SafetyNetState.IDLE
|
||||
safetyNetTitle.value = when (response) {
|
||||
SafetyNetHelper.RESPONSE_ERR -> R.string.safetyNet_res_invalid.res()
|
||||
else -> R.string.safetyNet_api_error.res()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@JvmOverloads
|
||||
fun refresh(invalidate: Boolean = true) {
|
||||
if (invalidate)
|
||||
Info.envRef.invalidate()
|
||||
|
||||
isActive.value = Info.env.isActive
|
||||
|
||||
val fetchUpdate = if (isConnected.value)
|
||||
magiskRepo.fetchUpdate().ignoreElement()
|
||||
else
|
||||
Completable.complete()
|
||||
|
||||
Completable.fromAction {
|
||||
// Ensure value is ready
|
||||
Info.env
|
||||
}.andThen(fetchUpdate)
|
||||
.applyViewModel(this)
|
||||
.doOnSubscribeUi {
|
||||
magiskState.value = MagiskState.LOADING
|
||||
_managerState.value = MagiskState.LOADING
|
||||
ctsState.value = SafetyNetState.IDLE
|
||||
basicIntegrityState.value = SafetyNetState.IDLE
|
||||
safetyNetTitle.value = R.string.safetyNet_check_text.res()
|
||||
}.subscribeK {
|
||||
updateSelf()
|
||||
ensureEnv()
|
||||
refreshVersions()
|
||||
}
|
||||
}
|
||||
|
||||
private fun refreshVersions() {
|
||||
magiskCurrentVersion.value = if (magiskState.value != MagiskState.NOT_INSTALLED) {
|
||||
VERSION_FMT.format(Info.env.magiskVersionString, Info.env.magiskVersionCode)
|
||||
} else {
|
||||
""
|
||||
}
|
||||
|
||||
managerCurrentVersion.value = if (isRunningAsStub) MGR_VER_FMT
|
||||
.format(BuildConfig.VERSION_NAME, BuildConfig.VERSION_CODE, Info.stub!!.version)
|
||||
else
|
||||
VERSION_FMT.format(BuildConfig.VERSION_NAME, BuildConfig.VERSION_CODE)
|
||||
}
|
||||
|
||||
private fun updateSelf() {
|
||||
magiskState.value = when (Info.env.magiskVersionCode) {
|
||||
in Int.MIN_VALUE .. 0 -> MagiskState.NOT_INSTALLED
|
||||
in 1 until Info.remote.magisk.versionCode -> MagiskState.OBSOLETE
|
||||
else -> MagiskState.UP_TO_DATE
|
||||
}
|
||||
|
||||
magiskLatestVersion.value =
|
||||
VERSION_FMT.format(Info.remote.magisk.version, Info.remote.magisk.versionCode)
|
||||
|
||||
_managerState.value = when (Info.remote.app.versionCode) {
|
||||
in Int.MIN_VALUE .. 0 -> MagiskState.NOT_INSTALLED //wrong update channel
|
||||
in (BuildConfig.VERSION_CODE + 1) .. Int.MAX_VALUE -> MagiskState.OBSOLETE
|
||||
else -> {
|
||||
if (Info.stub?.version ?: Int.MAX_VALUE < Info.remote.stub.versionCode)
|
||||
MagiskState.OBSOLETE
|
||||
else
|
||||
MagiskState.UP_TO_DATE
|
||||
}
|
||||
}
|
||||
|
||||
managerLatestVersion.value = MGR_VER_FMT
|
||||
.format(Info.remote.app.version, Info.remote.app.versionCode, Info.remote.stub.versionCode)
|
||||
}
|
||||
|
||||
private fun ensureEnv() {
|
||||
val invalidStates =
|
||||
listOf(MagiskState.NOT_INSTALLED, MagiskState.LOADING)
|
||||
|
||||
// Don't bother checking env when magisk is not installed, loading or already has been shown
|
||||
if (invalidStates.any { it == magiskState.value } || shownDialog) return
|
||||
|
||||
if (!Shell.su("env_check").exec().isSuccess) {
|
||||
shownDialog = true
|
||||
EnvFixEvent().publish()
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val VERSION_FMT = "%s (%d)"
|
||||
private const val MGR_VER_FMT = "%s (%d) (%d)"
|
||||
}
|
||||
|
||||
}
|
@ -1,57 +0,0 @@
|
||||
package com.topjohnwu.magisk.ui.log
|
||||
|
||||
|
||||
import android.os.Bundle
|
||||
import android.view.Menu
|
||||
import android.view.MenuInflater
|
||||
import android.view.MenuItem
|
||||
import android.view.View
|
||||
import com.topjohnwu.magisk.R
|
||||
import com.topjohnwu.magisk.base.BaseFragment
|
||||
import com.topjohnwu.magisk.databinding.FragmentLogBinding
|
||||
import com.topjohnwu.magisk.model.events.PageChangedEvent
|
||||
import com.topjohnwu.magisk.model.events.ViewEvent
|
||||
import org.koin.androidx.viewmodel.ext.android.viewModel
|
||||
|
||||
class LogFragment : BaseFragment<LogViewModel, FragmentLogBinding>() {
|
||||
|
||||
override val layoutRes: Int = R.layout.fragment_log
|
||||
override val viewModel: LogViewModel by viewModel()
|
||||
|
||||
override fun onEventDispatched(event: ViewEvent) {
|
||||
super.onEventDispatched(event)
|
||||
when (event) {
|
||||
is PageChangedEvent -> activity.invalidateOptionsMenu()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
binding.logTabs.setupWithViewPager(binding.logContainer, true)
|
||||
}
|
||||
|
||||
override fun onStart() {
|
||||
super.onStart()
|
||||
setHasOptionsMenu(true)
|
||||
activity.setTitle(R.string.log)
|
||||
}
|
||||
|
||||
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
|
||||
inflater.inflate(R.menu.menu_log, menu)
|
||||
menu.findItem(R.id.menu_save).isVisible = viewModel.currentPage.value == 1
|
||||
}
|
||||
|
||||
override fun onOptionsItemSelected(item: MenuItem): Boolean {
|
||||
when (item.itemId) {
|
||||
R.id.menu_save -> activity.withExternalRW {
|
||||
onSuccess {
|
||||
viewModel.saveLog()
|
||||
}
|
||||
}
|
||||
R.id.menu_clear -> viewModel.clearLog()
|
||||
R.id.menu_refresh -> viewModel.refresh()
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
}
|
@ -1,120 +0,0 @@
|
||||
package com.topjohnwu.magisk.ui.log
|
||||
|
||||
import android.content.res.Resources
|
||||
import com.topjohnwu.magisk.BR
|
||||
import com.topjohnwu.magisk.Config
|
||||
import com.topjohnwu.magisk.Const
|
||||
import com.topjohnwu.magisk.R
|
||||
import com.topjohnwu.magisk.base.viewmodel.BaseViewModel
|
||||
import com.topjohnwu.magisk.data.repository.LogRepository
|
||||
import com.topjohnwu.magisk.databinding.ComparableRvItem
|
||||
import com.topjohnwu.magisk.extensions.addOnPropertyChangedCallback
|
||||
import com.topjohnwu.magisk.extensions.doOnSubscribeUi
|
||||
import com.topjohnwu.magisk.extensions.subscribeK
|
||||
import com.topjohnwu.magisk.model.binding.BindingAdapter
|
||||
import com.topjohnwu.magisk.model.entity.recycler.ConsoleRvItem
|
||||
import com.topjohnwu.magisk.model.entity.recycler.LogItemRvItem
|
||||
import com.topjohnwu.magisk.model.entity.recycler.LogRvItem
|
||||
import com.topjohnwu.magisk.model.entity.recycler.MagiskLogRvItem
|
||||
import com.topjohnwu.magisk.model.events.PageChangedEvent
|
||||
import com.topjohnwu.magisk.model.events.SnackbarEvent
|
||||
import com.topjohnwu.magisk.utils.DiffObservableList
|
||||
import com.topjohnwu.magisk.utils.KObservableField
|
||||
import com.topjohnwu.superuser.Shell
|
||||
import me.tatarka.bindingcollectionadapter2.BindingViewPagerAdapter
|
||||
import me.tatarka.bindingcollectionadapter2.OnItemBind
|
||||
import timber.log.Timber
|
||||
import java.io.File
|
||||
import java.util.*
|
||||
|
||||
class LogViewModel(
|
||||
private val resources: Resources,
|
||||
private val logRepo: LogRepository
|
||||
) : BaseViewModel(), BindingViewPagerAdapter.PageTitles<ComparableRvItem<*>> {
|
||||
|
||||
val itemsAdapter = BindingAdapter()
|
||||
val items = DiffObservableList(ComparableRvItem.callback)
|
||||
val itemBinding = OnItemBind<ComparableRvItem<*>> { itemBinding, _, item ->
|
||||
item.bind(itemBinding)
|
||||
itemBinding.bindExtra(BR.viewModel, this@LogViewModel)
|
||||
}
|
||||
val currentPage = KObservableField(0)
|
||||
private val currentItem get() = items[currentPage.value]
|
||||
|
||||
private val logItem get() = items[0] as LogRvItem
|
||||
private val magiskLogItem get() = items[1] as MagiskLogRvItem
|
||||
|
||||
val scrollPosition = KObservableField(0)
|
||||
|
||||
init {
|
||||
currentPage.addOnPropertyChangedCallback {
|
||||
it ?: return@addOnPropertyChangedCallback
|
||||
PageChangedEvent().publish()
|
||||
}
|
||||
|
||||
items.addAll(listOf(LogRvItem(), MagiskLogRvItem()))
|
||||
refresh()
|
||||
}
|
||||
|
||||
override fun getPageTitle(position: Int, item: ComparableRvItem<*>?) = when (item) {
|
||||
is LogRvItem -> resources.getString(R.string.superuser)
|
||||
is MagiskLogRvItem -> resources.getString(R.string.magisk)
|
||||
else -> ""
|
||||
}
|
||||
|
||||
fun scrollDownPressed() {
|
||||
scrollPosition.value = magiskLogItem.items.size - 1
|
||||
}
|
||||
|
||||
fun refresh() {
|
||||
fetchLogs().subscribeK { logItem.update(it) }
|
||||
fetchMagiskLog().subscribeK { magiskLogItem.update(it) }
|
||||
}
|
||||
|
||||
fun saveLog() {
|
||||
val now = Calendar.getInstance()
|
||||
val filename = "magisk_log_%04d%02d%02d_%02d%02d%02d.log".format(
|
||||
now.get(Calendar.YEAR), now.get(Calendar.MONTH) + 1,
|
||||
now.get(Calendar.DAY_OF_MONTH), now.get(Calendar.HOUR_OF_DAY),
|
||||
now.get(Calendar.MINUTE), now.get(Calendar.SECOND)
|
||||
)
|
||||
|
||||
val logFile = File(Config.downloadDirectory, filename)
|
||||
runCatching {
|
||||
logFile.createNewFile()
|
||||
}.onFailure {
|
||||
Timber.e(it)
|
||||
return
|
||||
}
|
||||
|
||||
Shell.su("cat ${Const.MAGISK_LOG} > $logFile").submit {
|
||||
SnackbarEvent(logFile.path).publish()
|
||||
}
|
||||
}
|
||||
|
||||
fun clearLog() = when (currentItem) {
|
||||
is LogRvItem -> clearLogs { refresh() }
|
||||
is MagiskLogRvItem -> clearMagiskLogs { refresh() }
|
||||
else -> Unit
|
||||
}
|
||||
|
||||
private fun clearLogs(callback: () -> Unit) = logRepo.clearLogs()
|
||||
.doOnSubscribeUi(callback)
|
||||
.subscribeK { SnackbarEvent(R.string.logs_cleared).publish() }
|
||||
.add()
|
||||
|
||||
private fun clearMagiskLogs(callback: () -> Unit) = logRepo.clearMagiskLogs()
|
||||
.doOnComplete(callback)
|
||||
.subscribeK { SnackbarEvent(R.string.logs_cleared).publish() }
|
||||
.add()
|
||||
|
||||
private fun fetchLogs() = logRepo.fetchLogs()
|
||||
.flattenAsFlowable { it }
|
||||
.map { LogItemRvItem(it) }
|
||||
.toList()
|
||||
|
||||
private fun fetchMagiskLog() = logRepo.fetchMagiskLogs()
|
||||
.map { ConsoleRvItem(it) }
|
||||
.toList()
|
||||
|
||||
}
|
@ -1,111 +0,0 @@
|
||||
package com.topjohnwu.magisk.ui.module
|
||||
|
||||
import android.content.res.Resources
|
||||
import com.topjohnwu.magisk.BR
|
||||
import com.topjohnwu.magisk.R
|
||||
import com.topjohnwu.magisk.base.viewmodel.BaseViewModel
|
||||
import com.topjohnwu.magisk.data.database.RepoDao
|
||||
import com.topjohnwu.magisk.databinding.ComparableRvItem
|
||||
import com.topjohnwu.magisk.extensions.*
|
||||
import com.topjohnwu.magisk.model.entity.module.Module
|
||||
import com.topjohnwu.magisk.model.entity.recycler.ModuleRvItem
|
||||
import com.topjohnwu.magisk.model.entity.recycler.RepoRvItem
|
||||
import com.topjohnwu.magisk.model.entity.recycler.SectionRvItem
|
||||
import com.topjohnwu.magisk.model.events.InstallModuleEvent
|
||||
import com.topjohnwu.magisk.model.events.OpenChangelogEvent
|
||||
import com.topjohnwu.magisk.model.events.OpenFilePickerEvent
|
||||
import com.topjohnwu.magisk.tasks.RepoUpdater
|
||||
import com.topjohnwu.magisk.utils.DiffObservableList
|
||||
import com.topjohnwu.magisk.utils.KObservableField
|
||||
import io.reactivex.Single
|
||||
import io.reactivex.disposables.Disposable
|
||||
import me.tatarka.bindingcollectionadapter2.OnItemBind
|
||||
|
||||
class ModuleViewModel(
|
||||
private val resources: Resources,
|
||||
private val repoUpdater: RepoUpdater,
|
||||
private val repoDB: RepoDao
|
||||
) : BaseViewModel() {
|
||||
|
||||
val query = KObservableField("")
|
||||
|
||||
private val allItems = mutableListOf<ComparableRvItem<*>>()
|
||||
|
||||
val itemsInstalled = DiffObservableList(ComparableRvItem.callback)
|
||||
val itemsRemote = DiffObservableList(ComparableRvItem.callback)
|
||||
val itemBinding = OnItemBind<ComparableRvItem<*>> { itemBinding, _, item ->
|
||||
item.bind(itemBinding)
|
||||
itemBinding.bindExtra(BR.viewModel, this@ModuleViewModel)
|
||||
}
|
||||
|
||||
private var queryDisposable: Disposable? = null
|
||||
|
||||
init {
|
||||
query.addOnPropertyChangedCallback {
|
||||
queryDisposable?.dispose()
|
||||
queryDisposable = query()
|
||||
}
|
||||
refresh(false)
|
||||
}
|
||||
|
||||
fun fabPressed() = OpenFilePickerEvent().publish()
|
||||
fun repoPressed(item: RepoRvItem) = OpenChangelogEvent(item.item).publish()
|
||||
fun downloadPressed(item: RepoRvItem) = InstallModuleEvent(item.item).publish()
|
||||
|
||||
fun refresh(force: Boolean) {
|
||||
Single.fromCallable { Module.loadModules() }
|
||||
.flattenAsFlowable { it }
|
||||
.map { ModuleRvItem(it) }
|
||||
.toList()
|
||||
.map { it to itemsInstalled.calculateDiff(it) }
|
||||
.doOnSuccessUi { itemsInstalled.update(it.first, it.second) }
|
||||
.flatMap { repoUpdater(force) }
|
||||
.flattenAsFlowable { repoDB.repos }
|
||||
.map { RepoRvItem(it) }
|
||||
.toList()
|
||||
.doOnSuccess { allItems.update(it) }
|
||||
.flatMap { queryRaw() }
|
||||
.applyViewModel(this)
|
||||
.subscribeK { itemsRemote.update(it.first, it.second) }
|
||||
}
|
||||
|
||||
private fun query() = queryRaw()
|
||||
.subscribeK { itemsRemote.update(it.first, it.second) }
|
||||
|
||||
private fun queryRaw(query: String = this.query.value) = allItems.toSingle()
|
||||
.map { it.filterIsInstance<RepoRvItem>() }
|
||||
.flattenAsFlowable { it }
|
||||
.filter {
|
||||
it.item.name.contains(query, ignoreCase = true) ||
|
||||
it.item.author.contains(query, ignoreCase = true) ||
|
||||
it.item.description.contains(query, ignoreCase = true)
|
||||
}
|
||||
.toList()
|
||||
.map { if (query.isEmpty()) it.divide() else it }
|
||||
.map { it to itemsRemote.calculateDiff(it) }
|
||||
|
||||
private fun List<RepoRvItem>.divide(): List<ComparableRvItem<*>> {
|
||||
val installed = itemsInstalled.filterIsInstance<ModuleRvItem>()
|
||||
|
||||
fun <T : ComparableRvItem<*>> List<T>.withTitle(text: Int) =
|
||||
if (isEmpty()) this else listOf(SectionRvItem(resources.getString(text))) + this
|
||||
|
||||
val groupedItems = groupBy { repo ->
|
||||
installed.firstOrNull { it.item.id == repo.item.id }?.let {
|
||||
if (it.item.versionCode < repo.item.versionCode) MODULE_UPDATABLE
|
||||
else MODULE_INSTALLED
|
||||
} ?: MODULE_REMOTE
|
||||
}
|
||||
|
||||
return groupedItems.getOrElse(MODULE_UPDATABLE) { listOf() }.withTitle(R.string.update_available) +
|
||||
groupedItems.getOrElse(MODULE_INSTALLED) { listOf() }.withTitle(R.string.installed) +
|
||||
groupedItems.getOrElse(MODULE_REMOTE) { listOf() }.withTitle(R.string.not_installed)
|
||||
}
|
||||
|
||||
companion object {
|
||||
protected const val MODULE_INSTALLED = 0
|
||||
protected const val MODULE_REMOTE = 1
|
||||
protected const val MODULE_UPDATABLE = 2
|
||||
}
|
||||
|
||||
}
|
@ -1,99 +0,0 @@
|
||||
package com.topjohnwu.magisk.ui.module
|
||||
|
||||
import android.app.Activity
|
||||
import android.content.Intent
|
||||
import android.os.Bundle
|
||||
import android.view.Menu
|
||||
import android.view.MenuInflater
|
||||
import android.view.MenuItem
|
||||
import android.view.View
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.topjohnwu.magisk.Const
|
||||
import com.topjohnwu.magisk.R
|
||||
import com.topjohnwu.magisk.base.BaseFragment
|
||||
import com.topjohnwu.magisk.databinding.FragmentModulesBinding
|
||||
import com.topjohnwu.magisk.extensions.reboot
|
||||
import com.topjohnwu.magisk.intent
|
||||
import com.topjohnwu.magisk.model.events.OpenFilePickerEvent
|
||||
import com.topjohnwu.magisk.model.events.ViewEvent
|
||||
import com.topjohnwu.magisk.ui.flash.FlashActivity
|
||||
import com.topjohnwu.superuser.Shell
|
||||
import org.koin.androidx.viewmodel.ext.android.sharedViewModel
|
||||
|
||||
class ModulesFragment : BaseFragment<ModuleViewModel, FragmentModulesBinding>() {
|
||||
|
||||
override val layoutRes: Int = R.layout.fragment_modules
|
||||
override val viewModel: ModuleViewModel by sharedViewModel()
|
||||
|
||||
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
|
||||
if (requestCode == Const.ID.FETCH_ZIP && resultCode == Activity.RESULT_OK && data != null) {
|
||||
// Get the URI of the selected file
|
||||
val intent = activity.intent<FlashActivity>()
|
||||
intent.setData(data.data).putExtra(Const.Key.FLASH_ACTION, Const.Value.FLASH_ZIP)
|
||||
startActivity(intent)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
|
||||
binding.modulesContent.addOnScrollListener(object : RecyclerView.OnScrollListener() {
|
||||
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
|
||||
binding.modulesRefreshLayout.isEnabled = recyclerView.getChildAt(0).top >= 0
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
override fun onEventDispatched(event: ViewEvent) {
|
||||
super.onEventDispatched(event)
|
||||
when (event) {
|
||||
is OpenFilePickerEvent -> selectFile()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onStart() {
|
||||
super.onStart()
|
||||
setHasOptionsMenu(true)
|
||||
requireActivity().setTitle(R.string.modules)
|
||||
}
|
||||
|
||||
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
|
||||
inflater.inflate(R.menu.menu_reboot, menu)
|
||||
}
|
||||
|
||||
override fun onOptionsItemSelected(item: MenuItem): Boolean {
|
||||
when (item.itemId) {
|
||||
R.id.reboot -> {
|
||||
reboot()
|
||||
return true
|
||||
}
|
||||
R.id.reboot_recovery -> {
|
||||
Shell.su("/system/bin/reboot recovery").submit()
|
||||
return true
|
||||
}
|
||||
R.id.reboot_bootloader -> {
|
||||
reboot("bootloader")
|
||||
return true
|
||||
}
|
||||
R.id.reboot_download -> {
|
||||
reboot("download")
|
||||
return true
|
||||
}
|
||||
R.id.reboot_edl -> {
|
||||
reboot("edl")
|
||||
return true
|
||||
}
|
||||
else -> return false
|
||||
}
|
||||
}
|
||||
|
||||
private fun selectFile() {
|
||||
activity.withExternalRW {
|
||||
onSuccess {
|
||||
val intent = Intent(Intent.ACTION_GET_CONTENT)
|
||||
intent.type = "application/zip"
|
||||
startActivityForResult(intent, Const.ID.FETCH_ZIP)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,116 +0,0 @@
|
||||
package com.topjohnwu.magisk.ui.module
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.app.AlertDialog
|
||||
import android.view.Menu
|
||||
import android.view.MenuInflater
|
||||
import android.view.MenuItem
|
||||
import android.widget.SearchView
|
||||
import com.topjohnwu.magisk.Config
|
||||
import com.topjohnwu.magisk.R
|
||||
import com.topjohnwu.magisk.base.BaseFragment
|
||||
import com.topjohnwu.magisk.databinding.FragmentReposBinding
|
||||
import com.topjohnwu.magisk.model.download.DownloadService
|
||||
import com.topjohnwu.magisk.model.entity.internal.Configuration
|
||||
import com.topjohnwu.magisk.model.entity.internal.DownloadSubject
|
||||
import com.topjohnwu.magisk.model.entity.module.Repo
|
||||
import com.topjohnwu.magisk.model.events.InstallModuleEvent
|
||||
import com.topjohnwu.magisk.model.events.OpenChangelogEvent
|
||||
import com.topjohnwu.magisk.model.events.ViewEvent
|
||||
import com.topjohnwu.magisk.view.MarkDownWindow
|
||||
import com.topjohnwu.magisk.view.dialogs.CustomAlertDialog
|
||||
import org.koin.androidx.viewmodel.ext.android.sharedViewModel
|
||||
|
||||
class ReposFragment : BaseFragment<ModuleViewModel, FragmentReposBinding>(),
|
||||
SearchView.OnQueryTextListener {
|
||||
|
||||
override val layoutRes: Int = R.layout.fragment_repos
|
||||
override val viewModel: ModuleViewModel by sharedViewModel()
|
||||
|
||||
override fun onStart() {
|
||||
super.onStart()
|
||||
setHasOptionsMenu(true)
|
||||
requireActivity().setTitle(R.string.downloads)
|
||||
}
|
||||
|
||||
override fun onEventDispatched(event: ViewEvent) {
|
||||
super.onEventDispatched(event)
|
||||
when (event) {
|
||||
is OpenChangelogEvent -> openChangelog(event.item)
|
||||
is InstallModuleEvent -> installModule(event.item)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
|
||||
inflater.inflate(R.menu.menu_repo, menu)
|
||||
|
||||
val query = viewModel.query.value
|
||||
val searchItem = menu.findItem(R.id.repo_search)
|
||||
val searchView = searchItem.actionView as? SearchView
|
||||
|
||||
searchView?.run {
|
||||
setOnQueryTextListener(this@ReposFragment)
|
||||
setQuery(query, false)
|
||||
}
|
||||
|
||||
if (query.isNotBlank()) {
|
||||
searchItem.expandActionView()
|
||||
searchView?.isIconified = false
|
||||
} else {
|
||||
searchItem.collapseActionView()
|
||||
searchView?.isIconified = true
|
||||
}
|
||||
}
|
||||
|
||||
override fun onOptionsItemSelected(item: MenuItem): Boolean {
|
||||
if (item.itemId == R.id.repo_sort) {
|
||||
AlertDialog.Builder(activity)
|
||||
.setTitle(R.string.sorting_order)
|
||||
.setSingleChoiceItems(
|
||||
R.array.sorting_orders,
|
||||
Config.repoOrder
|
||||
) { d, which ->
|
||||
Config.repoOrder = which
|
||||
viewModel.refresh(false)
|
||||
d.dismiss()
|
||||
}.show()
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
override fun onQueryTextSubmit(p0: String?): Boolean {
|
||||
viewModel.query.value = p0.orEmpty()
|
||||
return false
|
||||
}
|
||||
|
||||
override fun onQueryTextChange(p0: String?): Boolean {
|
||||
viewModel.query.value = p0.orEmpty()
|
||||
return false
|
||||
}
|
||||
|
||||
private fun openChangelog(item: Repo) {
|
||||
MarkDownWindow.show(requireActivity(), null, item.readme)
|
||||
}
|
||||
|
||||
@SuppressLint("MissingPermission")
|
||||
private fun installModule(item: Repo) {
|
||||
val context = activity
|
||||
|
||||
fun download(install: Boolean) = context.withExternalRW {
|
||||
onSuccess {
|
||||
DownloadService(context) {
|
||||
val config = if (install) Configuration.Flash.Primary else Configuration.Download
|
||||
subject = DownloadSubject.Module(item, config)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
CustomAlertDialog(context)
|
||||
.setTitle(context.getString(R.string.repo_install_title, item.name))
|
||||
.setMessage(context.getString(R.string.repo_install_msg, item.downloadFilename))
|
||||
.setCancelable(true)
|
||||
.setPositiveButton(R.string.install) { _, _ -> download(true) }
|
||||
.setNeutralButton(R.string.download) { _, _ -> download(false) }
|
||||
.show()
|
||||
}
|
||||
}
|
@ -1,347 +0,0 @@
|
||||
package com.topjohnwu.magisk.ui.settings
|
||||
|
||||
import android.content.SharedPreferences
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.os.Environment
|
||||
import android.view.LayoutInflater
|
||||
import android.widget.EditText
|
||||
import android.widget.Toast
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import androidx.databinding.DataBindingUtil
|
||||
import androidx.preference.ListPreference
|
||||
import androidx.preference.Preference
|
||||
import androidx.preference.PreferenceCategory
|
||||
import androidx.preference.SwitchPreferenceCompat
|
||||
import com.topjohnwu.magisk.*
|
||||
import com.topjohnwu.magisk.base.BasePreferenceFragment
|
||||
import com.topjohnwu.magisk.data.database.RepoDao
|
||||
import com.topjohnwu.magisk.databinding.CustomDownloadDialogBinding
|
||||
import com.topjohnwu.magisk.databinding.DialogCustomNameBinding
|
||||
import com.topjohnwu.magisk.extensions.subscribeK
|
||||
import com.topjohnwu.magisk.model.download.DownloadService
|
||||
import com.topjohnwu.magisk.model.entity.internal.Configuration
|
||||
import com.topjohnwu.magisk.model.entity.internal.DownloadSubject
|
||||
import com.topjohnwu.magisk.model.observer.Observer
|
||||
import com.topjohnwu.magisk.utils.*
|
||||
import com.topjohnwu.superuser.Shell
|
||||
import io.reactivex.Completable
|
||||
import org.koin.android.ext.android.inject
|
||||
import java.io.File
|
||||
|
||||
class SettingsFragment : BasePreferenceFragment() {
|
||||
|
||||
private val repoDB: RepoDao by inject()
|
||||
|
||||
private lateinit var updateChannel: ListPreference
|
||||
private lateinit var autoRes: ListPreference
|
||||
private lateinit var suNotification: ListPreference
|
||||
private lateinit var requestTimeout: ListPreference
|
||||
private lateinit var rootConfig: ListPreference
|
||||
private lateinit var multiuserConfig: ListPreference
|
||||
private lateinit var nsConfig: ListPreference
|
||||
|
||||
override fun onStart() {
|
||||
super.onStart()
|
||||
setHasOptionsMenu(true)
|
||||
requireActivity().setTitle(R.string.settings)
|
||||
}
|
||||
|
||||
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
|
||||
preferenceManager.setStorageDeviceProtected()
|
||||
setPreferencesFromResource(R.xml.app_settings, rootKey)
|
||||
|
||||
findPreference<PreferenceCategory>("redesign_cat")?.isVisible = BuildConfig.DEBUG
|
||||
|
||||
// Get preferences
|
||||
updateChannel = findPreference(Config.Key.UPDATE_CHANNEL)!!
|
||||
rootConfig = findPreference(Config.Key.ROOT_ACCESS)!!
|
||||
autoRes = findPreference(Config.Key.SU_AUTO_RESPONSE)!!
|
||||
requestTimeout = findPreference(Config.Key.SU_REQUEST_TIMEOUT)!!
|
||||
suNotification = findPreference(Config.Key.SU_NOTIFICATION)!!
|
||||
multiuserConfig = findPreference(Config.Key.SU_MULTIUSER_MODE)!!
|
||||
nsConfig = findPreference(Config.Key.SU_MNT_NS)!!
|
||||
val reauth = findPreference<SwitchPreferenceCompat>(Config.Key.SU_REAUTH)!!
|
||||
val biometric = findPreference<SwitchPreferenceCompat>(Config.Key.SU_BIOMETRIC)!!
|
||||
val generalCategory = findPreference<PreferenceCategory>("general")!!
|
||||
val magiskCategory = findPreference<PreferenceCategory>("magisk")!!
|
||||
val suCategory = findPreference<PreferenceCategory>("superuser")!!
|
||||
val hideManager = findPreference<Preference>("hide")!!
|
||||
val restoreManager = findPreference<Preference>("restore")!!
|
||||
|
||||
// Remove/Disable entries
|
||||
|
||||
// Only show canary channels if user is already on canary channel
|
||||
// or the user have already chosen canary channel
|
||||
if (!Utils.isCanary && Config.updateChannel < Config.Value.CANARY_CHANNEL) {
|
||||
// Remove the last 2 entries
|
||||
val entries = updateChannel.entries
|
||||
updateChannel.entries = entries.copyOf(entries.size - 2)
|
||||
}
|
||||
|
||||
// Remove dangerous settings in secondary user
|
||||
if (Const.USER_ID > 0) {
|
||||
suCategory.removePreference(multiuserConfig)
|
||||
}
|
||||
|
||||
// Remove re-authentication option on Android O, it will not work
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
suCategory.removePreference(reauth)
|
||||
}
|
||||
|
||||
// Disable biometric option if not possible
|
||||
if (!BiometricHelper.isSupported) {
|
||||
biometric.isEnabled = false
|
||||
biometric.isChecked = false
|
||||
biometric.setSummary(R.string.no_biometric)
|
||||
}
|
||||
|
||||
if (Const.USER_ID == 0 && Info.isConnected.value && Info.env.isActive) {
|
||||
if (activity.packageName == BuildConfig.APPLICATION_ID) {
|
||||
generalCategory.removePreference(restoreManager)
|
||||
hideManager.setOnPreferenceClickListener {
|
||||
showManagerNameDialog {
|
||||
PatchAPK.hideManager(requireContext(), it)
|
||||
}
|
||||
true
|
||||
}
|
||||
} else {
|
||||
generalCategory.removePreference(hideManager)
|
||||
restoreManager.setOnPreferenceClickListener {
|
||||
DownloadService(requireContext()) {
|
||||
subject = DownloadSubject.Manager(Configuration.APK.Restore)
|
||||
}
|
||||
true
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Remove if not primary user, no connection, or no root
|
||||
generalCategory.removePreference(restoreManager)
|
||||
generalCategory.removePreference(hideManager)
|
||||
}
|
||||
|
||||
if (!Utils.showSuperUser()) {
|
||||
preferenceScreen.removePreference(suCategory)
|
||||
}
|
||||
|
||||
if (!Info.env.isActive) {
|
||||
preferenceScreen.removePreference(magiskCategory)
|
||||
generalCategory.removePreference(hideManager)
|
||||
}
|
||||
|
||||
findPreference<Preference>("clear")?.also {
|
||||
if (Info.env.isActive) {
|
||||
it.setOnPreferenceClickListener {
|
||||
Completable.fromAction { repoDB.clear() }.subscribeK {
|
||||
Utils.toast(R.string.repo_cache_cleared, Toast.LENGTH_SHORT)
|
||||
}
|
||||
true
|
||||
}
|
||||
} else {
|
||||
generalCategory.removePreference(it)
|
||||
}
|
||||
}
|
||||
|
||||
findPreference<Preference>("hosts")?.setOnPreferenceClickListener {
|
||||
Shell.su("add_hosts_module").submit {
|
||||
Utils.toast(R.string.settings_hosts_toast, Toast.LENGTH_SHORT)
|
||||
}
|
||||
true
|
||||
}
|
||||
|
||||
findPreference<Preference>(Config.Key.DOWNLOAD_PATH)?.apply {
|
||||
summary = Config.downloadPath
|
||||
setOnPreferenceClickListener { pref ->
|
||||
activity.withExternalRW {
|
||||
onSuccess {
|
||||
showDownloadDialog {
|
||||
Config.downloadPath = it
|
||||
pref.summary = it
|
||||
}
|
||||
}
|
||||
}
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
updateChannel.setOnPreferenceChangeListener { _, value ->
|
||||
val channel = value.toString().toInt()
|
||||
val previous = Config.updateChannel
|
||||
|
||||
if (channel == Config.Value.CUSTOM_CHANNEL) {
|
||||
showUrlDialog(Config.customChannelUrl, {
|
||||
Config.updateChannel = previous
|
||||
}, {
|
||||
Config.customChannelUrl = it
|
||||
})
|
||||
}
|
||||
true
|
||||
}
|
||||
|
||||
setLocalePreference(findPreference(Config.Key.LOCALE)!!)
|
||||
|
||||
setSummary()
|
||||
}
|
||||
|
||||
override fun onSharedPreferenceChanged(prefs: SharedPreferences, key: String) {
|
||||
fun getStrInt() = prefs.getString(key, null)?.toInt() ?: 0
|
||||
|
||||
when (key) {
|
||||
Config.Key.ROOT_ACCESS -> Config.rootMode = getStrInt()
|
||||
Config.Key.SU_MULTIUSER_MODE -> Config.suMultiuserMode = getStrInt()
|
||||
Config.Key.SU_MNT_NS -> Config.suMntNamespaceMode = getStrInt()
|
||||
Config.Key.DARK_THEME -> requireActivity().recreate()
|
||||
Config.Key.COREONLY -> {
|
||||
if (prefs.getBoolean(key, false)) {
|
||||
runCatching {
|
||||
Const.MAGISK_DISABLE_FILE.createNewFile()
|
||||
}
|
||||
} else {
|
||||
Const.MAGISK_DISABLE_FILE.delete()
|
||||
}
|
||||
Utils.toast(R.string.settings_reboot_toast, Toast.LENGTH_LONG)
|
||||
}
|
||||
Config.Key.MAGISKHIDE -> if (prefs.getBoolean(key, false)) {
|
||||
Shell.su("magiskhide --enable").submit()
|
||||
} else {
|
||||
Shell.su("magiskhide --disable").submit()
|
||||
}
|
||||
Config.Key.LOCALE -> {
|
||||
refreshLocale()
|
||||
activity.recreate()
|
||||
}
|
||||
Config.Key.CHECK_UPDATES -> Utils.scheduleUpdateCheck(activity)
|
||||
}
|
||||
setSummary(key)
|
||||
}
|
||||
|
||||
override fun onPreferenceTreeClick(preference: Preference): Boolean {
|
||||
when (preference.key) {
|
||||
Config.Key.SU_BIOMETRIC -> {
|
||||
val checked = (preference as SwitchPreferenceCompat).isChecked
|
||||
preference.isChecked = !checked
|
||||
BiometricHelper.authenticate(requireActivity()) {
|
||||
preference.isChecked = checked
|
||||
Config.suBiometric = checked
|
||||
}
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
private fun setLocalePreference(lp: ListPreference) {
|
||||
lp.isEnabled = false
|
||||
availableLocales.subscribeK { (names, values) ->
|
||||
lp.isEnabled = true
|
||||
lp.entries = names
|
||||
lp.entryValues = values
|
||||
lp.summary = currentLocale.getDisplayName(currentLocale)
|
||||
}
|
||||
}
|
||||
|
||||
private fun setSummary(key: String) {
|
||||
when (key) {
|
||||
Config.Key.ROOT_ACCESS -> rootConfig.summary = resources
|
||||
.getStringArray(R.array.su_access)[Config.rootMode]
|
||||
Config.Key.SU_MULTIUSER_MODE -> multiuserConfig.summary = resources
|
||||
.getStringArray(R.array.multiuser_summary)[Config.suMultiuserMode]
|
||||
Config.Key.SU_MNT_NS -> nsConfig.summary = resources
|
||||
.getStringArray(R.array.namespace_summary)[Config.suMntNamespaceMode]
|
||||
Config.Key.UPDATE_CHANNEL -> {
|
||||
var ch = Config.updateChannel
|
||||
ch = if (ch < 0) Config.Value.STABLE_CHANNEL else ch
|
||||
updateChannel.summary = resources
|
||||
.getStringArray(R.array.update_channel)[ch]
|
||||
}
|
||||
Config.Key.SU_AUTO_RESPONSE -> autoRes.summary = resources
|
||||
.getStringArray(R.array.auto_response)[Config.suAutoReponse]
|
||||
Config.Key.SU_NOTIFICATION -> suNotification.summary = resources
|
||||
.getStringArray(R.array.su_notification)[Config.suNotification]
|
||||
Config.Key.SU_REQUEST_TIMEOUT -> requestTimeout.summary =
|
||||
getString(R.string.request_timeout_summary, Config.suDefaultTimeout)
|
||||
}
|
||||
}
|
||||
|
||||
private fun setSummary() {
|
||||
setSummary(Config.Key.ROOT_ACCESS)
|
||||
setSummary(Config.Key.SU_MULTIUSER_MODE)
|
||||
setSummary(Config.Key.SU_MNT_NS)
|
||||
setSummary(Config.Key.UPDATE_CHANNEL)
|
||||
setSummary(Config.Key.SU_AUTO_RESPONSE)
|
||||
setSummary(Config.Key.SU_NOTIFICATION)
|
||||
setSummary(Config.Key.SU_REQUEST_TIMEOUT)
|
||||
}
|
||||
|
||||
private inline fun showUrlDialog(
|
||||
initialValue: String,
|
||||
crossinline onCancel: () -> Unit = {},
|
||||
crossinline onSuccess: (String) -> Unit
|
||||
) {
|
||||
val v = LayoutInflater
|
||||
.from(requireActivity())
|
||||
.inflate(R.layout.custom_channel_dialog, null)
|
||||
|
||||
val url = v.findViewById<EditText>(R.id.custom_url).apply {
|
||||
setText(initialValue)
|
||||
}
|
||||
|
||||
AlertDialog.Builder(requireActivity())
|
||||
.setTitle(R.string.settings_update_custom)
|
||||
.setView(v)
|
||||
.setPositiveButton(android.R.string.ok) { _, _ -> onSuccess(url.text.toString()) }
|
||||
.setNegativeButton(android.R.string.cancel) { _, _ -> onCancel() }
|
||||
.setOnCancelListener { onCancel() }
|
||||
.show()
|
||||
}
|
||||
|
||||
inner class DownloadDialogData(initialValue: String) {
|
||||
val text = KObservableField(initialValue)
|
||||
val path = Observer(text) {
|
||||
File(Environment.getExternalStorageDirectory(), text.value).absolutePath
|
||||
}
|
||||
}
|
||||
|
||||
private inline fun showDownloadDialog(
|
||||
initialValue: String = Config.downloadPath,
|
||||
crossinline onSuccess: (String) -> Unit
|
||||
) {
|
||||
val data = DownloadDialogData(initialValue)
|
||||
val binding: CustomDownloadDialogBinding = DataBindingUtil
|
||||
.inflate(layoutInflater, R.layout.custom_download_dialog, null, false)
|
||||
binding.also { it.data = data }
|
||||
|
||||
AlertDialog.Builder(requireActivity())
|
||||
.setTitle(R.string.settings_download_path_title)
|
||||
.setView(binding.root)
|
||||
.setPositiveButton(android.R.string.ok) { _, _ ->
|
||||
Utils.ensureDownloadPath(data.text.value)?.let { onSuccess(data.text.value) }
|
||||
?: Utils.toast(R.string.settings_download_path_error, Toast.LENGTH_SHORT)
|
||||
}
|
||||
.setNegativeButton(android.R.string.cancel, null)
|
||||
.show()
|
||||
}
|
||||
|
||||
private inline fun showManagerNameDialog(
|
||||
crossinline onSuccess: (String) -> Unit
|
||||
) {
|
||||
val data = ManagerNameData()
|
||||
val view = DialogCustomNameBinding
|
||||
.inflate(LayoutInflater.from(requireContext()))
|
||||
.also { it.data = data }
|
||||
|
||||
AlertDialog.Builder(requireActivity())
|
||||
.setTitle(R.string.settings_app_name)
|
||||
.setView(view.root)
|
||||
.setPositiveButton(android.R.string.ok) { _, _ ->
|
||||
if (view.dialogNameInput.error.isNullOrBlank()) {
|
||||
onSuccess(data.name.value)
|
||||
}
|
||||
}
|
||||
.setNegativeButton(android.R.string.cancel, null)
|
||||
.show()
|
||||
}
|
||||
|
||||
inner class ManagerNameData {
|
||||
val name = KObservableField(resources.getString(R.string.re_app_name))
|
||||
}
|
||||
}
|
@ -1,25 +0,0 @@
|
||||
package com.topjohnwu.magisk.ui.superuser
|
||||
|
||||
import com.topjohnwu.magisk.R
|
||||
import com.topjohnwu.magisk.base.BaseFragment
|
||||
import com.topjohnwu.magisk.databinding.FragmentSuperuserBinding
|
||||
import org.koin.androidx.viewmodel.ext.android.viewModel
|
||||
|
||||
class SuperuserFragment :
|
||||
BaseFragment<SuperuserViewModel, FragmentSuperuserBinding>() {
|
||||
|
||||
override val layoutRes: Int = R.layout.fragment_superuser
|
||||
override val viewModel: SuperuserViewModel by viewModel()
|
||||
|
||||
override fun onStart() {
|
||||
super.onStart()
|
||||
setHasOptionsMenu(true)
|
||||
requireActivity().setTitle(R.string.superuser)
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
viewModel.updatePolicies()
|
||||
}
|
||||
|
||||
}
|
@ -1,151 +0,0 @@
|
||||
package com.topjohnwu.magisk.ui.superuser
|
||||
|
||||
import android.content.pm.PackageManager
|
||||
import android.content.res.Resources
|
||||
import com.topjohnwu.magisk.BR
|
||||
import com.topjohnwu.magisk.R
|
||||
import com.topjohnwu.magisk.base.viewmodel.BaseViewModel
|
||||
import com.topjohnwu.magisk.data.database.PolicyDao
|
||||
import com.topjohnwu.magisk.databinding.ComparableRvItem
|
||||
import com.topjohnwu.magisk.extensions.applySchedulers
|
||||
import com.topjohnwu.magisk.extensions.subscribeK
|
||||
import com.topjohnwu.magisk.extensions.toggle
|
||||
import com.topjohnwu.magisk.model.entity.MagiskPolicy
|
||||
import com.topjohnwu.magisk.model.entity.recycler.PolicyRvItem
|
||||
import com.topjohnwu.magisk.model.events.PolicyEnableEvent
|
||||
import com.topjohnwu.magisk.model.events.PolicyUpdateEvent
|
||||
import com.topjohnwu.magisk.model.events.SnackbarEvent
|
||||
import com.topjohnwu.magisk.utils.BiometricHelper
|
||||
import com.topjohnwu.magisk.utils.DiffObservableList
|
||||
import com.topjohnwu.magisk.utils.RxBus
|
||||
import com.topjohnwu.magisk.view.dialogs.CustomAlertDialog
|
||||
import io.reactivex.Single
|
||||
import io.reactivex.disposables.Disposable
|
||||
import me.tatarka.bindingcollectionadapter2.ItemBinding
|
||||
|
||||
class SuperuserViewModel(
|
||||
private val policyDB: PolicyDao,
|
||||
private val packageManager: PackageManager,
|
||||
private val resources: Resources,
|
||||
rxBus: RxBus
|
||||
) : BaseViewModel() {
|
||||
|
||||
val items = DiffObservableList(ComparableRvItem.callback)
|
||||
val itemBinding = ItemBinding.of<ComparableRvItem<*>> { itemBinding, _, item ->
|
||||
item.bind(itemBinding)
|
||||
itemBinding.bindExtra(BR.viewModel, this@SuperuserViewModel)
|
||||
}
|
||||
|
||||
private var ignoreNext: PolicyRvItem? = null
|
||||
private var fetchTask: Disposable? = null
|
||||
|
||||
init {
|
||||
rxBus.register<PolicyEnableEvent>()
|
||||
.filter {
|
||||
val isIgnored = it.item == ignoreNext
|
||||
if (isIgnored) ignoreNext = null
|
||||
!isIgnored
|
||||
}
|
||||
.subscribeK { togglePolicy(it.item, it.enable) }
|
||||
.add()
|
||||
rxBus.register<PolicyUpdateEvent>()
|
||||
.subscribeK { updatePolicy(it) }
|
||||
.add()
|
||||
|
||||
updatePolicies()
|
||||
}
|
||||
|
||||
fun updatePolicies() {
|
||||
if (fetchTask?.isDisposed?.not() == true) return
|
||||
fetchTask = policyDB.fetchAll()
|
||||
.flattenAsFlowable { it }
|
||||
.map { PolicyRvItem(it, it.applicationInfo.loadIcon(packageManager)) }
|
||||
.toList()
|
||||
.map {
|
||||
it.sortedWith(compareBy(
|
||||
{ it.item.appName.toLowerCase() },
|
||||
{ it.item.packageName }
|
||||
))
|
||||
}
|
||||
.map { it to items.calculateDiff(it) }
|
||||
.applySchedulers()
|
||||
.applyViewModel(this)
|
||||
.subscribeK { items.update(it.first, it.second) }
|
||||
}
|
||||
|
||||
fun deletePressed(item: PolicyRvItem) {
|
||||
fun updateState() = deletePolicy(item.item)
|
||||
.map { items.filterIsInstance<PolicyRvItem>().toMutableList() }
|
||||
.map { it.removeAll { it.item.packageName == item.item.packageName }; it }
|
||||
.map { it to items.calculateDiff(it) }
|
||||
.subscribeK { items.update(it.first, it.second) }
|
||||
.add()
|
||||
|
||||
withView {
|
||||
if (BiometricHelper.isEnabled) {
|
||||
BiometricHelper.authenticate(this) { updateState() }
|
||||
} else {
|
||||
CustomAlertDialog(this)
|
||||
.setTitle(R.string.su_revoke_title)
|
||||
.setMessage(getString(R.string.su_revoke_msg, item.item.appName))
|
||||
.setPositiveButton(android.R.string.yes) { _, _ -> updateState() }
|
||||
.setNegativeButton(android.R.string.no, null)
|
||||
.setCancelable(true)
|
||||
.show()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun updatePolicy(it: PolicyUpdateEvent) = when (it) {
|
||||
is PolicyUpdateEvent.Notification -> updatePolicy(it.item) {
|
||||
val textId =
|
||||
if (it.notification) R.string.su_snack_notif_on else R.string.su_snack_notif_off
|
||||
val text = resources.getString(textId).format(it.appName)
|
||||
SnackbarEvent(text).publish()
|
||||
}
|
||||
is PolicyUpdateEvent.Log -> updatePolicy(it.item) {
|
||||
val textId =
|
||||
if (it.logging) R.string.su_snack_log_on else R.string.su_snack_log_off
|
||||
val text = resources.getString(textId).format(it.appName)
|
||||
SnackbarEvent(text).publish()
|
||||
}
|
||||
}
|
||||
|
||||
private fun updatePolicy(item: MagiskPolicy, onSuccess: (MagiskPolicy) -> Unit) =
|
||||
updatePolicy(item)
|
||||
.subscribeK { onSuccess(it) }
|
||||
.add()
|
||||
|
||||
private fun togglePolicy(item: PolicyRvItem, enable: Boolean) {
|
||||
fun updateState() {
|
||||
val app = item.item.copy(policy = if (enable) MagiskPolicy.ALLOW else MagiskPolicy.DENY)
|
||||
|
||||
updatePolicy(app)
|
||||
.map { it.policy == MagiskPolicy.ALLOW }
|
||||
.subscribeK {
|
||||
val textId = if (it) R.string.su_snack_grant else R.string.su_snack_deny
|
||||
val text = resources.getString(textId).format(item.item.appName)
|
||||
SnackbarEvent(text).publish()
|
||||
}
|
||||
.add()
|
||||
}
|
||||
|
||||
if (BiometricHelper.isEnabled) {
|
||||
withView {
|
||||
BiometricHelper.authenticate(this, onError = {
|
||||
ignoreNext = item
|
||||
item.isEnabled.toggle()
|
||||
}) { updateState() }
|
||||
}
|
||||
} else {
|
||||
updateState()
|
||||
}
|
||||
}
|
||||
|
||||
private fun updatePolicy(policy: MagiskPolicy) =
|
||||
policyDB.update(policy).andThen(Single.just(policy))
|
||||
|
||||
private fun deletePolicy(policy: MagiskPolicy) =
|
||||
policyDB.delete(policy.uid).andThen(Single.just(policy))
|
||||
|
||||
}
|
@ -77,19 +77,6 @@ object Shortcuts {
|
||||
.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK)
|
||||
)
|
||||
.setIcon(getIcon(R.drawable.sc_extension))
|
||||
.setRank(3)
|
||||
.build()
|
||||
)
|
||||
shortCuts.add(
|
||||
ShortcutInfo.Builder(context, "downloads")
|
||||
.setShortLabel(context.getString(R.string.downloads))
|
||||
.setIntent(
|
||||
Intent(intent)
|
||||
.putExtra(Const.Key.OPEN_SECTION, "downloads")
|
||||
.setAction(Intent.ACTION_VIEW)
|
||||
.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK)
|
||||
)
|
||||
.setIcon(getIcon(R.drawable.sc_cloud_download))
|
||||
.setRank(2)
|
||||
.build()
|
||||
)
|
||||
|
@ -1,38 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<layout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools">
|
||||
|
||||
<data>
|
||||
|
||||
<variable
|
||||
name="viewModel"
|
||||
type="com.topjohnwu.magisk.ui.MainViewModel" />
|
||||
|
||||
</data>
|
||||
|
||||
<androidx.drawerlayout.widget.DrawerLayout
|
||||
android:id="@+id/drawer_layout"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:fitsSystemWindows="true"
|
||||
tools:openDrawer="start">
|
||||
|
||||
<include
|
||||
android:id="@+id/main_include"
|
||||
layout="@layout/activity_main_content"
|
||||
viewModel="@{viewModel}" />
|
||||
|
||||
<com.google.android.material.navigation.NavigationView
|
||||
android:id="@+id/nav_view"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_gravity="start"
|
||||
android:fitsSystemWindows="true"
|
||||
onNavigationClick="@{(item) -> viewModel.navigationItemPressed(item)}"
|
||||
app:menu="@menu/drawer" />
|
||||
|
||||
</androidx.drawerlayout.widget.DrawerLayout>
|
||||
|
||||
|
||||
</layout>
|
@ -1,46 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<layout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||
|
||||
<data>
|
||||
|
||||
<variable
|
||||
name="viewModel"
|
||||
type="com.topjohnwu.magisk.ui.MainViewModel" />
|
||||
|
||||
</data>
|
||||
|
||||
<androidx.coordinatorlayout.widget.CoordinatorLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical">
|
||||
|
||||
<com.google.android.material.appbar.AppBarLayout
|
||||
android:layout_width="match_parent"
|
||||
app:elevation="0dp"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<androidx.appcompat.widget.Toolbar
|
||||
android:id="@+id/main_toolbar"
|
||||
onNavigationClick="@{() -> viewModel.navPressed()}"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="?attr/actionBarSize"
|
||||
android:background="@color/primary"
|
||||
app:navigationIcon="@drawable/ic_menu"
|
||||
app:title="@string/magisk"
|
||||
app:titleTextColor="@android:color/white" />
|
||||
|
||||
</com.google.android.material.appbar.AppBarLayout>
|
||||
|
||||
<androidx.fragment.app.FragmentContainerView
|
||||
android:id="@+id/main_nav_host"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:background="?android:windowBackground"
|
||||
app:layout_behavior="@string/appbar_scrolling_view_behavior" />
|
||||
|
||||
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
||||
|
||||
|
||||
</layout>
|
||||
|
@ -1,24 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical"
|
||||
android:padding="@dimen/margin_generic">
|
||||
|
||||
<com.google.android.material.textfield.TextInputLayout
|
||||
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox.Dense"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:hint="@string/settings_update_custom_msg"
|
||||
app:hintEnabled="true">
|
||||
|
||||
<com.google.android.material.textfield.TextInputEditText
|
||||
android:id="@+id/custom_url"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:inputType="textUri" />
|
||||
|
||||
</com.google.android.material.textfield.TextInputLayout>
|
||||
|
||||
</LinearLayout>
|
@ -1,46 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<layout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools">
|
||||
|
||||
<data>
|
||||
|
||||
<variable
|
||||
name="data"
|
||||
type="com.topjohnwu.magisk.ui.settings.SettingsFragment.DownloadDialogData" />
|
||||
|
||||
</data>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical"
|
||||
android:padding="@dimen/margin_generic">
|
||||
|
||||
<androidx.appcompat.widget.AppCompatTextView
|
||||
android:id="@+id/dialog_custom_download_title"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@{@string/settings_download_path_message(data.path)}"
|
||||
tools:text="@string/settings_download_path_message" />
|
||||
|
||||
<com.google.android.material.textfield.TextInputLayout
|
||||
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox.Dense"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="@dimen/margin_generic"
|
||||
android:hint="@string/settings_download_path_title"
|
||||
app:hintEnabled="true">
|
||||
|
||||
<com.google.android.material.textfield.TextInputEditText
|
||||
android:id="@+id/dialog_custom_download_text"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:inputType="textUri"
|
||||
android:text="@={data.text}" />
|
||||
|
||||
</com.google.android.material.textfield.TextInputLayout>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</layout>
|
@ -1,46 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<layout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools">
|
||||
|
||||
<data>
|
||||
|
||||
<import type="com.topjohnwu.magisk.extensions.XStringKt" />
|
||||
|
||||
<variable
|
||||
name="data"
|
||||
type="com.topjohnwu.magisk.ui.settings.SettingsFragment.ManagerNameData" />
|
||||
|
||||
</data>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:clipToPadding="false"
|
||||
android:orientation="vertical"
|
||||
android:paddingStart="@dimen/margin_generic"
|
||||
android:paddingTop="@dimen/margin_generic"
|
||||
android:paddingEnd="@dimen/margin_generic">
|
||||
|
||||
<com.google.android.material.textfield.TextInputLayout
|
||||
android:id="@+id/dialog_name_input"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:hint="@string/settings_app_name_hint"
|
||||
app:counterEnabled="true"
|
||||
app:counterMaxLength="14"
|
||||
app:counterOverflowTextColor="@color/colorError"
|
||||
app:error="@{data.name.length() > 14 || data.name.empty || XStringKt.isEmptyInternal(data.name) ? @string/settings_app_name_error : @string/empty}"
|
||||
app:errorEnabled="true"
|
||||
app:helperText="@string/settings_app_name_helper">
|
||||
|
||||
<com.google.android.material.textfield.TextInputEditText
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@={data.name}"
|
||||
tools:text="@string/re_app_name" />
|
||||
|
||||
</com.google.android.material.textfield.TextInputLayout>
|
||||
|
||||
</LinearLayout>
|
||||
</layout>
|
@ -17,7 +17,7 @@
|
||||
|
||||
<import type="com.topjohnwu.magisk.BuildConfig" />
|
||||
|
||||
<import type="com.topjohnwu.magisk.ui.home.MagiskState" />
|
||||
<import type="com.topjohnwu.magisk.redesign.home.MagiskState" />
|
||||
|
||||
<import type="com.topjohnwu.magisk.extensions.XAndroidKt" />
|
||||
|
||||
@ -801,4 +801,4 @@
|
||||
|
||||
</androidx.core.widget.NestedScrollView>
|
||||
|
||||
</layout>
|
||||
</layout>
|
||||
|
@ -1,37 +0,0 @@
|
||||
<layout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||
|
||||
<data>
|
||||
|
||||
<variable
|
||||
name="viewModel"
|
||||
type="com.topjohnwu.magisk.ui.log.LogViewModel" />
|
||||
|
||||
</data>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical">
|
||||
|
||||
<com.google.android.material.tabs.TabLayout
|
||||
android:id="@+id/log_tabs"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="?attr/colorPrimary"
|
||||
app:elevation="4dp"
|
||||
app:tabSelectedTextColor="@android:color/white"
|
||||
app:tabTextColor="@android:color/secondary_text_dark" />
|
||||
|
||||
<androidx.viewpager.widget.ViewPager
|
||||
android:id="@+id/log_container"
|
||||
itemBinding="@{viewModel.itemBinding}"
|
||||
items="@{viewModel.items}"
|
||||
pageTitles="@{viewModel}"
|
||||
position="@={viewModel.currentPage}"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</layout>
|
@ -1,503 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<layout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools">
|
||||
|
||||
<data>
|
||||
|
||||
<import type="com.topjohnwu.magisk.base.viewmodel.LoadingViewModel.State" />
|
||||
|
||||
<import type="com.topjohnwu.magisk.ui.home.SafetyNetState" />
|
||||
|
||||
<import type="com.topjohnwu.magisk.ui.home.MagiskState" />
|
||||
|
||||
<import type="com.topjohnwu.magisk.ui.home.MagiskItem" />
|
||||
|
||||
<import type="com.topjohnwu.magisk.R" />
|
||||
|
||||
<variable
|
||||
name="viewModel"
|
||||
type="com.topjohnwu.magisk.ui.home.HomeViewModel" />
|
||||
|
||||
</data>
|
||||
|
||||
<androidx.swiperefreshlayout.widget.SwipeRefreshLayout
|
||||
android:id="@+id/swipeRefreshLayout"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="fill_parent"
|
||||
android:orientation="vertical"
|
||||
app:onRefreshListener="@{() -> viewModel.refresh()}"
|
||||
app:refreshing="@{viewModel.loading}">
|
||||
|
||||
<ScrollView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_marginBottom="8dp"
|
||||
android:orientation="vertical">
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/linearLayout"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:padding="@dimen/margin_generic">
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/icon"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="0dp"
|
||||
android:layout_marginEnd="16dp"
|
||||
app:layout_constraintBottom_toBottomOf="@+id/app_name"
|
||||
app:layout_constraintEnd_toStartOf="@+id/app_name"
|
||||
app:layout_constraintHorizontal_bias="0.42"
|
||||
app:layout_constraintHorizontal_chainStyle="packed"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="@+id/app_name"
|
||||
app:srcCompat="@drawable/ic_logo" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/app_name"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center_vertical"
|
||||
android:paddingTop="10dp"
|
||||
android:paddingBottom="10dp"
|
||||
android:text="@string/app_name"
|
||||
android:textAppearance="@style/TextAppearance.AppCompat.Headline"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintHorizontal_bias="0.5"
|
||||
app:layout_constraintStart_toEndOf="@+id/icon"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
<View
|
||||
style="@style/Widget.Divider.Horizontal"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_margin="@dimen/margin_generic" />
|
||||
|
||||
<include
|
||||
android:id="@+id/home_magisk_version"
|
||||
additionalInfo="@{viewModel.magiskAdditionalInfo}"
|
||||
currentVersion="@{viewModel.magiskCurrentVersion}"
|
||||
item="@{MagiskItem.MAGISK}"
|
||||
latestVersion="@{viewModel.magiskLatestVersion}"
|
||||
layout="@layout/include_update_card"
|
||||
state="@{viewModel.magiskState}"
|
||||
text="@{viewModel.magiskStateText}"
|
||||
viewModel="@{viewModel}" />
|
||||
|
||||
<include
|
||||
android:id="@+id/home_manager_version"
|
||||
additionalInfo="@{viewModel.managerAdditionalInfo}"
|
||||
currentVersion="@{viewModel.managerCurrentVersion}"
|
||||
item="@{MagiskItem.MANAGER}"
|
||||
latestVersion="@{viewModel.managerLatestVersion}"
|
||||
layout="@layout/include_update_card"
|
||||
state="@{viewModel.managerState}"
|
||||
text="@{viewModel.managerStateText}"
|
||||
viewModel="@{viewModel}"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="@dimen/margin_generic" />
|
||||
|
||||
<View
|
||||
style="@style/Widget.Divider.Horizontal"
|
||||
gone="@{!viewModel.isConnected}"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_margin="@dimen/margin_generic" />
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:animateLayoutChanges="true"
|
||||
gone="@{!viewModel.isConnected}"
|
||||
android:onClick="@{() -> viewModel.advancedPressed()}"
|
||||
android:orientation="vertical">
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="?attr/selectableItemBackground"
|
||||
android:minHeight="48dp">
|
||||
|
||||
<androidx.appcompat.widget.AppCompatImageView
|
||||
android:id="@+id/arrow"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="16dp"
|
||||
android:rotation="@{viewModel.isAdvancedExpanded ? 180 : 0}"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintStart_toEndOf="@+id/title"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:srcCompat="@drawable/ic_arrow"
|
||||
app:tint="?attr/imageColorTint" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/title"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:text="@string/advanced_settings_title"
|
||||
android:textStyle="bold"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:id="@+id/install_option_expand"
|
||||
gone="@{!viewModel.isAdvancedExpanded}"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical">
|
||||
|
||||
<CheckBox
|
||||
android:id="@+id/keep_force_enc"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:checked="@={viewModel.isForceEncryption}"
|
||||
android:text="@string/keep_force_encryption"
|
||||
app:layout_constraintLeft_toLeftOf="parent"
|
||||
app:layout_constraintRight_toRightOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintWidth_default="wrap"
|
||||
app:layout_constraintWidth_min="300dp" />
|
||||
|
||||
<CheckBox
|
||||
android:id="@+id/keep_verity"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:checked="@={viewModel.isKeepVerity}"
|
||||
android:text="@string/keep_dm_verity"
|
||||
app:layout_constraintLeft_toLeftOf="parent"
|
||||
app:layout_constraintRight_toRightOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/keep_force_enc"
|
||||
app:layout_constraintWidth_default="wrap"
|
||||
app:layout_constraintWidth_min="300dp" />
|
||||
|
||||
<CheckBox
|
||||
android:id="@+id/recovery_mode"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:checked="@={viewModel.isRecovery}"
|
||||
android:text="@string/recovery_mode"
|
||||
app:layout_constraintLeft_toLeftOf="parent"
|
||||
app:layout_constraintRight_toRightOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/keep_verity"
|
||||
app:layout_constraintWidth_default="wrap"
|
||||
app:layout_constraintWidth_min="300dp" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<View
|
||||
style="@style/Widget.Divider.Horizontal"
|
||||
gone="@{!viewModel.isActive || !viewModel.isConnected}"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_margin="@dimen/margin_generic" />
|
||||
|
||||
<LinearLayout
|
||||
gone="@{!viewModel.isConnected || !viewModel.hasGMS}"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical">
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingTop="@dimen/margin_generic_half"
|
||||
android:paddingBottom="@dimen/margin_generic_half">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/sn_logo"
|
||||
android:layout_width="30dp"
|
||||
android:layout_height="30dp"
|
||||
android:tint="@color/green500"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toStartOf="@+id/safetyNet_status"
|
||||
app:layout_constraintHorizontal_chainStyle="packed"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:srcCompat="@drawable/ic_safetynet" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/safetyNet_status"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginLeft="@dimen/margin_generic"
|
||||
android:layout_marginRight="@dimen/margin_generic"
|
||||
android:gravity="center"
|
||||
android:maxLines="1"
|
||||
android:text="@{viewModel.safetyNetTitle}"
|
||||
android:textStyle="bold"
|
||||
app:autoSizeMaxTextSize="14sp"
|
||||
app:autoSizeTextType="uniform"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toStartOf="@+id/safetyNet_refresh"
|
||||
app:layout_constraintStart_toEndOf="@+id/sn_logo"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintWidth_min="200dp" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/safetyNet_refresh"
|
||||
invisible="@{viewModel.ctsState == SafetyNetState.LOADING || viewModel.basicIntegrityState == SafetyNetState.LOADING}"
|
||||
android:layout_width="25dp"
|
||||
android:layout_height="25dp"
|
||||
android:onClick="@{() -> viewModel.safetyNetPressed()}"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toEndOf="@+id/safetyNet_status"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:srcCompat="@drawable/ic_refresh" />
|
||||
|
||||
<ProgressBar
|
||||
android:id="@+id/safetyNet_check_progress"
|
||||
style="@style/Widget.Progress"
|
||||
goneUnless="@{viewModel.ctsState == SafetyNetState.LOADING || viewModel.basicIntegrityState == SafetyNetState.LOADING}"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
app:layout_constraintBottom_toBottomOf="@+id/safetyNet_refresh"
|
||||
app:layout_constraintEnd_toEndOf="@+id/safetyNet_refresh"
|
||||
app:layout_constraintStart_toStartOf="@+id/safetyNet_refresh"
|
||||
app:layout_constraintTop_toTopOf="@+id/safetyNet_refresh" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:id="@+id/safetyNet_expand"
|
||||
gone="@{viewModel.safetyNetState == State.LOADING}"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingBottom="10dp">
|
||||
|
||||
<androidx.appcompat.widget.AppCompatImageView
|
||||
android:id="@+id/cts_status_icon"
|
||||
srcCompat="@{viewModel.ctsState == SafetyNetState.PASS ? R.drawable.ic_check_circle : R.drawable.ic_cancel}"
|
||||
android:layout_width="25dp"
|
||||
android:layout_height="25dp"
|
||||
android:layout_marginEnd="8dp"
|
||||
app:layout_constraintBottom_toBottomOf="@+id/cts_status"
|
||||
app:layout_constraintEnd_toStartOf="@+id/cts_status"
|
||||
app:layout_constraintTop_toTopOf="@+id/cts_status"
|
||||
app:tint="@{viewModel.ctsState == SafetyNetState.PASS ? @color/colorCorrect : @color/colorError}"
|
||||
tools:srcCompat="@drawable/ic_check_circle" />
|
||||
|
||||
<androidx.appcompat.widget.AppCompatImageView
|
||||
android:id="@+id/basic_status_icon"
|
||||
srcCompat="@{viewModel.basicIntegrityState == SafetyNetState.PASS ? R.drawable.ic_check_circle : R.drawable.ic_cancel}"
|
||||
android:layout_width="25dp"
|
||||
android:layout_height="25dp"
|
||||
android:layout_marginEnd="8dp"
|
||||
app:layout_constraintBottom_toBottomOf="@+id/basic_status"
|
||||
app:layout_constraintEnd_toStartOf="@+id/basic_status"
|
||||
app:layout_constraintHorizontal_bias="0.5"
|
||||
app:layout_constraintHorizontal_chainStyle="packed"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="@+id/basic_status"
|
||||
app:tint="@{viewModel.basicIntegrityState == SafetyNetState.PASS ? @color/colorCorrect : @color/colorError}"
|
||||
tools:srcCompat="@drawable/ic_check_circle" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/cts_status"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:padding="6dp"
|
||||
android:text="@{String.format(`ctsProfile: %b`, viewModel.ctsState == SafetyNetState.PASS)}"
|
||||
android:textStyle="bold"
|
||||
app:layout_constraintEnd_toEndOf="@+id/basic_status"
|
||||
app:layout_constraintStart_toStartOf="@+id/basic_status"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
tools:text="ctsProfile: true" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/basic_status"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="4dp"
|
||||
android:padding="6dp"
|
||||
android:text="@{String.format(`basicIntegrity: %b`, viewModel.basicIntegrityState == SafetyNetState.PASS)}"
|
||||
android:textStyle="bold"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintHorizontal_bias="0.5"
|
||||
app:layout_constraintStart_toEndOf="@+id/basic_status_icon"
|
||||
app:layout_constraintTop_toBottomOf="@+id/cts_status"
|
||||
tools:text="basicIntegrity: true" />
|
||||
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<View
|
||||
style="@style/Widget.Divider.Horizontal"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_margin="@dimen/margin_generic" />
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<FrameLayout
|
||||
android:id="@+id/twitter"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:clickable="true"
|
||||
android:focusable="true"
|
||||
android:foreground="?android:attr/selectableItemBackground"
|
||||
android:onClick="@{() -> viewModel.twitterPressed()}"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toStartOf="@+id/github"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/paypal">
|
||||
|
||||
<ImageView
|
||||
android:layout_width="35dp"
|
||||
android:layout_height="35dp"
|
||||
android:layout_gravity="center_horizontal"
|
||||
android:layout_margin="10dp"
|
||||
android:tint="?attr/imageColorTint"
|
||||
app:srcCompat="@drawable/ic_twitter" />
|
||||
|
||||
</FrameLayout>
|
||||
|
||||
<FrameLayout
|
||||
android:id="@+id/github"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:clickable="true"
|
||||
android:focusable="true"
|
||||
android:foreground="?android:attr/selectableItemBackground"
|
||||
android:onClick="@{() -> viewModel.githubPressed()}"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toStartOf="@+id/xda"
|
||||
app:layout_constraintStart_toEndOf="@+id/twitter">
|
||||
|
||||
<ImageView
|
||||
android:layout_width="35dp"
|
||||
android:layout_height="35dp"
|
||||
android:layout_gravity="center_horizontal"
|
||||
android:layout_margin="10dp"
|
||||
android:tint="?attr/imageColorTint"
|
||||
app:srcCompat="@drawable/ic_github" />
|
||||
|
||||
</FrameLayout>
|
||||
|
||||
<FrameLayout
|
||||
android:id="@+id/xda"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:clickable="true"
|
||||
android:focusable="true"
|
||||
android:foreground="?android:attr/selectableItemBackground"
|
||||
android:onClick="@{() -> viewModel.xdaPressed()}"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toEndOf="@id/github">
|
||||
|
||||
<ImageView
|
||||
android:layout_width="35dp"
|
||||
android:layout_height="35dp"
|
||||
android:layout_gravity="center_horizontal"
|
||||
android:layout_margin="10dp"
|
||||
android:tint="?attr/imageColorTint"
|
||||
app:srcCompat="@drawable/ic_xda" />
|
||||
|
||||
</FrameLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/patreon"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="?android:attr/selectableItemBackground"
|
||||
android:clickable="true"
|
||||
android:focusable="true"
|
||||
android:gravity="center_horizontal"
|
||||
android:onClick="@{() -> viewModel.patreonPressed()}"
|
||||
android:orientation="horizontal"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toEndOf="@+id/paypal"
|
||||
app:layout_constraintTop_toTopOf="parent">
|
||||
|
||||
<ImageView
|
||||
android:layout_width="35dp"
|
||||
android:layout_height="35dp"
|
||||
android:layout_gravity="center"
|
||||
android:layout_margin="10dp"
|
||||
android:tint="?attr/imageColorTint"
|
||||
app:srcCompat="@drawable/ic_patreon" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:layout_margin="5dp"
|
||||
android:text="Patreon"
|
||||
android:textAppearance="?android:attr/textAppearanceMedium"
|
||||
android:textStyle="bold" />
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/paypal"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="?android:attr/selectableItemBackground"
|
||||
android:clickable="true"
|
||||
android:focusable="true"
|
||||
android:gravity="center_horizontal"
|
||||
android:onClick="@{() -> viewModel.paypalPressed()}"
|
||||
android:orientation="horizontal"
|
||||
app:layout_constraintEnd_toStartOf="@+id/patreon"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent">
|
||||
|
||||
<ImageView
|
||||
android:layout_width="35dp"
|
||||
android:layout_height="35dp"
|
||||
android:layout_gravity="center"
|
||||
android:layout_margin="10dp"
|
||||
android:tint="?attr/imageColorTint"
|
||||
app:srcCompat="@drawable/ic_paypal" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:layout_margin="5dp"
|
||||
android:text="PayPal"
|
||||
android:textAppearance="?android:attr/textAppearanceMedium"
|
||||
android:textStyle="bold" />
|
||||
</LinearLayout>
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
<View
|
||||
style="@style/Widget.Divider.Horizontal"
|
||||
gone="@{!viewModel.isActive}"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_margin="@dimen/margin_generic" />
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
style="@style/Widget.Button.Text"
|
||||
gone="@{!viewModel.isActive}"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:onClick="@{() -> viewModel.uninstallPressed()}"
|
||||
android:text="@string/uninstall" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</ScrollView>
|
||||
|
||||
</androidx.swiperefreshlayout.widget.SwipeRefreshLayout>
|
||||
|
||||
</layout>
|
@ -1,36 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<layout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools">
|
||||
|
||||
<data>
|
||||
|
||||
<variable
|
||||
name="viewModel"
|
||||
type="com.topjohnwu.magisk.ui.hide.HideViewModel" />
|
||||
|
||||
</data>
|
||||
|
||||
<androidx.swiperefreshlayout.widget.SwipeRefreshLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
app:onRefreshListener="@{() -> viewModel.refresh()}"
|
||||
app:refreshing="@{viewModel.loading}">
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
dividerColor="@{@android:color/transparent}"
|
||||
dividerSize="@{@dimen/margin_generic}"
|
||||
itemBinding="@{viewModel.itemBinding}"
|
||||
items="@{viewModel.items}"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:clipToPadding="false"
|
||||
android:orientation="vertical"
|
||||
android:padding="@dimen/margin_generic"
|
||||
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
|
||||
tools:listitem="@layout/item_hide_app" />
|
||||
|
||||
</androidx.swiperefreshlayout.widget.SwipeRefreshLayout>
|
||||
|
||||
</layout>
|
||||
|
@ -1,70 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<layout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools">
|
||||
|
||||
<data>
|
||||
|
||||
<variable
|
||||
name="viewModel"
|
||||
type="com.topjohnwu.magisk.ui.module.ModuleViewModel" />
|
||||
|
||||
</data>
|
||||
|
||||
<androidx.coordinatorlayout.widget.CoordinatorLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<androidx.swiperefreshlayout.widget.SwipeRefreshLayout
|
||||
android:id="@+id/modules_refresh_layout"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical"
|
||||
app:layout_behavior="@string/appbar_scrolling_view_behavior"
|
||||
app:onRefreshListener="@{() -> viewModel.refresh(false)}"
|
||||
app:refreshing="@{viewModel.loading}">
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/modules_content"
|
||||
dividerColor="@{@android:color/transparent}"
|
||||
dividerSize="@{@dimen/margin_generic}"
|
||||
itemBinding="@{viewModel.itemBinding}"
|
||||
items="@{viewModel.itemsInstalled}"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:clipToPadding="false"
|
||||
android:orientation="vertical"
|
||||
android:padding="@dimen/margin_generic"
|
||||
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
|
||||
tools:listitem="@layout/item_module" />
|
||||
|
||||
</androidx.swiperefreshlayout.widget.SwipeRefreshLayout>
|
||||
|
||||
<androidx.appcompat.widget.AppCompatTextView
|
||||
android:id="@+id/modules_status_text"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_gravity="center"
|
||||
android:fontFamily="sans-serif-light"
|
||||
android:gravity="center"
|
||||
android:text="@string/no_modules_found"
|
||||
android:textSize="20sp"
|
||||
android:textStyle="italic"
|
||||
android:visibility="gone"
|
||||
tools:visibility="visible" />
|
||||
|
||||
<com.google.android.material.floatingactionbutton.FloatingActionButton
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="bottom|center_horizontal"
|
||||
android:layout_margin="@dimen/fab_padding"
|
||||
android:onClick="@{() -> viewModel.fabPressed()}"
|
||||
android:focusable="true"
|
||||
android:clickable="true"
|
||||
app:fabSize="normal"
|
||||
app:layout_behavior="com.google.android.material.floatingactionbutton.FloatingActionButton$Behavior"
|
||||
app:srcCompat="@drawable/ic_add" />
|
||||
|
||||
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
||||
|
||||
</layout>
|
@ -1,56 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<layout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools">
|
||||
|
||||
<data>
|
||||
|
||||
<variable
|
||||
name="viewModel"
|
||||
type="com.topjohnwu.magisk.ui.module.ModuleViewModel" />
|
||||
|
||||
</data>
|
||||
|
||||
<FrameLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical">
|
||||
|
||||
<androidx.swiperefreshlayout.widget.SwipeRefreshLayout
|
||||
android:id="@+id/repos_refresh_layout"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical"
|
||||
app:onRefreshListener="@{() -> viewModel.refresh(true)}"
|
||||
app:refreshing="@{viewModel.loading}">
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/repos_content"
|
||||
dividerColor="@{@android:color/transparent}"
|
||||
dividerSize="@{@dimen/margin_generic}"
|
||||
itemBinding="@{viewModel.itemBinding}"
|
||||
items="@{viewModel.itemsRemote}"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:clipToPadding="false"
|
||||
android:orientation="vertical"
|
||||
android:padding="@dimen/margin_generic"
|
||||
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
|
||||
tools:listitem="@layout/item_repo" />
|
||||
|
||||
</androidx.swiperefreshlayout.widget.SwipeRefreshLayout>
|
||||
|
||||
<androidx.appcompat.widget.AppCompatTextView
|
||||
android:id="@+id/repos_status_text"
|
||||
gone="@{!(viewModel.loaded && viewModel.itemsRemote.size == 0)}"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_gravity="center"
|
||||
android:fontFamily="sans-serif-light"
|
||||
android:gravity="center"
|
||||
android:text="@string/no_modules_found"
|
||||
android:textSize="20sp"
|
||||
android:textStyle="italic" />
|
||||
</FrameLayout>
|
||||
|
||||
</layout>
|
@ -1,53 +0,0 @@
|
||||
<layout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools">
|
||||
|
||||
<data>
|
||||
|
||||
<variable
|
||||
name="viewModel"
|
||||
type="com.topjohnwu.magisk.ui.superuser.SuperuserViewModel" />
|
||||
|
||||
</data>
|
||||
|
||||
<FrameLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/superuser_content"
|
||||
dividerColor="@{@android:color/transparent}"
|
||||
dividerSize="@{@dimen/margin_generic}"
|
||||
gone="@{!viewModel.loaded}"
|
||||
itemBinding="@{viewModel.itemBinding}"
|
||||
items="@{viewModel.items}"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:clipToPadding="false"
|
||||
android:orientation="vertical"
|
||||
android:padding="@dimen/margin_generic"
|
||||
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
|
||||
tools:listitem="@layout/item_policy" />
|
||||
|
||||
<ProgressBar
|
||||
style="@style/Widget.Progress"
|
||||
gone="@{!viewModel.loading}"
|
||||
android:layout_gravity="center" />
|
||||
|
||||
<androidx.appcompat.widget.AppCompatTextView
|
||||
android:id="@+id/superuser_empty"
|
||||
gone="@{!viewModel.loaded || viewModel.items.size > 0}"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_gravity="center"
|
||||
android:fontFamily="sans-serif-light"
|
||||
android:gravity="center"
|
||||
android:text="@string/no_apps_found"
|
||||
android:textSize="20sp"
|
||||
android:textStyle="italic" />
|
||||
|
||||
</FrameLayout>
|
||||
|
||||
|
||||
</layout>
|
||||
|
@ -1,151 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<layout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools">
|
||||
|
||||
<data>
|
||||
|
||||
<import type="com.topjohnwu.magisk.ui.home.MagiskState" />
|
||||
|
||||
<import type="com.topjohnwu.magisk.ui.home.MagiskItem" />
|
||||
|
||||
<import type="com.topjohnwu.magisk.R" />
|
||||
|
||||
<variable
|
||||
name="item"
|
||||
type="com.topjohnwu.magisk.ui.home.MagiskItem" />
|
||||
|
||||
<variable
|
||||
name="state"
|
||||
type="com.topjohnwu.magisk.ui.home.MagiskState" />
|
||||
|
||||
<variable
|
||||
name="text"
|
||||
type="String" />
|
||||
|
||||
<variable
|
||||
name="currentVersion"
|
||||
type="String" />
|
||||
|
||||
<variable
|
||||
name="latestVersion"
|
||||
type="String" />
|
||||
|
||||
<variable
|
||||
name="additionalInfo"
|
||||
type="String" />
|
||||
|
||||
<variable
|
||||
name="viewModel"
|
||||
type="com.topjohnwu.magisk.ui.home.HomeViewModel" />
|
||||
|
||||
</data>
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:animateLayoutChanges="true"
|
||||
android:background="?android:attr/selectableItemBackground"
|
||||
android:onClick="@{() -> viewModel.cardPressed(item)}">
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/install"
|
||||
style="@style/Widget.Button.Text"
|
||||
gone="@{state == MagiskState.LOADING || !viewModel.isConnected}"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginEnd="8dp"
|
||||
android:onClick="@{() -> viewModel.installPressed(item)}"
|
||||
android:text="@{state != MagiskState.OBSOLETE ? @string/install : @string/update}"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
tools:text="@string/install" />
|
||||
|
||||
<androidx.appcompat.widget.AppCompatImageView
|
||||
android:id="@+id/status_icon"
|
||||
invisible="@{state == MagiskState.LOADING}"
|
||||
srcCompat="@{state == MagiskState.UP_TO_DATE ? R.drawable.ic_check_circle : (state == MagiskState.OBSOLETE ? R.drawable.ic_update : R.drawable.ic_help)}"
|
||||
android:layout_width="25dp"
|
||||
android:layout_height="25dp"
|
||||
android:layout_marginStart="16dp"
|
||||
android:layout_marginEnd="16dp"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toStartOf="@+id/status"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:tint="@{state == MagiskState.UP_TO_DATE ? @color/colorCorrect : (state == MagiskState.OBSOLETE ? @color/colorUpdate : @color/colorError)}" />
|
||||
|
||||
<ProgressBar
|
||||
android:id="@+id/progress"
|
||||
style="@style/Widget.Progress"
|
||||
gone="@{state != MagiskState.LOADING}"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
app:layout_constraintBottom_toBottomOf="@+id/status_icon"
|
||||
app:layout_constraintEnd_toEndOf="@+id/status_icon"
|
||||
app:layout_constraintStart_toStartOf="@+id/status_icon"
|
||||
app:layout_constraintTop_toTopOf="@+id/status_icon" />
|
||||
|
||||
<androidx.appcompat.widget.AppCompatTextView
|
||||
android:id="@+id/status"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginEnd="8dp"
|
||||
style="@style/Widget.Text.Emphasize"
|
||||
android:paddingTop="3dp"
|
||||
android:paddingBottom="3dp"
|
||||
android:text="@{text}"
|
||||
android:maxLines="2"
|
||||
app:layout_constraintBottom_toTopOf="@+id/current_version"
|
||||
app:layout_constraintEnd_toStartOf="@+id/install"
|
||||
app:layout_constraintStart_toEndOf="@+id/status_icon"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintVertical_chainStyle="packed"
|
||||
tools:text="@tools:sample/lorem/random" />
|
||||
|
||||
<androidx.appcompat.widget.AppCompatTextView
|
||||
android:id="@+id/current_version"
|
||||
gone="@{state == MagiskState.LOADING || currentVersion.length == 0}"
|
||||
android:layout_width="0dp"
|
||||
style="@style/Widget.Text.Caption.Inactive"
|
||||
android:layout_height="wrap_content"
|
||||
android:maxLines="2"
|
||||
android:text="@{state != MagiskState.LOADING ? @string/current_installed(currentVersion) : @string/checking_for_updates}"
|
||||
app:layout_constraintBottom_toTopOf="@+id/latest_version"
|
||||
app:layout_constraintEnd_toEndOf="@+id/status"
|
||||
app:layout_constraintStart_toStartOf="@+id/status"
|
||||
app:layout_constraintTop_toBottomOf="@+id/status"
|
||||
tools:text="@tools:sample/lorem/random" />
|
||||
|
||||
<androidx.appcompat.widget.AppCompatTextView
|
||||
android:id="@+id/latest_version"
|
||||
gone="@{currentVersion == latestVersion || !viewModel.isConnected}"
|
||||
android:layout_width="0dp"
|
||||
style="@style/Widget.Text.Caption.Inactive"
|
||||
android:layout_height="wrap_content"
|
||||
android:maxLines="2"
|
||||
android:text="@{state != MagiskState.LOADING ? @string/latest_version(latestVersion) : @string/checking_for_updates}"
|
||||
app:layout_constraintBottom_toTopOf="@+id/additional"
|
||||
app:layout_constraintEnd_toEndOf="@+id/status"
|
||||
app:layout_constraintStart_toStartOf="@+id/status"
|
||||
app:layout_constraintTop_toBottomOf="@+id/current_version"
|
||||
tools:text="@tools:sample/lorem/random" />
|
||||
|
||||
<androidx.appcompat.widget.AppCompatTextView
|
||||
android:id="@+id/additional"
|
||||
gone="@{additionalInfo.length == 0}"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
style="@style/Widget.Text.Caption.Inactive"
|
||||
android:maxLines="2"
|
||||
android:text="@{additionalInfo}"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="@+id/status"
|
||||
app:layout_constraintStart_toStartOf="@+id/status"
|
||||
app:layout_constraintTop_toBottomOf="@+id/latest_version"
|
||||
tools:text="@tools:sample/lorem/random" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
</layout>
|
@ -1,126 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<layout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools">
|
||||
|
||||
<data>
|
||||
|
||||
<variable
|
||||
name="item"
|
||||
type="com.topjohnwu.magisk.model.entity.recycler.HideRvItem" />
|
||||
|
||||
<variable
|
||||
name="viewModel"
|
||||
type="com.topjohnwu.magisk.ui.hide.HideViewModel" />
|
||||
|
||||
</data>
|
||||
|
||||
<com.google.android.material.card.MaterialCardView
|
||||
style="@style/Widget.Card"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:foreground="?attr/selectableItemBackground"
|
||||
android:minHeight="?android:attr/listPreferredItemHeight"
|
||||
android:onClick="@{() -> item.toggleExpansion()}"
|
||||
app:cardElevation="@dimen/card_elevation">
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:padding="@dimen/margin_generic_half">
|
||||
|
||||
<androidx.appcompat.widget.AppCompatImageView
|
||||
android:id="@+id/hide_app_icon"
|
||||
android:layout_width="48dp"
|
||||
android:layout_height="48dp"
|
||||
android:gravity="end"
|
||||
android:src="@{item.item.icon}"
|
||||
app:layout_constraintBottom_toTopOf="@+id/hide_app_processes"
|
||||
app:layout_constraintEnd_toStartOf="@+id/hide_app_name"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
tools:src="@drawable/ic_magisk" />
|
||||
|
||||
<androidx.appcompat.widget.AppCompatTextView
|
||||
android:id="@+id/hide_app_name"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="@dimen/margin_generic"
|
||||
android:layout_marginEnd="@dimen/margin_generic"
|
||||
android:ellipsize="end"
|
||||
android:maxLines="1"
|
||||
android:text="@{item.item.name}"
|
||||
android:textAppearance="?android:attr/textAppearanceMedium"
|
||||
android:textIsSelectable="false"
|
||||
app:layout_constraintBottom_toTopOf="@+id/hide_app_package"
|
||||
app:layout_constraintEnd_toStartOf="@+id/hide_app_checkbox"
|
||||
app:layout_constraintStart_toEndOf="@+id/hide_app_icon"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintVertical_chainStyle="packed"
|
||||
tools:text="Magisk" />
|
||||
|
||||
<androidx.appcompat.widget.AppCompatTextView
|
||||
android:id="@+id/hide_app_package"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:ellipsize="end"
|
||||
android:maxLines="1"
|
||||
android:text="@{item.packageName}"
|
||||
android:textAppearance="?android:attr/textAppearanceSmall"
|
||||
android:textColor="@android:color/tertiary_text_dark"
|
||||
android:textIsSelectable="false"
|
||||
app:layout_constraintBottom_toTopOf="@+id/hide_app_processes"
|
||||
app:layout_constraintEnd_toStartOf="@id/hide_app_arrow"
|
||||
app:layout_constraintStart_toStartOf="@+id/hide_app_name"
|
||||
app:layout_constraintTop_toBottomOf="@+id/hide_app_name"
|
||||
tools:text="com.topjohnwu.magisk" />
|
||||
|
||||
<androidx.appcompat.widget.AppCompatImageView
|
||||
android:id="@+id/hide_app_arrow"
|
||||
gone="@{item.items.size == 1}"
|
||||
android:layout_width="24dp"
|
||||
android:layout_height="24dp"
|
||||
android:layout_marginStart="5dp"
|
||||
android:layout_marginEnd="5dp"
|
||||
android:background="@android:color/transparent"
|
||||
android:rotation="@{item.isExpanded() ? 180 : 0}"
|
||||
app:layout_constraintBottom_toBottomOf="@+id/hide_app_package"
|
||||
app:layout_constraintEnd_toEndOf="@+id/hide_app_name"
|
||||
app:layout_constraintStart_toEndOf="@+id/hide_app_package"
|
||||
app:layout_constraintTop_toTopOf="@+id/hide_app_package"
|
||||
app:srcCompat="@drawable/ic_arrow"
|
||||
app:tint="?attr/imageColorTint" />
|
||||
|
||||
<androidx.appcompat.widget.AppCompatImageView
|
||||
android:id="@+id/hide_app_checkbox"
|
||||
android:focusable="true"
|
||||
android:clickable="true"
|
||||
style="@style/Widget.Icon"
|
||||
isChecked="@{item.isHiddenState}"
|
||||
android:onClick="@{() -> item.toggle()}"
|
||||
app:layout_constraintBottom_toTopOf="@+id/hide_app_processes"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toEndOf="@+id/hide_app_name"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:tint="?attr/imageColorTint"
|
||||
tools:src="@drawable/ic_checked" />
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/hide_app_processes"
|
||||
gone="@{!item.isExpanded}"
|
||||
itemBinding="@{viewModel.itemBinding}"
|
||||
items="@{item.items}"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:paddingTop="@dimen/margin_generic_half"
|
||||
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
tools:listitem="@layout/item_hide_process" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
</com.google.android.material.card.MaterialCardView>
|
||||
|
||||
</layout>
|
@ -1,57 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<layout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools">
|
||||
|
||||
<data>
|
||||
|
||||
<variable
|
||||
name="item"
|
||||
type="com.topjohnwu.magisk.model.entity.recycler.HideProcessRvItem" />
|
||||
|
||||
<variable
|
||||
name="viewModel"
|
||||
type="com.topjohnwu.magisk.ui.hide.HideViewModel" />
|
||||
|
||||
</data>
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="?selectableItemBackground"
|
||||
android:clickable="false"
|
||||
android:focusable="false"
|
||||
android:onClick="@{() -> item.toggle()}">
|
||||
|
||||
<androidx.appcompat.widget.AppCompatTextView
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginEnd="@dimen/margin_generic"
|
||||
android:ellipsize="marquee"
|
||||
android:singleLine="true"
|
||||
android:text="@{item.process}"
|
||||
android:textAppearance="?android:attr/textAppearanceSmall"
|
||||
android:textColor="@android:color/tertiary_text_dark"
|
||||
android:textIsSelectable="false"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toStartOf="@+id/hide_process_icon"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
tools:text="com.topjohnwu.magisk.process" />
|
||||
|
||||
<androidx.appcompat.widget.AppCompatImageView
|
||||
android:id="@+id/hide_process_icon"
|
||||
style="@style/Widget.Icon"
|
||||
isChecked="@{item.isHidden}"
|
||||
android:background="@null"
|
||||
android:clickable="false"
|
||||
android:focusable="false"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:tint="?attr/imageColorTint"
|
||||
tools:src="@drawable/ic_checked" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
</layout>
|
@ -1,149 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<layout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools">
|
||||
|
||||
<data>
|
||||
|
||||
<import type="com.topjohnwu.magisk.R" />
|
||||
|
||||
<variable
|
||||
name="item"
|
||||
type="com.topjohnwu.magisk.model.entity.recycler.ModuleRvItem" />
|
||||
|
||||
<variable
|
||||
name="viewModel"
|
||||
type="com.topjohnwu.magisk.ui.module.ModuleViewModel" />
|
||||
|
||||
</data>
|
||||
|
||||
<com.google.android.material.card.MaterialCardView
|
||||
style="@style/Widget.Card"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:foreground="?attr/selectableItemBackground"
|
||||
android:minHeight="?android:attr/listPreferredItemHeight"
|
||||
app:cardElevation="2dp"
|
||||
tools:layout_gravity="center">
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:padding="@dimen/margin_generic">
|
||||
|
||||
<androidx.appcompat.widget.AppCompatTextView
|
||||
android:id="@+id/title"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@{item.item.name}"
|
||||
android:textAppearance="?android:attr/textAppearanceMedium"
|
||||
android:textIsSelectable="false"
|
||||
app:layout_constraintBottom_toTopOf="@+id/version_name"
|
||||
app:layout_constraintEnd_toStartOf="@+id/checkbox"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
tools:text="Magisk Module" />
|
||||
|
||||
<androidx.appcompat.widget.AppCompatTextView
|
||||
android:id="@+id/version_name"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@{item.item.version.length == 0 ? @string/no_info_provided : item.item.version}"
|
||||
android:textAppearance="?android:attr/textAppearanceSmall"
|
||||
android:textColor="@android:color/tertiary_text_dark"
|
||||
android:textIsSelectable="false"
|
||||
android:textStyle="bold|italic"
|
||||
app:layout_constraintBottom_toTopOf="@+id/author"
|
||||
app:layout_constraintEnd_toEndOf="@+id/title"
|
||||
app:layout_constraintStart_toStartOf="@+id/title"
|
||||
app:layout_constraintTop_toBottomOf="@+id/title"
|
||||
tools:text="v1" />
|
||||
|
||||
<androidx.appcompat.widget.AppCompatTextView
|
||||
android:id="@+id/author"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@{item.item.author.length == 0 ? @string/no_info_provided : @string/author(item.item.author)}"
|
||||
android:textAppearance="?android:attr/textAppearanceSmall"
|
||||
android:textColor="@android:color/tertiary_text_dark"
|
||||
android:textIsSelectable="false"
|
||||
android:textStyle="bold|italic"
|
||||
app:layout_constraintBottom_toTopOf="@+id/description"
|
||||
app:layout_constraintEnd_toEndOf="@+id/title"
|
||||
app:layout_constraintStart_toStartOf="@+id/title"
|
||||
app:layout_constraintTop_toBottomOf="@+id/version_name"
|
||||
tools:text="topjohnwu" />
|
||||
|
||||
<androidx.appcompat.widget.AppCompatTextView
|
||||
android:id="@+id/description"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center_vertical"
|
||||
android:text="@{item.item.description.length == 0 ? @string/no_info_provided : item.item.description}"
|
||||
android:textAppearance="?android:attr/textAppearanceSmall"
|
||||
android:textIsSelectable="false"
|
||||
app:layout_constraintBottom_toTopOf="@+id/notice"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="@+id/title"
|
||||
app:layout_constraintTop_toBottomOf="@+id/author"
|
||||
tools:lines="4"
|
||||
tools:text="@tools:sample/lorem/random" />
|
||||
|
||||
<androidx.appcompat.widget.AppCompatTextView
|
||||
android:id="@+id/notice"
|
||||
gone="@{item.lastActionNotice.length == 0}"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center_vertical"
|
||||
android:text="@{item.lastActionNotice}"
|
||||
android:textAppearance="?android:attr/textAppearanceSmall"
|
||||
android:textColor="@color/red500"
|
||||
android:visibility="visible"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="@+id/title"
|
||||
app:layout_constraintTop_toBottomOf="@+id/description" />
|
||||
|
||||
<androidx.appcompat.widget.AppCompatImageView
|
||||
android:id="@+id/checkbox"
|
||||
android:focusable="true"
|
||||
android:clickable="true"
|
||||
style="@style/Widget.Icon"
|
||||
isChecked="@{item.isChecked}"
|
||||
android:layout_marginEnd="@dimen/margin_generic"
|
||||
android:onClick="@{() -> item.toggle()}"
|
||||
app:layout_constraintBottom_toTopOf="@+id/description"
|
||||
app:layout_constraintEnd_toStartOf="@+id/delete"
|
||||
app:layout_constraintStart_toEndOf="@+id/title"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:tint="?attr/imageColorTint"
|
||||
tools:src="@drawable/ic_checked" />
|
||||
|
||||
<View
|
||||
style="@style/Widget.Divider.Vertical"
|
||||
android:backgroundTint="?attr/imageColorTint"
|
||||
app:layout_constraintBottom_toBottomOf="@+id/checkbox"
|
||||
app:layout_constraintEnd_toStartOf="@+id/delete"
|
||||
app:layout_constraintStart_toEndOf="@+id/checkbox"
|
||||
app:layout_constraintTop_toTopOf="@+id/checkbox" />
|
||||
|
||||
<androidx.appcompat.widget.AppCompatImageView
|
||||
android:id="@+id/delete"
|
||||
android:focusable="true"
|
||||
android:clickable="true"
|
||||
style="@style/Widget.Icon"
|
||||
srcCompat="@{item.isDeletable ? R.drawable.ic_undelete : R.drawable.ic_delete}"
|
||||
android:onClick="@{() -> item.toggleDelete()}"
|
||||
app:layout_constraintBottom_toBottomOf="@+id/checkbox"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toEndOf="@+id/checkbox"
|
||||
app:layout_constraintTop_toTopOf="@+id/checkbox"
|
||||
app:tint="?attr/imageColorTint"
|
||||
tools:ignore="ContentDescription"
|
||||
tools:src="@drawable/ic_delete" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
</com.google.android.material.card.MaterialCardView>
|
||||
|
||||
</layout>
|
@ -1,37 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<layout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools">
|
||||
|
||||
<data>
|
||||
|
||||
<variable
|
||||
name="item"
|
||||
type="com.topjohnwu.magisk.model.entity.recycler.LogRvItem" />
|
||||
|
||||
<variable
|
||||
name="viewModel"
|
||||
type="com.topjohnwu.magisk.ui.log.LogViewModel" />
|
||||
|
||||
</data>
|
||||
|
||||
<FrameLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
dividerColor="@{@android:color/transparent}"
|
||||
dividerSize="@{@dimen/margin_generic}"
|
||||
itemBinding="@{viewModel.itemBinding}"
|
||||
items="@{item.items}"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:clipToPadding="false"
|
||||
android:orientation="vertical"
|
||||
android:padding="@dimen/margin_generic"
|
||||
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
|
||||
tools:listitem="@layout/item_superuser_log" />
|
||||
|
||||
</FrameLayout>
|
||||
|
||||
</layout>
|
@ -1,56 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<layout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools">
|
||||
|
||||
<data>
|
||||
|
||||
<variable
|
||||
name="item"
|
||||
type="com.topjohnwu.magisk.model.entity.recycler.MagiskLogRvItem" />
|
||||
|
||||
<variable
|
||||
name="viewModel"
|
||||
type="com.topjohnwu.magisk.ui.log.LogViewModel" />
|
||||
|
||||
</data>
|
||||
|
||||
<FrameLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<HorizontalScrollView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
adapter="@{viewModel.itemsAdapter}"
|
||||
itemBinding="@{viewModel.itemBinding}"
|
||||
items="@{item.items}"
|
||||
scrollPosition="@={viewModel.scrollPosition}"
|
||||
scrollPositionSmooth="@{true}"
|
||||
android:focusable="true"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical"
|
||||
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
|
||||
tools:listitem="@layout/item_console" />
|
||||
|
||||
</HorizontalScrollView>
|
||||
|
||||
<com.google.android.material.floatingactionbutton.FloatingActionButton
|
||||
hide="@{viewModel.scrollPosition == item.items.size - 1 || item.items.size == 0}"
|
||||
android:focusable="true"
|
||||
android:clickable="true"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="bottom|end"
|
||||
android:layout_margin="@dimen/margin_generic"
|
||||
android:onClick="@{() -> viewModel.scrollDownPressed()}"
|
||||
app:srcCompat="@drawable/ic_arrow_down"
|
||||
app:tint="@color/colorTextInverse" />
|
||||
|
||||
</FrameLayout>
|
||||
|
||||
|
||||
</layout>
|
@ -1,211 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<layout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools">
|
||||
|
||||
<data>
|
||||
|
||||
<variable
|
||||
name="item"
|
||||
type="com.topjohnwu.magisk.model.entity.recycler.PolicyRvItem" />
|
||||
|
||||
<variable
|
||||
name="viewModel"
|
||||
type="com.topjohnwu.magisk.ui.superuser.SuperuserViewModel" />
|
||||
|
||||
</data>
|
||||
|
||||
<com.google.android.material.card.MaterialCardView
|
||||
style="@style/Widget.Card"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:foreground="?attr/selectableItemBackground"
|
||||
android:minHeight="?android:attr/listPreferredItemHeight"
|
||||
android:onClick="@{() -> item.toggle()}"
|
||||
app:cardCornerRadius="@dimen/radius_generic"
|
||||
app:cardElevation="2dp"
|
||||
app:cardPreventCornerOverlap="true">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:animateLayoutChanges="true"
|
||||
android:orientation="vertical">
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:padding="@dimen/margin_generic_half">
|
||||
|
||||
<androidx.appcompat.widget.AppCompatImageView
|
||||
android:id="@+id/app_icon"
|
||||
android:layout_width="50dp"
|
||||
android:layout_height="50dp"
|
||||
android:gravity="end"
|
||||
android:src="@{item.icon}"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toStartOf="@+id/app_name"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
tools:srcCompat="@drawable/ic_logo" />
|
||||
|
||||
<androidx.appcompat.widget.AppCompatTextView
|
||||
android:id="@+id/app_name"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginLeft="@dimen/margin_generic"
|
||||
android:layout_marginRight="@dimen/margin_generic"
|
||||
android:ellipsize="end"
|
||||
android:maxLines="1"
|
||||
android:text="@{item.item.appName}"
|
||||
android:textAppearance="?android:attr/textAppearanceMedium"
|
||||
android:textIsSelectable="false"
|
||||
app:layout_constraintBottom_toTopOf="@+id/package_name"
|
||||
app:layout_constraintEnd_toStartOf="@+id/master_switch"
|
||||
app:layout_constraintStart_toEndOf="@+id/app_icon"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintVertical_chainStyle="packed"
|
||||
tools:text="@string/app_name" />
|
||||
|
||||
<androidx.appcompat.widget.AppCompatTextView
|
||||
android:id="@+id/package_name"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:ellipsize="end"
|
||||
android:maxLines="1"
|
||||
android:text="@{item.item.packageName}"
|
||||
android:textAppearance="?android:attr/textAppearanceSmall"
|
||||
android:textColor="@android:color/tertiary_text_dark"
|
||||
android:textIsSelectable="false"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="@id/app_name"
|
||||
app:layout_constraintStart_toStartOf="@id/app_name"
|
||||
app:layout_constraintTop_toBottomOf="@id/app_name"
|
||||
tools:text="com.topjohnwu.magisk" />
|
||||
|
||||
<androidx.appcompat.widget.SwitchCompat
|
||||
android:id="@+id/master_switch"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center_vertical"
|
||||
android:checked="@={item.isEnabled}"
|
||||
android:gravity="center_vertical"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toEndOf="@id/app_name"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:id="@+id/expand_layout"
|
||||
gone="@{!item.isExpanded}"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingBottom="@dimen/margin_generic_half"
|
||||
android:paddingTop="@dimen/margin_generic_half"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/package_name">
|
||||
|
||||
<androidx.appcompat.widget.AppCompatImageView
|
||||
android:id="@+id/bell"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center_vertical"
|
||||
android:layout_marginStart="16dp"
|
||||
android:tint="@color/icon_grey"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toStartOf="@+id/notification_switch"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:srcCompat="@drawable/ic_notifications" />
|
||||
|
||||
<androidx.appcompat.widget.SwitchCompat
|
||||
android:id="@+id/notification_switch"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center_vertical"
|
||||
android:checked="@={item.shouldNotify}"
|
||||
android:gravity="center_vertical"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toStartOf="@+id/guideline"
|
||||
app:layout_constraintStart_toEndOf="@+id/bell"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<androidx.constraintlayout.widget.Guideline
|
||||
android:id="@+id/guideline"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
app:layout_constraintGuide_percent="0.4" />
|
||||
|
||||
<androidx.appcompat.widget.AppCompatImageView
|
||||
android:id="@+id/bug"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center_vertical"
|
||||
android:tint="@color/icon_grey"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toStartOf="@+id/logging_switch"
|
||||
app:layout_constraintStart_toStartOf="@+id/guideline"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:srcCompat="@drawable/ic_bug_report" />
|
||||
|
||||
<androidx.appcompat.widget.SwitchCompat
|
||||
android:id="@+id/logging_switch"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center_vertical"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:checked="@={item.shouldLog}"
|
||||
android:gravity="center_vertical"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toStartOf="@+id/guideline2"
|
||||
app:layout_constraintStart_toEndOf="@+id/bug"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<androidx.constraintlayout.widget.Guideline
|
||||
android:id="@+id/guideline2"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
app:layout_constraintGuide_percent="0.8" />
|
||||
|
||||
<androidx.appcompat.widget.AppCompatImageView
|
||||
android:id="@+id/more_info"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:layout_weight="1"
|
||||
android:background="?android:attr/selectableItemBackground"
|
||||
android:tint="@color/icon_grey"
|
||||
android:visibility="gone"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toStartOf="@+id/delete"
|
||||
app:layout_constraintStart_toStartOf="@id/guideline2"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:srcCompat="@drawable/ic_more" />
|
||||
|
||||
<androidx.appcompat.widget.AppCompatImageView
|
||||
android:id="@+id/delete"
|
||||
style="@style/Widget.Icon"
|
||||
android:layout_gravity="center"
|
||||
android:layout_weight="1"
|
||||
android:onClick="@{() -> viewModel.deletePressed(item)}"
|
||||
android:tint="@color/icon_grey"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toEndOf="@id/more_info"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:srcCompat="@drawable/ic_delete" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</com.google.android.material.card.MaterialCardView>
|
||||
|
||||
</layout>
|
@ -1,130 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<layout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools">
|
||||
|
||||
<data>
|
||||
|
||||
<variable
|
||||
name="item"
|
||||
type="com.topjohnwu.magisk.model.entity.recycler.RepoRvItem" />
|
||||
|
||||
<variable
|
||||
name="viewModel"
|
||||
type="com.topjohnwu.magisk.ui.module.ModuleViewModel" />
|
||||
|
||||
</data>
|
||||
|
||||
<com.google.android.material.card.MaterialCardView
|
||||
style="@style/Widget.Card"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:foreground="?attr/selectableItemBackground"
|
||||
android:minHeight="?android:attr/listPreferredItemHeight"
|
||||
android:onClick="@{() -> viewModel.repoPressed(item)}"
|
||||
app:cardElevation="2dp"
|
||||
tools:layout_gravity="center">
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:padding="@dimen/margin_generic">
|
||||
|
||||
<androidx.appcompat.widget.AppCompatTextView
|
||||
android:id="@+id/title"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@{item.item.name}"
|
||||
android:textAppearance="?android:attr/textAppearanceMedium"
|
||||
android:textIsSelectable="false"
|
||||
app:layout_constraintBottom_toTopOf="@+id/version_name"
|
||||
app:layout_constraintEnd_toStartOf="@+id/download"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
tools:text="Magisk Module" />
|
||||
|
||||
<androidx.appcompat.widget.AppCompatTextView
|
||||
android:id="@+id/version_name"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@{item.item.version.length == 0 ? @string/no_info_provided : item.item.version}"
|
||||
android:textAppearance="?android:attr/textAppearanceSmall"
|
||||
android:textColor="@android:color/tertiary_text_dark"
|
||||
android:textIsSelectable="false"
|
||||
android:textStyle="bold|italic"
|
||||
app:layout_constraintBottom_toTopOf="@+id/author"
|
||||
app:layout_constraintEnd_toEndOf="@+id/title"
|
||||
app:layout_constraintStart_toStartOf="@+id/title"
|
||||
app:layout_constraintTop_toBottomOf="@+id/title"
|
||||
tools:text="v1" />
|
||||
|
||||
<androidx.appcompat.widget.AppCompatTextView
|
||||
android:id="@+id/author"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@{item.item.author.length == 0 ? @string/no_info_provided : @string/author(item.item.author)}"
|
||||
android:textAppearance="?android:attr/textAppearanceSmall"
|
||||
android:textColor="@android:color/tertiary_text_dark"
|
||||
android:textIsSelectable="false"
|
||||
android:textStyle="bold|italic"
|
||||
app:layout_constraintBottom_toTopOf="@+id/description"
|
||||
app:layout_constraintEnd_toEndOf="@+id/title"
|
||||
app:layout_constraintStart_toStartOf="@+id/title"
|
||||
app:layout_constraintTop_toBottomOf="@+id/version_name"
|
||||
tools:text="topjohnwu" />
|
||||
|
||||
<androidx.appcompat.widget.AppCompatTextView
|
||||
android:id="@+id/description"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center_vertical"
|
||||
android:text="@{item.item.description.length == 0 ? @string/no_info_provided : item.item.description}"
|
||||
android:textAppearance="?android:attr/textAppearanceSmall"
|
||||
android:textIsSelectable="false"
|
||||
app:layout_constraintBottom_toTopOf="@+id/update_time"
|
||||
app:layout_constraintEnd_toEndOf="@id/title"
|
||||
app:layout_constraintStart_toStartOf="@+id/title"
|
||||
app:layout_constraintTop_toBottomOf="@+id/author"
|
||||
tools:lines="4"
|
||||
tools:text="@tools:sample/lorem/random" />
|
||||
|
||||
<androidx.appcompat.widget.AppCompatTextView
|
||||
android:id="@+id/update_time"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center_vertical"
|
||||
android:text="@{@string/updated_on(item.item.lastUpdateString)}"
|
||||
android:textAppearance="?android:attr/textAppearanceSmall"
|
||||
android:textColor="@android:color/tertiary_text_dark"
|
||||
android:textStyle="bold|italic"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="@+id/title"
|
||||
app:layout_constraintStart_toStartOf="@+id/title"
|
||||
app:layout_constraintTop_toBottomOf="@+id/description"
|
||||
tools:text="@tools:sample/date/ddmmyy" />
|
||||
|
||||
<androidx.appcompat.widget.AppCompatImageView
|
||||
android:id="@+id/download"
|
||||
android:focusable="true"
|
||||
android:clickable="true"
|
||||
style="@style/Widget.Icon"
|
||||
isEnabled="@{viewModel.isConnected}"
|
||||
android:alpha="@{viewModel.isConnected ? 1f : .2f}"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center_vertical"
|
||||
android:layout_marginStart="8dp"
|
||||
android:onClick="@{() -> viewModel.downloadPressed(item)}"
|
||||
android:tint="?attr/imageColorTint"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toEndOf="@+id/title"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:srcCompat="@drawable/ic_file_download_black" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
</com.google.android.material.card.MaterialCardView>
|
||||
|
||||
</layout>
|
||||
|
@ -1,84 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<layout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools">
|
||||
|
||||
<data>
|
||||
|
||||
<variable
|
||||
name="item"
|
||||
type="com.topjohnwu.magisk.model.entity.recycler.LogItemRvItem" />
|
||||
|
||||
<variable
|
||||
name="viewModel"
|
||||
type="com.topjohnwu.magisk.ui.log.LogViewModel" />
|
||||
|
||||
</data>
|
||||
|
||||
<com.google.android.material.card.MaterialCardView
|
||||
style="@style/Widget.Card"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
app:cardElevation="2dp">
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:animateLayoutChanges="true">
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:id="@+id/log_header"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="?attr/selectableItemBackground"
|
||||
android:onClick="@{() -> item.toggle()}"
|
||||
android:orientation="horizontal"
|
||||
android:paddingLeft="@dimen/margin_generic"
|
||||
android:paddingTop="@dimen/margin_generic_half"
|
||||
android:paddingRight="@dimen/margin_generic"
|
||||
android:paddingBottom="@dimen/margin_generic_half"
|
||||
app:layout_constraintTop_toTopOf="parent">
|
||||
|
||||
<androidx.appcompat.widget.AppCompatTextView
|
||||
style="@style/Widget.Text.Emphasize"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
android:gravity="center_vertical"
|
||||
android:text="@{item.date}"
|
||||
android:textColor="?android:attr/textColorPrimary"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toStartOf="@+id/log_header_indicator"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
tools:text="@tools:sample/date/ddmmyy" />
|
||||
|
||||
<androidx.appcompat.widget.AppCompatImageView
|
||||
android:id="@+id/log_header_indicator"
|
||||
style="@style/Widget.Icon"
|
||||
android:background="@android:color/transparent"
|
||||
android:rotation="@{item.isExpanded() ? 180 : 0}"
|
||||
android:tint="?attr/imageColorTint"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:srcCompat="@drawable/ic_arrow"
|
||||
tools:rotation="180" />
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
gone="@{!item.isExpanded}"
|
||||
itemBinding="@{viewModel.itemBinding}"
|
||||
items="@{item.items}"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
|
||||
app:layout_constraintTop_toBottomOf="@+id/log_header"
|
||||
tools:itemCount="3"
|
||||
tools:listitem="@layout/item_superuser_log_entry" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
</com.google.android.material.card.MaterialCardView>
|
||||
|
||||
</layout>
|
@ -1,112 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<layout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools">
|
||||
|
||||
<data>
|
||||
|
||||
<variable
|
||||
name="item"
|
||||
type="com.topjohnwu.magisk.model.entity.recycler.LogItemEntryRvItem" />
|
||||
|
||||
<variable
|
||||
name="viewModel"
|
||||
type="com.topjohnwu.magisk.ui.log.LogViewModel" />
|
||||
|
||||
</data>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:animateLayoutChanges="true"
|
||||
android:background="?attr/selectableItemBackground"
|
||||
android:onClick="@{() -> item.toggle()}"
|
||||
android:orientation="vertical">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
android:paddingLeft="@dimen/margin_generic"
|
||||
android:paddingTop="@dimen/margin_generic_half"
|
||||
android:paddingRight="@dimen/margin_generic"
|
||||
android:paddingBottom="@dimen/margin_generic_half">
|
||||
|
||||
<androidx.appcompat.widget.AppCompatTextView
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="2"
|
||||
android:ellipsize="end"
|
||||
android:maxLines="1"
|
||||
android:text="@{item.item.appName}"
|
||||
android:textAppearance="?android:attr/textAppearanceSmall"
|
||||
android:textColor="?android:attr/textColorPrimary"
|
||||
android:textIsSelectable="false"
|
||||
tools:text="@tools:sample/lorem" />
|
||||
|
||||
<androidx.appcompat.widget.AppCompatTextView
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:gravity="center_horizontal"
|
||||
android:text="@{item.item.action ? @string/grant : @string/deny}"
|
||||
android:textAppearance="?android:attr/textAppearanceSmall"
|
||||
android:textColor="?android:attr/textColorSecondary"
|
||||
tools:text="@string/grant" />
|
||||
|
||||
<androidx.appcompat.widget.AppCompatTextView
|
||||
android:id="@+id/time"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:gravity="center_horizontal"
|
||||
android:text="@{item.item.timeString}"
|
||||
android:textAppearance="?android:attr/textAppearanceSmall"
|
||||
android:textColor="?android:attr/textColorSecondary"
|
||||
tools:text="@tools:sample/date/hhmm" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
gone="@{!item.isExpanded}"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center_horizontal"
|
||||
android:gravity="center"
|
||||
android:orientation="horizontal"
|
||||
android:paddingStart="@dimen/margin_generic"
|
||||
android:paddingTop="@dimen/margin_generic_half"
|
||||
android:paddingEnd="@dimen/margin_generic"
|
||||
android:paddingBottom="@dimen/margin_generic_half">
|
||||
|
||||
<androidx.appcompat.widget.AppCompatTextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:text="@{@string/pid(item.item.fromPid)}"
|
||||
android:textColor="?android:attr/textColorSecondary"
|
||||
android:textSize="12sp"
|
||||
tools:text="@string/pid" />
|
||||
|
||||
<androidx.appcompat.widget.AppCompatTextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:text="@{@string/target_uid(item.item.toUid)}"
|
||||
android:textColor="?android:attr/textColorSecondary"
|
||||
android:textSize="12sp"
|
||||
tools:text="@string/target_uid" />
|
||||
|
||||
<androidx.appcompat.widget.AppCompatTextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="2"
|
||||
android:text="@{@string/command(item.item.command)}"
|
||||
android:textColor="?android:attr/textColorSecondary"
|
||||
android:textSize="12sp"
|
||||
tools:text="@string/command" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</layout>
|
@ -1,59 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<menu xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
|
||||
<group
|
||||
android:checkableBehavior="single"
|
||||
android:id="@+id/main_group">
|
||||
|
||||
<item
|
||||
android:id="@+id/magiskFragment"
|
||||
android:icon="@drawable/ic_magisk_outline"
|
||||
android:title="@string/magisk"/>
|
||||
|
||||
<item
|
||||
android:id="@+id/superuserFragment"
|
||||
android:icon="@drawable/ic_superuser"
|
||||
android:title="@string/superuser"
|
||||
android:visible="false"/>
|
||||
<item
|
||||
android:id="@+id/magiskHideFragment"
|
||||
android:icon="@drawable/ic_magiskhide"
|
||||
android:title="@string/magiskhide"
|
||||
android:visible="false"/>
|
||||
|
||||
</group>
|
||||
|
||||
<group
|
||||
android:checkableBehavior="single"
|
||||
android:id="@+id/second_group">
|
||||
|
||||
<item
|
||||
android:id="@+id/modulesFragment"
|
||||
android:icon="@drawable/ic_extension"
|
||||
android:title="@string/modules"
|
||||
android:visible="false"/>
|
||||
|
||||
<item
|
||||
android:id="@+id/reposFragment"
|
||||
android:icon="@drawable/ic_cloud_download"
|
||||
android:title="@string/downloads"/>
|
||||
|
||||
</group>
|
||||
|
||||
<group
|
||||
android:checkableBehavior="single"
|
||||
android:id="@+id/third_group">
|
||||
|
||||
<item
|
||||
android:id="@+id/logFragment"
|
||||
android:icon="@drawable/ic_bug_report"
|
||||
android:title="@string/log" />
|
||||
|
||||
<item
|
||||
android:id="@+id/settings"
|
||||
android:icon="@drawable/ic_settings"
|
||||
android:title="@string/settings"/>
|
||||
|
||||
</group>
|
||||
|
||||
</menu>
|
@ -1,24 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<menu xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||
|
||||
<item
|
||||
android:id="@+id/menu_save"
|
||||
android:icon="@drawable/ic_save"
|
||||
android:title="@string/menuSaveLog"
|
||||
app:showAsAction="ifRoom"/>
|
||||
|
||||
<item
|
||||
android:id="@+id/menu_clear"
|
||||
android:icon="@drawable/ic_delete"
|
||||
android:title="@string/menuClearLog"
|
||||
app:showAsAction="ifRoom"/>
|
||||
|
||||
<item
|
||||
android:id="@+id/menu_refresh"
|
||||
android:icon="@drawable/ic_refresh"
|
||||
android:title="@string/menuReload"
|
||||
app:showAsAction="ifRoom"/>
|
||||
|
||||
|
||||
</menu>
|
@ -1,16 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<menu xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||
|
||||
<item
|
||||
android:id="@+id/app_search"
|
||||
android:title=""
|
||||
app:actionViewClass="android.widget.SearchView"
|
||||
app:showAsAction="always"/>
|
||||
|
||||
<item
|
||||
android:id="@+id/show_system"
|
||||
android:title="@string/show_system_app"
|
||||
android:checkable="true"/>
|
||||
|
||||
</menu>
|
Loading…
Reference in New Issue
Block a user