Remote address book storage disabled & address book removal (#1639)
* Commented out address book * Commented out address book * Address book deletion
This commit is contained in:
parent
8652b91a99
commit
711feb4251
|
@ -17,7 +17,7 @@ and this application adheres to [Semantic Versioning](https://semver.org/spec/v2
|
|||
- Address Book, Create/Update/Delete Contact, Create Contact by QR screens added
|
||||
|
||||
### Added
|
||||
- Address book local and remote storage support
|
||||
- Address book local storage support
|
||||
- New Integrations screen in settings
|
||||
- New QR Code detail screen has been added
|
||||
- The new Request ZEC screens have been added. They provide a way to build ZIP 321 Uri consisting of the amount,
|
||||
|
|
|
@ -21,7 +21,7 @@ directly impact users rather than highlighting other key architectural updates.*
|
|||
|
||||
### Added
|
||||
- New Integrations screen in settings
|
||||
- Address book local and remote storage support
|
||||
- Address book local storage support
|
||||
- New QR Code detail screen has been added
|
||||
- The new Request ZEC screens have been added. They provide a way to build ZIP 321 Uri consisting of the amount, message, and receiver address and then creates a QR code image of it.
|
||||
|
||||
|
|
|
@ -140,20 +140,20 @@ dependencies {
|
|||
api(libs.androidx.fragment)
|
||||
api(libs.androidx.fragment.compose)
|
||||
api(libs.androidx.activity)
|
||||
api(libs.google.http.client.gson) {
|
||||
exclude(group = "io.grpc")
|
||||
}
|
||||
api(libs.google.api.client.android) {
|
||||
exclude(group = "org.apache.httpcomponents")
|
||||
exclude(group = "io.grpc")
|
||||
}
|
||||
api(libs.google.api.services.drive) {
|
||||
exclude(group = "org.apache.httpcomponents")
|
||||
exclude(group = "io.grpc")
|
||||
}
|
||||
api(libs.play.services.auth) {
|
||||
exclude(group = "io.grpc")
|
||||
}
|
||||
// api(libs.google.http.client.gson) {
|
||||
// exclude(group = "io.grpc")
|
||||
// }
|
||||
// api(libs.google.api.client.android) {
|
||||
// exclude(group = "org.apache.httpcomponents")
|
||||
// exclude(group = "io.grpc")
|
||||
// }
|
||||
// api(libs.google.api.services.drive) {
|
||||
// exclude(group = "org.apache.httpcomponents")
|
||||
// exclude(group = "io.grpc")
|
||||
// }
|
||||
// api(libs.play.services.auth) {
|
||||
// exclude(group = "io.grpc")
|
||||
// }
|
||||
api(libs.bundles.androidx.biometric)
|
||||
|
||||
androidTestImplementation(projects.testLib)
|
||||
|
|
|
@ -2,8 +2,6 @@ package co.electriccoin.zcash.di
|
|||
|
||||
import co.electriccoin.zcash.ui.common.datasource.LocalAddressBookDataSource
|
||||
import co.electriccoin.zcash.ui.common.datasource.LocalAddressBookDataSourceImpl
|
||||
import co.electriccoin.zcash.ui.common.datasource.RemoteAddressBookDataSource
|
||||
import co.electriccoin.zcash.ui.common.datasource.RemoteAddressBookDataSourceImpl
|
||||
import org.koin.core.module.dsl.singleOf
|
||||
import org.koin.dsl.bind
|
||||
import org.koin.dsl.module
|
||||
|
@ -11,5 +9,5 @@ import org.koin.dsl.module
|
|||
val dataSourceModule =
|
||||
module {
|
||||
singleOf(::LocalAddressBookDataSourceImpl) bind LocalAddressBookDataSource::class
|
||||
singleOf(::RemoteAddressBookDataSourceImpl) bind RemoteAddressBookDataSource::class
|
||||
// singleOf(::RemoteAddressBookDataSourceImpl) bind RemoteAddressBookDataSource::class
|
||||
}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package co.electriccoin.zcash.di
|
||||
|
||||
import co.electriccoin.zcash.ui.common.usecase.CopyToClipboardUseCase
|
||||
import co.electriccoin.zcash.ui.common.usecase.DeleteAddressBookUseCase
|
||||
import co.electriccoin.zcash.ui.common.usecase.DeleteContactUseCase
|
||||
import co.electriccoin.zcash.ui.common.usecase.GetAddressesUseCase
|
||||
import co.electriccoin.zcash.ui.common.usecase.GetContactByAddressUseCase
|
||||
|
@ -46,6 +47,7 @@ val useCaseModule =
|
|||
singleOf(::RescanBlockchainUseCase)
|
||||
singleOf(::GetTransparentAddressUseCase)
|
||||
singleOf(::ObserveAddressBookContactsUseCase)
|
||||
singleOf(::DeleteAddressBookUseCase)
|
||||
singleOf(::ValidateContactAddressUseCase)
|
||||
singleOf(::ValidateContactNameUseCase)
|
||||
singleOf(::SaveContactUseCase)
|
||||
|
|
|
@ -7,7 +7,6 @@ import android.content.pm.ActivityInfo
|
|||
import android.os.Bundle
|
||||
import android.os.SystemClock
|
||||
import androidx.activity.enableEdgeToEdge
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import androidx.annotation.VisibleForTesting
|
||||
import androidx.compose.foundation.layout.fillMaxHeight
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
|
@ -33,7 +32,6 @@ import co.electriccoin.zcash.ui.common.compose.BindCompLocalProvider
|
|||
import co.electriccoin.zcash.ui.common.extension.setContentCompat
|
||||
import co.electriccoin.zcash.ui.common.model.OnboardingState
|
||||
import co.electriccoin.zcash.ui.common.model.WalletRestoringState
|
||||
import co.electriccoin.zcash.ui.common.repository.AddressBookRepositoryImpl
|
||||
import co.electriccoin.zcash.ui.common.viewmodel.AuthenticationUIState
|
||||
import co.electriccoin.zcash.ui.common.viewmodel.AuthenticationViewModel
|
||||
import co.electriccoin.zcash.ui.common.viewmodel.HomeViewModel
|
||||
|
@ -55,18 +53,12 @@ import co.electriccoin.zcash.ui.screen.securitywarning.WrapSecurityWarning
|
|||
import co.electriccoin.zcash.ui.screen.support.WrapSupport
|
||||
import co.electriccoin.zcash.ui.screen.warning.viewmodel.StorageCheckViewModel
|
||||
import co.electriccoin.zcash.work.WorkIds
|
||||
import com.google.android.gms.auth.api.signin.GoogleSignIn
|
||||
import com.google.android.gms.auth.api.signin.GoogleSignInOptions
|
||||
import com.google.android.gms.common.Scopes
|
||||
import com.google.android.gms.common.api.Scope
|
||||
import com.google.android.gms.common.api.Status
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.combine
|
||||
import kotlinx.coroutines.flow.filterNotNull
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.launch
|
||||
import org.koin.android.ext.android.inject
|
||||
import org.koin.androidx.viewmodel.ext.android.viewModel
|
||||
import kotlin.time.Duration
|
||||
import kotlin.time.Duration.Companion.milliseconds
|
||||
|
@ -85,34 +77,34 @@ class MainActivity : FragmentActivity() {
|
|||
|
||||
val configurationOverrideFlow = MutableStateFlow<ConfigurationOverride?>(null)
|
||||
|
||||
private val addressBookRepository by inject<AddressBookRepositoryImpl>()
|
||||
// private val addressBookRepository by inject<AddressBookRepositoryImpl>()
|
||||
|
||||
private val googleSignInLauncher =
|
||||
registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
|
||||
when (result.resultCode) {
|
||||
RESULT_OK -> {
|
||||
addressBookRepository.onGoogleSignInSuccess()
|
||||
}
|
||||
|
||||
RESULT_CANCELED -> {
|
||||
val status = result.data?.extras?.getParcelable<Status>("googleSignInStatus")
|
||||
addressBookRepository.onGoogleSignInCancelled(status)
|
||||
}
|
||||
|
||||
else -> {
|
||||
addressBookRepository.onGoogleSignInError()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private val googleConsentLauncher =
|
||||
registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
|
||||
when (result.resultCode) {
|
||||
RESULT_OK -> requestGoogleSignIn()
|
||||
RESULT_CANCELED -> addressBookRepository.onGoogleSignInCancelled(null)
|
||||
else -> addressBookRepository.onGoogleSignInError()
|
||||
}
|
||||
}
|
||||
// private val googleSignInLauncher =
|
||||
// registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
|
||||
// when (result.resultCode) {
|
||||
// RESULT_OK -> {
|
||||
// addressBookRepository.onGoogleSignInSuccess()
|
||||
// }
|
||||
//
|
||||
// RESULT_CANCELED -> {
|
||||
// val status = result.data?.extras?.getParcelable<Status>("googleSignInStatus")
|
||||
// addressBookRepository.onGoogleSignInCancelled(status)
|
||||
// }
|
||||
//
|
||||
// else -> {
|
||||
// addressBookRepository.onGoogleSignInError()
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// private val googleConsentLauncher =
|
||||
// registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
|
||||
// when (result.resultCode) {
|
||||
// RESULT_OK -> requestGoogleSignIn()
|
||||
// RESULT_CANCELED -> addressBookRepository.onGoogleSignInCancelled(null)
|
||||
// else -> addressBookRepository.onGoogleSignInError()
|
||||
// }
|
||||
// }
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
|
@ -125,31 +117,31 @@ class MainActivity : FragmentActivity() {
|
|||
|
||||
monitorForBackgroundSync()
|
||||
|
||||
lifecycleScope.launch {
|
||||
addressBookRepository.googleSignInRequest.collect {
|
||||
requestGoogleSignIn()
|
||||
}
|
||||
}
|
||||
|
||||
lifecycleScope.launch {
|
||||
addressBookRepository.googleRemoteConsentRequest.collect { intent ->
|
||||
googleConsentLauncher.launch(intent)
|
||||
}
|
||||
}
|
||||
// lifecycleScope.launch {
|
||||
// addressBookRepository.googleSignInRequest.collect {
|
||||
// requestGoogleSignIn()
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// lifecycleScope.launch {
|
||||
// addressBookRepository.googleRemoteConsentRequest.collect { intent ->
|
||||
// googleConsentLauncher.launch(intent)
|
||||
// }
|
||||
// }
|
||||
}
|
||||
|
||||
private fun requestGoogleSignIn() {
|
||||
val googleSignInClient =
|
||||
GoogleSignIn.getClient(
|
||||
this@MainActivity,
|
||||
GoogleSignInOptions
|
||||
.Builder(GoogleSignInOptions.DEFAULT_SIGN_IN)
|
||||
.requestScopes(Scope(Scopes.DRIVE_APPFOLDER))
|
||||
.build()
|
||||
)
|
||||
|
||||
googleSignInLauncher.launch(googleSignInClient.signInIntent)
|
||||
}
|
||||
// private fun requestGoogleSignIn() {
|
||||
// val googleSignInClient =
|
||||
// GoogleSignIn.getClient(
|
||||
// this@MainActivity,
|
||||
// GoogleSignInOptions
|
||||
// .Builder(GoogleSignInOptions.DEFAULT_SIGN_IN)
|
||||
// .requestScopes(Scope(Scopes.DRIVE_APPFOLDER))
|
||||
// .build()
|
||||
// )
|
||||
//
|
||||
// googleSignInLauncher.launch(googleSignInClient.signInIntent)
|
||||
// }
|
||||
|
||||
/**
|
||||
* Sets whether the screen rotation is enabled or screen orientation is locked in the portrait mode.
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
package co.electriccoin.zcash.ui.common.datasource
|
||||
|
||||
import co.electriccoin.zcash.spackle.io.deleteSuspend
|
||||
import co.electriccoin.zcash.ui.common.model.AddressBook
|
||||
import co.electriccoin.zcash.ui.common.model.AddressBookContact
|
||||
import co.electriccoin.zcash.ui.common.provider.AddressBookProvider
|
||||
|
@ -25,6 +26,8 @@ interface LocalAddressBookDataSource {
|
|||
suspend fun deleteContact(addressBookContact: AddressBookContact): AddressBook
|
||||
|
||||
suspend fun saveContacts(contacts: AddressBook)
|
||||
|
||||
suspend fun deleteAddressBook()
|
||||
}
|
||||
|
||||
class LocalAddressBookDataSourceImpl(
|
||||
|
@ -128,6 +131,11 @@ class LocalAddressBookDataSourceImpl(
|
|||
this@LocalAddressBookDataSourceImpl.addressBook = contacts
|
||||
}
|
||||
|
||||
override suspend fun deleteAddressBook() {
|
||||
addressBookStorageProvider.getStorageFile()?.deleteSuspend()
|
||||
addressBook = null
|
||||
}
|
||||
|
||||
private fun readLocalFileToAddressBook(): AddressBook? {
|
||||
val file = addressBookStorageProvider.getStorageFile() ?: return null
|
||||
return addressBookProvider.readAddressBookFromFile(file)
|
||||
|
|
|
@ -1,197 +1,197 @@
|
|||
@file:Suppress("DEPRECATION")
|
||||
|
||||
package co.electriccoin.zcash.ui.common.datasource
|
||||
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import co.electriccoin.zcash.spackle.Twig
|
||||
import co.electriccoin.zcash.ui.BuildConfig
|
||||
import co.electriccoin.zcash.ui.common.datasource.RemoteAddressBookDataSource.RemoteConsentResult
|
||||
import co.electriccoin.zcash.ui.common.model.AddressBook
|
||||
import co.electriccoin.zcash.ui.common.provider.AddressBookProvider
|
||||
import co.electriccoin.zcash.ui.common.provider.AddressBookStorageProvider
|
||||
import com.google.android.gms.auth.UserRecoverableAuthException
|
||||
import com.google.android.gms.auth.api.signin.GoogleSignIn
|
||||
import com.google.android.gms.common.Scopes
|
||||
import com.google.api.client.extensions.android.http.AndroidHttp
|
||||
import com.google.api.client.googleapis.extensions.android.gms.auth.GoogleAccountCredential
|
||||
import com.google.api.client.googleapis.extensions.android.gms.auth.UserRecoverableAuthIOException
|
||||
import com.google.api.client.googleapis.json.GoogleJsonResponseException
|
||||
import com.google.api.client.http.FileContent
|
||||
import com.google.api.client.http.HttpStatusCodes
|
||||
import com.google.api.client.json.gson.GsonFactory
|
||||
import com.google.api.services.drive.Drive
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
import java.io.File
|
||||
import java.io.IOException
|
||||
import com.google.api.services.drive.model.File as GoogleDriveFile
|
||||
|
||||
interface RemoteAddressBookDataSource {
|
||||
@Throws(
|
||||
UserRecoverableAuthException::class,
|
||||
UserRecoverableAuthIOException::class,
|
||||
IOException::class,
|
||||
IllegalArgumentException::class,
|
||||
GoogleJsonResponseException::class,
|
||||
)
|
||||
suspend fun fetchContacts(): AddressBook?
|
||||
|
||||
@Throws(
|
||||
UserRecoverableAuthException::class,
|
||||
UserRecoverableAuthIOException::class,
|
||||
IOException::class,
|
||||
IllegalArgumentException::class,
|
||||
GoogleJsonResponseException::class,
|
||||
)
|
||||
suspend fun uploadContacts()
|
||||
|
||||
suspend fun getRemoteConsent(): RemoteConsentResult
|
||||
|
||||
sealed interface RemoteConsentResult {
|
||||
data object HasRemoteConsent : RemoteConsentResult
|
||||
|
||||
data class NoRemoteConsent(val intent: Intent?) : RemoteConsentResult
|
||||
|
||||
data object Error : RemoteConsentResult
|
||||
}
|
||||
}
|
||||
|
||||
class RemoteAddressBookDataSourceImpl(
|
||||
private val context: Context,
|
||||
private val addressBookStorageProvider: AddressBookStorageProvider,
|
||||
private val addressBookProvider: AddressBookProvider,
|
||||
) : RemoteAddressBookDataSource {
|
||||
override suspend fun fetchContacts(): AddressBook? =
|
||||
withContext(Dispatchers.IO) {
|
||||
fun fetchRemoteFile(service: Drive): GoogleDriveFile? {
|
||||
return try {
|
||||
service.files().list().setSpaces(DRIVE_PRIVATE_APP_FOLDER).execute().files
|
||||
.find { it.name == DRIVE_ADDRESS_BOOK_FILE_NAME }
|
||||
} catch (e: GoogleJsonResponseException) {
|
||||
Twig.info(e) { "No files found on google drive name $DRIVE_ADDRESS_BOOK_FILE_NAME" }
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
fun downloadRemoteFile(
|
||||
service: Drive,
|
||||
file: GoogleDriveFile
|
||||
): File? {
|
||||
return try {
|
||||
val localFile = addressBookStorageProvider.getOrCreateTempStorageFile()
|
||||
|
||||
localFile.outputStream().use { outputStream ->
|
||||
service.files().get(file.id).executeMediaAndDownloadTo(outputStream)
|
||||
}
|
||||
|
||||
localFile
|
||||
} catch (e: GoogleJsonResponseException) {
|
||||
Twig.info(e) { "No files found on google drive name $DRIVE_ADDRESS_BOOK_FILE_NAME" }
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
var localTempFile: File? = null
|
||||
|
||||
return@withContext try {
|
||||
val drive = createGoogleDriveService()
|
||||
val remoteFile = fetchRemoteFile(drive)
|
||||
|
||||
if (remoteFile == null) {
|
||||
Twig.info { "No address book file found to upload" }
|
||||
return@withContext null
|
||||
}
|
||||
|
||||
localTempFile = downloadRemoteFile(drive, remoteFile) ?: return@withContext null
|
||||
|
||||
addressBookProvider.readAddressBookFromFile(localTempFile)
|
||||
} finally {
|
||||
localTempFile?.delete()
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun uploadContacts() =
|
||||
withContext(Dispatchers.IO) {
|
||||
fun deleteExistingRemoteFiles(service: Drive) {
|
||||
try {
|
||||
val files =
|
||||
service.files().list().setSpaces(DRIVE_PRIVATE_APP_FOLDER).execute().files
|
||||
.filter { it.name == DRIVE_ADDRESS_BOOK_FILE_NAME }
|
||||
files.forEach {
|
||||
service.files().delete(it.id).execute()
|
||||
}
|
||||
} catch (e: GoogleJsonResponseException) {
|
||||
if (e.statusCode == HttpStatusCodes.STATUS_CODE_NOT_FOUND) {
|
||||
Twig.info(e) { "No files found on google drive name $DRIVE_ADDRESS_BOOK_FILE_NAME" }
|
||||
} else {
|
||||
throw e
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun createRemoteFile(
|
||||
file: File,
|
||||
service: Drive
|
||||
) {
|
||||
val metadata =
|
||||
GoogleDriveFile()
|
||||
.setParents(listOf(DRIVE_PRIVATE_APP_FOLDER))
|
||||
.setMimeType("application/octet-stream")
|
||||
.setName(file.name)
|
||||
val fileContent = FileContent("application/octet-stream", file)
|
||||
|
||||
service.files().create(metadata, fileContent).execute()
|
||||
}
|
||||
|
||||
val drive = createGoogleDriveService()
|
||||
val localFile = addressBookStorageProvider.getStorageFile()
|
||||
|
||||
if (localFile == null) {
|
||||
Twig.info { "No address book file found to upload" }
|
||||
return@withContext
|
||||
}
|
||||
|
||||
deleteExistingRemoteFiles(drive)
|
||||
createRemoteFile(localFile, drive)
|
||||
}
|
||||
|
||||
@Suppress("TooGenericExceptionCaught", "SwallowedException")
|
||||
override suspend fun getRemoteConsent(): RemoteConsentResult =
|
||||
withContext(Dispatchers.IO) {
|
||||
val drive = createGoogleDriveService()
|
||||
|
||||
try {
|
||||
drive.files().list().setSpaces(DRIVE_PRIVATE_APP_FOLDER).execute()
|
||||
RemoteConsentResult.HasRemoteConsent
|
||||
} catch (e: UserRecoverableAuthException) {
|
||||
RemoteConsentResult.NoRemoteConsent(e.intent)
|
||||
} catch (e: UserRecoverableAuthIOException) {
|
||||
RemoteConsentResult.NoRemoteConsent(e.intent)
|
||||
} catch (e: Exception) {
|
||||
RemoteConsentResult.Error
|
||||
}
|
||||
}
|
||||
|
||||
private fun createGoogleDriveService(): Drive {
|
||||
val account = GoogleSignIn.getLastSignedInAccount(context)
|
||||
|
||||
val credentials =
|
||||
GoogleAccountCredential.usingOAuth2(context, listOf(Scopes.DRIVE_APPFOLDER))
|
||||
.apply {
|
||||
selectedAccount = account?.account ?: allAccounts.firstOrNull()
|
||||
}
|
||||
|
||||
return Drive
|
||||
.Builder(
|
||||
AndroidHttp.newCompatibleTransport(),
|
||||
GsonFactory(),
|
||||
credentials
|
||||
)
|
||||
.setApplicationName(if (BuildConfig.DEBUG) "secant-android-debug" else "Zashi")
|
||||
.build()
|
||||
}
|
||||
}
|
||||
|
||||
private const val DRIVE_PRIVATE_APP_FOLDER = "appDataFolder"
|
||||
private const val DRIVE_ADDRESS_BOOK_FILE_NAME = "address_book"
|
||||
@file:Suppress("standard:no-empty-file", "no-empty-file")
|
||||
//
|
||||
// package co.electriccoin.zcash.ui.common.datasource
|
||||
//
|
||||
// import android.content.Context
|
||||
// import android.content.Intent
|
||||
// import co.electriccoin.zcash.spackle.Twig
|
||||
// import co.electriccoin.zcash.ui.BuildConfig
|
||||
// import co.electriccoin.zcash.ui.common.datasource.RemoteAddressBookDataSource.RemoteConsentResult
|
||||
// import co.electriccoin.zcash.ui.common.model.AddressBook
|
||||
// import co.electriccoin.zcash.ui.common.provider.AddressBookProvider
|
||||
// import co.electriccoin.zcash.ui.common.provider.AddressBookStorageProvider
|
||||
// import com.google.android.gms.auth.UserRecoverableAuthException
|
||||
// import com.google.android.gms.auth.api.signin.GoogleSignIn
|
||||
// import com.google.android.gms.common.Scopes
|
||||
// import com.google.api.client.extensions.android.http.AndroidHttp
|
||||
// import com.google.api.client.googleapis.extensions.android.gms.auth.GoogleAccountCredential
|
||||
// import com.google.api.client.googleapis.extensions.android.gms.auth.UserRecoverableAuthIOException
|
||||
// import com.google.api.client.googleapis.json.GoogleJsonResponseException
|
||||
// import com.google.api.client.http.FileContent
|
||||
// import com.google.api.client.http.HttpStatusCodes
|
||||
// import com.google.api.client.json.gson.GsonFactory
|
||||
// import com.google.api.services.drive.Drive
|
||||
// import kotlinx.coroutines.Dispatchers
|
||||
// import kotlinx.coroutines.withContext
|
||||
// import java.io.File
|
||||
// import java.io.IOException
|
||||
// import com.google.api.services.drive.model.File as GoogleDriveFile
|
||||
//
|
||||
// interface RemoteAddressBookDataSource {
|
||||
// @Throws(
|
||||
// UserRecoverableAuthException::class,
|
||||
// UserRecoverableAuthIOException::class,
|
||||
// IOException::class,
|
||||
// IllegalArgumentException::class,
|
||||
// GoogleJsonResponseException::class,
|
||||
// )
|
||||
// suspend fun fetchContacts(): AddressBook?
|
||||
//
|
||||
// @Throws(
|
||||
// UserRecoverableAuthException::class,
|
||||
// UserRecoverableAuthIOException::class,
|
||||
// IOException::class,
|
||||
// IllegalArgumentException::class,
|
||||
// GoogleJsonResponseException::class,
|
||||
// )
|
||||
// suspend fun uploadContacts()
|
||||
//
|
||||
// suspend fun getRemoteConsent(): RemoteConsentResult
|
||||
//
|
||||
// sealed interface RemoteConsentResult {
|
||||
// data object HasRemoteConsent : RemoteConsentResult
|
||||
//
|
||||
// data class NoRemoteConsent(val intent: Intent?) : RemoteConsentResult
|
||||
//
|
||||
// data object Error : RemoteConsentResult
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// class RemoteAddressBookDataSourceImpl(
|
||||
// private val context: Context,
|
||||
// private val addressBookStorageProvider: AddressBookStorageProvider,
|
||||
// private val addressBookProvider: AddressBookProvider,
|
||||
// ) : RemoteAddressBookDataSource {
|
||||
// override suspend fun fetchContacts(): AddressBook? =
|
||||
// withContext(Dispatchers.IO) {
|
||||
// fun fetchRemoteFile(service: Drive): GoogleDriveFile? {
|
||||
// return try {
|
||||
// service.files().list().setSpaces(DRIVE_PRIVATE_APP_FOLDER).execute().files
|
||||
// .find { it.name == DRIVE_ADDRESS_BOOK_FILE_NAME }
|
||||
// } catch (e: GoogleJsonResponseException) {
|
||||
// Twig.info(e) { "No files found on google drive name $DRIVE_ADDRESS_BOOK_FILE_NAME" }
|
||||
// null
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// fun downloadRemoteFile(
|
||||
// service: Drive,
|
||||
// file: GoogleDriveFile
|
||||
// ): File? {
|
||||
// return try {
|
||||
// val localFile = addressBookStorageProvider.getOrCreateTempStorageFile()
|
||||
//
|
||||
// localFile.outputStream().use { outputStream ->
|
||||
// service.files().get(file.id).executeMediaAndDownloadTo(outputStream)
|
||||
// }
|
||||
//
|
||||
// localFile
|
||||
// } catch (e: GoogleJsonResponseException) {
|
||||
// Twig.info(e) { "No files found on google drive name $DRIVE_ADDRESS_BOOK_FILE_NAME" }
|
||||
// null
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// var localTempFile: File? = null
|
||||
//
|
||||
// return@withContext try {
|
||||
// val drive = createGoogleDriveService()
|
||||
// val remoteFile = fetchRemoteFile(drive)
|
||||
//
|
||||
// if (remoteFile == null) {
|
||||
// Twig.info { "No address book file found to upload" }
|
||||
// return@withContext null
|
||||
// }
|
||||
//
|
||||
// localTempFile = downloadRemoteFile(drive, remoteFile) ?: return@withContext null
|
||||
//
|
||||
// addressBookProvider.readAddressBookFromFile(localTempFile)
|
||||
// } finally {
|
||||
// localTempFile?.delete()
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// override suspend fun uploadContacts() =
|
||||
// withContext(Dispatchers.IO) {
|
||||
// fun deleteExistingRemoteFiles(service: Drive) {
|
||||
// try {
|
||||
// val files =
|
||||
// service.files().list().setSpaces(DRIVE_PRIVATE_APP_FOLDER).execute().files
|
||||
// .filter { it.name == DRIVE_ADDRESS_BOOK_FILE_NAME }
|
||||
// files.forEach {
|
||||
// service.files().delete(it.id).execute()
|
||||
// }
|
||||
// } catch (e: GoogleJsonResponseException) {
|
||||
// if (e.statusCode == HttpStatusCodes.STATUS_CODE_NOT_FOUND) {
|
||||
// Twig.info(e) { "No files found on google drive name $DRIVE_ADDRESS_BOOK_FILE_NAME" }
|
||||
// } else {
|
||||
// throw e
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// fun createRemoteFile(
|
||||
// file: File,
|
||||
// service: Drive
|
||||
// ) {
|
||||
// val metadata =
|
||||
// GoogleDriveFile()
|
||||
// .setParents(listOf(DRIVE_PRIVATE_APP_FOLDER))
|
||||
// .setMimeType("application/octet-stream")
|
||||
// .setName(file.name)
|
||||
// val fileContent = FileContent("application/octet-stream", file)
|
||||
//
|
||||
// service.files().create(metadata, fileContent).execute()
|
||||
// }
|
||||
//
|
||||
// val drive = createGoogleDriveService()
|
||||
// val localFile = addressBookStorageProvider.getStorageFile()
|
||||
//
|
||||
// if (localFile == null) {
|
||||
// Twig.info { "No address book file found to upload" }
|
||||
// return@withContext
|
||||
// }
|
||||
//
|
||||
// deleteExistingRemoteFiles(drive)
|
||||
// createRemoteFile(localFile, drive)
|
||||
// }
|
||||
//
|
||||
// @Suppress("TooGenericExceptionCaught", "SwallowedException")
|
||||
// override suspend fun getRemoteConsent(): RemoteConsentResult =
|
||||
// withContext(Dispatchers.IO) {
|
||||
// val drive = createGoogleDriveService()
|
||||
//
|
||||
// try {
|
||||
// drive.files().list().setSpaces(DRIVE_PRIVATE_APP_FOLDER).execute()
|
||||
// RemoteConsentResult.HasRemoteConsent
|
||||
// } catch (e: UserRecoverableAuthException) {
|
||||
// RemoteConsentResult.NoRemoteConsent(e.intent)
|
||||
// } catch (e: UserRecoverableAuthIOException) {
|
||||
// RemoteConsentResult.NoRemoteConsent(e.intent)
|
||||
// } catch (e: Exception) {
|
||||
// RemoteConsentResult.Error
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// private fun createGoogleDriveService(): Drive {
|
||||
// val account = GoogleSignIn.getLastSignedInAccount(context)
|
||||
//
|
||||
// val credentials =
|
||||
// GoogleAccountCredential.usingOAuth2(context, listOf(Scopes.DRIVE_APPFOLDER))
|
||||
// .apply {
|
||||
// selectedAccount = account?.account ?: allAccounts.firstOrNull()
|
||||
// }
|
||||
//
|
||||
// return Drive
|
||||
// .Builder(
|
||||
// AndroidHttp.newCompatibleTransport(),
|
||||
// GsonFactory(),
|
||||
// credentials
|
||||
// )
|
||||
// .setApplicationName(if (BuildConfig.DEBUG) "secant-android-debug" else "Zashi")
|
||||
// .build()
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// private const val DRIVE_PRIVATE_APP_FOLDER = "appDataFolder"
|
||||
// private const val DRIVE_ADDRESS_BOOK_FILE_NAME = "address_book"
|
||||
|
|
|
@ -2,50 +2,28 @@
|
|||
|
||||
package co.electriccoin.zcash.ui.common.repository
|
||||
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import co.electriccoin.zcash.spackle.Twig
|
||||
import co.electriccoin.zcash.ui.common.datasource.LocalAddressBookDataSource
|
||||
import co.electriccoin.zcash.ui.common.datasource.RemoteAddressBookDataSource
|
||||
import co.electriccoin.zcash.ui.common.datasource.RemoteAddressBookDataSource.RemoteConsentResult
|
||||
import co.electriccoin.zcash.ui.common.model.AddressBook
|
||||
import co.electriccoin.zcash.ui.common.model.AddressBookContact
|
||||
import com.google.android.gms.auth.GoogleAuthException
|
||||
import com.google.android.gms.auth.UserRecoverableAuthException
|
||||
import com.google.android.gms.auth.api.signin.GoogleSignIn
|
||||
import com.google.android.gms.common.Scopes
|
||||
import com.google.android.gms.common.api.Scope
|
||||
import com.google.android.gms.common.api.Status
|
||||
import com.google.api.client.googleapis.extensions.android.gms.auth.UserRecoverableAuthIOException
|
||||
import com.google.api.client.googleapis.json.GoogleJsonResponseException
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.NonCancellable
|
||||
import kotlinx.coroutines.SupervisorJob
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.SharingStarted
|
||||
import kotlinx.coroutines.flow.WhileSubscribed
|
||||
import kotlinx.coroutines.flow.first
|
||||
import kotlinx.coroutines.flow.onSubscription
|
||||
import kotlinx.coroutines.flow.stateIn
|
||||
import kotlinx.coroutines.flow.update
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.sync.Mutex
|
||||
import kotlinx.coroutines.sync.withLock
|
||||
import kotlinx.coroutines.withContext
|
||||
import kotlinx.datetime.Clock
|
||||
import java.io.IOException
|
||||
import kotlin.math.max
|
||||
import kotlin.time.Duration.Companion.seconds
|
||||
|
||||
interface AddressBookRepository {
|
||||
val addressBook: Flow<AddressBook?>
|
||||
|
||||
val googleSignInRequest: Flow<Unit>
|
||||
// val googleSignInRequest: Flow<Unit>
|
||||
|
||||
val googleRemoteConsentRequest: Flow<Intent>
|
||||
// val googleRemoteConsentRequest: Flow<Intent>
|
||||
|
||||
suspend fun saveContact(
|
||||
name: String,
|
||||
|
@ -60,26 +38,28 @@ interface AddressBookRepository {
|
|||
|
||||
suspend fun deleteContact(contact: AddressBookContact)
|
||||
|
||||
fun onGoogleSignInSuccess()
|
||||
suspend fun deleteAddressBook()
|
||||
|
||||
fun onGoogleSignInCancelled(status: Status?)
|
||||
|
||||
fun onGoogleSignInError()
|
||||
// fun onGoogleSignInSuccess()
|
||||
//
|
||||
// fun onGoogleSignInCancelled(status: Status?)
|
||||
//
|
||||
// fun onGoogleSignInError()
|
||||
}
|
||||
|
||||
@Suppress("TooManyFunctions")
|
||||
class AddressBookRepositoryImpl(
|
||||
private val localAddressBookDataSource: LocalAddressBookDataSource,
|
||||
private val remoteAddressBookDataSource: RemoteAddressBookDataSource,
|
||||
private val context: Context
|
||||
// private val remoteAddressBookDataSource: RemoteAddressBookDataSource,
|
||||
// private val context: Context
|
||||
) : AddressBookRepository {
|
||||
private val scope = CoroutineScope(Dispatchers.Main.immediate + SupervisorJob())
|
||||
// private val scope = CoroutineScope(Dispatchers.Main.immediate + SupervisorJob())
|
||||
|
||||
private val semaphore = Mutex()
|
||||
|
||||
private val addressBookCache = MutableStateFlow<AddressBook?>(null)
|
||||
|
||||
private var internalOperation: InternalOperation? = null
|
||||
// private var internalOperation: InternalOperation? = null
|
||||
|
||||
override val addressBook: Flow<AddressBook?> =
|
||||
addressBookCache
|
||||
|
@ -88,13 +68,13 @@ class AddressBookRepositoryImpl(
|
|||
ensureSynchronization()
|
||||
}
|
||||
}
|
||||
.stateIn(scope = scope, started = SharingStarted.WhileSubscribed(60.seconds), initialValue = null)
|
||||
// .stateIn(scope = scope, started = SharingStarted.WhileSubscribed(60.seconds), initialValue = null)
|
||||
|
||||
override val googleSignInRequest = MutableSharedFlow<Unit>()
|
||||
// override val googleSignInRequest = MutableSharedFlow<Unit>()
|
||||
|
||||
override val googleRemoteConsentRequest = MutableSharedFlow<Intent>()
|
||||
// override val googleRemoteConsentRequest = MutableSharedFlow<Intent>()
|
||||
|
||||
private val internalOperationCompleted = MutableSharedFlow<InternalOperation>()
|
||||
// private val internalOperationCompleted = MutableSharedFlow<InternalOperation>()
|
||||
|
||||
override suspend fun saveContact(
|
||||
name: String,
|
||||
|
@ -112,67 +92,74 @@ class AddressBookRepositoryImpl(
|
|||
InternalOperation.Delete(contact = contact)
|
||||
)
|
||||
|
||||
override fun onGoogleSignInSuccess() {
|
||||
scope.launch {
|
||||
withNonCancellableSemaphore {
|
||||
internalOperation?.let {
|
||||
Twig.info { "Google sign in success" }
|
||||
executeInternalOperation(operation = it)
|
||||
this@AddressBookRepositoryImpl.internalOperation = null
|
||||
internalOperationCompleted.emit(it)
|
||||
}
|
||||
}
|
||||
override suspend fun deleteAddressBook() =
|
||||
withNonCancellableSemaphore {
|
||||
localAddressBookDataSource.deleteAddressBook()
|
||||
addressBookCache.update { null }
|
||||
}
|
||||
}
|
||||
|
||||
override fun onGoogleSignInCancelled(status: Status?) {
|
||||
scope.launch {
|
||||
withNonCancellableSemaphore {
|
||||
Twig.info { "Google sign in cancelled, $status" }
|
||||
internalOperation?.let {
|
||||
executeInternalOperation(operation = it)
|
||||
this@AddressBookRepositoryImpl.internalOperation = null
|
||||
internalOperationCompleted.emit(it)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onGoogleSignInError() {
|
||||
scope.launch {
|
||||
withNonCancellableSemaphore {
|
||||
internalOperation?.let {
|
||||
Twig.info { "Address Book: onGoogleSignInError" }
|
||||
executeInternalOperation(operation = it)
|
||||
this@AddressBookRepositoryImpl.internalOperation = null
|
||||
internalOperationCompleted.emit(it)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// override fun onGoogleSignInSuccess() {
|
||||
// scope.launch {
|
||||
// withNonCancellableSemaphore {
|
||||
// internalOperation?.let {
|
||||
// Twig.info { "Google sign in success" }
|
||||
// executeInternalOperation(operation = it)
|
||||
// this@AddressBookRepositoryImpl.internalOperation = null
|
||||
// internalOperationCompleted.emit(it)
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// override fun onGoogleSignInCancelled(status: Status?) {
|
||||
// scope.launch {
|
||||
// withNonCancellableSemaphore {
|
||||
// Twig.info { "Google sign in cancelled, $status" }
|
||||
// internalOperation?.let {
|
||||
// executeInternalOperation(operation = it)
|
||||
// this@AddressBookRepositoryImpl.internalOperation = null
|
||||
// internalOperationCompleted.emit(it)
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// override fun onGoogleSignInError() {
|
||||
// scope.launch {
|
||||
// withNonCancellableSemaphore {
|
||||
// internalOperation?.let {
|
||||
// Twig.info { "Address Book: onGoogleSignInError" }
|
||||
// executeInternalOperation(operation = it)
|
||||
// this@AddressBookRepositoryImpl.internalOperation = null
|
||||
// internalOperationCompleted.emit(it)
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
private suspend fun ensureSynchronization(
|
||||
forceUpdate: Boolean = false,
|
||||
operation: InternalOperation? = null
|
||||
) {
|
||||
if (forceUpdate || addressBookCache.value == null) {
|
||||
val remote =
|
||||
executeRemoteAddressBookSafe {
|
||||
val contacts = remoteAddressBookDataSource.fetchContacts()
|
||||
Twig.info { "Address Book: ensureSynchronization - remote address book loaded" }
|
||||
contacts
|
||||
}
|
||||
// val remote =
|
||||
// executeRemoteAddressBookSafe {
|
||||
// val contacts = remoteAddressBookDataSource.fetchContacts()
|
||||
// Twig.info { "Address Book: ensureSynchronization - remote address book loaded" }
|
||||
// contacts
|
||||
// }
|
||||
val merged =
|
||||
mergeContacts(
|
||||
local = localAddressBookDataSource.getContacts(),
|
||||
remote = remote,
|
||||
// remote = remote,
|
||||
remote = null,
|
||||
fromOperation = operation
|
||||
)
|
||||
localAddressBookDataSource.saveContacts(merged)
|
||||
executeRemoteAddressBookSafe {
|
||||
remoteAddressBookDataSource.uploadContacts()
|
||||
Twig.info { "Address Book: ensureSynchronization - remote address book uploaded" }
|
||||
}
|
||||
// executeRemoteAddressBookSafe {
|
||||
// remoteAddressBookDataSource.uploadContacts()
|
||||
// Twig.info { "Address Book: ensureSynchronization - remote address book uploaded" }
|
||||
// }
|
||||
addressBookCache.update { merged }
|
||||
}
|
||||
}
|
||||
|
@ -208,38 +195,39 @@ class AddressBookRepositoryImpl(
|
|||
}
|
||||
|
||||
private suspend fun withGoogleDrivePermission(internalOperation: InternalOperation) {
|
||||
val remoteConsent = getRemoteConsent()
|
||||
// val remoteConsent = getRemoteConsent()
|
||||
|
||||
if (hasGoogleDrivePermission() && remoteConsent in
|
||||
listOf(RemoteConsentResult.HasRemoteConsent, RemoteConsentResult.Error)
|
||||
) {
|
||||
withNonCancellableSemaphore {
|
||||
executeInternalOperation(operation = internalOperation)
|
||||
}
|
||||
} else {
|
||||
withNonCancellableSemaphore {
|
||||
if (remoteConsent is RemoteConsentResult.NoRemoteConsent && remoteConsent.intent != null) {
|
||||
Twig.info { "Address Book: withGoogleDrivePermission - request consent" }
|
||||
this.internalOperation = internalOperation
|
||||
googleRemoteConsentRequest.emit(remoteConsent.intent)
|
||||
} else {
|
||||
Twig.info { "Address Book: withGoogleDrivePermission - request permission" }
|
||||
this.internalOperation = internalOperation
|
||||
googleSignInRequest.emit(Unit)
|
||||
}
|
||||
}
|
||||
internalOperationCompleted.first { it == internalOperation }
|
||||
// if (hasGoogleDrivePermission() && remoteConsent in
|
||||
// listOf(RemoteConsentResult.HasRemoteConsent, RemoteConsentResult.Error)
|
||||
// ) {
|
||||
withNonCancellableSemaphore {
|
||||
executeInternalOperation(operation = internalOperation)
|
||||
}
|
||||
// } else {
|
||||
// withNonCancellableSemaphore {
|
||||
// if (remoteConsent is RemoteConsentResult.NoRemoteConsent && remoteConsent.intent != null) {
|
||||
// Twig.info { "Address Book: withGoogleDrivePermission - request consent" }
|
||||
// this.internalOperation = internalOperation
|
||||
// googleRemoteConsentRequest.emit(remoteConsent.intent)
|
||||
// } else {
|
||||
// Twig.info { "Address Book: withGoogleDrivePermission - request permission" }
|
||||
// this.internalOperation = internalOperation
|
||||
// googleSignInRequest.emit(Unit)
|
||||
// }
|
||||
// }
|
||||
// internalOperationCompleted.first { it == internalOperation }
|
||||
// }
|
||||
}
|
||||
|
||||
private suspend fun hasGoogleDrivePermission() =
|
||||
withContext(Dispatchers.IO) {
|
||||
GoogleSignIn.hasPermissions(GoogleSignIn.getLastSignedInAccount(context), Scope(GOOGLE_DRIVE_SCOPE))
|
||||
}
|
||||
// private suspend fun hasGoogleDrivePermission() =
|
||||
// withContext(Dispatchers.IO) {
|
||||
// GoogleSignIn.hasPermissions(GoogleSignIn.getLastSignedInAccount(context), Scope(GOOGLE_DRIVE_SCOPE))
|
||||
// }
|
||||
|
||||
private suspend fun getRemoteConsent() = remoteAddressBookDataSource.getRemoteConsent()
|
||||
// private suspend fun getRemoteConsent() = remoteAddressBookDataSource.getRemoteConsent()
|
||||
|
||||
private suspend fun executeInternalOperation(operation: InternalOperation) {
|
||||
ensureSynchronization()
|
||||
val local =
|
||||
when (operation) {
|
||||
is InternalOperation.Delete -> {
|
||||
|
@ -262,11 +250,11 @@ class AddressBookRepositoryImpl(
|
|||
}
|
||||
}
|
||||
addressBookCache.update { local }
|
||||
scope.launch {
|
||||
withNonCancellableSemaphore {
|
||||
ensureSynchronization(forceUpdate = true, operation = operation)
|
||||
}
|
||||
}
|
||||
// scope.launch {
|
||||
// withNonCancellableSemaphore {
|
||||
// ensureSynchronization(forceUpdate = true, operation = operation)
|
||||
// }
|
||||
// }
|
||||
}
|
||||
|
||||
private suspend fun withNonCancellableSemaphore(block: suspend () -> Unit) {
|
||||
|
@ -275,37 +263,37 @@ class AddressBookRepositoryImpl(
|
|||
}
|
||||
}
|
||||
|
||||
@Suppress("TooGenericExceptionCaught")
|
||||
private suspend fun <T> executeRemoteAddressBookSafe(block: suspend () -> T): T? {
|
||||
if (hasGoogleDrivePermission().not()) {
|
||||
return null
|
||||
}
|
||||
|
||||
return try {
|
||||
block()
|
||||
} catch (e: UserRecoverableAuthException) {
|
||||
Twig.error(e) { "Address Book: remote execution failed" }
|
||||
null
|
||||
} catch (e: UserRecoverableAuthIOException) {
|
||||
Twig.error(e) { "Address Book: remote execution failed" }
|
||||
null
|
||||
} catch (e: GoogleAuthException) {
|
||||
Twig.error(e) { "Address Book: remote execution failed" }
|
||||
null
|
||||
} catch (e: GoogleJsonResponseException) {
|
||||
Twig.error(e) { "Address Book: remote execution failed" }
|
||||
null
|
||||
} catch (e: IOException) {
|
||||
Twig.error(e) { "Address Book: remote execution failed" }
|
||||
null
|
||||
} catch (e: IllegalArgumentException) {
|
||||
Twig.error(e) { "Address Book: remote execution failed" }
|
||||
null
|
||||
} catch (e: Exception) {
|
||||
Twig.error(e) { "Address Book: remote execution failed" }
|
||||
null
|
||||
}
|
||||
}
|
||||
// @Suppress("TooGenericExceptionCaught")
|
||||
// private suspend fun <T> executeRemoteAddressBookSafe(block: suspend () -> T): T? {
|
||||
// if (hasGoogleDrivePermission().not()) {
|
||||
// return null
|
||||
// }
|
||||
//
|
||||
// return try {
|
||||
// block()
|
||||
// } catch (e: UserRecoverableAuthException) {
|
||||
// Twig.error(e) { "Address Book: remote execution failed" }
|
||||
// null
|
||||
// } catch (e: UserRecoverableAuthIOException) {
|
||||
// Twig.error(e) { "Address Book: remote execution failed" }
|
||||
// null
|
||||
// } catch (e: GoogleAuthException) {
|
||||
// Twig.error(e) { "Address Book: remote execution failed" }
|
||||
// null
|
||||
// } catch (e: GoogleJsonResponseException) {
|
||||
// Twig.error(e) { "Address Book: remote execution failed" }
|
||||
// null
|
||||
// } catch (e: IOException) {
|
||||
// Twig.error(e) { "Address Book: remote execution failed" }
|
||||
// null
|
||||
// } catch (e: IllegalArgumentException) {
|
||||
// Twig.error(e) { "Address Book: remote execution failed" }
|
||||
// null
|
||||
// } catch (e: Exception) {
|
||||
// Twig.error(e) { "Address Book: remote execution failed" }
|
||||
// null
|
||||
// }
|
||||
// }
|
||||
}
|
||||
|
||||
private sealed interface InternalOperation {
|
||||
|
@ -316,4 +304,4 @@ private sealed interface InternalOperation {
|
|||
data class Delete(val contact: AddressBookContact) : InternalOperation
|
||||
}
|
||||
|
||||
private const val GOOGLE_DRIVE_SCOPE = Scopes.DRIVE_APPFOLDER
|
||||
// private const val GOOGLE_DRIVE_SCOPE = Scopes.DRIVE_APPFOLDER
|
||||
|
|
|
@ -0,0 +1,14 @@
|
|||
package co.electriccoin.zcash.ui.common.usecase
|
||||
|
||||
import co.electriccoin.zcash.ui.common.repository.AddressBookRepository
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
|
||||
class DeleteAddressBookUseCase(
|
||||
private val addressBookRepository: AddressBookRepository
|
||||
) {
|
||||
suspend operator fun invoke() =
|
||||
withContext(Dispatchers.IO) {
|
||||
addressBookRepository.deleteAddressBook()
|
||||
}
|
||||
}
|
|
@ -28,6 +28,7 @@ import co.electriccoin.zcash.ui.common.provider.GetDefaultServersProvider
|
|||
import co.electriccoin.zcash.ui.common.repository.BalanceRepository
|
||||
import co.electriccoin.zcash.ui.common.repository.ExchangeRateRepository
|
||||
import co.electriccoin.zcash.ui.common.repository.WalletRepository
|
||||
import co.electriccoin.zcash.ui.common.usecase.DeleteAddressBookUseCase
|
||||
import co.electriccoin.zcash.ui.preference.StandardPreferenceKeys
|
||||
import co.electriccoin.zcash.ui.screen.account.ext.TransactionOverviewExt
|
||||
import co.electriccoin.zcash.ui.screen.account.ext.getSortHeight
|
||||
|
@ -44,7 +45,6 @@ import kotlinx.coroutines.flow.combine
|
|||
import kotlinx.coroutines.flow.filterNotNull
|
||||
import kotlinx.coroutines.flow.firstOrNull
|
||||
import kotlinx.coroutines.flow.flatMapLatest
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.flow.stateIn
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
|
@ -62,6 +62,7 @@ class WalletViewModel(
|
|||
private val encryptedPreferenceProvider: EncryptedPreferenceProvider,
|
||||
private val standardPreferenceProvider: StandardPreferenceProvider,
|
||||
private val getAvailableServers: GetDefaultServersProvider,
|
||||
private val deleteAddressBookUseCase: DeleteAddressBookUseCase,
|
||||
) : AndroidViewModel(application) {
|
||||
val navigationCommand = exchangeRateRepository.navigationCommand
|
||||
|
||||
|
@ -209,16 +210,6 @@ class WalletViewModel(
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This method only has an effect if the synchronizer currently is loaded.
|
||||
*/
|
||||
fun rescanBlockchain() {
|
||||
viewModelScope.launch {
|
||||
walletCoordinator.rescanBlockchain()
|
||||
persistWalletRestoringState(WalletRestoringState.RESTORING)
|
||||
}
|
||||
}
|
||||
|
||||
private fun clearAppStateFlow(): Flow<Boolean> =
|
||||
callbackFlow {
|
||||
viewModelScope.launch {
|
||||
|
@ -228,6 +219,7 @@ class WalletViewModel(
|
|||
val encryptedPrefsCleared =
|
||||
encryptedPreferenceProvider()
|
||||
.clearPreferences()
|
||||
deleteAddressBookUseCase()
|
||||
|
||||
Twig.info { "Both preferences cleared: ${standardPrefsCleared && encryptedPrefsCleared}" }
|
||||
|
||||
|
|
Loading…
Reference in New Issue