mirror of
https://github.com/topjohnwu/Magisk.git
synced 2025-10-21 14:28:52 +00:00
Updated log screen with new arch
This commit is contained in:
@@ -3,6 +3,7 @@ package com.topjohnwu.magisk.di
|
||||
import com.topjohnwu.magisk.ui.MainViewModel
|
||||
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 org.koin.androidx.viewmodel.dsl.viewModel
|
||||
@@ -15,4 +16,5 @@ val viewModelModules = module {
|
||||
viewModel { SuperuserViewModel(get(), get(), get(), get()) }
|
||||
viewModel { HideViewModel(get(), get()) }
|
||||
viewModel { ModuleViewModel(get()) }
|
||||
viewModel { LogViewModel(get(), get()) }
|
||||
}
|
||||
|
@@ -0,0 +1,11 @@
|
||||
package com.topjohnwu.magisk.model.entity.recycler
|
||||
|
||||
import com.skoumal.teanity.databinding.ComparableRvItem
|
||||
import com.topjohnwu.magisk.R
|
||||
|
||||
class ConsoleRvItem(val item: String) : ComparableRvItem<ConsoleRvItem>() {
|
||||
override val layoutRes: Int = R.layout.item_console
|
||||
|
||||
override fun contentSameAs(other: ConsoleRvItem) = itemSameAs(other)
|
||||
override fun itemSameAs(other: ConsoleRvItem) = item == other.item
|
||||
}
|
@@ -0,0 +1,75 @@
|
||||
package com.topjohnwu.magisk.model.entity.recycler
|
||||
|
||||
import androidx.databinding.ObservableList
|
||||
import com.skoumal.teanity.databinding.ComparableRvItem
|
||||
import com.skoumal.teanity.util.DiffObservableList
|
||||
import com.skoumal.teanity.util.KObservableField
|
||||
import com.topjohnwu.magisk.R
|
||||
import com.topjohnwu.magisk.model.entity.SuLogEntry
|
||||
import com.topjohnwu.magisk.utils.toggle
|
||||
|
||||
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(
|
||||
val items: ObservableList<ComparableRvItem<*>>
|
||||
) : ComparableRvItem<LogItemRvItem>() {
|
||||
override val layoutRes: Int = R.layout.item_superuser_log
|
||||
|
||||
val date = items.filterIsInstance<LogItemEntryRvItem>().firstOrNull()
|
||||
?.item?.dateString.orEmpty()
|
||||
val isExpanded = KObservableField(false)
|
||||
|
||||
fun toggle() = isExpanded.toggle()
|
||||
|
||||
override fun contentSameAs(other: LogItemRvItem): Boolean = items
|
||||
.any { !other.items.contains(it) }
|
||||
|
||||
override fun itemSameAs(other: LogItemRvItem): Boolean = date == other.date
|
||||
}
|
||||
|
||||
class LogItemEntryRvItem(val item: SuLogEntry) : 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.fromUid == other.item.fromUid &&
|
||||
item.toUid == other.item.toUid &&
|
||||
item.fromPid == other.item.fromPid &&
|
||||
item.packageName == other.item.packageName &&
|
||||
item.command == other.item.command &&
|
||||
item.action == other.item.action &&
|
||||
item.date == other.item.date
|
||||
|
||||
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
|
||||
}
|
@@ -23,4 +23,6 @@ class ViewActionEvent(val action: Activity.() -> Unit) : ViewEvent()
|
||||
class OpenFilePickerEvent : ViewEvent()
|
||||
|
||||
class OpenChangelogEvent(val item: Repo) : ViewEvent()
|
||||
class InstallModuleEvent(val item: Repo) : ViewEvent()
|
||||
class InstallModuleEvent(val item: Repo) : ViewEvent()
|
||||
|
||||
class PageChangedEvent : ViewEvent()
|
@@ -1,45 +0,0 @@
|
||||
package com.topjohnwu.magisk.ui.log;
|
||||
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import androidx.viewpager.widget.ViewPager;
|
||||
|
||||
import com.google.android.material.tabs.TabLayout;
|
||||
import com.topjohnwu.magisk.R;
|
||||
import com.topjohnwu.magisk.model.adapters.TabFragmentAdapter;
|
||||
import com.topjohnwu.magisk.ui.base.BaseFragment;
|
||||
|
||||
import butterknife.BindView;
|
||||
|
||||
public class LogFragment extends BaseFragment {
|
||||
|
||||
@BindView(R.id.container) ViewPager viewPager;
|
||||
@BindView(R.id.tab) TabLayout tab;
|
||||
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, ViewGroup container,
|
||||
Bundle savedInstanceState) {
|
||||
// Inflate the layout for this fragment
|
||||
View v = inflater.inflate(R.layout.fragment_log, container, false);
|
||||
unbinder = new LogFragment_ViewBinding(this, v);
|
||||
|
||||
/*if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
||||
((MainActivity) requireActivity()).toolbar.setElevation(0);
|
||||
}*/
|
||||
|
||||
TabFragmentAdapter adapter = new TabFragmentAdapter(getChildFragmentManager());
|
||||
|
||||
adapter.addTab(new SuLogFragment(), getString(R.string.superuser));
|
||||
adapter.addTab(new MagiskLogFragment(), getString(R.string.magisk));
|
||||
tab.setupWithViewPager(viewPager);
|
||||
tab.setVisibility(View.VISIBLE);
|
||||
|
||||
viewPager.setAdapter(adapter);
|
||||
|
||||
return v;
|
||||
}
|
||||
}
|
53
app/src/main/java/com/topjohnwu/magisk/ui/log/LogFragment.kt
Normal file
53
app/src/main/java/com/topjohnwu/magisk/ui/log/LogFragment.kt
Normal file
@@ -0,0 +1,53 @@
|
||||
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.skoumal.teanity.viewevents.ViewEvent
|
||||
import com.topjohnwu.magisk.R
|
||||
import com.topjohnwu.magisk.databinding.FragmentLogBinding
|
||||
import com.topjohnwu.magisk.model.events.PageChangedEvent
|
||||
import com.topjohnwu.magisk.ui.base.MagiskFragment
|
||||
import org.koin.androidx.viewmodel.ext.android.viewModel
|
||||
|
||||
class LogFragment : MagiskFragment<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 -> magiskActivity.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)
|
||||
magiskActivity.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 -> viewModel.saveLog()
|
||||
R.id.menu_clear -> viewModel.clearLog()
|
||||
R.id.menu_refresh -> viewModel.refresh()
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
}
|
122
app/src/main/java/com/topjohnwu/magisk/ui/log/LogViewModel.kt
Normal file
122
app/src/main/java/com/topjohnwu/magisk/ui/log/LogViewModel.kt
Normal file
@@ -0,0 +1,122 @@
|
||||
package com.topjohnwu.magisk.ui.log
|
||||
|
||||
import android.content.res.Resources
|
||||
import androidx.databinding.ObservableArrayList
|
||||
import com.skoumal.teanity.databinding.ComparableRvItem
|
||||
import com.skoumal.teanity.extensions.addOnPropertyChangedCallback
|
||||
import com.skoumal.teanity.extensions.doOnSuccessUi
|
||||
import com.skoumal.teanity.extensions.subscribeK
|
||||
import com.skoumal.teanity.util.DiffObservableList
|
||||
import com.skoumal.teanity.util.KObservableField
|
||||
import com.skoumal.teanity.viewevents.SnackbarEvent
|
||||
import com.topjohnwu.magisk.BR
|
||||
import com.topjohnwu.magisk.Const
|
||||
import com.topjohnwu.magisk.R
|
||||
import com.topjohnwu.magisk.data.database.MagiskDB
|
||||
import com.topjohnwu.magisk.model.entity.recycler.*
|
||||
import com.topjohnwu.magisk.model.events.PageChangedEvent
|
||||
import com.topjohnwu.magisk.ui.base.MagiskViewModel
|
||||
import com.topjohnwu.magisk.utils.toSingle
|
||||
import com.topjohnwu.magisk.utils.zip
|
||||
import com.topjohnwu.superuser.Shell
|
||||
import io.reactivex.Single
|
||||
import me.tatarka.bindingcollectionadapter2.BindingViewPagerAdapter
|
||||
import me.tatarka.bindingcollectionadapter2.OnItemBind
|
||||
import java.io.File
|
||||
import java.io.IOException
|
||||
import java.util.*
|
||||
|
||||
class LogViewModel(
|
||||
private val resources: Resources,
|
||||
private val database: MagiskDB
|
||||
) : MagiskViewModel(), BindingViewPagerAdapter.PageTitles<ComparableRvItem<*>> {
|
||||
|
||||
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
|
||||
|
||||
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 refresh() = zip(updateLogs(), updateMagiskLog()) { _, _ -> true }
|
||||
.subscribeK()
|
||||
.add()
|
||||
|
||||
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(Const.EXTERNAL_PATH, filename)
|
||||
try {
|
||||
logFile.createNewFile()
|
||||
} catch (e: IOException) {
|
||||
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) {
|
||||
Single.fromCallable { database.clearLogs() }
|
||||
.subscribeK {
|
||||
SnackbarEvent(R.string.logs_cleared).publish()
|
||||
callback()
|
||||
}
|
||||
.add()
|
||||
}
|
||||
|
||||
private fun clearMagiskLogs(callback: () -> Unit) {
|
||||
Shell.su("echo -n > " + Const.MAGISK_LOG).submit {
|
||||
SnackbarEvent(R.string.logs_cleared).publish()
|
||||
callback()
|
||||
}
|
||||
}
|
||||
|
||||
private fun updateLogs() = Single.fromCallable { database.logs }
|
||||
.flattenAsFlowable { it }
|
||||
.map { it.map { LogItemEntryRvItem(it) } }
|
||||
.map { LogItemRvItem(ObservableArrayList<ComparableRvItem<*>>().apply { addAll(it) }) }
|
||||
.toList()
|
||||
.doOnSuccessUi { logItem.update(it) }
|
||||
|
||||
private fun updateMagiskLog() = Shell.su("tail -n 5000 ${Const.MAGISK_LOG}").toSingle()
|
||||
.map { it.exec() }
|
||||
.map { it.out }
|
||||
.flattenAsFlowable { it }
|
||||
.map { ConsoleRvItem(it) }
|
||||
.toList()
|
||||
.doOnSuccessUi { magiskLogItem.update(it) }
|
||||
|
||||
}
|
@@ -6,7 +6,10 @@ import androidx.annotation.DrawableRes
|
||||
import androidx.appcompat.widget.AppCompatImageView
|
||||
import androidx.appcompat.widget.Toolbar
|
||||
import androidx.databinding.BindingAdapter
|
||||
import androidx.databinding.InverseBindingAdapter
|
||||
import androidx.databinding.InverseBindingListener
|
||||
import androidx.drawerlayout.widget.DrawerLayout
|
||||
import androidx.viewpager.widget.ViewPager
|
||||
import com.google.android.material.navigation.NavigationView
|
||||
import com.topjohnwu.magisk.R
|
||||
import com.topjohnwu.magisk.model.entity.state.IndeterminateState
|
||||
@@ -57,3 +60,24 @@ fun setChecked(view: AppCompatImageView, isChecked: IndeterminateState) {
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
@BindingAdapter("position")
|
||||
fun setPosition(view: ViewPager, position: Int) {
|
||||
view.currentItem = position
|
||||
}
|
||||
|
||||
@InverseBindingAdapter(attribute = "position", event = "positionChanged")
|
||||
fun getPosition(view: ViewPager) = view.currentItem
|
||||
|
||||
@BindingAdapter("positionChanged")
|
||||
fun setPositionChangedListener(view: ViewPager, listener: InverseBindingListener) {
|
||||
view.addOnPageChangeListener(object : ViewPager.OnPageChangeListener {
|
||||
override fun onPageSelected(position: Int) = listener.onChange()
|
||||
override fun onPageScrollStateChanged(state: Int) = listener.onChange()
|
||||
override fun onPageScrolled(
|
||||
position: Int,
|
||||
positionOffset: Float,
|
||||
positionOffsetPixels: Int
|
||||
) = listener.onChange()
|
||||
})
|
||||
}
|
@@ -1,5 +1,9 @@
|
||||
package com.topjohnwu.magisk.utils
|
||||
|
||||
import io.reactivex.Single
|
||||
import io.reactivex.functions.BiFunction
|
||||
|
||||
fun <T : Any> T.toSingle() = Single.just(this)
|
||||
fun <T : Any> T.toSingle() = Single.just(this)
|
||||
|
||||
fun <T1, T2, R> zip(t1: Single<T1>, t2: Single<T2>, zipper: (T1, T2) -> R) =
|
||||
Single.zip(t1, t2, BiFunction<T1, T2, R> { rt1, rt2 -> zipper(rt1, rt2) })
|
Reference in New Issue
Block a user