Updated flash screen with new arch
This commit is contained in:
parent
07eb7dda2d
commit
14ff22fbcd
@ -35,7 +35,7 @@
|
|||||||
android:name="a.f"
|
android:name="a.f"
|
||||||
android:configChanges="keyboardHidden|orientation|screenSize"
|
android:configChanges="keyboardHidden|orientation|screenSize"
|
||||||
android:screenOrientation="nosensor"
|
android:screenOrientation="nosensor"
|
||||||
android:theme="@style/AppTheme.NoDrawer" />
|
android:theme="@style/MagiskTheme.Flashing" />
|
||||||
|
|
||||||
<!-- Superuser -->
|
<!-- Superuser -->
|
||||||
|
|
||||||
|
@ -35,6 +35,9 @@ public class Const {
|
|||||||
|
|
||||||
public static final int USER_ID = Process.myUid() / 100000;
|
public static final int USER_ID = Process.myUid() / 100000;
|
||||||
|
|
||||||
|
// Generic
|
||||||
|
public static final String MAGISK_INSTALL_LOG_FILENAME = "magisk_install_log_%s.log";
|
||||||
|
|
||||||
public static final class MAGISK_VER {
|
public static final class MAGISK_VER {
|
||||||
public static final int MIN_SUPPORT = 18000;
|
public static final int MIN_SUPPORT = 18000;
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
package com.topjohnwu.magisk.di
|
package com.topjohnwu.magisk.di
|
||||||
|
|
||||||
|
import android.net.Uri
|
||||||
import com.topjohnwu.magisk.ui.MainViewModel
|
import com.topjohnwu.magisk.ui.MainViewModel
|
||||||
|
import com.topjohnwu.magisk.ui.flash.FlashViewModel
|
||||||
import com.topjohnwu.magisk.ui.hide.HideViewModel
|
import com.topjohnwu.magisk.ui.hide.HideViewModel
|
||||||
import com.topjohnwu.magisk.ui.home.HomeViewModel
|
import com.topjohnwu.magisk.ui.home.HomeViewModel
|
||||||
import com.topjohnwu.magisk.ui.log.LogViewModel
|
import com.topjohnwu.magisk.ui.log.LogViewModel
|
||||||
@ -17,4 +19,5 @@ val viewModelModules = module {
|
|||||||
viewModel { HideViewModel(get(), get()) }
|
viewModel { HideViewModel(get(), get()) }
|
||||||
viewModel { ModuleViewModel(get(), get()) }
|
viewModel { ModuleViewModel(get(), get()) }
|
||||||
viewModel { LogViewModel(get(), get()) }
|
viewModel { LogViewModel(get(), get()) }
|
||||||
|
viewModel { (action: String, uri: Uri?) -> FlashViewModel(action, uri, get()) }
|
||||||
}
|
}
|
||||||
|
@ -3,6 +3,7 @@ package com.topjohnwu.magisk.model.events
|
|||||||
import android.app.Activity
|
import android.app.Activity
|
||||||
import com.skoumal.teanity.viewevents.ViewEvent
|
import com.skoumal.teanity.viewevents.ViewEvent
|
||||||
import com.topjohnwu.magisk.model.entity.Repo
|
import com.topjohnwu.magisk.model.entity.Repo
|
||||||
|
import io.reactivex.subjects.PublishSubject
|
||||||
|
|
||||||
|
|
||||||
data class OpenLinkEvent(val url: String) : ViewEvent()
|
data class OpenLinkEvent(val url: String) : ViewEvent()
|
||||||
@ -25,4 +26,11 @@ class OpenFilePickerEvent : ViewEvent()
|
|||||||
class OpenChangelogEvent(val item: Repo) : ViewEvent()
|
class OpenChangelogEvent(val item: Repo) : ViewEvent()
|
||||||
class InstallModuleEvent(val item: Repo) : ViewEvent()
|
class InstallModuleEvent(val item: Repo) : ViewEvent()
|
||||||
|
|
||||||
class PageChangedEvent : ViewEvent()
|
class PageChangedEvent : ViewEvent()
|
||||||
|
|
||||||
|
class PermissionEvent(
|
||||||
|
val permissions: List<String>,
|
||||||
|
val callback: PublishSubject<Boolean>
|
||||||
|
) : ViewEvent()
|
||||||
|
|
||||||
|
class BackPressEvent : ViewEvent()
|
@ -0,0 +1,7 @@
|
|||||||
|
package com.topjohnwu.magisk.model.flash
|
||||||
|
|
||||||
|
interface FlashResultListener {
|
||||||
|
|
||||||
|
fun onResult(isSuccess: Boolean)
|
||||||
|
|
||||||
|
}
|
@ -13,7 +13,7 @@ sealed class Flashing(
|
|||||||
uri: Uri,
|
uri: Uri,
|
||||||
private val console: MutableList<String>,
|
private val console: MutableList<String>,
|
||||||
log: MutableList<String>,
|
log: MutableList<String>,
|
||||||
private val resultListener: (Result<Boolean>) -> Unit
|
private val resultListener: FlashResultListener
|
||||||
) : FlashZip(uri, console, log) {
|
) : FlashZip(uri, console, log) {
|
||||||
|
|
||||||
override fun onResult(success: Boolean) {
|
override fun onResult(success: Boolean) {
|
||||||
@ -21,14 +21,14 @@ sealed class Flashing(
|
|||||||
console.add("! Installation failed")
|
console.add("! Installation failed")
|
||||||
}
|
}
|
||||||
|
|
||||||
resultListener(Result.success(success))
|
resultListener.onResult(success)
|
||||||
}
|
}
|
||||||
|
|
||||||
class Install(
|
class Install(
|
||||||
uri: Uri,
|
uri: Uri,
|
||||||
console: MutableList<String>,
|
console: MutableList<String>,
|
||||||
log: MutableList<String>,
|
log: MutableList<String>,
|
||||||
resultListener: (Result<Boolean>) -> Unit = {}
|
resultListener: FlashResultListener
|
||||||
) : Flashing(uri, console, log, resultListener) {
|
) : Flashing(uri, console, log, resultListener) {
|
||||||
|
|
||||||
override fun onResult(success: Boolean) {
|
override fun onResult(success: Boolean) {
|
||||||
@ -44,7 +44,7 @@ sealed class Flashing(
|
|||||||
uri: Uri,
|
uri: Uri,
|
||||||
console: MutableList<String>,
|
console: MutableList<String>,
|
||||||
log: MutableList<String>,
|
log: MutableList<String>,
|
||||||
resultListener: (Result<Boolean>) -> Unit = {}
|
resultListener: FlashResultListener
|
||||||
) : Flashing(uri, console, log, resultListener) {
|
) : Flashing(uri, console, log, resultListener) {
|
||||||
|
|
||||||
private val context: Context by inject()
|
private val context: Context by inject()
|
||||||
|
@ -6,8 +6,8 @@ import com.topjohnwu.superuser.Shell
|
|||||||
|
|
||||||
sealed class Patching(
|
sealed class Patching(
|
||||||
private val console: MutableList<String>,
|
private val console: MutableList<String>,
|
||||||
logs: List<String>,
|
logs: MutableList<String>,
|
||||||
private val resultListener: (Result<Boolean>) -> Unit
|
private val resultListener: FlashResultListener
|
||||||
) : MagiskInstaller(console, logs) {
|
) : MagiskInstaller(console, logs) {
|
||||||
|
|
||||||
override fun onResult(success: Boolean) {
|
override fun onResult(success: Boolean) {
|
||||||
@ -17,14 +17,14 @@ sealed class Patching(
|
|||||||
Shell.sh("rm -rf $installDir").submit()
|
Shell.sh("rm -rf $installDir").submit()
|
||||||
console.add("! Installation failed")
|
console.add("! Installation failed")
|
||||||
}
|
}
|
||||||
resultListener(Result.success(success))
|
resultListener.onResult(success)
|
||||||
}
|
}
|
||||||
|
|
||||||
class File(
|
class File(
|
||||||
private val uri: Uri,
|
private val uri: Uri,
|
||||||
console: MutableList<String>,
|
console: MutableList<String>,
|
||||||
logs: List<String>,
|
logs: MutableList<String>,
|
||||||
resultListener: (Result<Boolean>) -> Unit = {}
|
resultListener: FlashResultListener
|
||||||
) : Patching(console, logs, resultListener) {
|
) : Patching(console, logs, resultListener) {
|
||||||
override fun operations() =
|
override fun operations() =
|
||||||
extractZip() && handleFile(uri) && patchBoot() && storeBoot()
|
extractZip() && handleFile(uri) && patchBoot() && storeBoot()
|
||||||
@ -32,8 +32,8 @@ sealed class Patching(
|
|||||||
|
|
||||||
class SecondSlot(
|
class SecondSlot(
|
||||||
console: MutableList<String>,
|
console: MutableList<String>,
|
||||||
logs: List<String>,
|
logs: MutableList<String>,
|
||||||
resultListener: (Result<Boolean>) -> Unit = {}
|
resultListener: FlashResultListener
|
||||||
) : Patching(console, logs, resultListener) {
|
) : Patching(console, logs, resultListener) {
|
||||||
override fun operations() =
|
override fun operations() =
|
||||||
findSecondaryImage() && extractZip() && patchBoot() && flashBoot() && postOTA()
|
findSecondaryImage() && extractZip() && patchBoot() && flashBoot() && postOTA()
|
||||||
@ -41,8 +41,8 @@ sealed class Patching(
|
|||||||
|
|
||||||
class Direct(
|
class Direct(
|
||||||
console: MutableList<String>,
|
console: MutableList<String>,
|
||||||
logs: List<String>,
|
logs: MutableList<String>,
|
||||||
resultListener: (Result<Boolean>) -> Unit = {}
|
resultListener: FlashResultListener
|
||||||
) : Patching(console, logs, resultListener) {
|
) : Patching(console, logs, resultListener) {
|
||||||
override fun operations() =
|
override fun operations() =
|
||||||
findImage() && extractZip() && patchBoot() && flashBoot()
|
findImage() && extractZip() && patchBoot() && flashBoot()
|
||||||
|
@ -16,6 +16,7 @@ import com.ncapdevi.fragnav.FragNavController
|
|||||||
import com.ncapdevi.fragnav.FragNavTransactionOptions
|
import com.ncapdevi.fragnav.FragNavTransactionOptions
|
||||||
import com.skoumal.teanity.viewevents.ViewEvent
|
import com.skoumal.teanity.viewevents.ViewEvent
|
||||||
import com.topjohnwu.magisk.Config
|
import com.topjohnwu.magisk.Config
|
||||||
|
import com.topjohnwu.magisk.model.events.BackPressEvent
|
||||||
import com.topjohnwu.magisk.model.events.PermissionEvent
|
import com.topjohnwu.magisk.model.events.PermissionEvent
|
||||||
import com.topjohnwu.magisk.model.events.ViewActionEvent
|
import com.topjohnwu.magisk.model.events.ViewActionEvent
|
||||||
import com.topjohnwu.magisk.model.navigation.MagiskAnimBuilder
|
import com.topjohnwu.magisk.model.navigation.MagiskAnimBuilder
|
||||||
@ -36,7 +37,8 @@ abstract class MagiskActivity<ViewModel : MagiskViewModel, Binding : ViewDataBin
|
|||||||
|
|
||||||
protected open val defaultPosition: Int = 0
|
protected open val defaultPosition: Int = 0
|
||||||
|
|
||||||
protected val navigationController by lazy {
|
protected val navigationController get() = if (navHostId == 0) null else _navigationController
|
||||||
|
private val _navigationController by lazy {
|
||||||
if (navHostId == 0) throw IllegalStateException("Did you forget to override \"navHostId\"?")
|
if (navHostId == 0) throw IllegalStateException("Did you forget to override \"navHostId\"?")
|
||||||
FragNavController(supportFragmentManager, navHostId)
|
FragNavController(supportFragmentManager, navHostId)
|
||||||
}
|
}
|
||||||
@ -54,7 +56,7 @@ abstract class MagiskActivity<ViewModel : MagiskViewModel, Binding : ViewDataBin
|
|||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
navigationController.apply {
|
navigationController?.apply {
|
||||||
rootFragmentListener = this@MagiskActivity
|
rootFragmentListener = this@MagiskActivity
|
||||||
initialize(defaultPosition, savedInstanceState)
|
initialize(defaultPosition, savedInstanceState)
|
||||||
}
|
}
|
||||||
@ -62,13 +64,14 @@ abstract class MagiskActivity<ViewModel : MagiskViewModel, Binding : ViewDataBin
|
|||||||
|
|
||||||
override fun onSaveInstanceState(outState: Bundle) {
|
override fun onSaveInstanceState(outState: Bundle) {
|
||||||
super.onSaveInstanceState(outState)
|
super.onSaveInstanceState(outState)
|
||||||
navigationController.onSaveInstanceState(outState)
|
navigationController?.onSaveInstanceState(outState)
|
||||||
}
|
}
|
||||||
|
|
||||||
@CallSuper
|
@CallSuper
|
||||||
override fun onEventDispatched(event: ViewEvent) {
|
override fun onEventDispatched(event: ViewEvent) {
|
||||||
super.onEventDispatched(event)
|
super.onEventDispatched(event)
|
||||||
when (event) {
|
when (event) {
|
||||||
|
is BackPressEvent -> onBackPressed()
|
||||||
is MagiskNavigationEvent -> navigateTo(event)
|
is MagiskNavigationEvent -> navigateTo(event)
|
||||||
is ViewActionEvent -> event.action(this)
|
is ViewActionEvent -> event.action(this)
|
||||||
is PermissionEvent -> withPermissions(*event.permissions.toTypedArray()) {
|
is PermissionEvent -> withPermissions(*event.permissions.toTypedArray()) {
|
||||||
@ -86,15 +89,15 @@ abstract class MagiskActivity<ViewModel : MagiskViewModel, Binding : ViewDataBin
|
|||||||
override fun navigateTo(event: MagiskNavigationEvent) {
|
override fun navigateTo(event: MagiskNavigationEvent) {
|
||||||
val directions = event.navDirections
|
val directions = event.navDirections
|
||||||
|
|
||||||
navigationController.defaultTransactionOptions = FragNavTransactionOptions.newBuilder()
|
navigationController?.defaultTransactionOptions = FragNavTransactionOptions.newBuilder()
|
||||||
.customAnimations(event.animOptions)
|
.customAnimations(event.animOptions)
|
||||||
.build()
|
.build()
|
||||||
|
|
||||||
navigationController.currentStack
|
navigationController?.currentStack
|
||||||
?.indexOfFirst { it.javaClass == event.navOptions.popUpTo }
|
?.indexOfFirst { it.javaClass == event.navOptions.popUpTo }
|
||||||
?.let { if (it == -1) null else it } // invalidate if class is not found
|
?.let { if (it == -1) null else it } // invalidate if class is not found
|
||||||
?.let { if (event.navOptions.inclusive) it + 1 else it }
|
?.let { if (event.navOptions.inclusive) it + 1 else it }
|
||||||
?.let { navigationController.popFragments(it) }
|
?.let { navigationController?.popFragments(it) }
|
||||||
|
|
||||||
when (directions.isActivity) {
|
when (directions.isActivity) {
|
||||||
true -> navigateToActivity(event)
|
true -> navigateToActivity(event)
|
||||||
@ -127,21 +130,21 @@ abstract class MagiskActivity<ViewModel : MagiskViewModel, Binding : ViewDataBin
|
|||||||
when (val index = baseFragments.indexOfFirst { it.java.name == destination.name }) {
|
when (val index = baseFragments.indexOfFirst { it.java.name == destination.name }) {
|
||||||
-1 -> destination.newInstance()
|
-1 -> destination.newInstance()
|
||||||
.apply { arguments = event.navDirections.args }
|
.apply { arguments = event.navDirections.args }
|
||||||
.let { navigationController.pushFragment(it) }
|
.let { navigationController?.pushFragment(it) }
|
||||||
// When it's desired that fragments of same class are put on top of one another edit this
|
// When it's desired that fragments of same class are put on top of one another edit this
|
||||||
else -> navigationController.switchTab(index)
|
else -> navigationController?.switchTab(index)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onBackPressed() {
|
override fun onBackPressed() {
|
||||||
val fragment = navigationController.currentFrag as? MagiskFragment<*, *>
|
val fragment = navigationController?.currentFrag as? MagiskFragment<*, *>
|
||||||
|
|
||||||
if (fragment?.onBackPressed() == true) {
|
if (fragment?.onBackPressed() == true) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
navigationController.popFragment()
|
navigationController?.popFragment() ?: throw UnsupportedOperationException()
|
||||||
} catch (e: UnsupportedOperationException) {
|
} catch (e: UnsupportedOperationException) {
|
||||||
super.onBackPressed()
|
super.onBackPressed()
|
||||||
}
|
}
|
||||||
|
@ -5,6 +5,7 @@ import androidx.databinding.ViewDataBinding
|
|||||||
import androidx.fragment.app.Fragment
|
import androidx.fragment.app.Fragment
|
||||||
import com.skoumal.teanity.view.TeanityFragment
|
import com.skoumal.teanity.view.TeanityFragment
|
||||||
import com.skoumal.teanity.viewevents.ViewEvent
|
import com.skoumal.teanity.viewevents.ViewEvent
|
||||||
|
import com.topjohnwu.magisk.model.events.BackPressEvent
|
||||||
import com.topjohnwu.magisk.model.events.PermissionEvent
|
import com.topjohnwu.magisk.model.events.PermissionEvent
|
||||||
import com.topjohnwu.magisk.model.events.ViewActionEvent
|
import com.topjohnwu.magisk.model.events.ViewActionEvent
|
||||||
import com.topjohnwu.magisk.model.navigation.MagiskNavigationEvent
|
import com.topjohnwu.magisk.model.navigation.MagiskNavigationEvent
|
||||||
@ -27,6 +28,7 @@ abstract class MagiskFragment<ViewModel : MagiskViewModel, Binding : ViewDataBin
|
|||||||
override fun onEventDispatched(event: ViewEvent) {
|
override fun onEventDispatched(event: ViewEvent) {
|
||||||
super.onEventDispatched(event)
|
super.onEventDispatched(event)
|
||||||
when (event) {
|
when (event) {
|
||||||
|
is BackPressEvent -> magiskActivity.onBackPressed()
|
||||||
is MagiskNavigationEvent -> navigateTo(event)
|
is MagiskNavigationEvent -> navigateTo(event)
|
||||||
is ViewActionEvent -> event.action(requireActivity())
|
is ViewActionEvent -> event.action(requireActivity())
|
||||||
is PermissionEvent -> magiskActivity.withPermissions(*event.permissions.toTypedArray()) {
|
is PermissionEvent -> magiskActivity.withPermissions(*event.permissions.toTypedArray()) {
|
||||||
|
@ -2,6 +2,7 @@ package com.topjohnwu.magisk.ui.base
|
|||||||
|
|
||||||
import android.app.Activity
|
import android.app.Activity
|
||||||
import com.skoumal.teanity.viewmodel.LoadingViewModel
|
import com.skoumal.teanity.viewmodel.LoadingViewModel
|
||||||
|
import com.topjohnwu.magisk.model.events.BackPressEvent
|
||||||
import com.topjohnwu.magisk.model.events.PermissionEvent
|
import com.topjohnwu.magisk.model.events.PermissionEvent
|
||||||
import com.topjohnwu.magisk.model.events.ViewActionEvent
|
import com.topjohnwu.magisk.model.events.ViewActionEvent
|
||||||
import com.topjohnwu.magisk.utils.Event
|
import com.topjohnwu.magisk.utils.Event
|
||||||
@ -23,4 +24,7 @@ abstract class MagiskViewModel : LoadingViewModel(), Event.AutoListener {
|
|||||||
val subject = PublishSubject.create<Boolean>()
|
val subject = PublishSubject.create<Boolean>()
|
||||||
return subject.doOnSubscribe { PermissionEvent(permissions.toList(), subject).publish() }
|
return subject.doOnSubscribe { PermissionEvent(permissions.toList(), subject).publish() }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun back() = BackPressEvent().publish()
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -1,274 +0,0 @@
|
|||||||
package com.topjohnwu.magisk.ui.flash;
|
|
||||||
|
|
||||||
import android.content.Intent;
|
|
||||||
import android.net.Uri;
|
|
||||||
import android.os.Bundle;
|
|
||||||
import android.view.View;
|
|
||||||
import android.widget.Button;
|
|
||||||
import android.widget.LinearLayout;
|
|
||||||
import android.widget.Toast;
|
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
|
||||||
import androidx.appcompat.app.ActionBar;
|
|
||||||
import androidx.appcompat.widget.Toolbar;
|
|
||||||
import androidx.recyclerview.widget.RecyclerView;
|
|
||||||
|
|
||||||
import com.topjohnwu.magisk.Const;
|
|
||||||
import com.topjohnwu.magisk.R;
|
|
||||||
import com.topjohnwu.magisk.model.adapters.StringListAdapter;
|
|
||||||
import com.topjohnwu.magisk.tasks.FlashZip;
|
|
||||||
import com.topjohnwu.magisk.tasks.MagiskInstaller;
|
|
||||||
import com.topjohnwu.magisk.ui.base.BaseActivity;
|
|
||||||
import com.topjohnwu.magisk.utils.RootUtils;
|
|
||||||
import com.topjohnwu.magisk.utils.Utils;
|
|
||||||
import com.topjohnwu.superuser.CallbackList;
|
|
||||||
import com.topjohnwu.superuser.Shell;
|
|
||||||
import com.topjohnwu.superuser.internal.UiThreadHandler;
|
|
||||||
|
|
||||||
import java.io.File;
|
|
||||||
import java.io.FileWriter;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Calendar;
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Locale;
|
|
||||||
|
|
||||||
import butterknife.BindColor;
|
|
||||||
import butterknife.BindView;
|
|
||||||
import butterknife.OnClick;
|
|
||||||
|
|
||||||
public class FlashActivity extends BaseActivity {
|
|
||||||
|
|
||||||
@BindView(R.id.toolbar) Toolbar toolbar;
|
|
||||||
@BindView(R.id.button_panel) LinearLayout buttonPanel;
|
|
||||||
@BindView(R.id.reboot) Button reboot;
|
|
||||||
@BindView(R.id.recyclerView) RecyclerView rv;
|
|
||||||
@BindColor(android.R.color.white) int white;
|
|
||||||
|
|
||||||
private List<String> console, logs;
|
|
||||||
|
|
||||||
@OnClick(R.id.reboot)
|
|
||||||
void reboot() {
|
|
||||||
RootUtils.reboot();
|
|
||||||
}
|
|
||||||
|
|
||||||
@OnClick(R.id.save_logs)
|
|
||||||
void saveLogs() {
|
|
||||||
runWithExternalRW(() -> {
|
|
||||||
Calendar now = Calendar.getInstance();
|
|
||||||
String filename = String.format(Locale.US,
|
|
||||||
"magisk_install_log_%04d%02d%02d_%02d%02d%02d.log",
|
|
||||||
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));
|
|
||||||
|
|
||||||
File logFile = new File(Const.EXTERNAL_PATH, filename);
|
|
||||||
try (FileWriter writer = new FileWriter(logFile)) {
|
|
||||||
for (String s : logs) {
|
|
||||||
writer.write(s);
|
|
||||||
writer.write('\n');
|
|
||||||
}
|
|
||||||
} catch (IOException e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
Utils.toast(logFile.getPath(), Toast.LENGTH_LONG);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
@OnClick(R.id.close)
|
|
||||||
public void close() {
|
|
||||||
finish();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onBackPressed() {
|
|
||||||
// Prevent user accidentally press back button
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getDarkTheme() {
|
|
||||||
return R.style.AppTheme_NoDrawer_Dark;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onCreate(Bundle savedInstanceState) {
|
|
||||||
super.onCreate(savedInstanceState);
|
|
||||||
setContentView(R.layout.activity_flash);
|
|
||||||
new FlashActivity_ViewBinding(this);
|
|
||||||
|
|
||||||
setSupportActionBar(toolbar);
|
|
||||||
ActionBar ab = getSupportActionBar();
|
|
||||||
if (ab != null) {
|
|
||||||
ab.setTitle(R.string.flashing);
|
|
||||||
}
|
|
||||||
setFloating();
|
|
||||||
setFinishOnTouchOutside(false);
|
|
||||||
if (!Shell.rootAccess())
|
|
||||||
reboot.setVisibility(View.GONE);
|
|
||||||
|
|
||||||
logs = Collections.synchronizedList(new ArrayList<>());
|
|
||||||
console = new ConsoleList();
|
|
||||||
rv.setAdapter(new ConsoleAdapter());
|
|
||||||
|
|
||||||
Intent intent = getIntent();
|
|
||||||
Uri uri = intent.getData();
|
|
||||||
|
|
||||||
switch (intent.getStringExtra(Const.Key.FLASH_ACTION)) {
|
|
||||||
case Const.Value.FLASH_ZIP:
|
|
||||||
new FlashModule(uri).exec();
|
|
||||||
break;
|
|
||||||
case Const.Value.UNINSTALL:
|
|
||||||
new Uninstall(uri).exec();
|
|
||||||
break;
|
|
||||||
case Const.Value.FLASH_MAGISK:
|
|
||||||
new DirectInstall().exec();
|
|
||||||
break;
|
|
||||||
case Const.Value.FLASH_INACTIVE_SLOT:
|
|
||||||
new SecondSlot().exec();
|
|
||||||
break;
|
|
||||||
case Const.Value.PATCH_FILE:
|
|
||||||
new PatchFile(uri).exec();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private class ConsoleAdapter extends StringListAdapter<ConsoleAdapter.ViewHolder> {
|
|
||||||
|
|
||||||
ConsoleAdapter() {
|
|
||||||
super(console, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected int itemLayoutRes() {
|
|
||||||
return R.layout.list_item_console;
|
|
||||||
}
|
|
||||||
|
|
||||||
@NonNull
|
|
||||||
@Override
|
|
||||||
public ViewHolder createViewHolder(@NonNull View v) {
|
|
||||||
return new ViewHolder(v);
|
|
||||||
}
|
|
||||||
|
|
||||||
class ViewHolder extends StringListAdapter.ViewHolder {
|
|
||||||
|
|
||||||
public ViewHolder(@NonNull View itemView) {
|
|
||||||
super(itemView);
|
|
||||||
txt.setTextColor(white);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected int textViewResId() {
|
|
||||||
return R.id.txt;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private class ConsoleList extends CallbackList<String> {
|
|
||||||
|
|
||||||
ConsoleList() {
|
|
||||||
super(new ArrayList<>());
|
|
||||||
}
|
|
||||||
|
|
||||||
private void updateUI() {
|
|
||||||
rv.getAdapter().notifyItemChanged(size() - 1);
|
|
||||||
rv.postDelayed(() -> rv.smoothScrollToPosition(size() - 1), 10);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onAddElement(String s) {
|
|
||||||
logs.add(s);
|
|
||||||
updateUI();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String set(int i, String s) {
|
|
||||||
String ret = super.set(i, s);
|
|
||||||
UiThreadHandler.run(this::updateUI);
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private class FlashModule extends FlashZip {
|
|
||||||
|
|
||||||
FlashModule(Uri uri) {
|
|
||||||
super(uri, console, logs);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onResult(boolean success) {
|
|
||||||
if (success) {
|
|
||||||
Utils.loadModules();
|
|
||||||
} else {
|
|
||||||
console.add("! Installation failed");
|
|
||||||
reboot.setVisibility(View.GONE);
|
|
||||||
}
|
|
||||||
buttonPanel.setVisibility(View.VISIBLE);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private class Uninstall extends FlashModule {
|
|
||||||
|
|
||||||
Uninstall(Uri uri) {
|
|
||||||
super(uri);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onResult(boolean success) {
|
|
||||||
if (success)
|
|
||||||
UiThreadHandler.handler.postDelayed(Shell.su("pm uninstall " + getPackageName())::exec, 3000);
|
|
||||||
else
|
|
||||||
super.onResult(false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private abstract class BaseInstaller extends MagiskInstaller {
|
|
||||||
BaseInstaller() {
|
|
||||||
super(console, logs);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onResult(boolean success) {
|
|
||||||
if (success) {
|
|
||||||
console.add("- All done!");
|
|
||||||
} else {
|
|
||||||
Shell.sh("rm -rf " + installDir).submit();
|
|
||||||
console.add("! Installation failed");
|
|
||||||
reboot.setVisibility(View.GONE);
|
|
||||||
}
|
|
||||||
buttonPanel.setVisibility(View.VISIBLE);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private class DirectInstall extends BaseInstaller {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected boolean operations() {
|
|
||||||
return findImage() && extractZip() && patchBoot() && flashBoot();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private class SecondSlot extends BaseInstaller {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected boolean operations() {
|
|
||||||
return findSecondaryImage() && extractZip() && patchBoot() && flashBoot() && postOTA();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private class PatchFile extends BaseInstaller {
|
|
||||||
|
|
||||||
private Uri uri;
|
|
||||||
|
|
||||||
PatchFile(Uri u) {
|
|
||||||
uri = u;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected boolean operations() {
|
|
||||||
return extractZip() && handleFile(uri) && patchBoot() && storeBoot();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -0,0 +1,24 @@
|
|||||||
|
package com.topjohnwu.magisk.ui.flash
|
||||||
|
|
||||||
|
import com.topjohnwu.magisk.Const
|
||||||
|
import com.topjohnwu.magisk.R
|
||||||
|
import com.topjohnwu.magisk.databinding.ActivityFlashBinding
|
||||||
|
import com.topjohnwu.magisk.ui.base.MagiskActivity
|
||||||
|
import org.koin.androidx.viewmodel.ext.android.viewModel
|
||||||
|
import org.koin.core.parameter.parametersOf
|
||||||
|
|
||||||
|
open class FlashActivity : MagiskActivity<FlashViewModel, ActivityFlashBinding>() {
|
||||||
|
|
||||||
|
override val layoutRes: Int = R.layout.activity_flash
|
||||||
|
override val viewModel: FlashViewModel by viewModel {
|
||||||
|
val uri = intent.data
|
||||||
|
val action = intent.getStringExtra(Const.Key.FLASH_ACTION) ?: let { finish();"" }
|
||||||
|
parametersOf(action, uri)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onBackPressed() {
|
||||||
|
if (viewModel.loading) return
|
||||||
|
super.onBackPressed()
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,104 @@
|
|||||||
|
package com.topjohnwu.magisk.ui.flash
|
||||||
|
|
||||||
|
import android.Manifest.permission.READ_EXTERNAL_STORAGE
|
||||||
|
import android.Manifest.permission.WRITE_EXTERNAL_STORAGE
|
||||||
|
import android.content.res.Resources
|
||||||
|
import android.net.Uri
|
||||||
|
import android.os.Handler
|
||||||
|
import androidx.core.os.postDelayed
|
||||||
|
import androidx.databinding.ObservableArrayList
|
||||||
|
import com.skoumal.teanity.databinding.ComparableRvItem
|
||||||
|
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.model.entity.recycler.ConsoleRvItem
|
||||||
|
import com.topjohnwu.magisk.model.flash.FlashResultListener
|
||||||
|
import com.topjohnwu.magisk.model.flash.Flashing
|
||||||
|
import com.topjohnwu.magisk.model.flash.Patching
|
||||||
|
import com.topjohnwu.magisk.ui.base.MagiskViewModel
|
||||||
|
import com.topjohnwu.magisk.utils.*
|
||||||
|
import com.topjohnwu.superuser.Shell
|
||||||
|
import me.tatarka.bindingcollectionadapter2.ItemBinding
|
||||||
|
import java.io.File
|
||||||
|
|
||||||
|
class FlashViewModel(
|
||||||
|
action: String,
|
||||||
|
uri: Uri?,
|
||||||
|
private val resources: Resources
|
||||||
|
) : MagiskViewModel(), FlashResultListener {
|
||||||
|
|
||||||
|
val canShowReboot = Shell.rootAccess()
|
||||||
|
val showRestartTitle = KObservableField(false)
|
||||||
|
|
||||||
|
val behaviorText = KObservableField(resources.getString(R.string.flashing))
|
||||||
|
|
||||||
|
val items = DiffObservableList(ComparableRvItem.callback)
|
||||||
|
val itemBinding = ItemBinding.of<ComparableRvItem<*>> { itemBinding, _, item ->
|
||||||
|
item.bind(itemBinding)
|
||||||
|
itemBinding.bindExtra(BR.viewModel, this@FlashViewModel)
|
||||||
|
}
|
||||||
|
|
||||||
|
private val rawItems = ObservableArrayList<String>()
|
||||||
|
|
||||||
|
init {
|
||||||
|
rawItems.sendUpdatesTo(items) { it.map { ConsoleRvItem(it) } }
|
||||||
|
|
||||||
|
state = State.LOADING
|
||||||
|
|
||||||
|
val uri = uri ?: Uri.EMPTY
|
||||||
|
when (action) {
|
||||||
|
Const.Value.FLASH_ZIP -> Flashing
|
||||||
|
.Install(uri, rawItems, rawItems, this)
|
||||||
|
.exec()
|
||||||
|
Const.Value.UNINSTALL -> Flashing
|
||||||
|
.Uninstall(uri, rawItems, rawItems, this)
|
||||||
|
.exec()
|
||||||
|
Const.Value.FLASH_MAGISK -> Patching
|
||||||
|
.Direct(rawItems, rawItems, this)
|
||||||
|
.exec()
|
||||||
|
Const.Value.FLASH_INACTIVE_SLOT -> Patching
|
||||||
|
.SecondSlot(rawItems, rawItems, this)
|
||||||
|
.exec()
|
||||||
|
Const.Value.PATCH_FILE -> Patching
|
||||||
|
.File(uri, rawItems, rawItems, this)
|
||||||
|
.exec()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onResult(isSuccess: Boolean) {
|
||||||
|
state = if (isSuccess) State.LOADED else State.LOADING_FAILED
|
||||||
|
behaviorText.value = when {
|
||||||
|
isSuccess -> resources.getString(R.string.done)
|
||||||
|
else -> resources.getString(R.string.failure)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isSuccess) {
|
||||||
|
Handler().postDelayed(500) {
|
||||||
|
showRestartTitle.value = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun savePressed() = withPermissions(READ_EXTERNAL_STORAGE, WRITE_EXTERNAL_STORAGE)
|
||||||
|
.map { now }
|
||||||
|
.map { it.toTime(timeFormatFull) }
|
||||||
|
.map { Const.MAGISK_INSTALL_LOG_FILENAME.format(it) }
|
||||||
|
.map { File(Const.EXTERNAL_PATH, it) }
|
||||||
|
.map { file ->
|
||||||
|
val log = items.filterIsInstance<ConsoleRvItem>()
|
||||||
|
.joinToString("\n") { it.item }
|
||||||
|
file.writeText(log)
|
||||||
|
file.path
|
||||||
|
}
|
||||||
|
.subscribeK { SnackbarEvent(it).publish() }
|
||||||
|
.add()
|
||||||
|
|
||||||
|
fun restartPressed() = RootUtils.reboot()
|
||||||
|
|
||||||
|
fun backPressed() = back()
|
||||||
|
|
||||||
|
}
|
@ -1,6 +1,7 @@
|
|||||||
package com.topjohnwu.magisk.utils
|
package com.topjohnwu.magisk.utils
|
||||||
|
|
||||||
import android.view.View
|
import android.view.View
|
||||||
|
import android.widget.TextView
|
||||||
import androidx.annotation.ColorInt
|
import androidx.annotation.ColorInt
|
||||||
import androidx.annotation.DrawableRes
|
import androidx.annotation.DrawableRes
|
||||||
import androidx.appcompat.widget.AppCompatImageView
|
import androidx.appcompat.widget.AppCompatImageView
|
||||||
@ -9,10 +10,15 @@ import androidx.databinding.BindingAdapter
|
|||||||
import androidx.databinding.InverseBindingAdapter
|
import androidx.databinding.InverseBindingAdapter
|
||||||
import androidx.databinding.InverseBindingListener
|
import androidx.databinding.InverseBindingListener
|
||||||
import androidx.drawerlayout.widget.DrawerLayout
|
import androidx.drawerlayout.widget.DrawerLayout
|
||||||
|
import androidx.interpolator.view.animation.FastOutSlowInInterpolator
|
||||||
import androidx.viewpager.widget.ViewPager
|
import androidx.viewpager.widget.ViewPager
|
||||||
import com.google.android.material.navigation.NavigationView
|
import com.google.android.material.navigation.NavigationView
|
||||||
|
import com.skoumal.teanity.extensions.subscribeK
|
||||||
import com.topjohnwu.magisk.R
|
import com.topjohnwu.magisk.R
|
||||||
import com.topjohnwu.magisk.model.entity.state.IndeterminateState
|
import com.topjohnwu.magisk.model.entity.state.IndeterminateState
|
||||||
|
import io.reactivex.Observable
|
||||||
|
import io.reactivex.disposables.Disposable
|
||||||
|
import java.util.concurrent.TimeUnit
|
||||||
|
|
||||||
|
|
||||||
@BindingAdapter("onNavigationClick")
|
@BindingAdapter("onNavigationClick")
|
||||||
@ -80,4 +86,28 @@ fun setPositionChangedListener(view: ViewPager, listener: InverseBindingListener
|
|||||||
positionOffsetPixels: Int
|
positionOffsetPixels: Int
|
||||||
) = listener.onChange()
|
) = listener.onChange()
|
||||||
})
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
@BindingAdapter("invisibleScale")
|
||||||
|
fun setInvisibleWithScale(view: View, isInvisible: Boolean) {
|
||||||
|
view.animate()
|
||||||
|
.scaleX(if (isInvisible) 0f else 1f)
|
||||||
|
.scaleY(if (isInvisible) 0f else 1f)
|
||||||
|
.setInterpolator(FastOutSlowInInterpolator())
|
||||||
|
.start()
|
||||||
|
}
|
||||||
|
|
||||||
|
@BindingAdapter("movieBehavior", "movieBehaviorText")
|
||||||
|
fun setMovieBehavior(view: TextView, isMovieBehavior: Boolean, text: String) {
|
||||||
|
(view.tag as? Disposable)?.dispose()
|
||||||
|
if (isMovieBehavior) {
|
||||||
|
val observer = Observable
|
||||||
|
.interval(150, TimeUnit.MILLISECONDS)
|
||||||
|
.subscribeK {
|
||||||
|
view.text = text.replaceRandomWithSpecial()
|
||||||
|
}
|
||||||
|
view.tag = observer
|
||||||
|
} else {
|
||||||
|
view.text = text
|
||||||
|
}
|
||||||
}
|
}
|
@ -1,6 +1,49 @@
|
|||||||
package com.topjohnwu.magisk.utils
|
package com.topjohnwu.magisk.utils
|
||||||
|
|
||||||
|
import androidx.databinding.ObservableList
|
||||||
|
import com.skoumal.teanity.extensions.subscribeK
|
||||||
|
import com.skoumal.teanity.util.DiffObservableList
|
||||||
|
import io.reactivex.disposables.Disposable
|
||||||
|
|
||||||
fun <T> MutableList<T>.update(newList: List<T>) {
|
fun <T> MutableList<T>.update(newList: List<T>) {
|
||||||
clear()
|
clear()
|
||||||
addAll(newList)
|
addAll(newList)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun <T1, T2> ObservableList<T1>.sendUpdatesTo(
|
||||||
|
target: DiffObservableList<T2>,
|
||||||
|
mapper: (List<T1>) -> List<T2>
|
||||||
|
) {
|
||||||
|
addOnListChangedCallback(object :
|
||||||
|
ObservableList.OnListChangedCallback<ObservableList<T1>>() {
|
||||||
|
override fun onChanged(sender: ObservableList<T1>?) {
|
||||||
|
updateAsync(sender ?: return)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onItemRangeRemoved(sender: ObservableList<T1>?, p0: Int, p1: Int) {
|
||||||
|
updateAsync(sender ?: return)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onItemRangeMoved(sender: ObservableList<T1>?, p0: Int, p1: Int, p2: Int) {
|
||||||
|
updateAsync(sender ?: return)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onItemRangeInserted(sender: ObservableList<T1>?, p0: Int, p1: Int) {
|
||||||
|
updateAsync(sender ?: return)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onItemRangeChanged(sender: ObservableList<T1>?, p0: Int, p1: Int) {
|
||||||
|
updateAsync(sender ?: return)
|
||||||
|
}
|
||||||
|
|
||||||
|
private var updater: Disposable? = null
|
||||||
|
|
||||||
|
private fun updateAsync(sender: List<T1>) {
|
||||||
|
updater?.dispose()
|
||||||
|
updater = sender.toSingle()
|
||||||
|
.map { mapper(it) }
|
||||||
|
.map { it to target.calculateDiff(it) }
|
||||||
|
.subscribeK { target.update(it.first, it.second) }
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
11
app/src/main/java/com/topjohnwu/magisk/utils/XString.kt
Normal file
11
app/src/main/java/com/topjohnwu/magisk/utils/XString.kt
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
package com.topjohnwu.magisk.utils
|
||||||
|
|
||||||
|
val specialChars = arrayOf('!', '@', '#', '$', '%', '&', '?')
|
||||||
|
|
||||||
|
fun String.replaceRandomWithSpecial(): String {
|
||||||
|
var random: Char
|
||||||
|
do {
|
||||||
|
random = random()
|
||||||
|
} while (random == '.')
|
||||||
|
return replace(random, specialChars.random())
|
||||||
|
}
|
18
app/src/main/java/com/topjohnwu/magisk/utils/XTime.kt
Normal file
18
app/src/main/java/com/topjohnwu/magisk/utils/XTime.kt
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
package com.topjohnwu.magisk.utils
|
||||||
|
|
||||||
|
import java.text.ParseException
|
||||||
|
import java.text.SimpleDateFormat
|
||||||
|
import java.util.*
|
||||||
|
|
||||||
|
val now get() = System.currentTimeMillis()
|
||||||
|
|
||||||
|
fun Long.toTime(format: SimpleDateFormat) = format.format(this).orEmpty()
|
||||||
|
fun String.toTime(format: SimpleDateFormat) = try {
|
||||||
|
format.parse(this)?.time ?: -1
|
||||||
|
} catch (e: ParseException) {
|
||||||
|
-1L
|
||||||
|
}
|
||||||
|
|
||||||
|
private val locale get() = Locale.getDefault()
|
||||||
|
val timeFormatFull by lazy { SimpleDateFormat("YYYY/MM/DD_HH:mm:ss", locale) }
|
||||||
|
val timeFormatStandard by lazy { SimpleDateFormat("YYYY-MM-DD'T'HH:mm:ss'Z'", locale) }
|
10
app/src/main/res/drawable/ic_back.xml
Normal file
10
app/src/main/res/drawable/ic_back.xml
Normal 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="@color/colorText"
|
||||||
|
android:pathData="M20,11V13H8L13.5,18.5L12.08,19.92L4.16,12L12.08,4.08L13.5,5.5L8,11H20Z" />
|
||||||
|
</vector>
|
10
app/src/main/res/drawable/ic_restart.xml
Normal file
10
app/src/main/res/drawable/ic_restart.xml
Normal 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="#000"
|
||||||
|
android:pathData="M12,4C14.1,4 16.1,4.8 17.6,6.3C20.7,9.4 20.7,14.5 17.6,17.6C15.8,19.5 13.3,20.2 10.9,19.9L11.4,17.9C13.1,18.1 14.9,17.5 16.2,16.2C18.5,13.9 18.5,10.1 16.2,7.7C15.1,6.6 13.5,6 12,6V10.6L7,5.6L12,0.6V4M6.3,17.6C3.7,15 3.3,11 5.1,7.9L6.6,9.4C5.5,11.6 5.9,14.4 7.8,16.2C8.3,16.7 8.9,17.1 9.6,17.4L9,19.4C8,19 7.1,18.4 6.3,17.6Z" />
|
||||||
|
</vector>
|
@ -1,61 +1,150 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
<layout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
android:layout_width="match_parent"
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
android:layout_height="match_parent"
|
xmlns:tools="http://schemas.android.com/tools">
|
||||||
android:background="@color/flashing_background_color"
|
|
||||||
android:orientation="vertical">
|
|
||||||
|
|
||||||
<include layout="@layout/toolbar" />
|
<data>
|
||||||
|
|
||||||
<HorizontalScrollView xmlns:android="http://schemas.android.com/apk/res/android"
|
<variable
|
||||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
name="viewModel"
|
||||||
|
type="com.topjohnwu.magisk.ui.flash.FlashViewModel" />
|
||||||
|
|
||||||
|
</data>
|
||||||
|
|
||||||
|
<androidx.coordinatorlayout.widget.CoordinatorLayout
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="0dp"
|
android:layout_height="match_parent">
|
||||||
android:layout_weight="1">
|
|
||||||
|
|
||||||
<androidx.recyclerview.widget.RecyclerView
|
<com.google.android.material.appbar.AppBarLayout
|
||||||
android:id="@+id/recyclerView"
|
android:layout_width="match_parent"
|
||||||
android:layout_width="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
|
android:theme="@style/AppBarLayoutTheme.Flashing">
|
||||||
|
|
||||||
|
<androidx.appcompat.widget.Toolbar
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="?attr/actionBarSize"
|
||||||
|
app:contentInsetLeft="0dp"
|
||||||
|
app:contentInsetStart="0dp"
|
||||||
|
app:popupTheme="@style/ToolbarPopupTheme.Flashing">
|
||||||
|
|
||||||
|
<androidx.constraintlayout.widget.ConstraintLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:animateLayoutChanges="true">
|
||||||
|
|
||||||
|
<FrameLayout
|
||||||
|
invisibleScale="@{viewModel.loading}"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:background="?attr/selectableItemBackgroundBorderless"
|
||||||
|
android:onClick="@{() -> viewModel.backPressed()}"
|
||||||
|
app:layout_constraintDimensionRatio="1:1"
|
||||||
|
app:layout_constraintLeft_toLeftOf="parent">
|
||||||
|
|
||||||
|
<androidx.appcompat.widget.AppCompatImageView
|
||||||
|
style="@style/Widget.Icon"
|
||||||
|
android:layout_gravity="center"
|
||||||
|
android:background="@android:color/transparent"
|
||||||
|
app:srcCompat="@drawable/ic_back"
|
||||||
|
app:tint="@android:color/white" />
|
||||||
|
|
||||||
|
</FrameLayout>
|
||||||
|
|
||||||
|
<androidx.appcompat.widget.AppCompatTextView
|
||||||
|
style="@style/Widget.Text.Emphasize"
|
||||||
|
movieBehavior="@{viewModel.loading}"
|
||||||
|
movieBehaviorText="@{viewModel.behaviorText}"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:fontFamily="monospace"
|
||||||
|
android:textColor="@android:color/white"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintLeft_toLeftOf="parent"
|
||||||
|
app:layout_constraintRight_toRightOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
|
tools:text="Flashing..." />
|
||||||
|
|
||||||
|
<FrameLayout
|
||||||
|
invisibleScale="@{viewModel.loading}"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:background="?attr/selectableItemBackgroundBorderless"
|
||||||
|
android:onClick="@{() -> viewModel.savePressed()}"
|
||||||
|
app:layout_constraintDimensionRatio="1:1"
|
||||||
|
app:layout_constraintRight_toRightOf="parent">
|
||||||
|
|
||||||
|
<androidx.appcompat.widget.AppCompatImageView
|
||||||
|
style="@style/Widget.Icon"
|
||||||
|
android:layout_gravity="center"
|
||||||
|
android:background="@android:color/transparent"
|
||||||
|
app:srcCompat="@drawable/ic_save"
|
||||||
|
app:tint="@android:color/white" />
|
||||||
|
|
||||||
|
</FrameLayout>
|
||||||
|
|
||||||
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
|
|
||||||
|
</androidx.appcompat.widget.Toolbar>
|
||||||
|
|
||||||
|
</com.google.android.material.appbar.AppBarLayout>
|
||||||
|
|
||||||
|
<androidx.constraintlayout.widget.ConstraintLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
android:paddingStart="8dp"
|
app:layout_behavior="@string/appbar_scrolling_view_behavior">
|
||||||
android:paddingEnd="8dp"
|
|
||||||
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager" />
|
|
||||||
|
|
||||||
</HorizontalScrollView>
|
<androidx.recyclerview.widget.RecyclerView
|
||||||
|
itemBinding="@{viewModel.itemBinding}"
|
||||||
|
items="@{viewModel.items}"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:orientation="vertical"
|
||||||
|
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
|
||||||
|
tools:listitem="@layout/item_console" />
|
||||||
|
|
||||||
<LinearLayout
|
<com.google.android.material.card.MaterialCardView
|
||||||
android:id="@+id/button_panel"
|
invisibleScale="@{!viewModel.loaded || !viewModel.canShowReboot}"
|
||||||
style="?android:buttonStyle"
|
android:layout_width="wrap_content"
|
||||||
android:layout_width="match_parent"
|
android:layout_height="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_margin="@dimen/margin_generic"
|
||||||
android:layout_marginTop="10dp"
|
app:cardBackgroundColor="@color/colorSecondary"
|
||||||
android:orientation="horizontal"
|
app:cardCornerRadius="26dp"
|
||||||
android:visibility="gone">
|
app:cardPreventCornerOverlap="true"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintRight_toRightOf="parent">
|
||||||
|
|
||||||
<Button
|
<LinearLayout
|
||||||
android:id="@+id/close"
|
android:layout_width="wrap_content"
|
||||||
style="?android:borderlessButtonStyle"
|
android:layout_height="wrap_content"
|
||||||
android:layout_width="0dp"
|
android:animateLayoutChanges="true"
|
||||||
android:layout_height="50dp"
|
android:background="?attr/selectableItemBackground"
|
||||||
android:layout_weight="1"
|
android:gravity="center"
|
||||||
android:text="@string/close" />
|
android:onClick="@{() -> viewModel.restartPressed()}"
|
||||||
|
android:padding="@dimen/margin_generic_half">
|
||||||
|
|
||||||
<Button
|
<androidx.appcompat.widget.AppCompatImageView
|
||||||
android:id="@+id/save_logs"
|
style="@style/Widget.Icon"
|
||||||
style="?android:borderlessButtonStyle"
|
android:layout_width="wrap_content"
|
||||||
android:layout_width="0dp"
|
android:layout_height="wrap_content"
|
||||||
android:layout_height="50dp"
|
android:background="@android:color/transparent"
|
||||||
android:layout_weight="1"
|
app:srcCompat="@drawable/ic_restart"
|
||||||
android:text="@string/menuSaveLog" />
|
app:tint="@color/colorTextTinted" />
|
||||||
|
|
||||||
<Button
|
<androidx.appcompat.widget.AppCompatTextView
|
||||||
android:id="@+id/reboot"
|
style="@style/Widget.Text.Emphasize.Tinted"
|
||||||
style="?android:borderlessButtonStyle"
|
gone="@{!viewModel.showRestartTitle}"
|
||||||
android:layout_width="0dp"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="50dp"
|
android:layout_height="wrap_content"
|
||||||
android:layout_weight="1"
|
android:layout_marginLeft="@dimen/margin_generic_half"
|
||||||
android:text="@string/reboot" />
|
android:paddingRight="@dimen/margin_generic_half"
|
||||||
|
android:text="@string/reboot" />
|
||||||
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
</LinearLayout>
|
</com.google.android.material.card.MaterialCardView>
|
||||||
|
|
||||||
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
|
|
||||||
|
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
||||||
|
|
||||||
|
</layout>
|
@ -99,7 +99,9 @@
|
|||||||
|
|
||||||
<string name="dtbo_patched_title">DTBO was patched!</string>
|
<string name="dtbo_patched_title">DTBO was patched!</string>
|
||||||
<string name="dtbo_patched_reboot">Magisk Manager has patched dtbo.img. Please reboot.</string>
|
<string name="dtbo_patched_reboot">Magisk Manager has patched dtbo.img. Please reboot.</string>
|
||||||
<string name="flashing">Flashing</string>
|
<string name="flashing">Flashing…</string>
|
||||||
|
<string name="done">Done!</string>
|
||||||
|
<string name="failure">Failed</string>
|
||||||
<string name="hide_manager_title">Hiding Magisk Manager…</string>
|
<string name="hide_manager_title">Hiding Magisk Manager…</string>
|
||||||
<string name="hide_manager_fail_toast">Hide Magisk Manager failed.</string>
|
<string name="hide_manager_fail_toast">Hide Magisk Manager failed.</string>
|
||||||
<string name="open_link_failed_toast">No application found to open the link.</string>
|
<string name="open_link_failed_toast">No application found to open the link.</string>
|
||||||
|
Loading…
Reference in New Issue
Block a user