Add migration system.
This commit is contained in:
@@ -0,0 +1,263 @@
|
||||
package exh.ui.migration
|
||||
|
||||
import android.app.Activity
|
||||
import android.content.DialogInterface
|
||||
import android.content.Intent
|
||||
import android.content.pm.ActivityInfo
|
||||
import android.graphics.Color
|
||||
import android.net.Uri
|
||||
import android.os.Build
|
||||
import android.text.Html
|
||||
import android.view.Gravity
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.ImageView
|
||||
import android.widget.LinearLayout
|
||||
import android.widget.TextView
|
||||
import com.afollestad.materialdialogs.MaterialDialog
|
||||
import eu.kanade.tachiyomi.R
|
||||
import eu.kanade.tachiyomi.data.database.DatabaseHelper
|
||||
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
|
||||
import eu.kanade.tachiyomi.data.preference.getOrDefault
|
||||
import eu.kanade.tachiyomi.ui.main.MainActivity
|
||||
import eu.kanade.tachiyomi.util.toast
|
||||
import uy.kohesive.injekt.injectLazy
|
||||
|
||||
/**
|
||||
* Guide to migrate thel ibrary between two TachiyomiEH apps
|
||||
*/
|
||||
|
||||
class LibraryMigrationManager(val context: MainActivity,
|
||||
val dismissQueue: MutableList<DialogInterface>? = null) {
|
||||
val preferenceHelper: PreferencesHelper by injectLazy()
|
||||
|
||||
val databaseHelper: DatabaseHelper by injectLazy()
|
||||
|
||||
private fun mainTachiyomiEHActivity()
|
||||
= context.packageManager.getLaunchIntentForPackage(TACHIYOMI_EH_PACKAGE)
|
||||
|
||||
fun askMigrationIfNecessary() {
|
||||
//Check already migrated
|
||||
val ms = preferenceHelper.migrationStatus().getOrDefault()
|
||||
if(ms == MigrationStatus.COMPLETED) return
|
||||
|
||||
val ma = mainTachiyomiEHActivity()
|
||||
|
||||
//Old version not installed, migration not required
|
||||
if(ma == null) {
|
||||
preferenceHelper.migrationStatus().set(MigrationStatus.COMPLETED)
|
||||
return
|
||||
}
|
||||
|
||||
context.requestPermissionsOnMarshmallow()
|
||||
if(ms == MigrationStatus.NOT_INITIALIZED) {
|
||||
//We need migration
|
||||
jumpToMigrationStep(MigrationStatus.NOTIFY_USER)
|
||||
} else {
|
||||
//Migration process already started, jump to step
|
||||
jumpToMigrationStep(ms)
|
||||
}
|
||||
}
|
||||
|
||||
fun notifyUserMigration() {
|
||||
redDialog()
|
||||
.title("Migration necessary")
|
||||
.content("Due to an unplanned technical error, this update could not be applied on top of the old app and was instead installed as a separate app!\n\n" +
|
||||
"To keep your library/favorited galleries after this update, you must migrate it over from the old app.\n\n" +
|
||||
"This migration process is not automatic, tap 'CONTINUE' to be guided through it.")
|
||||
.positiveText("Continue")
|
||||
.negativeText("Cancel")
|
||||
.onPositive { materialDialog, dialogAction -> jumpToMigrationStep(MigrationStatus.OPEN_BACKUP_MENU) }
|
||||
.onNegative { materialDialog, dialogAction -> warnUserMigration() }
|
||||
.show()
|
||||
}
|
||||
|
||||
fun warnUserMigration() {
|
||||
redDialog()
|
||||
.title("Are you sure?")
|
||||
.content("You are cancelling the migration process! If you do not migrate your library, you will lose all of your favorited galleries!\n\n" +
|
||||
"Press 'MIGRATE' to restart the migration process, press 'OK' if you still wish to cancel the migration process.")
|
||||
.positiveText("Ok")
|
||||
.negativeText("Migrate")
|
||||
.onPositive { materialDialog, dialogAction -> completeMigration() }
|
||||
.onNegative { materialDialog, dialogAction -> notifyUserMigration() }
|
||||
.show()
|
||||
}
|
||||
|
||||
fun openBackupMenuMigrationStep() {
|
||||
val view = MigrationViewBuilder()
|
||||
.text("1. Use the 'LAUNCH OLD APP' button below to launch the old app.")
|
||||
.text("2. Tap on the 'three-lines' button at the top-left of the screen as shown below:")
|
||||
.image(R.drawable.eh_migration_hamburgers)
|
||||
.text("3. Highlight the 'Backup' item by tapping on it as shown below:")
|
||||
.image(R.drawable.eh_migration_backup)
|
||||
.text("4. Return to this app but <b>do not close</b> the old app.")
|
||||
.text("5. When you have completed the above steps, tap 'CONTINUE'.")
|
||||
.toView(context)
|
||||
|
||||
migrationStepDialog(1, null, MigrationStatus.PERFORM_BACKUP)
|
||||
.customView(view, true)
|
||||
.neutralText("Launch Old App")
|
||||
.onNeutral { materialDialog, dialogAction ->
|
||||
//Auto dismiss messes this up so we have to reopen the dialog manually
|
||||
val ma = mainTachiyomiEHActivity()
|
||||
if(ma != null) {
|
||||
context.startActivity(ma)
|
||||
} else {
|
||||
context.toast("Failed to launch old app! Try launching it manually.")
|
||||
}
|
||||
openBackupMenuMigrationStep()
|
||||
}
|
||||
.show()
|
||||
}
|
||||
|
||||
fun performBackupMigrationStep() {
|
||||
val view = MigrationViewBuilder()
|
||||
.text("6. Return to the old app.")
|
||||
.text("7. Tap on the 'BACKUP' button in the old app (shown below):")
|
||||
.image(R.drawable.eh_migration_backup_button)
|
||||
.text("8. In the menu that appears, tap on 'Complete migration' (shown below):")
|
||||
.image(R.drawable.eh_migration_share_icon)
|
||||
.toView(context)
|
||||
|
||||
migrationStepDialog(2, MigrationStatus.OPEN_BACKUP_MENU, null)
|
||||
.customView(view, true)
|
||||
.show()
|
||||
}
|
||||
|
||||
fun finalizeMigration() {
|
||||
migrationDialog()
|
||||
.title("Migration complete")
|
||||
.content(fromHtmlCompat("Your library has been migrated over to the new app!<br><br>" +
|
||||
"You may now uninstall the old app by pressing the 'UNINSTALL OLD APP' button below!<br><br>" +
|
||||
"<b>If you were previously using ExHentai, your library may appear blank, just log in again to fix this.</b><br><br>" +
|
||||
"Then tap 'OK' to exit the migration process!"))
|
||||
.positiveText("Ok")
|
||||
.neutralText("Uninstall Old App")
|
||||
.onPositive { materialDialog, dialogAction ->
|
||||
completeMigration()
|
||||
//Check if the metadata needs to be updated
|
||||
databaseHelper.getLibraryMangas().asRxSingle().subscribe {
|
||||
if (it.size > 0)
|
||||
context.runOnUiThread {
|
||||
MetadataFetchDialog().tryAskMigration(context)
|
||||
}
|
||||
}
|
||||
}
|
||||
.onNeutral { materialDialog, dialogAction ->
|
||||
val packageUri = Uri.parse("package:$TACHIYOMI_EH_PACKAGE")
|
||||
val uninstallIntent = Intent(Intent.ACTION_UNINSTALL_PACKAGE, packageUri)
|
||||
context.startActivity(uninstallIntent)
|
||||
//Cancel out auto-dismiss
|
||||
finalizeMigration()
|
||||
}
|
||||
.show()
|
||||
}
|
||||
|
||||
fun migrationDialog() = MaterialDialog.Builder(context)
|
||||
.cancelable(false)
|
||||
.canceledOnTouchOutside(false)
|
||||
.showListener { dismissQueue?.add(it) }!!
|
||||
|
||||
fun migrationStepDialog(step: Int, previousStep: Int?, nextStep: Int?) = migrationDialog()
|
||||
.title("Migration part $step of ${MigrationStatus.MAX_MIGRATION_STEPS}")
|
||||
.apply {
|
||||
if(previousStep != null) {
|
||||
negativeText("Back")
|
||||
onNegative { materialDialog, dialogAction -> jumpToMigrationStep(previousStep) }
|
||||
}
|
||||
if(nextStep != null) {
|
||||
positiveText("Continue")
|
||||
onPositive { materialDialog, dialogAction -> jumpToMigrationStep(nextStep) }
|
||||
}
|
||||
}!!
|
||||
|
||||
fun redDialog() = migrationDialog()
|
||||
.backgroundColor(Color.parseColor("#F44336"))
|
||||
.titleColor(Color.WHITE)
|
||||
.contentColor(Color.WHITE)
|
||||
.positiveColor(Color.WHITE)
|
||||
.negativeColor(Color.WHITE)
|
||||
.neutralColor(Color.WHITE)!!
|
||||
|
||||
fun completeMigration() {
|
||||
preferenceHelper.migrationStatus().set(MigrationStatus.COMPLETED)
|
||||
|
||||
//Enable orientation changes again
|
||||
context.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_NOSENSOR
|
||||
}
|
||||
|
||||
fun jumpToMigrationStep(migrationStatus: Int) {
|
||||
preferenceHelper.migrationStatus().set(migrationStatus)
|
||||
|
||||
//Too lazy to actually deal with orientation changes
|
||||
context.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_NOSENSOR
|
||||
|
||||
when(migrationStatus) {
|
||||
MigrationStatus.NOTIFY_USER -> notifyUserMigration()
|
||||
MigrationStatus.OPEN_BACKUP_MENU -> openBackupMenuMigrationStep()
|
||||
MigrationStatus.PERFORM_BACKUP -> performBackupMigrationStep()
|
||||
MigrationStatus.FINALIZE_MIGRATION -> finalizeMigration()
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val TACHIYOMI_EH_PACKAGE = "eu.kanade.tachiyomi.eh"
|
||||
fun fromHtmlCompat(string: String)
|
||||
= if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.N)
|
||||
Html.fromHtml(string, Html.FROM_HTML_MODE_LEGACY)
|
||||
else
|
||||
Html.fromHtml(string)
|
||||
}
|
||||
|
||||
class MigrationViewBuilder {
|
||||
val elements = mutableListOf<MigrationElement>()
|
||||
fun text(text: String) = apply { elements += TextElement(text) }
|
||||
fun image(drawable: Int) = apply { elements += ImageElement(drawable) }
|
||||
|
||||
fun toView(context: Activity): View {
|
||||
val root = LinearLayout(context)
|
||||
val rootParams = root.layoutParams ?: ViewGroup.LayoutParams(0, 0)
|
||||
|
||||
fun ViewGroup.LayoutParams.setup() = apply {
|
||||
height = ViewGroup.LayoutParams.WRAP_CONTENT
|
||||
width = ViewGroup.LayoutParams.MATCH_PARENT
|
||||
}
|
||||
|
||||
fun dpToPx(dp: Float) = (dp * context.resources.displayMetrics.density + 0.5f).toInt()
|
||||
|
||||
rootParams.setup()
|
||||
root.layoutParams = rootParams
|
||||
root.gravity = Gravity.CENTER
|
||||
root.orientation = LinearLayout.VERTICAL
|
||||
|
||||
for(element in elements) {
|
||||
val view: View
|
||||
if(element is TextElement) {
|
||||
view = TextView(context)
|
||||
view.text = fromHtmlCompat(element.value)
|
||||
} else if(element is ImageElement) {
|
||||
view = ImageView(context)
|
||||
view.setImageResource(element.drawable)
|
||||
view.adjustViewBounds = true
|
||||
} else {
|
||||
throw IllegalArgumentException("Unknown migration view!")
|
||||
}
|
||||
val viewParams = view.layoutParams ?: ViewGroup.LayoutParams(0, 0)
|
||||
viewParams.setup()
|
||||
view.layoutParams = viewParams
|
||||
val eightDpAsPx = dpToPx(8f)
|
||||
view.setPadding(0, eightDpAsPx, 0, eightDpAsPx)
|
||||
|
||||
root.addView(view)
|
||||
}
|
||||
|
||||
return root
|
||||
}
|
||||
}
|
||||
|
||||
open class MigrationElement
|
||||
class TextElement(val value: String): MigrationElement()
|
||||
class ImageElement(val drawable: Int): MigrationElement()
|
||||
}
|
||||
|
||||
+32
-19
@@ -1,4 +1,4 @@
|
||||
package exh.ui
|
||||
package exh.ui.migration
|
||||
|
||||
import android.app.Activity
|
||||
import android.content.pm.ActivityInfo
|
||||
@@ -30,7 +30,7 @@ class MetadataFetchDialog {
|
||||
context.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_NOSENSOR
|
||||
|
||||
val progressDialog = MaterialDialog.Builder(context)
|
||||
.title("Migrating library")
|
||||
.title("Fetching library metadata")
|
||||
.content("Preparing library")
|
||||
.progress(false, 0, true)
|
||||
.cancelable(false)
|
||||
@@ -87,27 +87,40 @@ class MetadataFetchDialog {
|
||||
}
|
||||
|
||||
fun askMigration(activity: Activity) {
|
||||
MaterialDialog.Builder(activity)
|
||||
.title("Migrate library")
|
||||
.content("You need to migrate your library before tag searching in the library will function.\n\n" +
|
||||
"This migration may take a long time depending on your library size and will also use up a significant amount of internet bandwidth.\n\n" +
|
||||
"This process can be done later if required.")
|
||||
.positiveText("Migrate")
|
||||
.negativeText("Later")
|
||||
.onPositive { materialDialog, dialogAction -> show(activity) }
|
||||
.onNegative { materialDialog, dialogAction -> adviseMigrationLater(activity) }
|
||||
.cancelable(false)
|
||||
.canceledOnTouchOutside(false)
|
||||
.dismissListener {
|
||||
preferenceHelper.migrateLibraryAsked().set(true)
|
||||
}.show()
|
||||
var extra = ""
|
||||
db.getLibraryMangas().asRxSingle().subscribe {
|
||||
//Not logged in but have ExHentai galleries
|
||||
if(!preferenceHelper.enableExhentai().getOrDefault()) {
|
||||
it.find { it.source == 2 }?.let {
|
||||
extra = "<b><font color='red'>If you use ExHentai, please log in first before fetching your library metadata!</font></b><br><br>"
|
||||
}
|
||||
}
|
||||
activity.runOnUiThread {
|
||||
MaterialDialog.Builder(activity)
|
||||
.title("Fetch library metadata")
|
||||
.content(LibraryMigrationManager.fromHtmlCompat("You need to fetch your library's metadata before tag searching in the library will function.<br><br>" +
|
||||
"This process may take a long time depending on your library size and will also use up a significant amount of internet bandwidth.<br><br>" +
|
||||
extra +
|
||||
"This process can be done later if required."))
|
||||
.positiveText("Migrate")
|
||||
.negativeText("Later")
|
||||
.onPositive { materialDialog, dialogAction -> show(activity) }
|
||||
.onNegative { materialDialog, dialogAction -> adviseMigrationLater(activity) }
|
||||
.cancelable(false)
|
||||
.canceledOnTouchOutside(false)
|
||||
.dismissListener {
|
||||
preferenceHelper.migrateLibraryAsked().set(true)
|
||||
}.show()
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
fun adviseMigrationLater(activity: Activity) {
|
||||
MaterialDialog.Builder(activity)
|
||||
.title("Migration canceled")
|
||||
.content("Library migration has been canceled.\n\n" +
|
||||
"You can run this operation later by going to: Settings > EHentai > Migrate Library")
|
||||
.title("Metadata fetch canceled")
|
||||
.content("Library metadata fetch has been canceled.\n\n" +
|
||||
"You can run this operation later by going to: Settings > E-Hentai > Migrate library metadata")
|
||||
.positiveText("Ok")
|
||||
.cancelable(true)
|
||||
.canceledOnTouchOutside(true)
|
||||
@@ -0,0 +1,92 @@
|
||||
package exh.ui.migration
|
||||
|
||||
import android.content.Intent
|
||||
import android.os.Bundle
|
||||
import android.support.v4.app.ShareCompat
|
||||
import com.afollestad.materialdialogs.MaterialDialog
|
||||
import eu.kanade.tachiyomi.R
|
||||
import eu.kanade.tachiyomi.data.backup.BackupManager
|
||||
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
|
||||
import eu.kanade.tachiyomi.ui.base.activity.BaseActivity
|
||||
import eu.kanade.tachiyomi.ui.main.MainActivity
|
||||
import kotlinx.android.synthetic.main.toolbar.*
|
||||
import timber.log.Timber
|
||||
import uy.kohesive.injekt.Injekt
|
||||
import uy.kohesive.injekt.api.get
|
||||
import uy.kohesive.injekt.injectLazy
|
||||
import kotlin.concurrent.thread
|
||||
|
||||
/**
|
||||
* Read backups directly from another Tachiyomi app
|
||||
*/
|
||||
|
||||
class MigrationCompletionActivity : BaseActivity() {
|
||||
|
||||
private val backupManager by lazy { BackupManager(Injekt.get()) }
|
||||
|
||||
private val preferenceManager: PreferencesHelper by injectLazy()
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
setAppTheme()
|
||||
super.onCreate(savedInstanceState)
|
||||
setContentView(R.layout.eh_activity_finish_migration)
|
||||
|
||||
setup()
|
||||
|
||||
setupToolbar(toolbar, backNavigation = false)
|
||||
supportActionBar?.setDisplayHomeAsUpEnabled(false)
|
||||
}
|
||||
|
||||
fun setup() {
|
||||
try {
|
||||
val sc = ShareCompat.IntentReader.from(this)
|
||||
Timber.i("CP: " + sc.callingPackage)
|
||||
if(sc.isShareIntent) {
|
||||
//Try to restore backup
|
||||
thread {
|
||||
//Finish old MainActivity
|
||||
preferenceManager.finishMainActivity().set(true)
|
||||
try {
|
||||
backupManager.restoreFromStream(contentResolver.openInputStream(sc.stream))
|
||||
} catch(t: Throwable) {
|
||||
Timber.e(t, "Failed to restore manga/galleries!")
|
||||
migrationError("Failed to restore manga/galleries!")
|
||||
return@thread
|
||||
}
|
||||
|
||||
//Go back to MainActivity
|
||||
//Set final steps
|
||||
preferenceManager.migrationStatus().set(MigrationStatus.FINALIZE_MIGRATION)
|
||||
//Wait for MainActivity to finish
|
||||
Thread.sleep(1000)
|
||||
//Start new MainActivity
|
||||
val intent = Intent(this, MainActivity::class.java)
|
||||
intent.putExtra(MainActivity.Companion.FINALIZE_MIGRATION, true)
|
||||
finish()
|
||||
startActivity(intent)
|
||||
}
|
||||
}
|
||||
} catch(t: Throwable) {
|
||||
Timber.e(t, "Failed to migrate manga!")
|
||||
migrationError("An unknown error occurred during migration!")
|
||||
}
|
||||
}
|
||||
|
||||
fun migrationError(message: String) {
|
||||
runOnUiThread {
|
||||
MaterialDialog.Builder(this)
|
||||
.title("Migration error")
|
||||
.content(message)
|
||||
.positiveText("Ok")
|
||||
.cancelable(false)
|
||||
.canceledOnTouchOutside(false)
|
||||
.dismissListener { finish() }
|
||||
.show()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onBackPressed() {
|
||||
//Do not allow finishing this activity
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
package exh.ui.migration
|
||||
|
||||
class MigrationStatus {
|
||||
companion object {
|
||||
val NOT_INITIALIZED = -1
|
||||
val COMPLETED = 0
|
||||
|
||||
//Migration process
|
||||
val NOTIFY_USER = 1
|
||||
val OPEN_BACKUP_MENU = 2
|
||||
val PERFORM_BACKUP = 3
|
||||
val FINALIZE_MIGRATION = 4
|
||||
|
||||
val MAX_MIGRATION_STEPS = 2
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user