Updated superuser fragment to new arch

Fixed theme issues along the way
This commit is contained in:
Viktor De Pasquale 2019-04-17 18:27:03 +02:00
parent 8a8441c875
commit bcd1064e94
15 changed files with 476 additions and 85 deletions

View File

@ -10,4 +10,5 @@ val applicationModule = module {
single { RxBus() }
single { get<Context>().resources }
single { get<Context>() as App }
single { get<Context>().packageManager }
}

View File

@ -1,6 +1,9 @@
package com.topjohnwu.magisk.di
import com.topjohnwu.magisk.App
import org.koin.dsl.module
val databaseModule = module {}
val databaseModule = module {
single { get<App>().DB }
}

View File

@ -2,6 +2,7 @@ package com.topjohnwu.magisk.di
import com.topjohnwu.magisk.ui.MainViewModel
import com.topjohnwu.magisk.ui.home.HomeViewModel
import com.topjohnwu.magisk.ui.superuser.SuperuserViewModel
import org.koin.androidx.viewmodel.dsl.viewModel
import org.koin.dsl.module
@ -9,4 +10,5 @@ import org.koin.dsl.module
val viewModelModules = module {
viewModel { MainViewModel() }
viewModel { HomeViewModel(get(), get()) }
viewModel { SuperuserViewModel(get(), get(), get()) }
}

View File

@ -0,0 +1,23 @@
package com.topjohnwu.magisk.model.entity.recycler
import android.graphics.drawable.Drawable
import com.skoumal.teanity.databinding.ComparableRvItem
import com.skoumal.teanity.util.KObservableField
import com.topjohnwu.magisk.R
import com.topjohnwu.magisk.model.entity.Policy
import com.topjohnwu.magisk.utils.toggle
class PolicyRvItem(val item: Policy, val icon: Drawable) : ComparableRvItem<PolicyRvItem>() {
override val layoutRes: Int = R.layout.item_policy
val isExpanded = KObservableField(false)
val isEnabled = KObservableField(item.policy == Policy.ALLOW)
val shouldNotify = KObservableField(item.notification)
val shouldLog = KObservableField(item.logging)
fun toggle() = isExpanded.toggle()
override fun contentSameAs(other: PolicyRvItem): Boolean = itemSameAs(other)
override fun itemSameAs(other: PolicyRvItem): Boolean = item.uid == other.item.uid
}

View File

@ -1,5 +1,6 @@
package com.topjohnwu.magisk.model.events
import android.app.Activity
import com.skoumal.teanity.viewevents.ViewEvent
@ -15,3 +16,5 @@ class UninstallEvent : ViewEvent()
class EnvFixEvent : ViewEvent()
class UpdateSafetyNetEvent : ViewEvent()
class ViewActionEvent(val action: Activity.() -> Unit) : ViewEvent()

View File

@ -11,6 +11,7 @@ import com.ncapdevi.fragnav.FragNavController
import com.ncapdevi.fragnav.FragNavTransactionOptions
import com.skoumal.teanity.viewevents.ViewEvent
import com.topjohnwu.magisk.Config
import com.topjohnwu.magisk.model.events.ViewActionEvent
import com.topjohnwu.magisk.model.navigation.MagiskAnimBuilder
import com.topjohnwu.magisk.model.navigation.MagiskNavigationEvent
import com.topjohnwu.magisk.model.navigation.Navigator
@ -62,6 +63,7 @@ abstract class MagiskActivity<ViewModel : MagiskViewModel, Binding : ViewDataBin
super.onEventDispatched(event)
when (event) {
is MagiskNavigationEvent -> navigateTo(event)
is ViewActionEvent -> event.action(this)
}
}

View File

@ -5,6 +5,7 @@ import androidx.databinding.ViewDataBinding
import androidx.fragment.app.Fragment
import com.skoumal.teanity.view.TeanityFragment
import com.skoumal.teanity.viewevents.ViewEvent
import com.topjohnwu.magisk.model.events.ViewActionEvent
import com.topjohnwu.magisk.model.navigation.MagiskNavigationEvent
import com.topjohnwu.magisk.model.navigation.Navigator
import kotlin.reflect.KClass
@ -25,6 +26,7 @@ abstract class MagiskFragment<ViewModel : MagiskViewModel, Binding : ViewDataBin
super.onEventDispatched(event)
when (event) {
is MagiskNavigationEvent -> navigateTo(event)
is ViewActionEvent -> event.action(requireActivity())
}
}

