From cde31e31a5dd394c842db2b7dec93957d4993b3a Mon Sep 17 00:00:00 2001 From: Moez Bhatti Date: Wed, 22 Aug 2018 22:11:00 -0400 Subject: [PATCH] Show list of backups on restore click --- .../QKSMS/repository/BackupRepositoryImpl.kt | 45 +++++++++++ .../com/moez/QKSMS/util/QkFileObserver.kt | 38 +++++++++ .../main/java/com/moez/QKSMS/model/Backup.kt | 25 ++++++ .../moez/QKSMS/repository/BackupRepository.kt | 31 ++++++++ .../com/moez/QKSMS/common/base/QkAdapter.kt | 3 +- .../com/moez/QKSMS/common/base/QkPresenter.kt | 1 + .../QKSMS/feature/backup/BackupAdapter.kt | 62 +++++++++++++++ .../QKSMS/feature/backup/BackupController.kt | 41 ++++------ .../QKSMS/feature/backup/BackupPresenter.kt | 5 ++ .../moez/QKSMS/feature/backup/BackupState.kt | 5 +- .../moez/QKSMS/feature/backup/BackupView.kt | 5 +- .../com/moez/QKSMS/injection/AppModule.kt | 5 ++ .../main/res/layout/backup_list_dialog.xml | 78 +++++++++++++++++++ .../src/main/res/layout/backup_list_item.xml | 66 ++++++++++++++++ presentation/src/main/res/values/strings.xml | 8 ++ 15 files changed, 390 insertions(+), 28 deletions(-) create mode 100644 data/src/main/java/com/moez/QKSMS/repository/BackupRepositoryImpl.kt create mode 100644 data/src/main/java/com/moez/QKSMS/util/QkFileObserver.kt create mode 100644 domain/src/main/java/com/moez/QKSMS/model/Backup.kt create mode 100644 domain/src/main/java/com/moez/QKSMS/repository/BackupRepository.kt create mode 100644 presentation/src/main/java/com/moez/QKSMS/feature/backup/BackupAdapter.kt create mode 100644 presentation/src/main/res/layout/backup_list_dialog.xml create mode 100644 presentation/src/main/res/layout/backup_list_item.xml diff --git a/data/src/main/java/com/moez/QKSMS/repository/BackupRepositoryImpl.kt b/data/src/main/java/com/moez/QKSMS/repository/BackupRepositoryImpl.kt new file mode 100644 index 00000000..2609a98c --- /dev/null +++ b/data/src/main/java/com/moez/QKSMS/repository/BackupRepositoryImpl.kt @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2017 Moez Bhatti + * + * This file is part of QKSMS. + * + * QKSMS is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * QKSMS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with QKSMS. If not, see . + */ +package com.moez.QKSMS.repository + +import android.os.Environment +import com.moez.QKSMS.model.Backup +import com.moez.QKSMS.util.QkFileObserver +import io.reactivex.Observable +import java.io.File +import javax.inject.Inject + +class BackupRepositoryImpl @Inject constructor() : BackupRepository { + + companion object { + private val BACKUP_LOCATION = Environment.getExternalStorageDirectory().toString() + "/QKSMS/Backups" + } + + override fun getBackups(): Observable> = QkFileObserver(BACKUP_LOCATION) + .observable + .map { path -> File(path).listFiles() } + .map { files -> + files.map { file -> + val date = file.lastModified() + val size = file.length() + Backup(date, listOf(), size) + } + } + +} \ No newline at end of file diff --git a/data/src/main/java/com/moez/QKSMS/util/QkFileObserver.kt b/data/src/main/java/com/moez/QKSMS/util/QkFileObserver.kt new file mode 100644 index 00000000..304e215d --- /dev/null +++ b/data/src/main/java/com/moez/QKSMS/util/QkFileObserver.kt @@ -0,0 +1,38 @@ +/* + * Copyright (C) 2017 Moez Bhatti + * + * This file is part of QKSMS. + * + * QKSMS is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * QKSMS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with QKSMS. If not, see . + */ +package com.moez.QKSMS.util + +import android.os.FileObserver +import io.reactivex.Observable +import io.reactivex.subjects.BehaviorSubject + +class QkFileObserver(path: String) : FileObserver(path) { + + private val subject = BehaviorSubject.createDefault(path).toSerialized() + + val observable: Observable = subject + .doOnSubscribe { startWatching() } + .doOnDispose { stopWatching() } + .share() + + override fun onEvent(event: Int, path: String?) { + path?.let(subject::onNext) + } + +} \ No newline at end of file diff --git a/domain/src/main/java/com/moez/QKSMS/model/Backup.kt b/domain/src/main/java/com/moez/QKSMS/model/Backup.kt new file mode 100644 index 00000000..8c75bafb --- /dev/null +++ b/domain/src/main/java/com/moez/QKSMS/model/Backup.kt @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2017 Moez Bhatti + * + * This file is part of QKSMS. + * + * QKSMS is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * QKSMS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with QKSMS. If not, see . + */ +package com.moez.QKSMS.model + +data class Backup( + val date: Long, + val messages: List, + val size: Long +) \ No newline at end of file diff --git a/domain/src/main/java/com/moez/QKSMS/repository/BackupRepository.kt b/domain/src/main/java/com/moez/QKSMS/repository/BackupRepository.kt new file mode 100644 index 00000000..b1d04a04 --- /dev/null +++ b/domain/src/main/java/com/moez/QKSMS/repository/BackupRepository.kt @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2017 Moez Bhatti + * + * This file is part of QKSMS. + * + * QKSMS is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * QKSMS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with QKSMS. If not, see . + */ +package com.moez.QKSMS.repository + +import com.moez.QKSMS.model.Backup +import io.reactivex.Observable + +interface BackupRepository { + + /** + * Returns a list of all local backups + */ + fun getBackups(): Observable> + +} \ No newline at end of file diff --git a/presentation/src/main/java/com/moez/QKSMS/common/base/QkAdapter.kt b/presentation/src/main/java/com/moez/QKSMS/common/base/QkAdapter.kt index 28dc06e2..0101f435 100644 --- a/presentation/src/main/java/com/moez/QKSMS/common/base/QkAdapter.kt +++ b/presentation/src/main/java/com/moez/QKSMS/common/base/QkAdapter.kt @@ -19,6 +19,7 @@ package com.moez.QKSMS.common.base import android.view.View +import androidx.core.view.isVisible import androidx.recyclerview.widget.DiffUtil import androidx.recyclerview.widget.RecyclerView import com.moez.QKSMS.common.util.extensions.setVisible @@ -50,7 +51,7 @@ abstract class QkAdapter : RecyclerView.Adapter() { var emptyView: View? = null set(value) { field = value - value?.setVisible(false) + field?.isVisible = data.isEmpty() } val selectionChanges: Subject> = BehaviorSubject.create() diff --git a/presentation/src/main/java/com/moez/QKSMS/common/base/QkPresenter.kt b/presentation/src/main/java/com/moez/QKSMS/common/base/QkPresenter.kt index 05160915..08c695bc 100644 --- a/presentation/src/main/java/com/moez/QKSMS/common/base/QkPresenter.kt +++ b/presentation/src/main/java/com/moez/QKSMS/common/base/QkPresenter.kt @@ -25,6 +25,7 @@ abstract class QkPresenter, State>(initialState: St } open fun onCleared() { + disposables.dispose() } } \ No newline at end of file diff --git a/presentation/src/main/java/com/moez/QKSMS/feature/backup/BackupAdapter.kt b/presentation/src/main/java/com/moez/QKSMS/feature/backup/BackupAdapter.kt new file mode 100644 index 00000000..0274485c --- /dev/null +++ b/presentation/src/main/java/com/moez/QKSMS/feature/backup/BackupAdapter.kt @@ -0,0 +1,62 @@ +/* + * Copyright (C) 2017 Moez Bhatti + * + * This file is part of QKSMS. + * + * QKSMS is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * QKSMS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with QKSMS. If not, see . + */ +package com.moez.QKSMS.feature.backup + +import android.content.Context +import android.text.format.Formatter +import android.view.LayoutInflater +import android.view.ViewGroup +import com.moez.QKSMS.R +import com.moez.QKSMS.common.base.FlowableAdapter +import com.moez.QKSMS.common.base.QkViewHolder +import com.moez.QKSMS.common.util.DateFormatter +import com.moez.QKSMS.model.Backup +import io.reactivex.subjects.PublishSubject +import io.reactivex.subjects.Subject +import kotlinx.android.synthetic.main.backup_list_item.view.* +import javax.inject.Inject + +class BackupAdapter @Inject constructor( + private val context: Context, + private val dateFormatter: DateFormatter +) : FlowableAdapter() { + + val backupSelected: Subject = PublishSubject.create() + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): QkViewHolder { + val layoutInflater = LayoutInflater.from(parent.context) + val view = layoutInflater.inflate(R.layout.backup_list_item, parent, false) + + return QkViewHolder(view).apply { + view.setOnClickListener { backupSelected.onNext(getItem(adapterPosition)) } + } + } + + override fun onBindViewHolder(holder: QkViewHolder, position: Int) { + val backup = getItem(position) + val view = holder.itemView + + val count = backup.messages.size + + view.title.text = dateFormatter.getDetailedTimestamp(backup.date) + view.messages.text = context.resources.getQuantityString(R.plurals.backup_message_count, count, count) + view.size.text = Formatter.formatFileSize(context, backup.size) + } + +} \ No newline at end of file diff --git a/presentation/src/main/java/com/moez/QKSMS/feature/backup/BackupController.kt b/presentation/src/main/java/com/moez/QKSMS/feature/backup/BackupController.kt index e51a5252..a928e916 100644 --- a/presentation/src/main/java/com/moez/QKSMS/feature/backup/BackupController.kt +++ b/presentation/src/main/java/com/moez/QKSMS/feature/backup/BackupController.kt @@ -18,9 +18,7 @@ */ package com.moez.QKSMS.feature.backup -import android.app.Activity -import android.content.Intent -import android.net.Uri +import android.app.AlertDialog import android.view.View import com.jakewharton.rxbinding2.view.clicks import com.moez.QKSMS.R @@ -28,21 +26,26 @@ import com.moez.QKSMS.common.base.QkController import com.moez.QKSMS.common.util.extensions.setBackgroundTint import com.moez.QKSMS.common.util.extensions.setTint import com.moez.QKSMS.injection.appComponent +import com.moez.QKSMS.model.Backup import io.reactivex.Observable -import io.reactivex.subjects.PublishSubject -import io.reactivex.subjects.Subject import kotlinx.android.synthetic.main.backup_controller.* +import kotlinx.android.synthetic.main.backup_list_dialog.view.* import javax.inject.Inject class BackupController : QkController(), BackupView { - companion object { - private const val REQUEST_CODE = 427 - } - + @Inject lateinit var adapter: BackupAdapter @Inject override lateinit var presenter: BackupPresenter - private val restoreFileSubject: Subject = PublishSubject.create() + private val dialog by lazy { + val view = View.inflate(activity, R.layout.backup_list_dialog, null) + .apply { files.adapter = adapter.apply { emptyView = empty } } + + AlertDialog.Builder(activity) + .setView(view) + .setCancelable(true) + .create() + } init { appComponent.inject(this) @@ -66,17 +69,11 @@ class BackupController : QkController( } } - override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { - super.onActivityResult(requestCode, resultCode, data) - - if (requestCode == REQUEST_CODE && resultCode == Activity.RESULT_OK) { - data?.data?.run(restoreFileSubject::onNext) - } - } - override fun render(state: BackupState) { restore.isEnabled = state.upgraded + adapter.data = state.backups + fabIcon.setImageResource(when (state.upgraded) { true -> R.drawable.ic_file_upload_black_24dp false -> R.drawable.ic_star_black_24dp @@ -90,14 +87,10 @@ class BackupController : QkController( override fun restoreClicks(): Observable<*> = restore.clicks() - override fun restoreFileSelected(): Observable = restoreFileSubject + override fun restoreFileSelected(): Observable = adapter.backupSelected override fun fabClicks(): Observable<*> = fab.clicks() - override fun selectFile() { - val intent = Intent(Intent.ACTION_GET_CONTENT) - intent.type = "*/*" - startActivityForResult(Intent.createChooser(intent, null), REQUEST_CODE) - } + override fun selectFile() = dialog.show() } \ No newline at end of file diff --git a/presentation/src/main/java/com/moez/QKSMS/feature/backup/BackupPresenter.kt b/presentation/src/main/java/com/moez/QKSMS/feature/backup/BackupPresenter.kt index c1a59297..fee49932 100644 --- a/presentation/src/main/java/com/moez/QKSMS/feature/backup/BackupPresenter.kt +++ b/presentation/src/main/java/com/moez/QKSMS/feature/backup/BackupPresenter.kt @@ -21,17 +21,22 @@ package com.moez.QKSMS.feature.backup import com.moez.QKSMS.common.Navigator import com.moez.QKSMS.common.base.QkPresenter import com.moez.QKSMS.common.util.BillingManager +import com.moez.QKSMS.repository.BackupRepository import com.uber.autodispose.kotlin.autoDisposable import io.reactivex.rxkotlin.plusAssign import io.reactivex.rxkotlin.withLatestFrom import javax.inject.Inject class BackupPresenter @Inject constructor( + backupRepo: BackupRepository, private val billingManager: BillingManager, private val navigator: Navigator ) : QkPresenter(BackupState()) { init { + disposables += backupRepo.getBackups() + .subscribe { backups -> newState { copy(backups = backups) } } + disposables += billingManager.upgradeStatus .subscribe { upgraded -> newState { copy(upgraded = upgraded) } } } diff --git a/presentation/src/main/java/com/moez/QKSMS/feature/backup/BackupState.kt b/presentation/src/main/java/com/moez/QKSMS/feature/backup/BackupState.kt index 08865f6a..44901294 100644 --- a/presentation/src/main/java/com/moez/QKSMS/feature/backup/BackupState.kt +++ b/presentation/src/main/java/com/moez/QKSMS/feature/backup/BackupState.kt @@ -18,7 +18,10 @@ */ package com.moez.QKSMS.feature.backup +import com.moez.QKSMS.model.Backup + data class BackupState( val upgraded: Boolean = false, - val lastBackup: Long = 0 + val lastBackup: Long = 0, + val backups: List = listOf() ) \ No newline at end of file diff --git a/presentation/src/main/java/com/moez/QKSMS/feature/backup/BackupView.kt b/presentation/src/main/java/com/moez/QKSMS/feature/backup/BackupView.kt index 6a760f94..44fca2a6 100644 --- a/presentation/src/main/java/com/moez/QKSMS/feature/backup/BackupView.kt +++ b/presentation/src/main/java/com/moez/QKSMS/feature/backup/BackupView.kt @@ -18,14 +18,15 @@ */ package com.moez.QKSMS.feature.backup -import android.net.Uri import com.moez.QKSMS.common.base.QkViewContract +import com.moez.QKSMS.model.Backup import io.reactivex.Observable +import java.io.File interface BackupView : QkViewContract { fun restoreClicks(): Observable<*> - fun restoreFileSelected(): Observable + fun restoreFileSelected(): Observable fun fabClicks(): Observable<*> fun selectFile() diff --git a/presentation/src/main/java/com/moez/QKSMS/injection/AppModule.kt b/presentation/src/main/java/com/moez/QKSMS/injection/AppModule.kt index d213492c..29072c9e 100644 --- a/presentation/src/main/java/com/moez/QKSMS/injection/AppModule.kt +++ b/presentation/src/main/java/com/moez/QKSMS/injection/AppModule.kt @@ -53,6 +53,8 @@ import com.moez.QKSMS.mapper.CursorToPartImpl import com.moez.QKSMS.mapper.CursorToRecipient import com.moez.QKSMS.mapper.CursorToRecipientImpl import com.moez.QKSMS.mapper.RatingManagerImpl +import com.moez.QKSMS.repository.BackupRepository +import com.moez.QKSMS.repository.BackupRepositoryImpl import com.moez.QKSMS.repository.ContactRepository import com.moez.QKSMS.repository.ContactRepositoryImpl import com.moez.QKSMS.repository.ConversationRepository @@ -138,6 +140,9 @@ class AppModule(private var application: Application) { // Repository + @Provides + fun provideBackupRepository(repository: BackupRepositoryImpl): BackupRepository = repository + @Provides fun provideContactRepository(repository: ContactRepositoryImpl): ContactRepository = repository diff --git a/presentation/src/main/res/layout/backup_list_dialog.xml b/presentation/src/main/res/layout/backup_list_dialog.xml new file mode 100644 index 00000000..d4cf8803 --- /dev/null +++ b/presentation/src/main/res/layout/backup_list_dialog.xml @@ -0,0 +1,78 @@ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/presentation/src/main/res/layout/backup_list_item.xml b/presentation/src/main/res/layout/backup_list_item.xml new file mode 100644 index 00000000..fab56eda --- /dev/null +++ b/presentation/src/main/res/layout/backup_list_item.xml @@ -0,0 +1,66 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/presentation/src/main/res/values/strings.xml b/presentation/src/main/res/values/strings.xml index 2cba8063..46a071e1 100644 --- a/presentation/src/main/res/values/strings.xml +++ b/presentation/src/main/res/values/strings.xml @@ -150,6 +150,14 @@ Never Restore Select a backup + Backups + /QKSMS/Backups + No backups found + + No messages + %d message + %d messages + Currently, only SMS is supported by Backup and Restore. MMS support and scheduled backups will be coming soon! Backup now