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 android.content.Context;
|
||||||
|
|
||||||
import com.topjohnwu.magisk.model.update.UpdateCheckService;
|
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.work.WorkerParameters;
|
import androidx.work.WorkerParameters;
|
||||||
|
|
||||||
|
import com.topjohnwu.magisk.model.update.UpdateCheckService;
|
||||||
|
|
||||||
public class g extends w<UpdateCheckService> {
|
public class g extends w<UpdateCheckService> {
|
||||||
/* Stub */
|
/* Stub */
|
||||||
public g(@NonNull Context context, @NonNull WorkerParameters workerParams) {
|
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.download.DownloadService
|
||||||
import com.topjohnwu.magisk.model.receiver.GeneralReceiver
|
import com.topjohnwu.magisk.model.receiver.GeneralReceiver
|
||||||
import com.topjohnwu.magisk.model.update.UpdateCheckService
|
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.SplashActivity
|
||||||
import com.topjohnwu.magisk.ui.flash.FlashActivity
|
import com.topjohnwu.magisk.ui.flash.FlashActivity
|
||||||
import com.topjohnwu.magisk.ui.surequest.SuRequestActivity
|
import com.topjohnwu.magisk.ui.surequest.SuRequestActivity
|
||||||
import com.topjohnwu.magisk.utils.refreshLocale
|
import com.topjohnwu.magisk.utils.refreshLocale
|
||||||
import com.topjohnwu.magisk.utils.updateConfig
|
import com.topjohnwu.magisk.utils.updateConfig
|
||||||
import com.topjohnwu.magisk.redesign.MainActivity as RedesignActivity
|
|
||||||
|
|
||||||
fun AssetManager.addAssetPath(path: String) {
|
fun AssetManager.addAssetPath(path: String) {
|
||||||
DynAPK.addAssetPath(this, path)
|
DynAPK.addAssetPath(this, path)
|
||||||
@ -150,8 +149,7 @@ object ClassMap {
|
|||||||
GeneralReceiver::class.java to a.h::class.java,
|
GeneralReceiver::class.java to a.h::class.java,
|
||||||
DownloadService::class.java to a.j::class.java,
|
DownloadService::class.java to a.j::class.java,
|
||||||
SuRequestActivity::class.java to a.m::class.java,
|
SuRequestActivity::class.java to a.m::class.java,
|
||||||
ProcessPhoenix::class.java to a.r::class.java,
|
ProcessPhoenix::class.java to a.r::class.java
|
||||||
RedesignActivity::class.java to a.b::class.java
|
|
||||||
)
|
)
|
||||||
|
|
||||||
operator fun get(c: Class<*>) = map.getOrElse(c) { c }
|
operator fun get(c: Class<*>) = map.getOrElse(c) { c }
|
||||||
|
@ -1,25 +1,13 @@
|
|||||||
package com.topjohnwu.magisk.di
|
package com.topjohnwu.magisk.di
|
||||||
|
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import com.topjohnwu.magisk.ui.MainViewModel
|
|
||||||
import com.topjohnwu.magisk.ui.flash.FlashViewModel
|
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 com.topjohnwu.magisk.ui.surequest.SuRequestViewModel
|
||||||
import org.koin.androidx.viewmodel.dsl.viewModel
|
import org.koin.androidx.viewmodel.dsl.viewModel
|
||||||
import org.koin.dsl.module
|
import org.koin.dsl.module
|
||||||
|
|
||||||
|
|
||||||
val viewModelModules = 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) ->
|
viewModel { (action: String, file: Uri, additional: Uri) ->
|
||||||
FlashViewModel(action, file, additional, get())
|
FlashViewModel(action, file, additional, get())
|
||||||
}
|
}
|
||||||
|
@ -5,20 +5,13 @@ import android.view.ViewGroup
|
|||||||
import com.topjohnwu.magisk.R
|
import com.topjohnwu.magisk.R
|
||||||
import com.topjohnwu.magisk.databinding.ComparableRvItem
|
import com.topjohnwu.magisk.databinding.ComparableRvItem
|
||||||
import com.topjohnwu.magisk.extensions.addOnPropertyChangedCallback
|
import com.topjohnwu.magisk.extensions.addOnPropertyChangedCallback
|
||||||
import com.topjohnwu.magisk.extensions.inject
|
|
||||||
import com.topjohnwu.magisk.extensions.startAnimations
|
import com.topjohnwu.magisk.extensions.startAnimations
|
||||||
import com.topjohnwu.magisk.extensions.toggle
|
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.ProcessHideApp
|
||||||
import com.topjohnwu.magisk.model.entity.StatefulProcess
|
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.model.observer.Observer
|
||||||
import com.topjohnwu.magisk.redesign.hide.HideViewModel
|
import com.topjohnwu.magisk.redesign.hide.HideViewModel
|
||||||
import com.topjohnwu.magisk.utils.DiffObservableList
|
|
||||||
import com.topjohnwu.magisk.utils.KObservableField
|
import com.topjohnwu.magisk.utils.KObservableField
|
||||||
import com.topjohnwu.magisk.utils.RxBus
|
|
||||||
import kotlin.math.roundToInt
|
import kotlin.math.roundToInt
|
||||||
|
|
||||||
class HideItem(val item: ProcessHideApp) : ComparableRvItem<HideItem>() {
|
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
|
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 androidx.databinding.Bindable
|
||||||
import com.topjohnwu.magisk.BR
|
import com.topjohnwu.magisk.BR
|
||||||
import com.topjohnwu.magisk.R
|
import com.topjohnwu.magisk.R
|
||||||
import com.topjohnwu.magisk.databinding.ComparableRvItem
|
|
||||||
import com.topjohnwu.magisk.extensions.timeDateFormat
|
import com.topjohnwu.magisk.extensions.timeDateFormat
|
||||||
import com.topjohnwu.magisk.extensions.timeFormatMedium
|
|
||||||
import com.topjohnwu.magisk.extensions.toTime
|
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.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>() {
|
class LogItem(val item: MagiskLog) : ObservableItem<LogItem>() {
|
||||||
|
|
||||||
@ -106,4 +36,4 @@ class LogItem(val item: MagiskLog) : ObservableItem<LogItem>() {
|
|||||||
item.time == other.item.time &&
|
item.time == other.item.time &&
|
||||||
isTop == other.isTop &&
|
isTop == other.isTop &&
|
||||||
isBottom == other.isBottom
|
isBottom == other.isBottom
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,5 @@
|
|||||||
package com.topjohnwu.magisk.model.entity.recycler
|
package com.topjohnwu.magisk.model.entity.recycler
|
||||||
|
|
||||||
import android.content.res.Resources
|
|
||||||
import androidx.annotation.StringRes
|
|
||||||
import androidx.databinding.Bindable
|
import androidx.databinding.Bindable
|
||||||
import androidx.databinding.Observable
|
import androidx.databinding.Observable
|
||||||
import androidx.databinding.PropertyChangeRegistry
|
import androidx.databinding.PropertyChangeRegistry
|
||||||
@ -10,77 +8,11 @@ import androidx.recyclerview.widget.StaggeredGridLayoutManager
|
|||||||
import com.topjohnwu.magisk.BR
|
import com.topjohnwu.magisk.BR
|
||||||
import com.topjohnwu.magisk.R
|
import com.topjohnwu.magisk.R
|
||||||
import com.topjohnwu.magisk.databinding.ComparableRvItem
|
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.Module
|
||||||
import com.topjohnwu.magisk.model.entity.module.Repo
|
import com.topjohnwu.magisk.model.entity.module.Repo
|
||||||
import com.topjohnwu.magisk.redesign.module.ModuleViewModel
|
import com.topjohnwu.magisk.redesign.module.ModuleViewModel
|
||||||
import com.topjohnwu.magisk.utils.KObservableField
|
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>() {
|
object SafeModeNotice : ComparableRvItem<SafeModeNotice>() {
|
||||||
override val layoutRes = R.layout.item_safe_mode_notice
|
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)
|
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 android.graphics.drawable.Drawable
|
||||||
import com.topjohnwu.magisk.R
|
import com.topjohnwu.magisk.R
|
||||||
import com.topjohnwu.magisk.databinding.ComparableRvItem
|
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.extensions.toggle
|
||||||
import com.topjohnwu.magisk.model.entity.MagiskPolicy
|
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.model.events.PolicyUpdateEvent
|
||||||
import com.topjohnwu.magisk.redesign.superuser.SuperuserViewModel
|
import com.topjohnwu.magisk.redesign.superuser.SuperuserViewModel
|
||||||
import com.topjohnwu.magisk.utils.KObservableField
|
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>() {
|
class PolicyItem(val item: MagiskPolicy, val icon: Drawable) : ComparableRvItem<PolicyItem>() {
|
||||||
override val layoutRes = R.layout.item_policy_md2
|
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 contentSameAs(other: PolicyItem) = itemSameAs(other)
|
||||||
override fun itemSameAs(other: PolicyItem) = item.uid == other.item.uid
|
override fun itemSameAs(other: PolicyItem) = item.uid == other.item.uid
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -1,19 +1,11 @@
|
|||||||
package com.topjohnwu.magisk.model.events
|
package com.topjohnwu.magisk.model.events
|
||||||
|
|
||||||
import com.topjohnwu.magisk.model.entity.MagiskPolicy
|
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
|
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 {
|
sealed class PolicyUpdateEvent(val item: MagiskPolicy) : RxBus.Event {
|
||||||
class Notification(item: MagiskPolicy) : PolicyUpdateEvent(item)
|
class Notification(item: MagiskPolicy) : PolicyUpdateEvent(item)
|
||||||
class Log(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.home.HomeFragment
|
||||||
import com.topjohnwu.magisk.redesign.install.InstallFragment
|
import com.topjohnwu.magisk.redesign.install.InstallFragment
|
||||||
import com.topjohnwu.magisk.redesign.log.LogFragment
|
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.safetynet.SafetynetFragment
|
||||||
import com.topjohnwu.magisk.redesign.settings.SettingsFragment
|
import com.topjohnwu.magisk.redesign.settings.SettingsFragment
|
||||||
import com.topjohnwu.magisk.redesign.superuser.SuperuserFragment
|
import com.topjohnwu.magisk.redesign.superuser.SuperuserFragment
|
||||||
import com.topjohnwu.magisk.redesign.theme.ThemeFragment
|
import com.topjohnwu.magisk.redesign.theme.ThemeFragment
|
||||||
import com.topjohnwu.magisk.ui.module.ModulesFragment
|
|
||||||
import com.topjohnwu.magisk.ui.module.ReposFragment
|
|
||||||
|
|
||||||
object Navigation {
|
object Navigation {
|
||||||
|
|
||||||
@ -36,14 +35,10 @@ object Navigation {
|
|||||||
|
|
||||||
fun modules() = MagiskNavigationEvent {
|
fun modules() = MagiskNavigationEvent {
|
||||||
navDirections {
|
navDirections {
|
||||||
destination = ModulesFragment::class
|
destination = ModuleFragment::class
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun repos() = MagiskNavigationEvent {
|
|
||||||
navDirections { destination = ReposFragment::class }
|
|
||||||
}
|
|
||||||
|
|
||||||
fun hide() = MagiskNavigationEvent {
|
fun hide() = MagiskNavigationEvent {
|
||||||
navDirections {
|
navDirections {
|
||||||
destination = HideFragment::class
|
destination = HideFragment::class
|
||||||
@ -77,7 +72,6 @@ object Navigation {
|
|||||||
fun fromSection(section: String) = when (section) {
|
fun fromSection(section: String) = when (section) {
|
||||||
"superuser" -> superuser()
|
"superuser" -> superuser()
|
||||||
"modules" -> modules()
|
"modules" -> modules()
|
||||||
"downloads" -> repos()
|
|
||||||
"magiskhide" -> hide()
|
"magiskhide" -> hide()
|
||||||
"log" -> log()
|
"log" -> log()
|
||||||
"settings" -> settings()
|
"settings" -> settings()
|
||||||
|
@ -24,12 +24,15 @@ import com.topjohnwu.magisk.model.navigation.Navigation
|
|||||||
import com.topjohnwu.magisk.model.observer.Observer
|
import com.topjohnwu.magisk.model.observer.Observer
|
||||||
import com.topjohnwu.magisk.redesign.compat.CompatViewModel
|
import com.topjohnwu.magisk.redesign.compat.CompatViewModel
|
||||||
import com.topjohnwu.magisk.redesign.compat.itemBindingOf
|
import com.topjohnwu.magisk.redesign.compat.itemBindingOf
|
||||||
import com.topjohnwu.magisk.ui.home.MagiskState
|
|
||||||
import com.topjohnwu.magisk.utils.KObservableField
|
import com.topjohnwu.magisk.utils.KObservableField
|
||||||
import com.topjohnwu.superuser.Shell
|
import com.topjohnwu.superuser.Shell
|
||||||
import me.tatarka.bindingcollectionadapter2.BR
|
import me.tatarka.bindingcollectionadapter2.BR
|
||||||
import kotlin.math.roundToInt
|
import kotlin.math.roundToInt
|
||||||
|
|
||||||
|
enum class MagiskState {
|
||||||
|
NOT_INSTALLED, UP_TO_DATE, OBSOLETE, LOADING
|
||||||
|
}
|
||||||
|
|
||||||
class HomeViewModel(
|
class HomeViewModel(
|
||||||
private val repoMagisk: MagiskRepository
|
private val repoMagisk: MagiskRepository
|
||||||
) : CompatViewModel() {
|
) : CompatViewModel() {
|
||||||
|
@ -7,11 +7,15 @@ import com.topjohnwu.magisk.extensions.subscribeK
|
|||||||
import com.topjohnwu.magisk.model.events.SafetyNetResult
|
import com.topjohnwu.magisk.model.events.SafetyNetResult
|
||||||
import com.topjohnwu.magisk.model.events.UpdateSafetyNetEvent
|
import com.topjohnwu.magisk.model.events.UpdateSafetyNetEvent
|
||||||
import com.topjohnwu.magisk.redesign.compat.CompatViewModel
|
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.KObservableField
|
||||||
import com.topjohnwu.magisk.utils.RxBus
|
import com.topjohnwu.magisk.utils.RxBus
|
||||||
import com.topjohnwu.magisk.utils.SafetyNetHelper
|
import com.topjohnwu.magisk.utils.SafetyNetHelper
|
||||||
|
|
||||||
|
enum class SafetyNetState {
|
||||||
|
LOADING, PASS, FAILED, IDLE
|
||||||
|
}
|
||||||
|
|
||||||
class SafetynetViewModel(
|
class SafetynetViewModel(
|
||||||
rxBus: RxBus
|
rxBus: RxBus
|
||||||
) : CompatViewModel() {
|
) : CompatViewModel() {
|
||||||
@ -89,4 +93,4 @@ class SafetynetViewModel(
|
|||||||
private var safetyNetResult = -1
|
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)
|
.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK)
|
||||||
)
|
)
|
||||||
.setIcon(getIcon(R.drawable.sc_extension))
|
.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)
|
.setRank(2)
|
||||||
.build()
|
.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.BuildConfig" />
|
||||||
|
|
||||||
<import type="com.topjohnwu.magisk.ui.home.MagiskState" />
|
<import type="com.topjohnwu.magisk.redesign.home.MagiskState" />
|
||||||
|
|
||||||
<import type="com.topjohnwu.magisk.extensions.XAndroidKt" />
|
<import type="com.topjohnwu.magisk.extensions.XAndroidKt" />
|
||||||
|
|
||||||
@ -801,4 +801,4 @@
|
|||||||
|
|
||||||
</androidx.core.widget.NestedScrollView>
|
</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