View File

@ -1,6 +1,8 @@
package com.topjohnwu.magisk.ui.base
import android.app.Activity
import com.skoumal.teanity.viewmodel.LoadingViewModel
import com.topjohnwu.magisk.model.events.ViewActionEvent
import com.topjohnwu.magisk.utils.Event
import timber.log.Timber
@ -10,4 +12,8 @@ abstract class MagiskViewModel : LoadingViewModel(), Event.AutoListener {
override fun onEvent(event: Int) = Timber.i("Event of $event was not handled")
override fun getListeningEvents(): IntArray = intArrayOf()
fun withView(action: Activity.() -> Unit) {
ViewActionEvent(action).publish()
}
}

View File

@ -1,63 +0,0 @@
package com.topjohnwu.magisk.ui.superuser;
import android.content.pm.PackageManager;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import com.topjohnwu.magisk.R;
import com.topjohnwu.magisk.model.adapters.PolicyAdapter;
import com.topjohnwu.magisk.model.entity.Policy;
import com.topjohnwu.magisk.ui.base.BaseFragment;
import java.util.List;
import androidx.annotation.Nullable;
import androidx.recyclerview.widget.RecyclerView;
import butterknife.BindView;
public class SuperuserFragment extends BaseFragment {
@BindView(R.id.recyclerView) RecyclerView recyclerView;
@BindView(R.id.empty_rv) TextView emptyRv;
private PackageManager pm;
@Nullable
@Override
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragment_superuser, container, false);
unbinder = new SuperuserFragment_ViewBinding(this, view);
pm = requireActivity().getPackageManager();
return view;
}
@Override
public void onStart() {
super.onStart();
requireActivity().setTitle(getString(R.string.superuser));
}
@Override
public void onResume() {
super.onResume();
displayPolicyList();
}
private void displayPolicyList() {
List<Policy> policyList = app.getDB().getPolicyList();
if (policyList.size() == 0) {
emptyRv.setVisibility(View.VISIBLE);
recyclerView.setVisibility(View.GONE);
} else {
recyclerView.setAdapter(new PolicyAdapter(policyList, app.getDB(), pm));
emptyRv.setVisibility(View.GONE);
recyclerView.setVisibility(View.VISIBLE);
}
}
}

View File

@ -0,0 +1,24 @@
package com.topjohnwu.magisk.ui.superuser
import com.topjohnwu.magisk.R
import com.topjohnwu.magisk.databinding.FragmentSuperuserBinding
import com.topjohnwu.magisk.ui.base.MagiskFragment
import org.koin.androidx.viewmodel.ext.android.viewModel
class SuperuserFragment :
MagiskFragment<SuperuserViewModel, FragmentSuperuserBinding>() {
override val layoutRes: Int = R.layout.fragment_superuser
override val viewModel: SuperuserViewModel by viewModel()
override fun onStart() {
super.onStart()
magiskActivity.supportActionBar?.title = getString(R.string.superuser)
}
override fun onResume() {
super.onResume()
viewModel.updatePolicies()
}
}

View File

