Added magisk log screen

This commit is contained in:
Viktor De Pasquale 2019-11-21 17:31:37 +01:00
parent 94f0c61619
commit 67c50d7504
8 changed files with 262 additions and 23 deletions

View File

@ -164,4 +164,9 @@ open class MainActivity : CompatActivity<MainViewModel, ActivityMainMd2Binding>(
.start() .start()
} }
fun invalidateToolbar() {
//binding.mainToolbar.startAnimations()
binding.mainToolbar.invalidate()
}
} }

View File

@ -1,9 +1,17 @@
package com.topjohnwu.magisk.redesign.log package com.topjohnwu.magisk.redesign.log
import android.graphics.Insets import android.graphics.Insets
import android.os.Bundle
import android.view.Menu
import android.view.MenuInflater
import android.view.MenuItem
import android.view.View
import androidx.core.view.isVisible
import com.topjohnwu.magisk.R import com.topjohnwu.magisk.R
import com.topjohnwu.magisk.databinding.FragmentLogMd2Binding import com.topjohnwu.magisk.databinding.FragmentLogMd2Binding
import com.topjohnwu.magisk.redesign.MainActivity
import com.topjohnwu.magisk.redesign.compat.CompatFragment import com.topjohnwu.magisk.redesign.compat.CompatFragment
import com.topjohnwu.magisk.redesign.hide.MotionRevealHelper
import org.koin.androidx.viewmodel.ext.android.viewModel import org.koin.androidx.viewmodel.ext.android.viewModel
class LogFragment : CompatFragment<LogViewModel, FragmentLogMd2Binding>() { class LogFragment : CompatFragment<LogViewModel, FragmentLogMd2Binding>() {
@ -11,14 +19,58 @@ class LogFragment : CompatFragment<LogViewModel, FragmentLogMd2Binding>() {
override val layoutRes = R.layout.fragment_log_md2 override val layoutRes = R.layout.fragment_log_md2
override val viewModel by viewModel<LogViewModel>() override val viewModel by viewModel<LogViewModel>()
private var actionSave: MenuItem? = null
private var isMagiskLogVisible
get() = binding.logFilter.isVisible
set(value) {
MotionRevealHelper.withViews(binding.logFilter, binding.logFilterToggle, value)
actionSave?.isVisible = value
(activity as MainActivity).invalidateToolbar()
}
override fun consumeSystemWindowInsets(insets: Insets) = insets override fun consumeSystemWindowInsets(insets: Insets) = insets
override fun onStart() { override fun onStart() {
super.onStart() super.onStart()
setHasOptionsMenu(true)
activity.title = resources.getString(R.string.section_log) activity.title = resources.getString(R.string.section_log)
} }
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
binding.logFilterToggle.setOnClickListener {
isMagiskLogVisible = true
}
}
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
super.onCreateOptionsMenu(menu, inflater)
inflater.inflate(R.menu.menu_log_md2, menu)
actionSave = menu.findItem(R.id.action_save)?.also {
it.isVisible = isMagiskLogVisible
}
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
when (item.itemId) {
R.id.action_save -> viewModel.saveMagiskLog()
R.id.action_clear ->
if (isMagiskLogVisible) viewModel.clearMagiskLog()
else viewModel.clearLog()
}
return super.onOptionsItemSelected(item)
}
override fun onPreBind(binding: FragmentLogMd2Binding) = Unit override fun onPreBind(binding: FragmentLogMd2Binding) = Unit
override fun onBackPressed(): Boolean {
if (binding.logFilter.isVisible) {
isMagiskLogVisible = false
return true
}
return super.onBackPressed()
}
} }

View File

