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