Updated log screen with new arch

This commit is contained in:
Viktor De Pasquale
2019-04-22 14:11:41 +02:00
parent f21241d944
commit 7cc8c014eb
15 changed files with 627 additions and 69 deletions

View File

@@ -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()) }
}

View File

@@ -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
}

View File

@@ -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
}

View File

@@ -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()

View File

@@ -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;
}
}

View 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
}
}

View 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) }
}

View File

@@ -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()
})
}

View File

@@ -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) })