@ -0,0 +1,146 @@
package com.topjohnwu.magisk.ui.superuser
import android.content.pm.PackageManager
import android.content.res.Resources
import com.skoumal.teanity.databinding.ComparableRvItem
import com.skoumal.teanity.extensions.addOnPropertyChangedCallback
import com.skoumal.teanity.extensions.applySchedulers
import com.skoumal.teanity.util.DiffObservableList
import com.skoumal.teanity.viewevents.SnackbarEvent
import com.topjohnwu.magisk.BR
import com.topjohnwu.magisk.R
import com.topjohnwu.magisk.data.database.MagiskDB
import com.topjohnwu.magisk.model.entity.Policy
import com.topjohnwu.magisk.model.entity.recycler.PolicyRvItem
import com.topjohnwu.magisk.ui.base.MagiskViewModel
import com.topjohnwu.magisk.utils.FingerprintHelper
import com.topjohnwu.magisk.utils.toggle
import com.topjohnwu.magisk.view.dialogs.CustomAlertDialog
import com.topjohnwu.magisk.view.dialogs.FingerprintAuthDialog
import io.reactivex.Single
import me.tatarka.bindingcollectionadapter2.ItemBinding
import timber.log.Timber
class SuperuserViewModel(
private val database: MagiskDB,
private val packageManager: PackageManager,
private val resources: Resources
) : MagiskViewModel() {
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
init {
updatePolicies()
}
fun updatePolicies() {
Single.fromCallable { database.policyList }
.flattenAsFlowable { it }
.map { PolicyRvItem(it, it.info.loadIcon(packageManager)).setListeners() }
.toList()
.applySchedulers()
.applyViewModel(this)
.subscribe({
items.update(it)
}, Timber::e)
.add()
}
fun deletePressed(item: PolicyRvItem) {
fun updateState() = deletePolicy(item.item)
.subscribe({
items.remove(item)
}, Timber::e)
.add()
withView {
if (FingerprintHelper.useFingerprint()) {
FingerprintAuthDialog(this) { updateState() }.show()
} else {
CustomAlertDialog(this)
.setTitle(R.string.su_revoke_title)
.setMessage(getString(R.string.su_revoke_msg, item.item.appName))
.setPositiveButton(R.string.yes) { _, _ -> updateState() }
.setNegativeButton(R.string.no_thanks, null)
.setCancelable(true)
.show()
}
}
}
private fun PolicyRvItem.setListeners() = apply {
isEnabled.addOnPropertyChangedCallback {
it ?: return@addOnPropertyChangedCallback
if (ignoreNext == this) {
ignoreNext = null
return@addOnPropertyChangedCallback
}
fun updateState() {
item.policy = if (it) Policy.ALLOW else Policy.DENY
updatePolicy(item)
.map { it.policy == Policy.ALLOW }
.subscribe({
val textId = if (it) R.string.su_snack_grant else R.string.su_snack_deny
val text = resources.getString(textId).format(item.appName)
SnackbarEvent(text).publish()
}, Timber::e)
.add()
}
if (FingerprintHelper.useFingerprint()) {
withView {
FingerprintAuthDialog(this, { updateState() }, {
ignoreNext = this@setListeners
isEnabled.toggle()
}).show()
}
} else {
updateState()
}
}
shouldNotify.addOnPropertyChangedCallback {
it ?: return@addOnPropertyChangedCallback
item.notification = it
updatePolicy(item)
.map { it.notification }
.subscribe({
val textId = if (it) R.string.su_snack_notif_on else R.string.su_snack_notif_off
val text = resources.getString(textId).format(item.appName)
SnackbarEvent(text).publish()
}, Timber::e)
.add()
}
shouldLog.addOnPropertyChangedCallback {
it ?: return@addOnPropertyChangedCallback
item.logging = it
updatePolicy(item)
.map { it.logging }
.subscribe({
val textId = if (it) R.string.su_snack_log_on else R.string.su_snack_log_off
val text = resources.getString(textId).format(item.appName)
SnackbarEvent(text).publish()
}, Timber::e)
.add()
}
}
private fun updatePolicy(policy: Policy) =
Single.fromCallable { database.updatePolicy(policy); policy }
.applySchedulers()
private fun deletePolicy(policy: Policy) =
Single.fromCallable { database.deletePolicy(policy); policy }
.applySchedulers()
}

View File

@ -1,11 +1,42 @@
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
<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:orientation="vertical">
android:clipToPadding="false"
android:orientation="vertical"
android:padding="@dimen/margin_generic"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
tools:listitem="@layout/item_policy" />
<TextView
android:id="@+id/empty_rv"
<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 &gt; 0}"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="center"
@ -13,14 +44,10 @@
android:gravity="center"
android:text="@string/no_apps_found"
android:textSize="20sp"
android:textStyle="italic"
android:visibility="gone" />
android:textStyle="italic" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recyclerView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:dividerHeight="@dimen/card_divider_space"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager" />
</FrameLayout>
</layout>
</LinearLayout>

View File

@ -0,0 +1,213 @@
<?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"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_weight="1"
android:background="?android:attr/selectableItemBackground"
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>

View File

@ -9,6 +9,7 @@
<item name="imageColorTint">@color/dark_secondary_text</item>
<item name="colorControl">?android:attr/textColorSecondary</item>
<item name="colorAccentFallback">@color/accentFallback</item>
<item name="android:windowBackground">@android:color/black</item>
</style>
</resources>

View File

@ -10,6 +10,7 @@
<item name="colorAccent">@color/colorSecondary</item>
<item name="colorSecondary">@color/colorSecondary</item>
<item name="colorSecondaryVariant">@color/colorSecondaryDark</item>
<item name="android:windowBackground">@android:color/white</item>
</style>
<style name="ThemeFoundation.Colored.ExtraProps" />