@ -1,26 +1,52 @@
package com.topjohnwu.magisk.redesign.log package com.topjohnwu.magisk.redesign.log
import com.topjohnwu.magisk.BR import com.topjohnwu.magisk.BR
import com.topjohnwu.magisk.Config
import com.topjohnwu.magisk.Const
import com.topjohnwu.magisk.R
import com.topjohnwu.magisk.data.repository.LogRepository import com.topjohnwu.magisk.data.repository.LogRepository
import com.topjohnwu.magisk.databinding.ComparableRvItem
import com.topjohnwu.magisk.extensions.subscribeK 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.LogItem import com.topjohnwu.magisk.model.entity.recycler.LogItem
import com.topjohnwu.magisk.model.events.SnackbarEvent
import com.topjohnwu.magisk.redesign.compat.CompatViewModel import com.topjohnwu.magisk.redesign.compat.CompatViewModel
import com.topjohnwu.magisk.redesign.home.itemBindingOf import com.topjohnwu.magisk.redesign.home.itemBindingOf
import com.topjohnwu.magisk.redesign.superuser.diffListOf import com.topjohnwu.magisk.redesign.superuser.diffListOf
import com.topjohnwu.superuser.Shell
import io.reactivex.Completable
import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.disposables.Disposable
import io.reactivex.schedulers.Schedulers
import timber.log.Timber
import java.io.File
import java.util.*
class LogViewModel( class LogViewModel(
private val repo: LogRepository private val repo: LogRepository
) : CompatViewModel() { ) : CompatViewModel() {
// --- main view
val items = diffListOf<LogItem>() val items = diffListOf<LogItem>()
val itemBinding = itemBindingOf<LogItem> { val itemBinding = itemBindingOf<LogItem> {
it.bindExtra(BR.viewModel, this) it.bindExtra(BR.viewModel, this)
} }
override fun refresh() = repo.fetchLogsNowrap() // --- console
val consoleAdapter = BindingAdapter()
val itemsConsole = diffListOf<ComparableRvItem<*>>()
val itemConsoleBinding = itemBindingOf<ComparableRvItem<*>> {}
override fun refresh(): Disposable {
val logs = repo.fetchLogsNowrap()
.map { it.map { LogItem(it) } } .map { it.map { LogItem(it) } }
.observeOn(Schedulers.computation())
.map { it to items.calculateDiff(it) } .map { it to items.calculateDiff(it) }
.subscribeK { .observeOn(AndroidSchedulers.mainThread())
.doOnSuccess {
items.firstOrNull()?.isTop = false items.firstOrNull()?.isTop = false
items.lastOrNull()?.isBottom = false items.lastOrNull()?.isBottom = false
@ -29,5 +55,53 @@ class LogViewModel(
items.firstOrNull()?.isTop = true items.firstOrNull()?.isTop = true
items.lastOrNull()?.isBottom = true items.lastOrNull()?.isBottom = true
} }
.ignoreElement()
val console = repo.fetchMagiskLogs()
.map { it.map { ConsoleRvItem(it) } }
.observeOn(Schedulers.computation())
.map { it to itemsConsole.calculateDiff(it) }
.observeOn(AndroidSchedulers.mainThread())
.doOnSuccess { itemsConsole.update(it.first, it.second) }
.ignoreElement()
return Completable.merge(listOf(logs, console)).subscribeK()
}
fun saveMagiskLog() {
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 clearMagiskLog() = repo.clearMagiskLogs()
.ignoreElement()
.subscribeK {
SnackbarEvent(R.string.logs_cleared).publish()
requestRefresh()
}
.add()
fun clearLog() = repo.clearLogs()
.subscribeK {
SnackbarEvent(R.string.logs_cleared).publish()
requestRefresh()
}
.add()
} }

View File

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="?colorOnSurface"
android:pathData="M20,18H4V8H20M20,6H12L10,4H4A2,2 0 0,0 2,6V18A2,2 0 0,0 4,20H20A2,2 0 0,0 22,18V8A2,2 0 0,0 20,6M15,16H6V14H15V16M18,12H6V10H18V12Z" />
</vector>

View File

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="?colorOnSurface"
android:pathData="M17 3H5C3.89 3 3 3.9 3 5V19C3 20.1 3.89 21 5 21H19C20.1 21 21 20.1 21 19V7L17 3M19 19H5V5H16.17L19 7.83V19M12 12C10.34 12 9 13.34 9 15S10.34 18 12 18 15 16.66 15 15 13.66 12 12 12M6 6H15V10H6V6Z" />
</vector>

View File

@ -11,6 +11,10 @@
</data> </data>
<FrameLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.recyclerview.widget.RecyclerView <androidx.recyclerview.widget.RecyclerView
itemBinding="@{viewModel.itemBinding}" itemBinding="@{viewModel.itemBinding}"
items="@{viewModel.items}" items="@{viewModel.items}"
@ -24,6 +28,39 @@
android:paddingBottom="@{viewModel.insets.bottom}" android:paddingBottom="@{viewModel.insets.bottom}"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager" app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
tools:listitem="@layout/item_log_access_md2" tools:listitem="@layout/item_log_access_md2"
tools:paddingTop="40dp" /> tools:paddingTop="24dp" />
<com.google.android.material.floatingactionbutton.FloatingActionButton
android:id="@+id/log_filter_toggle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom|end"
android:layout_marginStart="@dimen/l1"
android:layout_marginEnd="@dimen/l1"
android:layout_marginBottom="@{viewModel.insets.bottom + (int) @dimen/l1}"
app:backgroundTint="?colorSurfaceSurfaceVariant"
app:srcCompat="@drawable/ic_folder_list"
app:tint="?colorPrimary"
tools:layout_marginBottom="64dp" />
<com.google.android.material.circularreveal.cardview.CircularRevealCardView
android:id="@+id/log_filter"
style="?styleCardVariant"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="bottom"
android:visibility="invisible"
app:cardCornerRadius="0dp">
<include
android:id="@+id/log_filter_include"
layout="@layout/include_log_magisk"
viewModel="@{viewModel}"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</com.google.android.material.circularreveal.cardview.CircularRevealCardView>
</FrameLayout>
</layout> </layout>

View File

@ -0,0 +1,34 @@
<?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.redesign.log.LogViewModel" />
</data>
<HorizontalScrollView
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.recyclerview.widget.RecyclerView
adapter="@{viewModel.consoleAdapter}"
itemBinding="@{viewModel.itemConsoleBinding}"
items="@{viewModel.itemsConsole}"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:clipToPadding="false"
android:orientation="vertical"
android:paddingTop="@{viewModel.insets.top + (int) @dimen/internal_action_bar_size}"
android:paddingBottom="@{viewModel.insets.bottom}"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
tools:listitem="@layout/item_console"
tools:paddingTop="24dp" />
</HorizontalScrollView>
</layout>

View File

@ -0,0 +1,17 @@
<?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/action_save"
android:icon="@drawable/ic_save_md2"
android:title="@string/menuSaveLog"
app:showAsAction="ifRoom" />
<item
android:id="@+id/action_clear"
android:icon="@drawable/ic_delete_md2"
android:title="@string/menuClearLog"
app:showAsAction="ifRoom" />
</menu>