From ce492ab7ac0e3e6bfae734dc23ff28752355fdc7 Mon Sep 17 00:00:00 2001 From: arkon Date: Wed, 6 May 2020 22:23:15 -0400 Subject: [PATCH] Refactor backup service --- .../tachiyomi/data/backup/BackupConst.kt | 2 + .../data/backup/BackupCreateService.kt | 111 ++++++++++++++---- .../tachiyomi/data/backup/BackupCreatorJob.kt | 8 +- .../tachiyomi/data/backup/BackupManager.kt | 28 ++--- .../tachiyomi/data/backup/BackupNotifier.kt | 84 +++++++++++++ .../data/backup/BackupRestoreService.kt | 6 +- .../data/notification/NotificationReceiver.kt | 48 ++++++++ .../data/notification/Notifications.kt | 8 +- .../ui/setting/SettingsBackupController.kt | 64 +--------- .../util/system/ContextExtensions.kt | 16 +++ app/src/main/res/values/strings.xml | 3 +- 11 files changed, 265 insertions(+), 113 deletions(-) create mode 100644 app/src/main/java/eu/kanade/tachiyomi/data/backup/BackupNotifier.kt diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/backup/BackupConst.kt b/app/src/main/java/eu/kanade/tachiyomi/data/backup/BackupConst.kt index e894750722..6b7ef5aad6 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/backup/BackupConst.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/backup/BackupConst.kt @@ -3,6 +3,8 @@ import eu.kanade.tachiyomi.BuildConfig.APPLICATION_ID as ID object BackupConst { + private const val NAME = "BackupRestoreServices" + const val EXTRA_FLAGS = "$ID.$NAME.EXTRA_FLAGS" const val INTENT_FILTER = "SettingsBackupFragment" const val ACTION_BACKUP_COMPLETED_DIALOG = "$ID.$INTENT_FILTER.ACTION_BACKUP_COMPLETED_DIALOG" const val ACTION_ERROR_BACKUP_DIALOG = "$ID.$INTENT_FILTER.ACTION_ERROR_BACKUP_DIALOG" diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/backup/BackupCreateService.kt b/app/src/main/java/eu/kanade/tachiyomi/data/backup/BackupCreateService.kt index 1721ff624c..a212db9de4 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/backup/BackupCreateService.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/backup/BackupCreateService.kt @@ -1,25 +1,22 @@ package eu.kanade.tachiyomi.data.backup -import android.app.IntentService +import android.app.Service import android.content.Context import android.content.Intent import android.net.Uri -import com.google.gson.JsonArray -import eu.kanade.tachiyomi.data.database.models.Manga -import eu.kanade.tachiyomi.BuildConfig.APPLICATION_ID as ID +import android.os.Build +import android.os.IBinder +import android.os.PowerManager +import com.hippo.unifile.UniFile +import eu.kanade.tachiyomi.data.notification.Notifications +import eu.kanade.tachiyomi.util.system.isServiceRunning /** - * [IntentService] used to backup [Manga] information to [JsonArray] + * Service for backing up library information to a JSON file. */ -class BackupCreateService : IntentService(NAME) { +class BackupCreateService : Service() { companion object { - // Name of class - private const val NAME = "BackupCreateService" - - // Options for backup - private const val EXTRA_FLAGS = "$ID.$NAME.EXTRA_FLAGS" - // Filter options internal const val BACKUP_CATEGORY = 0x1 internal const val BACKUP_CATEGORY_MASK = 0x1 @@ -31,6 +28,15 @@ class BackupCreateService : IntentService(NAME) { internal const val BACKUP_TRACK_MASK = 0x8 internal const val BACKUP_ALL = 0xF + /** + * Returns the status of the service. + * + * @param context the application context. + * @return true if the service is running, false otherwise. + */ + fun isRunning(context: Context): Boolean = + context.isServiceRunning(BackupCreateService::class.java) + /** * Make a backup from library * @@ -38,25 +44,78 @@ class BackupCreateService : IntentService(NAME) { * @param uri path of Uri * @param flags determines what to backup */ - fun makeBackup(context: Context, uri: Uri, flags: Int) { - val intent = Intent(context, BackupCreateService::class.java).apply { - putExtra(BackupConst.EXTRA_URI, uri) - putExtra(EXTRA_FLAGS, flags) + fun start(context: Context, uri: Uri, flags: Int) { + if (!isRunning(context)) { + val intent = Intent(context, BackupCreateService::class.java).apply { + putExtra(BackupConst.EXTRA_URI, uri) + putExtra(BackupConst.EXTRA_FLAGS, flags) + } + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) { + context.startService(intent) + } else { + context.startForegroundService(intent) + } } - context.startService(intent) } } - private val backupManager by lazy { BackupManager(this) } + /** + * Wake lock that will be held until the service is destroyed. + */ + private lateinit var wakeLock: PowerManager.WakeLock - override fun onHandleIntent(intent: Intent?) { - if (intent == null) return + private lateinit var backupManager: BackupManager + private lateinit var notifier: BackupNotifier - // Get values - val uri = intent.getParcelableExtra(BackupConst.EXTRA_URI) - val flags = intent.getIntExtra(EXTRA_FLAGS, 0) - // Create backup - if (uri != null) - backupManager.createBackup(uri, flags, false) + override fun onCreate() { + super.onCreate() + notifier = BackupNotifier(this) + + startForeground(Notifications.ID_BACKUP_PROGRESS, notifier.showBackupProgress().build()) + + wakeLock = (getSystemService(Context.POWER_SERVICE) as PowerManager).newWakeLock( + PowerManager.PARTIAL_WAKE_LOCK, "${javaClass.name}:WakeLock" + ) + wakeLock.acquire() + } + + override fun stopService(name: Intent?): Boolean { + destroyJob() + return super.stopService(name) + } + + override fun onDestroy() { + destroyJob() + super.onDestroy() + } + + private fun destroyJob() { + if (wakeLock.isHeld) { + wakeLock.release() + } + } + + /** + * This method needs to be implemented, but it's not used/needed. + */ + override fun onBind(intent: Intent): IBinder? = null + + override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { + if (intent == null) return START_NOT_STICKY + + try { + val uri = intent.getParcelableExtra(BackupConst.EXTRA_URI) + val backupFlags = intent.getIntExtra(BackupConst.EXTRA_FLAGS, 0) + backupManager = BackupManager(this) + + val backupFileUri = Uri.parse(backupManager.createBackup(uri, backupFlags, false)) + val unifile = UniFile.fromUri(this, backupFileUri) + notifier.showBackupComplete(unifile) + } catch (e: Exception) { + notifier.showBackupError(e.message) + } + + stopSelf(startId) + return START_NOT_STICKY } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/backup/BackupCreatorJob.kt b/app/src/main/java/eu/kanade/tachiyomi/data/backup/BackupCreatorJob.kt index f10b64ed3a..bfce0dc367 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/backup/BackupCreatorJob.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/backup/BackupCreatorJob.kt @@ -21,8 +21,12 @@ class BackupCreatorJob(private val context: Context, workerParams: WorkerParamet val backupManager = BackupManager(context) val uri = Uri.parse(preferences.backupsDirectory().getOrDefault()) val flags = BackupCreateService.BACKUP_ALL - backupManager.createBackup(uri, flags, true) - return Result.success() + return try { + backupManager.createBackup(uri, flags, true) + Result.success() + } catch (e: Exception) { + Result.failure() + } } companion object { diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/backup/BackupManager.kt b/app/src/main/java/eu/kanade/tachiyomi/data/backup/BackupManager.kt index 69bf1a57f2..0e86ccec6b 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/backup/BackupManager.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/backup/BackupManager.kt @@ -1,7 +1,6 @@ package eu.kanade.tachiyomi.data.backup import android.content.Context -import android.content.Intent import android.net.Uri import com.github.salomonbrys.kotson.fromJson import com.github.salomonbrys.kotson.registerTypeAdapter @@ -25,6 +24,7 @@ import eu.kanade.tachiyomi.data.backup.models.Backup import eu.kanade.tachiyomi.data.backup.models.Backup.CATEGORIES import eu.kanade.tachiyomi.data.backup.models.Backup.CHAPTERS import eu.kanade.tachiyomi.data.backup.models.Backup.CURRENT_VERSION +import eu.kanade.tachiyomi.data.backup.models.Backup.EXTENSIONS import eu.kanade.tachiyomi.data.backup.models.Backup.HISTORY import eu.kanade.tachiyomi.data.backup.models.Backup.MANGA import eu.kanade.tachiyomi.data.backup.models.Backup.TRACK @@ -51,7 +51,6 @@ import eu.kanade.tachiyomi.source.Source import eu.kanade.tachiyomi.source.SourceManager import eu.kanade.tachiyomi.source.fetchMangaDetailsAsync import eu.kanade.tachiyomi.util.chapter.syncChaptersWithSource -import eu.kanade.tachiyomi.util.system.sendLocalBroadcast import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext import rx.Observable @@ -120,7 +119,7 @@ class BackupManager(val context: Context, version: Int = CURRENT_VERSION) { * @param uri path of Uri * @param isJob backup called from job */ - fun createBackup(uri: Uri, flags: Int, isJob: Boolean) { + fun createBackup(uri: Uri, flags: Int, isJob: Boolean): String? { // Create root object val root = JsonObject() @@ -130,8 +129,11 @@ class BackupManager(val context: Context, version: Int = CURRENT_VERSION) { // Create category array val categoryEntries = JsonArray() + // Create extension ID/name mapping + val extensionEntries = JsonArray() + // Add value's to root - root[Backup.VERSION] = Backup.CURRENT_VERSION + root[Backup.VERSION] = CURRENT_VERSION root[Backup.MANGAS] = mangaEntries root[CATEGORIES] = categoryEntries @@ -173,6 +175,8 @@ class BackupManager(val context: Context, version: Int = CURRENT_VERSION) { newFile.openOutputStream().bufferedWriter().use { parser.toJson(root, it) } + + return newFile.uri.toString() } else { val file = UniFile.fromUri(context, uri) ?: throw Exception("Couldn't create backup file") @@ -180,23 +184,11 @@ class BackupManager(val context: Context, version: Int = CURRENT_VERSION) { parser.toJson(root, it) } - // Show completed dialog - val intent = Intent(BackupConst.INTENT_FILTER).apply { - putExtra(BackupConst.ACTION, BackupConst.ACTION_BACKUP_COMPLETED_DIALOG) - putExtra(BackupConst.EXTRA_URI, file.uri.toString()) - } - context.sendLocalBroadcast(intent) + return file.uri.toString() } } catch (e: Exception) { Timber.e(e) - if (!isJob) { - // Show error dialog - val intent = Intent(BackupConst.INTENT_FILTER).apply { - putExtra(BackupConst.ACTION, BackupConst.ACTION_ERROR_BACKUP_DIALOG) - putExtra(BackupConst.EXTRA_ERROR_MESSAGE, e.message) - } - context.sendLocalBroadcast(intent) - } + throw e } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/backup/BackupNotifier.kt b/app/src/main/java/eu/kanade/tachiyomi/data/backup/BackupNotifier.kt new file mode 100644 index 0000000000..6efd34f180 --- /dev/null +++ b/app/src/main/java/eu/kanade/tachiyomi/data/backup/BackupNotifier.kt @@ -0,0 +1,84 @@ +package eu.kanade.tachiyomi.data.backup + +import android.content.Context +import android.graphics.BitmapFactory +import androidx.core.app.NotificationCompat +import com.hippo.unifile.UniFile +import eu.kanade.tachiyomi.R +import eu.kanade.tachiyomi.data.notification.NotificationReceiver +import eu.kanade.tachiyomi.data.notification.Notifications +import eu.kanade.tachiyomi.data.preference.PreferencesHelper +import eu.kanade.tachiyomi.util.system.notificationBuilder +import eu.kanade.tachiyomi.util.system.notificationManager +import uy.kohesive.injekt.injectLazy + +internal class BackupNotifier(private val context: Context) { + + private val preferences: PreferencesHelper by injectLazy() + + private val progressNotificationBuilder = context.notificationBuilder(Notifications.CHANNEL_BACKUP_RESTORE) { + setLargeIcon(BitmapFactory.decodeResource(context.resources, R.mipmap.ic_launcher)) + setSmallIcon(R.drawable.ic_tachi) + setAutoCancel(false) + setOngoing(true) + } + + private val completeNotificationBuilder = context.notificationBuilder(Notifications.CHANNEL_BACKUP_RESTORE) { + setLargeIcon(BitmapFactory.decodeResource(context.resources, R.mipmap.ic_launcher)) + setSmallIcon(R.drawable.ic_tachi) + setAutoCancel(false) + } + + private fun NotificationCompat.Builder.show(id: Int) { + context.notificationManager.notify(id, build()) + } + + fun showBackupProgress(): NotificationCompat.Builder { + val builder = with(progressNotificationBuilder) { + setContentTitle(context.getString(R.string.creating_backup)) + + setProgress(0, 0, true) + setOnlyAlertOnce(true) + } + + builder.show(Notifications.ID_BACKUP_PROGRESS) + + return builder + } + + fun showBackupError(error: String?) { + context.notificationManager.cancel(Notifications.ID_BACKUP_PROGRESS) + + with(completeNotificationBuilder) { + setContentTitle(context.getString(R.string.backup_failed)) + setContentText(error) + + show(Notifications.ID_BACKUP_COMPLETE) + } + } + + fun showBackupComplete(unifile: UniFile) { + context.notificationManager.cancel(Notifications.ID_BACKUP_PROGRESS) + + with(completeNotificationBuilder) { + setContentTitle(context.getString(R.string.backup_created)) + + if (unifile.filePath != null) { + setContentText(unifile.filePath) + } + + // Clear old actions if they exist + if (mActions.isNotEmpty()) { + mActions.clear() + } + + addAction( + R.drawable.ic_share_grey_24dp, + context.getString(R.string.share), + NotificationReceiver.shareBackupPendingBroadcast(context, unifile.uri, Notifications.ID_BACKUP_COMPLETE) + ) + + show(Notifications.ID_BACKUP_COMPLETE) + } + } +} diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/backup/BackupRestoreService.kt b/app/src/main/java/eu/kanade/tachiyomi/data/backup/BackupRestoreService.kt index 93372556a4..84603eee6f 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/backup/BackupRestoreService.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/backup/BackupRestoreService.kt @@ -335,7 +335,7 @@ class BackupRestoreService : Service() { * keep a partially constructed progress notification for resuse */ private val progressNotification by lazy { - NotificationCompat.Builder(this, Notifications.CHANNEL_RESTORE) + NotificationCompat.Builder(this, Notifications.CHANNEL_BACKUP_RESTORE) .setContentTitle(getString(R.string.app_name)) .setSmallIcon(R.drawable.ic_tachi) .setOngoing(true) @@ -392,7 +392,7 @@ class BackupRestoreService : Service() { val restoreString = content.joinToString("\n") - val resultNotification = NotificationCompat.Builder(this, Notifications.CHANNEL_RESTORE) + val resultNotification = NotificationCompat.Builder(this, Notifications.CHANNEL_BACKUP_RESTORE) .setContentTitle(getString(R.string.restore_completed)) .setContentText(restoreString) .setStyle(NotificationCompat.BigTextStyle().bigText(restoreString)) @@ -410,7 +410,7 @@ class BackupRestoreService : Service() { * */ private fun showErrorNotification(errorMessage: String) { - val resultNotification = NotificationCompat.Builder(this, Notifications.CHANNEL_RESTORE) + val resultNotification = NotificationCompat.Builder(this, Notifications.CHANNEL_BACKUP_RESTORE) .setContentTitle(getString(R.string.restore_error)) .setContentText(errorMessage) .setSmallIcon(R.drawable.ic_error_grey) diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/notification/NotificationReceiver.kt b/app/src/main/java/eu/kanade/tachiyomi/data/notification/NotificationReceiver.kt index b1924bdcf2..3f2b96b062 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/notification/NotificationReceiver.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/notification/NotificationReceiver.kt @@ -66,6 +66,12 @@ class NotificationReceiver : BroadcastReceiver() { // Cancel library update and dismiss notification ACTION_CANCEL_LIBRARY_UPDATE -> cancelLibraryUpdate(context) ACTION_CANCEL_RESTORE -> cancelRestoreUpdate(context) + // Share backup file + ACTION_SHARE_BACKUP -> + shareBackup( + context, intent.getParcelableExtra(EXTRA_URI), + intent.getIntExtra(EXTRA_NOTIFICATION_ID, -1) + ) // Open reader activity ACTION_OPEN_CHAPTER -> { openChapter(context, intent.getLongExtra(EXTRA_MANGA_ID, -1), @@ -112,6 +118,25 @@ class NotificationReceiver : BroadcastReceiver() { // Close Navigation Shade } + /** + * Called to start share intent to share backup file + * + * @param context context of application + * @param path path of file + * @param notificationId id of notification + */ + private fun shareBackup(context: Context, uri: Uri, notificationId: Int) { + val sendIntent = Intent(Intent.ACTION_SEND).apply { + putExtra(Intent.EXTRA_STREAM, uri) + type = "application/json" + flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_GRANT_READ_URI_PERMISSION + } + // Dismiss notification + dismissNotification(context, notificationId) + // Launch share activity + context.startActivity(sendIntent) + } + /** * Starts reader activity * @@ -204,6 +229,9 @@ class NotificationReceiver : BroadcastReceiver() { // Called to delete image. private const val ACTION_DELETE_IMAGE = "$ID.$NAME.DELETE_IMAGE" + // Called to launch send intent. + private const val ACTION_SHARE_BACKUP = "$ID.$NAME.SEND_BACKUP" + // Called to cancel library update. private const val ACTION_CANCEL_LIBRARY_UPDATE = "$ID.$NAME.CANCEL_LIBRARY_UPDATE" @@ -231,6 +259,9 @@ class NotificationReceiver : BroadcastReceiver() { // Called to dismiss notification. private const val ACTION_DISMISS_NOTIFICATION = "$ID.$NAME.ACTION_DISMISS_NOTIFICATION" + // Value containing uri. + private const val EXTRA_URI = "$ID.$NAME.URI" + // Value containing notification id. private const val EXTRA_NOTIFICATION_ID = "$ID.$NAME.NOTIFICATION_ID" @@ -468,6 +499,23 @@ class NotificationReceiver : BroadcastReceiver() { return PendingIntent.getBroadcast(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT) } + /** + * Returns [PendingIntent] that starts a share activity for a backup file. + * + * @param context context of application + * @param uri uri of backup file + * @param notificationId id of notification + * @return [PendingIntent] + */ + internal fun shareBackupPendingBroadcast(context: Context, uri: Uri, notificationId: Int): PendingIntent { + val intent = Intent(context, NotificationReceiver::class.java).apply { + action = ACTION_SHARE_BACKUP + putExtra(EXTRA_URI, uri) + putExtra(EXTRA_NOTIFICATION_ID, notificationId) + } + return PendingIntent.getBroadcast(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT) + } + /** * Returns [PendingIntent] that starts a service which stops the restore service * diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/notification/Notifications.kt b/app/src/main/java/eu/kanade/tachiyomi/data/notification/Notifications.kt index a07bbad751..ee55c2dee4 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/notification/Notifications.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/notification/Notifications.kt @@ -45,10 +45,12 @@ object Notifications { const val CHANNEL_UPDATES_TO_EXTS = "updates_ext_channel" const val ID_UPDATES_TO_EXTS = -401 - const val CHANNEL_RESTORE = "backup_restore_channel" + const val CHANNEL_BACKUP_RESTORE = "backup_restore_channel" const val ID_RESTORE_PROGRESS = -501 const val ID_RESTORE_COMPLETE = -502 - const val ID_RESTORE_ERROR = -503 + const val ID_BACKUP_PROGRESS = -502 + const val ID_BACKUP_COMPLETE = -503 + const val ID_RESTORE_ERROR = -504 /** * Creates the notification channels introduced in Android Oreo. @@ -83,7 +85,7 @@ object Notifications { context.getString(R.string.new_chapters), NotificationManager.IMPORTANCE_DEFAULT ), NotificationChannel( - CHANNEL_RESTORE, + CHANNEL_BACKUP_RESTORE, context.getString(R.string.restoring_backup), NotificationManager.IMPORTANCE_LOW ).apply { diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsBackupController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsBackupController.kt index f1be175d2c..1be34017a6 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsBackupController.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsBackupController.kt @@ -4,10 +4,7 @@ import android.Manifest.permission.WRITE_EXTERNAL_STORAGE import android.app.Activity import android.app.Dialog import android.content.ActivityNotFoundException -import android.content.BroadcastReceiver -import android.content.Context import android.content.Intent -import android.content.IntentFilter import android.net.Uri import android.os.Bundle import android.view.View @@ -16,7 +13,6 @@ import com.afollestad.materialdialogs.MaterialDialog import com.afollestad.materialdialogs.list.listItemsMultiChoice import com.hippo.unifile.UniFile import eu.kanade.tachiyomi.R -import eu.kanade.tachiyomi.data.backup.BackupConst import eu.kanade.tachiyomi.data.backup.BackupCreateService import eu.kanade.tachiyomi.data.backup.BackupCreatorJob import eu.kanade.tachiyomi.data.backup.BackupRestoreService @@ -24,9 +20,7 @@ import eu.kanade.tachiyomi.data.backup.models.Backup import eu.kanade.tachiyomi.data.preference.getOrDefault import eu.kanade.tachiyomi.ui.base.controller.DialogController import eu.kanade.tachiyomi.util.system.getFilePicker -import eu.kanade.tachiyomi.util.system.registerLocalReceiver import eu.kanade.tachiyomi.util.system.toast -import eu.kanade.tachiyomi.util.system.unregisterLocalReceiver import eu.kanade.tachiyomi.util.view.requestPermissionsSafe import eu.kanade.tachiyomi.data.preference.PreferenceKeys as Keys @@ -37,22 +31,11 @@ class SettingsBackupController : SettingsController() { */ private var backupFlags = 0 - private val receiver = BackupBroadcastReceiver() - - init { - preferences.context.registerLocalReceiver(receiver, IntentFilter(BackupConst.INTENT_FILTER)) - } - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) requestPermissionsSafe(arrayOf(WRITE_EXTERNAL_STORAGE), 500) } - override fun onDestroy() { - super.onDestroy() - preferences.context.unregisterLocalReceiver(receiver) - } - override fun setupPreferenceScreen(screen: PreferenceScreen) = with(screen) { titleRes = R.string.backup @@ -170,7 +153,9 @@ class SettingsBackupController : SettingsController() { val file = UniFile.fromUri(activity, uri) - BackupCreateService.makeBackup(activity, file.uri, backupFlags) + activity.toast(R.string.creating_backup) + + BackupCreateService.start(activity, file.uri, backupFlags) } CODE_BACKUP_RESTORE -> if (data != null && resultCode == Activity.RESULT_OK) { val uri = data.data @@ -212,7 +197,7 @@ class SettingsBackupController : SettingsController() { .title(R.string.create_backup) .message(R.string.what_should_backup) .listItemsMultiChoice(items = options, disabledIndices = intArrayOf(0), - initialSelection = intArrayOf(0)) { _, positions, _ -> + initialSelection = intArrayOf(0, 1, 2, 3, 4)) { _, positions, _ -> var flags = 0 for (i in 1 until positions.size) { when (positions[i]) { @@ -230,33 +215,6 @@ class SettingsBackupController : SettingsController() { } } - class CreatedBackupDialog(bundle: Bundle? = null) : DialogController(bundle) { - constructor(uri: Uri) : this(Bundle().apply { - putParcelable(KEY_URI, uri) - }) - - override fun onCreateDialog(savedViewState: Bundle?): Dialog { - val activity = activity!! - val uniFile = UniFile.fromUri(activity, args.getParcelable(KEY_URI)) - return MaterialDialog(activity).apply { - title(R.string.backup_created) - if (uniFile.filePath != null) - message(text = resources?.getString(R.string.file_saved_at_, uniFile.filePath)) - positiveButton(R.string.close) - negativeButton(R.string.share) { - val sendIntent = Intent(Intent.ACTION_SEND) - sendIntent.type = "application/json" - sendIntent.putExtra(Intent.EXTRA_STREAM, uniFile.uri) - startActivity(Intent.createChooser(sendIntent, "")) - } - } - } - - private companion object { - const val KEY_URI = "BackupCreatedDialog.uri" - } - } - class RestoreBackupDialog(bundle: Bundle? = null) : DialogController(bundle) { constructor(uri: Uri) : this(Bundle().apply { putParcelable(KEY_URI, uri) @@ -280,20 +238,6 @@ class SettingsBackupController : SettingsController() { } } - inner class BackupBroadcastReceiver : BroadcastReceiver() { - override fun onReceive(context: Context, intent: Intent) { - when (intent.getStringExtra(BackupConst.ACTION)) { - BackupConst.ACTION_BACKUP_COMPLETED_DIALOG -> { - val uri = Uri.parse(intent.getStringExtra(BackupConst.EXTRA_URI)) - CreatedBackupDialog(uri).showDialog(router) - } - BackupConst.ACTION_ERROR_BACKUP_DIALOG -> { - context.toast(intent.getStringExtra(BackupConst.EXTRA_ERROR_MESSAGE)) - } - } - } - } - private companion object { const val CODE_BACKUP_CREATE = 501 const val CODE_BACKUP_RESTORE = 502 diff --git a/app/src/main/java/eu/kanade/tachiyomi/util/system/ContextExtensions.kt b/app/src/main/java/eu/kanade/tachiyomi/util/system/ContextExtensions.kt index b0889cec8b..498cedf7df 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/util/system/ContextExtensions.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/util/system/ContextExtensions.kt @@ -136,6 +136,22 @@ val Float.dpToPxEnd: Float val Resources.isLTR get() = configuration.layoutDirection == View.LAYOUT_DIRECTION_LTR +/** + * Helper method to create a notification builder. + * + * @param id the channel id. + * @param block the function that will execute inside the builder. + * @return a notification to be displayed or updated. + */ +fun Context.notificationBuilder(channelId: String, block: (NotificationCompat.Builder.() -> Unit)? = null): NotificationCompat.Builder { + val builder = NotificationCompat.Builder(this, channelId) + .setColor(ContextCompat.getColor(this, R.color.colorAccent)) + if (block != null) { + builder.block() + } + return builder +} + /** * Property to get the notification manager from the context. */ diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 5a62b9d7f9..72da915522 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -476,13 +476,14 @@ Service Backup frequency Max automatic backups + Backup failed Backup created Restore completed Restore error %1$s Restored. %2$s errors found %1$d skipped Restore uses the network to fetch data, carrier costs may apply.\n\nMake sure you have installed all necessary extensions and are logged in to sources and tracking services before restoring. - File saved at %1$s + Creating backup What do you want to backup? Restoring backup Restoring (%1$d/%2$d)