diff --git a/.gitignore b/.gitignore index ba2aeb0cf..cb5d30587 100644 --- a/.gitignore +++ b/.gitignore @@ -79,5 +79,7 @@ CMakeFiles/ CMakeCache.txt jniLibs/ -libwallet/ +libwallet/wallet.h +libwallet/arm64-v8a/libminotari_wallet_ffi.a +libwallet/x86_64/libminotari_wallet_ffi.a !app/src/main/jniLibs/ diff --git a/app/build.gradle b/app/build.gradle index c284001e6..9e31865e8 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -13,8 +13,8 @@ android { defaultConfig { applicationId "com.tari.android.wallet" minSdkVersion 26 - targetSdkVersion 33 - compileSdk 33 + targetSdkVersion 34 + compileSdk 34 versionCode buildNumber versionName versionNumber + "-libwallet-" + libwalletVersion testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" @@ -22,8 +22,9 @@ android { ndk { abiFilters = [] - abiFilters.addAll(supportedABIs) + abiFilters.addAll(["arm64-v8a", "x86_64"]) } + externalNativeBuild { cmake { arguments "-DANDROID_STL=c++_static" @@ -63,9 +64,9 @@ android { // PRIVATE BUILD: comment (add two slashes at the start of each line) all the `sentry` // block for private release builds support sentry { // https://docs.sentry.io/platforms/android/#gradle-configuration - autoUpload.set(true) - uploadNativeSymbols.set(true) - includeNativeSources.set(true) + autoUpload.set(false) + uploadNativeSymbols.set(false) + includeNativeSources.set(false) } } } @@ -84,11 +85,7 @@ android { applicationVariants.configureEach { variant -> variant.mergedFlavor.manifestPlaceholders.dropboxApiKey = loadDropboxProps().getProperty("dropbox_key") - if (variant.buildType.name == "debug") { - variant.mergedFlavor.manifestPlaceholders.sentryPublicDSN = "" - } else { - variant.mergedFlavor.manifestPlaceholders.sentryPublicDSN = loadSecretProps().getProperty("sentry.public_dsn") - } + variant.mergedFlavor.manifestPlaceholders.sentryPublicDSN = loadSecretProps().getProperty("sentry.public_dsn") } externalNativeBuild { @@ -201,24 +198,24 @@ Properties loadProps(fileName) { return properties } -//preBuild.dependsOn("downloadLibwallet") +preBuild.dependsOn("downloadLibwallet") dependencies { // kotlin implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version" implementation "org.jetbrains.kotlin:kotlin-reflect:$kotlin_version" // coroutines - implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.4" - implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.6.4" + implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.7.3" + implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.7.3" implementation "androidx.lifecycle:lifecycle-runtime-ktx:$lifecycle_version" implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:$lifecycle_version" implementation "androidx.lifecycle:lifecycle-reactivestreams-ktx:$lifecycle_version" implementation "androidx.lifecycle:lifecycle-livedata-ktx:$lifecycle_version" - implementation "androidx.fragment:fragment-ktx:1.5.7" + implementation "androidx.fragment:fragment-ktx:1.6.2" kapt "androidx.lifecycle:lifecycle-compiler:$lifecycle_version" // kotlin extensions - implementation "androidx.core:core-ktx:1.10.1" + implementation "androidx.core:core-ktx:1.12.0" // android implementation "androidx.appcompat:appcompat:1.6.1" // support lib @@ -227,30 +224,30 @@ dependencies { // android jetpack implementation "androidx.lifecycle:lifecycle-extensions:2.2.0" // recycler view - implementation "androidx.recyclerview:recyclerview:1.3.1-rc01" + implementation "androidx.recyclerview:recyclerview:1.3.2" // the new view pager implementation "androidx.viewpager2:viewpager2:1.0.0" // for tab layout - implementation "com.google.android.material:material:1.9.0" + implementation "com.google.android.material:material:1.10.0" // lottie - implementation 'com.airbnb.android:lottie:6.0.0' + implementation 'com.airbnb.android:lottie:6.1.0' // Glide - image processing & caching library - implementation 'com.github.bumptech.glide:glide:4.15.1' - kapt 'com.github.bumptech.glide:compiler:4.15.1' + implementation 'com.github.bumptech.glide:glide:4.16.0' + kapt 'com.github.bumptech.glide:compiler:4.16.0' // biometric implementation "androidx.biometric:biometric:1.1.0" // joda - implementation 'net.danlew:android.joda:2.12.1' + implementation 'net.danlew:android.joda:2.12.5' // parceler implementation "org.parceler:parceler-api:1.1.13" kapt "org.parceler:parceler:1.1.13" // dagger - DI - implementation 'com.google.dagger:dagger:2.45' - kapt 'com.google.dagger:dagger-compiler:2.45' + implementation 'com.google.dagger:dagger:2.48.1' + kapt 'com.google.dagger:dagger-compiler:2.48.1' // rx implementation "io.reactivex.rxjava2:rxandroid:2.1.1" @@ -267,7 +264,7 @@ dependencies { implementation "com.github.adorsys:secure-storage-android:0.0.2" // Tor control - implementation "info.guardianproject:tor-android:0.4.7.8" + implementation "info.guardianproject:tor-android:0.4.7.14" implementation "info.guardianproject:jtorctl:0.4.5.7" // used to read log files @@ -280,14 +277,14 @@ dependencies { // Retrofit2 - Popular (2020) REST API wrapper with Java interface implementation "com.squareup.retrofit2:retrofit:2.9.0" implementation "com.squareup.retrofit2:converter-gson:2.9.0" - implementation "com.squareup.okhttp3:logging-interceptor:5.0.0-alpha.10" + implementation "com.squareup.okhttp3:logging-interceptor:5.0.0-alpha.11" // Google services & Google drive implementation platform('com.google.firebase:firebase-bom:31.2.3') implementation 'com.google.firebase:firebase-crashlytics' - implementation 'com.google.android.gms:play-services-auth:20.4.1' - implementation 'com.google.http-client:google-http-client-gson:1.43.1' + implementation 'com.google.android.gms:play-services-auth:20.7.0' + implementation 'com.google.http-client:google-http-client-gson:1.43.3' implementation('com.google.api-client:google-api-client-android:2.2.0') { exclude group: "org.apache.httpcomponents" } @@ -295,12 +292,12 @@ dependencies { exclude group: "org.apache.httpcomponents" } // sentry - crash analytics - implementation 'io.sentry:sentry-android:6.16.0' - - implementation "com.dropbox.core:dropbox-core-sdk:5.4.4" + implementation 'io.sentry:sentry-android:6.33.1' - implementation "com.github.yat-labs:yat-lib-android:0.1.42" + implementation "com.dropbox.core:dropbox-core-sdk:5.4.6" +// implementation "com.github.yat-labs:yat-lib-android:0.1.42" + implementation project(":yatlib") // custom libraries region // flex layout @@ -309,7 +306,7 @@ dependencies { implementation "io.github.everythingme:overscroll-decor-android:1.1.1" // giphy, don"t update to 2.2.0 because of not compatible yet - implementation 'com.giphy.sdk:ui:2.3.1' + implementation 'com.giphy.sdk:ui:2.3.13' // spring animation implementation "androidx.dynamicanimation:dynamicanimation:1.0.0" implementation "com.github.MasayukiSuda:EasingInterpolator:v1.3.2" @@ -317,14 +314,14 @@ dependencies { // seismic - device shake detector implementation "com.squareup:seismic:1.0.3" - implementation "net.yslibrary.keyboardvisibilityevent:keyboardvisibilityevent:2.2.0" + implementation "net.yslibrary.keyboardvisibilityevent:keyboardvisibilityevent:2.3.0" - implementation 'com.itextpdf:itext7-core:7.2.5' + implementation 'com.itextpdf:itext7-core:8.0.2' - implementation("org.apache.maven:maven-artifact:3.0.3") + implementation("org.apache.maven:maven-artifact:3.9.5") // debugImplementation because LeakCanary should only run in debug builds. - debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.10' + debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.12' implementation 'com.github.vestrel00:contacts-android:0.2.4' @@ -332,8 +329,8 @@ dependencies { // test testImplementation "junit:junit:4.13.2" - testImplementation 'io.mockk:mockk:1.13.4' - androidTestImplementation 'io.mockk:mockk-android:1.13.4' + testImplementation 'io.mockk:mockk:1.13.8' + androidTestImplementation 'io.mockk:mockk-android:1.13.8' androidTestImplementation "androidx.test:core:1.5.0" androidTestImplementation "androidx.test.ext:junit:1.1.5" androidTestImplementation "androidx.test.espresso:espresso-core:3.5.1" diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro index 10bd9827e..6b02068cf 100644 --- a/app/proguard-rules.pro +++ b/app/proguard-rules.pro @@ -32,4 +32,17 @@ -keep enum com.tari.android.wallet.ui.fragment.settings.themeSelector.TariTheme { *; } --keep enum com.tari.android.wallet.data.sharedPrefs.securityStages.WalletSecurityStage { *; } \ No newline at end of file +-keep enum com.tari.android.wallet.data.sharedPrefs.securityStages.WalletSecurityStage { *; } + +-keep enum com.tari.android.wallet.application.Network { *; } + +-keep class com.tari.android.wallet.ui.common.gyphy.api.dto.SearchGIFResponse { *; } +-keep class com.tari.android.wallet.ui.common.gyphy.api.dto.SearchGIFsResponse { *; } +-keep class com.tari.android.wallet.ui.common.gyphy.api.dto.Data { *; } +-keep class com.tari.android.wallet.ui.common.gyphy.api.dto.Meta { *; } +-keep class com.tari.android.wallet.ui.common.gyphy.api.dto.Images { *; } +-keep class com.tari.android.wallet.ui.common.gyphy.api.dto.ImageVariant { *; } +-keep class com.tari.android.wallet.ui.common.gyphy.api.dto.Original { *; } + +-keep class **Fragment** { *; } +-keep class **ViewModel** { *; } diff --git a/app/regular/release/app-regular-release.aab b/app/regular/release/app-regular-release.aab deleted file mode 100644 index d9c71d382..000000000 Binary files a/app/regular/release/app-regular-release.aab and /dev/null differ diff --git a/app/src/androidTest/java/com/tari/android/wallet/FFIWalletTests.kt b/app/src/androidTest/java/com/tari/android/wallet/FFIWalletTests.kt index 8fae11b70..635df526d 100644 --- a/app/src/androidTest/java/com/tari/android/wallet/FFIWalletTests.kt +++ b/app/src/androidTest/java/com/tari/android/wallet/FFIWalletTests.kt @@ -43,7 +43,9 @@ import androidx.test.ext.junit.runners.AndroidJUnit4 import com.tari.android.wallet.data.sharedPrefs.SharedPrefsRepository import com.tari.android.wallet.data.sharedPrefs.baseNode.BaseNodeSharedRepository import com.tari.android.wallet.data.sharedPrefs.network.NetworkRepositoryImpl +import com.tari.android.wallet.data.sharedPrefs.security.SecurityPrefRepository import com.tari.android.wallet.data.sharedPrefs.securityStages.SecurityStagesRepository +import com.tari.android.wallet.data.sharedPrefs.sentry.SentryPrefRepository import com.tari.android.wallet.data.sharedPrefs.tariSettings.TariSettingsSharedRepository import com.tari.android.wallet.data.sharedPrefs.tor.TorSharedRepository import com.tari.android.wallet.di.ApplicationModule @@ -77,9 +79,10 @@ class FFIWalletTests { private val tariSettingsRepository = TariSettingsSharedRepository(prefs, networkRepository) private val securityStagesRepository = SecurityStagesRepository(prefs, networkRepository) private val contactSharedPrefRepository = ContactSharedPrefRepository(networkRepository, prefs) + private val sentryPrefRepository: SentryPrefRepository = SentryPrefRepository(prefs, networkRepository) private val torSharedRepository = TorSharedRepository(prefs, networkRepository) + private val securityPrefRepository = SecurityPrefRepository(context, prefs, networkRepository) private val sharedPrefsRepository = SharedPrefsRepository( - context, prefs, networkRepository, backupSettingsRepository, @@ -88,7 +91,9 @@ class FFIWalletTests { torSharedRepository, tariSettingsRepository, securityStagesRepository, - contactSharedPrefRepository + contactSharedPrefRepository, + sentryPrefRepository, + securityPrefRepository, ) private val walletDirPath = context.filesDir.absolutePath @@ -118,7 +123,7 @@ class FFIWalletTests { ) val logFile = File(walletDirPath, "test_log.log") // create wallet instance - wallet = FFIWallet(sharedPrefsRepository, SeedPhraseRepository(), networkRepository, commsConfig, logFile.absolutePath) + wallet = FFIWallet(sharedPrefsRepository, securityPrefRepository, SeedPhraseRepository(), networkRepository, commsConfig, logFile.absolutePath) // create listener listener = TestAddRecipientAddNodeListener() wallet.listener = listener diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 0169de819..b0b91b874 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -30,9 +30,12 @@ + + + @@ -55,7 +58,8 @@ android:name=".service.service.WalletService" android:description="@string/wallet_service_desc" android:enabled="true" - android:exported="false"> + android:exported="false" + android:foregroundServiceType="dataSync"> @@ -81,6 +85,7 @@ android:name=".ui.fragment.splash.SplashActivity" android:configChanges="keyboardHidden|orientation|screenSize" android:exported="true" + android:screenOrientation="portrait" android:theme="@style/AppTheme.Splash" android:windowSoftInputMode="stateAlwaysHidden"> @@ -93,18 +98,21 @@ @@ -137,11 +145,16 @@ android:host="y.at" android:scheme="tari" /> + + + @@ -156,13 +169,15 @@ android:name="com.google.android.gms.auth.api.signin.internal.SignInHubActivity" android:excludeFromRecents="true" android:exported="false" + android:screenOrientation="portrait" android:theme="@style/GoogleSignInActivity" tools:replace="android:theme" /> + android:launchMode="singleTask" + android:screenOrientation="portrait"> @@ -175,26 +190,32 @@ diff --git a/app/src/main/cpp/CMakeLists.txt b/app/src/main/cpp/CMakeLists.txt index 335ff6370..8a3bd0088 100644 --- a/app/src/main/cpp/CMakeLists.txt +++ b/app/src/main/cpp/CMakeLists.txt @@ -49,7 +49,7 @@ add_library( set_target_properties( wallet PROPERTIES - IMPORTED_LOCATION ${libs_DIR}/${ANDROID_ABI}/libtari_wallet_ffi.a + IMPORTED_LOCATION ${libs_DIR}/${ANDROID_ABI}/libminotari_wallet_ffi.a IMPORTED_LINK_INTERFACE_LIBRARIES "ssl;sqlite3" ) diff --git a/app/src/main/java/com/tari/android/wallet/application/MigrationManager.kt b/app/src/main/java/com/tari/android/wallet/application/MigrationManager.kt index 7b5d7c6ab..2d1da2e82 100644 --- a/app/src/main/java/com/tari/android/wallet/application/MigrationManager.kt +++ b/app/src/main/java/com/tari/android/wallet/application/MigrationManager.kt @@ -11,7 +11,7 @@ import javax.inject.Singleton @Singleton class MigrationManager @Inject constructor(private val manager: WalletManager) { - private val minValidVersion = DefaultArtifactVersion("0.50.0-hotfix.1") + private val minValidVersion = DefaultArtifactVersion("v0.52.0") private val simpleViewModel = SimpleViewModel() fun validateVersion(onValid: () -> Unit, onError: () -> Unit) { diff --git a/app/src/main/java/com/tari/android/wallet/application/TariWalletApplication.kt b/app/src/main/java/com/tari/android/wallet/application/TariWalletApplication.kt index e21afe021..780108bd6 100644 --- a/app/src/main/java/com/tari/android/wallet/application/TariWalletApplication.kt +++ b/app/src/main/java/com/tari/android/wallet/application/TariWalletApplication.kt @@ -38,7 +38,9 @@ import androidx.lifecycle.DefaultLifecycleObserver import androidx.lifecycle.LifecycleOwner import androidx.lifecycle.ProcessLifecycleOwner import com.orhanobut.logger.Logger +import com.tari.android.wallet.BuildConfig import com.tari.android.wallet.data.sharedPrefs.SharedPrefsRepository +import com.tari.android.wallet.data.sharedPrefs.security.SecurityPrefRepository import com.tari.android.wallet.di.DiContainer import com.tari.android.wallet.event.Event import com.tari.android.wallet.event.EventBus @@ -48,6 +50,7 @@ import com.tari.android.wallet.notification.NotificationHelper import com.tari.android.wallet.service.service.WalletServiceLauncher import com.tari.android.wallet.ui.common.gyphy.GiphyAdapter import com.tari.android.wallet.yat.YatAdapter +import io.sentry.android.core.SentryAndroid import java.lang.ref.WeakReference import javax.inject.Inject @@ -70,6 +73,9 @@ class TariWalletApplication : Application() { @Inject lateinit var sharedPrefsRepository: SharedPrefsRepository + @Inject + lateinit var securityPrefRepository: SecurityPrefRepository + @Inject lateinit var walletServiceLauncher: WalletServiceLauncher @@ -93,15 +99,27 @@ class TariWalletApplication : Application() { val currentActivity: Activity? get() = activityLifecycleCallbacks.currentActivity + @Suppress("KotlinConstantConditions") override fun onCreate() { super.onCreate() INSTANCE = WeakReference(this) + SentryAndroid.init(this) { + it.isDebug = BuildConfig.BUILD_TYPE == "debug" + val buildType = when (BuildConfig.BUILD_TYPE) { + "debug" -> "DEVELOPMENT" + "release" -> "PRODUCTION" + else -> "PRODUCTION" + } + it.environment = buildType + "_" + BuildConfig.FLAVOR + } + registerActivityLifecycleCallbacks(activityLifecycleCallbacks) DiContainer.initContainer(this) initApplication() + ProcessLifecycleOwner.get().lifecycle.addObserver(AppObserver()) logger.i("Application inited") } @@ -112,7 +130,7 @@ class TariWalletApplication : Application() { notificationHelper.createNotificationChannels() // user should authenticate every time the app starts up - sharedPrefsRepository.isAuthenticated = false + securityPrefRepository.isAuthenticated = false registerReceiver(connectionStateReceiver, connectionStateReceiver.intentFilter) @@ -149,6 +167,7 @@ class TariWalletApplication : Application() { override fun onDestroy(owner: LifecycleOwner) { super.onDestroy(owner) + securityPrefRepository.isAuthenticated = false logger.i("App was destroyed") walletServiceLauncher.stopOnAppBackgrounded() } diff --git a/app/src/main/java/com/tari/android/wallet/application/WalletManager.kt b/app/src/main/java/com/tari/android/wallet/application/WalletManager.kt index 5b80bb515..1a71f6fef 100644 --- a/app/src/main/java/com/tari/android/wallet/application/WalletManager.kt +++ b/app/src/main/java/com/tari/android/wallet/application/WalletManager.kt @@ -40,6 +40,7 @@ import com.tari.android.wallet.data.WalletConfig import com.tari.android.wallet.data.sharedPrefs.SharedPrefsRepository import com.tari.android.wallet.data.sharedPrefs.baseNode.BaseNodeSharedRepository import com.tari.android.wallet.data.sharedPrefs.network.NetworkRepository +import com.tari.android.wallet.data.sharedPrefs.security.SecurityPrefRepository import com.tari.android.wallet.data.sharedPrefs.tariSettings.TariSettingsSharedRepository import com.tari.android.wallet.event.EventBus import com.tari.android.wallet.ffi.* @@ -64,7 +65,8 @@ class WalletManager( private val baseNodeSharedRepository: BaseNodeSharedRepository, private val seedPhraseRepository: SeedPhraseRepository, private val networkRepository: NetworkRepository, - private var tariSettingsSharedRepository: TariSettingsSharedRepository, + private val tariSettingsSharedRepository: TariSettingsSharedRepository, + private val securityPrefRepository: SecurityPrefRepository, private val baseNodes: BaseNodes, private val torConfig: TorConfig ) { @@ -202,6 +204,7 @@ class WalletManager( val isNewInstallation = !WalletUtil.walletExists(walletConfig) val wallet = FFIWallet( sharedPrefsWrapper, + securityPrefRepository, seedPhraseRepository, networkRepository, getCommsConfig(), diff --git a/app/src/main/java/com/tari/android/wallet/application/baseNodes/BaseNodes.kt b/app/src/main/java/com/tari/android/wallet/application/baseNodes/BaseNodes.kt index 29553cd43..f935931a1 100644 --- a/app/src/main/java/com/tari/android/wallet/application/baseNodes/BaseNodes.kt +++ b/app/src/main/java/com/tari/android/wallet/application/baseNodes/BaseNodes.kt @@ -1,6 +1,8 @@ package com.tari.android.wallet.application.baseNodes import android.content.Context +import com.google.gson.Gson +import com.orhanobut.logger.Logger import com.tari.android.wallet.R import com.tari.android.wallet.application.Network import com.tari.android.wallet.application.WalletState @@ -43,6 +45,9 @@ class BaseNodes( }.addTo(compositeDisposable) } + val onionRegex = "(.+::[A-Za-z0-9 ]{64}::/onion3/[A-Za-z0-9]+:[\\d]+)" + val ipV4Regex = "(.+::[A-Za-z0-9 ]{64}::/ip4/[0-9]{1,3}[.][0-9]{1,3}[.][0-9]{1,3}[.][0-9]{1,3}/tcp/[0-9]{2,6})" + /** * Returns the list of base nodes in the resource file base_nodes.txt as pairs of * ({name}, {public_key_hex}, {public_address}). @@ -52,8 +57,22 @@ class BaseNodes( context.resources.openRawResource(getBaseNodeResource(networkRepository.currentNetwork!!.network)), "UTF-8" ) - Regex("(.+::[A-Za-z0-9 ]{64}::/onion3/[A-Za-z0-9]+:[\\d]+)").findAll(fileContent).map { matchResult -> + Logger.t(this::class.simpleName).e("baseNodeList: $fileContent") + val list = mutableListOf() + val onionBaseNodes = findAndAddBaseNode(fileContent, onionRegex).toList() + val ipV4BaseNodes = findAndAddBaseNode(fileContent, ipV4Regex).toList() + list.addAll(onionBaseNodes) + list.addAll(ipV4BaseNodes) + list.sortedBy { it.name } + } + + private fun findAndAddBaseNode(fileContent: String, regex: String): Sequence { + return Regex(regex).findAll(fileContent).map { matchResult -> val tripleString = matchResult.value.split("::") + if (tripleString.size ==2) { + 1 + } + Logger.t(this::class.simpleName).e("baseNodeList0: $tripleString, baseNodeList1: ${tripleString[1]}, baseNodeList2: ${tripleString[2]}") BaseNodeDto(tripleString[0], tripleString[1], tripleString[2]) } } @@ -78,19 +97,28 @@ class BaseNodes( } fun startSync() { - //essential for wallet creation flow - val baseNode = baseNodeSharedRepository.currentBaseNode ?: return - serviceConnection.currentState.service ?: return - if (EventBus.walletState.publishSubject.value != WalletState.Running) return + try { + Logger.t(this::class.simpleName).e("startSync") + //essential for wallet creation flow + val baseNode = baseNodeSharedRepository.currentBaseNode ?: return + serviceConnection.currentState.service ?: return + if (EventBus.walletState.publishSubject.value != WalletState.Running) return - val baseNodeKeyFFI = FFIPublicKey(HexString(baseNode.publicKeyHex)) - FFIWallet.instance?.addBaseNodePeer(baseNodeKeyFFI, baseNode.address) - baseNodeKeyFFI.destroy() - walletService.getWithError { error, wallet -> wallet.startBaseNodeSync(error) } + Logger.t(this::class.simpleName).e("startSync:publicKeyHex: ${baseNode.publicKeyHex}") + Logger.t(this::class.simpleName).e("startSync:address: ${baseNode.address}") + Logger.t(this::class.simpleName).e("startSync:address: ${Gson().toJson(baseNodeSharedRepository.userBaseNodes)}") + val baseNodeKeyFFI = FFIPublicKey(HexString(baseNode.publicKeyHex)) + FFIWallet.instance?.addBaseNodePeer(baseNodeKeyFFI, baseNode.address) + baseNodeKeyFFI.destroy() + walletService.getWithError { error, wallet -> wallet.startBaseNodeSync(error) } + } catch (e: Throwable) { + Logger.t(this::class.simpleName).e("startSync") + setNextBaseNode() + startSync() + } } - @Suppress("UNUSED_EXPRESSION") - private fun getBaseNodeResource(network: Network): Int = when(network) { + private fun getBaseNodeResource(network: Network): Int = when (network) { else -> R.raw.stagenet_base_nodes } } diff --git a/app/src/main/java/com/tari/android/wallet/application/deeplinks/DeepLink.kt b/app/src/main/java/com/tari/android/wallet/application/deeplinks/DeepLink.kt index 437cb4527..cfb87dca7 100644 --- a/app/src/main/java/com/tari/android/wallet/application/deeplinks/DeepLink.kt +++ b/app/src/main/java/com/tari/android/wallet/application/deeplinks/DeepLink.kt @@ -32,7 +32,9 @@ */ package com.tari.android.wallet.application.deeplinks +import com.tari.android.wallet.data.sharedPrefs.tor.TorBridgeConfiguration import com.tari.android.wallet.model.MicroTari +import com.tari.android.wallet.model.TariWalletAddress import java.math.BigInteger /** @@ -49,8 +51,7 @@ sealed class DeepLink { // tari://esmeralda/contacts?list[0][alias]=Name&list[0][hex]=hex&list[1][alias]=Name&list[1][hex]=hex class Contacts(val contacts: List) : DeepLink() { - constructor(params: Map) : this( - params.filterKeys { it.startsWith("list[") } + constructor(params: Map) : this(params.filterKeys { it.startsWith("list[") } .map { FormatExtractor(it.key, it.value) } .groupBy { it.index } .map { @@ -58,7 +59,7 @@ sealed class DeepLink { val hex = it.value.firstOrNull { it.name == hexKey }?.value.orEmpty() DeeplinkContact(alias, hex) } - ) + .filter { TariWalletAddress.validate(it.hex) }) override fun getParams(): Map = hashMapOf().apply { contacts.forEachIndexed { index, contact -> @@ -160,6 +161,10 @@ sealed class DeepLink { } } + class TorBridges(val torConfigurations: List): DeepLink() { + + } + companion object { fun getByCommand(command: String, params: Map): DeepLink? = when (command) { diff --git a/app/src/main/java/com/tari/android/wallet/application/deeplinks/DeeplinkFormatter.kt b/app/src/main/java/com/tari/android/wallet/application/deeplinks/DeeplinkFormatter.kt index 27dac3688..4084e5666 100644 --- a/app/src/main/java/com/tari/android/wallet/application/deeplinks/DeeplinkFormatter.kt +++ b/app/src/main/java/com/tari/android/wallet/application/deeplinks/DeeplinkFormatter.kt @@ -2,6 +2,8 @@ package com.tari.android.wallet.application.deeplinks import android.net.Uri import com.tari.android.wallet.data.sharedPrefs.network.NetworkRepository +import com.tari.android.wallet.data.sharedPrefs.tor.TorBridgeConfiguration +import com.tari.android.wallet.model.TariWalletAddress import java.net.URLDecoder import javax.inject.Inject import javax.inject.Singleton @@ -9,6 +11,11 @@ import javax.inject.Singleton @Singleton class DeeplinkFormatter @Inject constructor(private val networkRepository: NetworkRepository) { fun parse(deepLink: String): DeepLink? { + val torBridges = getTorDeeplink(deepLink) + if (torBridges.isNotEmpty()) { + return DeepLink.TorBridges(torBridges) + } + val uri = Uri.parse(URLDecoder.decode(deepLink, "UTF-8")) if (!uri.authority.equals(networkRepository.currentNetwork!!.network.uriComponent)) { @@ -24,10 +31,26 @@ class DeeplinkFormatter @Inject constructor(private val networkRepository: Netwo }.toMap() paramentrs = values.toMutableMap() } - return DeepLink.getByCommand(command, paramentrs) + + val parsedDeeplink = DeepLink.getByCommand(command, paramentrs) + + if (parsedDeeplink is DeepLink.Send) { + if (!TariWalletAddress.validate(parsedDeeplink.walletAddressHex)) return null + } + if (parsedDeeplink is DeepLink.UserProfile) { + if (!TariWalletAddress.validate(parsedDeeplink.tariAddressHex)) return null + } + + return parsedDeeplink } fun toDeeplink(deepLink: DeepLink): String { + if (deepLink is DeepLink.TorBridges) { + return deepLink.torConfigurations.joinToString("\n") { + "${it.ip}:${it.port} ${it.fingerprint}" + } + } + val fullPart = Uri.Builder() .scheme(scheme) .authority(networkRepository.currentNetwork!!.network.uriComponent) @@ -40,7 +63,21 @@ class DeeplinkFormatter @Inject constructor(private val networkRepository: Netwo return fullPart.build().toString() } + private fun getTorDeeplink(input: String): List { + return regex.findAll(input).mapNotNull { match -> + try { + val ipAddressAndPort = match.groupValues[1].split(":") + val sha1Hash = match.groupValues[2] + TorBridgeConfiguration("", ipAddressAndPort[0], ipAddressAndPort[1], sha1Hash) + } catch (e: Exception) { + null + } + }.toList() + } + companion object { const val scheme = "tari" + + val regex = Regex("""(\d+\.\d+\.\d+\.\d+:\d+) ([0-9A-Fa-f]+)""") } } \ No newline at end of file diff --git a/app/src/main/java/com/tari/android/wallet/application/deeplinks/DeeplinkViewModel.kt b/app/src/main/java/com/tari/android/wallet/application/deeplinks/DeeplinkViewModel.kt index 93b566633..c538de353 100644 --- a/app/src/main/java/com/tari/android/wallet/application/deeplinks/DeeplinkViewModel.kt +++ b/app/src/main/java/com/tari/android/wallet/application/deeplinks/DeeplinkViewModel.kt @@ -4,6 +4,7 @@ import com.tari.android.wallet.R import com.tari.android.wallet.application.baseNodes.BaseNodes import com.tari.android.wallet.data.sharedPrefs.baseNode.BaseNodeDto import com.tari.android.wallet.data.sharedPrefs.baseNode.BaseNodeSharedRepository +import com.tari.android.wallet.data.sharedPrefs.tor.TorSharedRepository import com.tari.android.wallet.ffi.FFITariWalletAddress import com.tari.android.wallet.ffi.HexString import com.tari.android.wallet.model.TariWalletAddress @@ -35,24 +36,28 @@ class DeeplinkViewModel : CommonViewModel() { @Inject lateinit var deeplinkHandler: DeeplinkHandler + @Inject + lateinit var torSharedRepository: TorSharedRepository + init { component.inject(this) } - fun tryToHandle(qrData: String) { - deeplinkHandler.handle(qrData)?.let { execute(it) } + fun tryToHandle(qrData: String, isQrData: Boolean = true) { + deeplinkHandler.handle(qrData)?.let { execute(it, isQrData) } } - fun execute(deeplink: DeepLink) { + fun execute(deeplink: DeepLink, isQrData: Boolean = true) { when (deeplink) { - is DeepLink.AddBaseNode -> addBaseNode(deeplink) - is DeepLink.Contacts -> addContacts(deeplink) - is DeepLink.Send -> sendAction(deeplink) - is DeepLink.UserProfile -> addUserProfile(deeplink) + is DeepLink.AddBaseNode -> addBaseNode(deeplink, isQrData) + is DeepLink.Contacts -> addContacts(deeplink, isQrData) + is DeepLink.Send -> sendAction(deeplink, isQrData) + is DeepLink.UserProfile -> addUserProfile(deeplink, isQrData) + is DeepLink.TorBridges -> addTorBridges(deeplink, isQrData) } } - fun addBaseNode(deeplink: DeepLink.AddBaseNode) { + fun addBaseNode(deeplink: DeepLink.AddBaseNode, isQrData: Boolean = true) { val baseNode = getData(deeplink) val args = ConfirmDialogArgs( resourceManager.getString(R.string.home_custom_base_node_title), @@ -61,13 +66,13 @@ class DeeplinkViewModel : CommonViewModel() { resourceManager.getString(R.string.common_lets_do_it), onConfirm = { dismissDialog.postValue(Unit) - addBaseNodeAction(baseNode) + addBaseNodeAction(baseNode, isQrData) } ).getModular(baseNode, resourceManager) modularDialog.postValue(args) } - fun addUserProfile(deeplink: DeepLink.UserProfile) { + fun addUserProfile(deeplink: DeepLink.UserProfile, isQrData: Boolean) { val contact = DeepLink.Contacts( listOf( DeepLink.Contacts.DeeplinkContact( @@ -76,10 +81,10 @@ class DeeplinkViewModel : CommonViewModel() { ) ) ) - addContacts(contact) + addContacts(contact, isQrData) } - fun addContacts(contacts: DeepLink.Contacts) { + fun addContacts(contacts: DeepLink.Contacts, isQrData: Boolean = true) { val contactDtos = getData(contacts) if (contactDtos.isEmpty()) return val names = contactDtos.joinToString(", ") { it.contact.getAlias().trim() } @@ -88,7 +93,7 @@ class DeeplinkViewModel : CommonViewModel() { HeadModule(resourceManager.getString(R.string.contact_deeplink_title)), BodyModule(resourceManager.getString(R.string.contact_deeplink_message, contactDtos.size.toString()) + ". " + names), ButtonModule(resourceManager.getString(R.string.common_confirm), ButtonStyle.Normal) { - addContactsAction(contactDtos) + addContactsAction(contactDtos, isQrData) dismissDialog.postValue(Unit) }, ButtonModule(resourceManager.getString(R.string.common_cancel), ButtonStyle.Close) @@ -97,12 +102,19 @@ class DeeplinkViewModel : CommonViewModel() { modularDialog.postValue(args) } - fun executeRawDeeplink(deeplink: DeepLink) { + fun addTorBridges(deeplink: DeepLink.TorBridges, isQrData: Boolean) { + deeplink.torConfigurations.forEach { + torSharedRepository.addTorBridgeConfiguration(it) + } + } + + fun executeRawDeeplink(deeplink: DeepLink, isQrData: Boolean = true) { when (deeplink) { is DeepLink.AddBaseNode -> addBaseNode(deeplink) - is DeepLink.Contacts -> addContactsAction(getData(deeplink)) - is DeepLink.Send -> sendAction(deeplink) - is DeepLink.UserProfile -> addContactsAction(getData(deeplink)?.let { listOf(it) } ?: listOf()) + is DeepLink.Contacts -> addContactsAction(getData(deeplink), isQrData) + is DeepLink.Send -> sendAction(deeplink, isQrData) + is DeepLink.UserProfile -> addContactsAction(getData(deeplink)?.let { listOf(it) } ?: listOf(), isQrData) + is DeepLink.TorBridges -> addTorBridges(deeplink, isQrData) } } @@ -128,16 +140,18 @@ class DeeplinkViewModel : CommonViewModel() { ContactDto(FFIContactDto(tariWalletAddress, userProfile.alias)) }.getOrNull() - private fun addContactsAction(contacts: List) { - _backPressed.postValue(Unit) + private fun addContactsAction(contacts: List, isQrData: Boolean) { + if (isQrData) { + backPressed.postValue(Unit) + } contacts.forEach { contactRepository.addContact(it) } } - private fun sendAction(deeplink: DeepLink.Send) { + private fun sendAction(deeplink: DeepLink.Send, isQrData: Boolean) { navigation.postValue(Navigation.TxListNavigation.ToSendWithDeeplink(deeplink)) } - private fun addBaseNodeAction(baseNodeDto: BaseNodeDto) { + private fun addBaseNodeAction(baseNodeDto: BaseNodeDto, isQrData: Boolean) { baseNodeRepository.addUserBaseNode(baseNodeDto) baseNodes.setBaseNode(baseNodeDto) } diff --git a/app/src/main/java/com/tari/android/wallet/application/securityStage/StagedWalletSecurityManager.kt b/app/src/main/java/com/tari/android/wallet/application/securityStage/StagedWalletSecurityManager.kt index bff78ec79..32597528a 100644 --- a/app/src/main/java/com/tari/android/wallet/application/securityStage/StagedWalletSecurityManager.kt +++ b/app/src/main/java/com/tari/android/wallet/application/securityStage/StagedWalletSecurityManager.kt @@ -3,7 +3,6 @@ package com.tari.android.wallet.application.securityStage import android.text.SpannableString import android.text.Spanned import com.tari.android.wallet.R -import com.tari.android.wallet.data.sharedPrefs.SharedPrefsRepository import com.tari.android.wallet.data.sharedPrefs.securityStages.DisabledTimestampsDto import com.tari.android.wallet.data.sharedPrefs.securityStages.SecurityStagesRepository import com.tari.android.wallet.data.sharedPrefs.securityStages.WalletSecurityStage @@ -35,9 +34,6 @@ class StagedWalletSecurityManager : CommonViewModel() { @Inject lateinit var backupPrefsRepository: BackupSettingsRepository - @Inject - lateinit var sharedPrefsRepository: SharedPrefsRepository - init { component.inject(this) diff --git a/app/src/main/java/com/tari/android/wallet/data/sharedPrefs/SharedPrefsRepository.kt b/app/src/main/java/com/tari/android/wallet/data/sharedPrefs/SharedPrefsRepository.kt index a802b35d7..85e04ed22 100644 --- a/app/src/main/java/com/tari/android/wallet/data/sharedPrefs/SharedPrefsRepository.kt +++ b/app/src/main/java/com/tari/android/wallet/data/sharedPrefs/SharedPrefsRepository.kt @@ -32,16 +32,16 @@ */ package com.tari.android.wallet.data.sharedPrefs -import android.content.Context import android.content.SharedPreferences import com.tari.android.wallet.data.repository.CommonRepository import com.tari.android.wallet.data.sharedPrefs.baseNode.BaseNodeSharedRepository import com.tari.android.wallet.data.sharedPrefs.delegates.SharedPrefBooleanDelegate import com.tari.android.wallet.data.sharedPrefs.delegates.SharedPrefStringDelegate -import com.tari.android.wallet.data.sharedPrefs.delegates.SharedPrefStringSecuredDelegate import com.tari.android.wallet.data.sharedPrefs.network.NetworkRepository import com.tari.android.wallet.data.sharedPrefs.network.formatKey +import com.tari.android.wallet.data.sharedPrefs.security.SecurityPrefRepository import com.tari.android.wallet.data.sharedPrefs.securityStages.SecurityStagesRepository +import com.tari.android.wallet.data.sharedPrefs.sentry.SentryPrefRepository import com.tari.android.wallet.data.sharedPrefs.tariSettings.TariSettingsSharedRepository import com.tari.android.wallet.data.sharedPrefs.tor.TorSharedRepository import com.tari.android.wallet.ui.fragment.contact_book.data.localStorage.ContactSharedPrefRepository @@ -60,7 +60,6 @@ import kotlin.random.Random @Singleton class SharedPrefsRepository @Inject constructor( - context: Context, sharedPrefs: SharedPreferences, networkRepository: NetworkRepository, private val backupSettingsRepository: BackupSettingsRepository, @@ -69,12 +68,13 @@ class SharedPrefsRepository @Inject constructor( private val torSharedRepository: TorSharedRepository, private val tariSettingsSharedRepository: TariSettingsSharedRepository, private val securityStagesRepository: SecurityStagesRepository, - private val contactSharedPrefRepository: ContactSharedPrefRepository + private val contactSharedPrefRepository: ContactSharedPrefRepository, + private val sentryPrefRepository: SentryPrefRepository, + private val securityPrefRepository: SecurityPrefRepository ) : CommonRepository(networkRepository) { private object Key { const val publicKeyHexString = "tari_wallet_public_key_hex_string" - const val isAuthenticated = "tari_wallet_is_authenticated" const val emojiId = "tari_wallet_emoji_id_" const val name = "tari_wallet_name_" const val surname = "tari_wallet_surname_" @@ -84,22 +84,17 @@ class SharedPrefsRepository @Inject constructor( const val onboardingAuthSetupStarted = "tari_wallet_onboarding_auth_setup_started" const val onboardingCompleted = "tari_wallet_onboarding_completed" const val onboardingDisplayedAtHome = "tari_wallet_onboarding_displayed_at_home" - const val walletDatabasePassphrase = "tari_wallet_database_passphrase" const val isDataCleared = "tari_is_data_cleared" } var publicKeyHexString: String? by SharedPrefStringDelegate(sharedPrefs, this, formatKey(Key.publicKeyHexString)) - var isAuthenticated: Boolean by SharedPrefBooleanDelegate(sharedPrefs, this, formatKey(Key.isAuthenticated)) - var emojiId: String? by SharedPrefStringDelegate(sharedPrefs, this, formatKey(Key.emojiId)) var name: String? by SharedPrefStringDelegate(sharedPrefs, this, formatKey(Key.name)) var surname: String? by SharedPrefStringDelegate(sharedPrefs, this, formatKey(Key.surname)) - var databasePassphrase: String? by SharedPrefStringSecuredDelegate(context, sharedPrefs, this, formatKey(Key.walletDatabasePassphrase)) - var onboardingStarted: Boolean by SharedPrefBooleanDelegate(sharedPrefs, this, formatKey(Key.onboardingStarted)) var onboardingCompleted: Boolean by SharedPrefBooleanDelegate(sharedPrefs, this, formatKey(Key.onboardingCompleted)) @@ -111,7 +106,7 @@ class SharedPrefsRepository @Inject constructor( var actionMenuSide: Boolean by SharedPrefBooleanDelegate(sharedPrefs, this, formatKey(Key.actionMenuSide)) val onboardingAuthWasInterrupted: Boolean - get() = onboardingAuthSetupStarted && !onboardingAuthSetupCompleted + get() = onboardingAuthSetupStarted && (!onboardingAuthSetupCompleted || securityPrefRepository.pinCode == null) val onboardingWasInterrupted: Boolean get() = onboardingStarted && !onboardingCompleted @@ -128,15 +123,16 @@ class SharedPrefsRepository @Inject constructor( tariSettingsSharedRepository.clear() securityStagesRepository.clear() contactSharedPrefRepository.clear() + sentryPrefRepository.clear() + securityPrefRepository.clear() publicKeyHexString = null - isAuthenticated = false emojiId = null - databasePassphrase = null onboardingStarted = false onboardingCompleted = false onboardingAuthSetupStarted = false onboardingAuthSetupCompleted = false onboardingDisplayedAtHome = false + } fun generateDatabasePassphrase(): String { diff --git a/app/src/main/java/com/tari/android/wallet/data/sharedPrefs/security/LoginAttemptDto.kt b/app/src/main/java/com/tari/android/wallet/data/sharedPrefs/security/LoginAttemptDto.kt new file mode 100644 index 000000000..6cdd127c9 --- /dev/null +++ b/app/src/main/java/com/tari/android/wallet/data/sharedPrefs/security/LoginAttemptDto.kt @@ -0,0 +1,7 @@ +package com.tari.android.wallet.data.sharedPrefs.security + +class LoginAttemptDto(val timeInMills: Long, val isSuccessful: Boolean) + +class LoginAttemptList : ArrayList() + +fun LoginAttemptList?.orEmpty() : LoginAttemptList = this ?: LoginAttemptList() \ No newline at end of file diff --git a/app/src/main/java/com/tari/android/wallet/data/sharedPrefs/security/SecurityPrefRepository.kt b/app/src/main/java/com/tari/android/wallet/data/sharedPrefs/security/SecurityPrefRepository.kt new file mode 100644 index 000000000..1b3ccbc6a --- /dev/null +++ b/app/src/main/java/com/tari/android/wallet/data/sharedPrefs/security/SecurityPrefRepository.kt @@ -0,0 +1,66 @@ +package com.tari.android.wallet.data.sharedPrefs.security + +import android.content.Context +import android.content.SharedPreferences +import com.tari.android.wallet.data.repository.CommonRepository +import com.tari.android.wallet.data.sharedPrefs.delegates.SharedPrefBooleanDelegate +import com.tari.android.wallet.data.sharedPrefs.delegates.SharedPrefBooleanNullableDelegate +import com.tari.android.wallet.data.sharedPrefs.delegates.SharedPrefGsonDelegate +import com.tari.android.wallet.data.sharedPrefs.delegates.SharedPrefStringSecuredDelegate +import com.tari.android.wallet.data.sharedPrefs.network.NetworkRepository +import com.tari.android.wallet.data.sharedPrefs.network.formatKey +import javax.inject.Inject +import javax.inject.Singleton + +@Singleton +class SecurityPrefRepository @Inject constructor( + context: Context, + sharedPrefs: SharedPreferences, + networkRepository: NetworkRepository, +) : CommonRepository(networkRepository) { + + companion object Key { + const val isAuthenticatedKey = "tari_wallet_is_authenticated" + const val isFeatureAuthenticatedKey = "tari_wallet_is_feature_authenticated" + const val pinCodeKey = "tari_is_pincode" + const val biometricsKey = "tari_is_biometrics" + const val walletDatabasePassphraseKey = "tari_wallet_database_passphrase" + const val loginAttemptsKey = "tari_login_attempts" + } + + var isAuthenticated: Boolean by SharedPrefBooleanDelegate(sharedPrefs, this, formatKey(isAuthenticatedKey)) + + var isFeatureAuthenticated: Boolean by SharedPrefBooleanDelegate(sharedPrefs, this, formatKey(isFeatureAuthenticatedKey)) + + var pinCode: String? by SharedPrefStringSecuredDelegate(context, sharedPrefs, this, formatKey(pinCodeKey), null) + + var biometricsAuth: Boolean? by SharedPrefBooleanNullableDelegate(sharedPrefs, this, formatKey(biometricsKey)) + + var databasePassphrase: String? by SharedPrefStringSecuredDelegate(context, sharedPrefs, this, formatKey(walletDatabasePassphraseKey)) + + var attempts: LoginAttemptList? by SharedPrefGsonDelegate( + sharedPrefs, + this, + formatKey(loginAttemptsKey), + LoginAttemptList::class.java, + LoginAttemptList() + ) + + fun saveAttempt(attempt: LoginAttemptDto) { + this.attempts = attempts.orEmpty().apply { + add(attempt) + } + if (attempt.isSuccessful) { + attempts = null + } + } + + fun clear() { + databasePassphrase = null + isAuthenticated = false + isFeatureAuthenticated = false + pinCode = null + biometricsAuth = null + attempts = null + } +} \ No newline at end of file diff --git a/app/src/main/java/com/tari/android/wallet/data/sharedPrefs/sentry/SentryPrefRepository.kt b/app/src/main/java/com/tari/android/wallet/data/sharedPrefs/sentry/SentryPrefRepository.kt new file mode 100644 index 000000000..b48a44a26 --- /dev/null +++ b/app/src/main/java/com/tari/android/wallet/data/sharedPrefs/sentry/SentryPrefRepository.kt @@ -0,0 +1,28 @@ +package com.tari.android.wallet.data.sharedPrefs.sentry + +import android.content.SharedPreferences +import com.tari.android.wallet.data.repository.CommonRepository +import com.tari.android.wallet.data.sharedPrefs.delegates.SharedPrefBooleanNullableDelegate +import com.tari.android.wallet.data.sharedPrefs.network.NetworkRepository +import com.tari.android.wallet.data.sharedPrefs.network.formatKey +import javax.inject.Inject +import javax.inject.Singleton + +@Singleton +class SentryPrefRepository @Inject constructor(sharedPrefs: SharedPreferences, networkRepository: NetworkRepository) : + CommonRepository(networkRepository) { + + private object Key { + const val disabledTimestamps = "tari_sentry_disabled" + } + + var isEnabled: Boolean? by SharedPrefBooleanNullableDelegate( + sharedPrefs, + this, + formatKey(Key.disabledTimestamps), + ) + + fun clear() { + isEnabled = null + } +} diff --git a/app/src/main/java/com/tari/android/wallet/di/ApplicationComponent.kt b/app/src/main/java/com/tari/android/wallet/di/ApplicationComponent.kt index b14feabf6..d893f09d9 100644 --- a/app/src/main/java/com/tari/android/wallet/di/ApplicationComponent.kt +++ b/app/src/main/java/com/tari/android/wallet/di/ApplicationComponent.kt @@ -42,6 +42,9 @@ import com.tari.android.wallet.ui.component.clipboardController.WalletAddressVie import com.tari.android.wallet.ui.component.networkStateIndicator.ConnectionIndicatorViewModel import com.tari.android.wallet.ui.fragment.auth.AuthActivity import com.tari.android.wallet.ui.fragment.auth.AuthViewModel +import com.tari.android.wallet.ui.fragment.biometrics.ChangeBiometricsViewModel +import com.tari.android.wallet.ui.fragment.chat_list.ChatListViewModel +import com.tari.android.wallet.ui.fragment.chat_list.chat.ChatViewModel import com.tari.android.wallet.ui.fragment.contact_book.contactSelection.ContactSelectionViewModel import com.tari.android.wallet.ui.fragment.contact_book.contacts.ContactsViewModel import com.tari.android.wallet.ui.fragment.contact_book.details.ContactDetailsViewModel @@ -57,6 +60,7 @@ import com.tari.android.wallet.ui.fragment.onboarding.activity.OnboardingFlowAct import com.tari.android.wallet.ui.fragment.onboarding.createWallet.CreateWalletViewModel import com.tari.android.wallet.ui.fragment.onboarding.inroduction.IntroductionViewModel import com.tari.android.wallet.ui.fragment.onboarding.localAuth.LocalAuthViewModel +import com.tari.android.wallet.ui.fragment.pinCode.EnterPinCodeViewModel import com.tari.android.wallet.ui.fragment.profile.WalletInfoViewModel import com.tari.android.wallet.ui.fragment.qr.QRScannerActivity import com.tari.android.wallet.ui.fragment.qr.QRScannerViewModel @@ -87,6 +91,7 @@ import com.tari.android.wallet.ui.fragment.settings.baseNodeConfig.addBaseNode.A import com.tari.android.wallet.ui.fragment.settings.baseNodeConfig.changeBaseNode.ChangeBaseNodeViewModel import com.tari.android.wallet.ui.fragment.settings.bluetoothSettings.BluetoothSettingsViewModel import com.tari.android.wallet.ui.fragment.settings.bugReporting.BugsReportingViewModel +import com.tari.android.wallet.ui.fragment.settings.dataCollection.DataCollectionViewModel import com.tari.android.wallet.ui.fragment.settings.deleteWallet.DeleteWalletViewModel import com.tari.android.wallet.ui.fragment.settings.logs.LogFilesManager import com.tari.android.wallet.ui.fragment.settings.logs.logFiles.LogFilesViewModel @@ -197,6 +202,11 @@ interface ApplicationComponent { fun inject(viewModel: QRScannerViewModel) fun inject(viewModel: TransferFragment) fun inject(viewModel: ContactBookActionMenuViewModel) + fun inject(viewModel: ChatListViewModel) + fun inject(viewModel: ChatViewModel) + fun inject(viewModel: DataCollectionViewModel) + fun inject(viewModel: EnterPinCodeViewModel) + fun inject(viewModel: ChangeBiometricsViewModel) fun getClipboardManager(): ClipboardManager } diff --git a/app/src/main/java/com/tari/android/wallet/di/WalletModule.kt b/app/src/main/java/com/tari/android/wallet/di/WalletModule.kt index a83647464..492766ddc 100644 --- a/app/src/main/java/com/tari/android/wallet/di/WalletModule.kt +++ b/app/src/main/java/com/tari/android/wallet/di/WalletModule.kt @@ -39,9 +39,8 @@ import com.tari.android.wallet.data.WalletConfig import com.tari.android.wallet.data.sharedPrefs.SharedPrefsRepository import com.tari.android.wallet.data.sharedPrefs.baseNode.BaseNodeSharedRepository import com.tari.android.wallet.data.sharedPrefs.network.NetworkRepository +import com.tari.android.wallet.data.sharedPrefs.security.SecurityPrefRepository import com.tari.android.wallet.data.sharedPrefs.tariSettings.TariSettingsSharedRepository -import com.tari.android.wallet.infrastructure.logging.BugReportingService -import com.tari.android.wallet.network.NetworkConnectionStateReceiver import com.tari.android.wallet.service.seedPhrase.SeedPhraseRepository import com.tari.android.wallet.tor.TorConfig import com.tari.android.wallet.tor.TorProxyManager @@ -63,6 +62,7 @@ class WalletModule { baseNodeSharedRepository: BaseNodeSharedRepository, seedPhraseRepository: SeedPhraseRepository, networkRepository: NetworkRepository, + securityPrefRepository: SecurityPrefRepository, tariSettingsSharedRepository: TariSettingsSharedRepository, baseNodes: BaseNodes ): WalletManager = WalletManager( @@ -73,6 +73,7 @@ class WalletModule { seedPhraseRepository, networkRepository, tariSettingsSharedRepository, + securityPrefRepository, baseNodes, torConfig ) diff --git a/app/src/main/java/com/tari/android/wallet/event/Event.kt b/app/src/main/java/com/tari/android/wallet/event/Event.kt index d7f1b5ecc..663a0967c 100644 --- a/app/src/main/java/com/tari/android/wallet/event/Event.kt +++ b/app/src/main/java/com/tari/android/wallet/event/Event.kt @@ -32,7 +32,12 @@ */ package com.tari.android.wallet.event -import com.tari.android.wallet.model.* +import com.tari.android.wallet.model.CancelledTx +import com.tari.android.wallet.model.CompletedTx +import com.tari.android.wallet.model.PendingInboundTx +import com.tari.android.wallet.model.PendingOutboundTx +import com.tari.android.wallet.model.TransactionSendStatus +import com.tari.android.wallet.model.TxId import com.tari.android.wallet.ui.fragment.send.finalize.TxFailureReason /** diff --git a/app/src/main/java/com/tari/android/wallet/ffi/FFIPublicKey.kt b/app/src/main/java/com/tari/android/wallet/ffi/FFIPublicKey.kt index 801808084..12eb11c16 100644 --- a/app/src/main/java/com/tari/android/wallet/ffi/FFIPublicKey.kt +++ b/app/src/main/java/com/tari/android/wallet/ffi/FFIPublicKey.kt @@ -54,11 +54,7 @@ class FFIPublicKey() : FFIBase() { } constructor(hex: HexString) : this() { - if (hex.toString().length == 64) { - runWithError { jniFromHex(hex.hex, it) } - } else { - throw FFIException(message = "HexString is not a valid PublicKey") - } + runWithError { jniFromHex(hex.hex, it) } } constructor(privateKey: FFIPrivateKey) : this() { diff --git a/app/src/main/java/com/tari/android/wallet/ffi/FFIWallet.kt b/app/src/main/java/com/tari/android/wallet/ffi/FFIWallet.kt index fcbf933db..6466643e9 100644 --- a/app/src/main/java/com/tari/android/wallet/ffi/FFIWallet.kt +++ b/app/src/main/java/com/tari/android/wallet/ffi/FFIWallet.kt @@ -34,6 +34,7 @@ package com.tari.android.wallet.ffi import com.tari.android.wallet.data.sharedPrefs.SharedPrefsRepository import com.tari.android.wallet.data.sharedPrefs.network.NetworkRepository +import com.tari.android.wallet.data.sharedPrefs.security.SecurityPrefRepository import com.tari.android.wallet.model.* import com.tari.android.wallet.model.recovery.WalletRestorationResult import com.tari.android.wallet.service.seedPhrase.SeedPhraseRepository @@ -50,6 +51,7 @@ import java.util.concurrent.atomic.AtomicReference class FFIWallet( val sharedPrefsRepository: SharedPrefsRepository, + val securityPrefRepository: SecurityPrefRepository, val seedPhraseRepository: SeedPhraseRepository, val networkRepository: NetworkRepository, val commsConfig: FFICommsConfig, @@ -224,10 +226,10 @@ class FFIWallet( val error = FFIError() logger.i("Pre jniCreate") - var passphrase = sharedPrefsRepository.databasePassphrase + var passphrase = securityPrefRepository.databasePassphrase if (passphrase.isNullOrEmpty()) { passphrase = sharedPrefsRepository.generateDatabasePassphrase() - sharedPrefsRepository.databasePassphrase = passphrase + securityPrefRepository.databasePassphrase = passphrase } try { diff --git a/app/src/main/java/com/tari/android/wallet/infrastructure/backup/BackupFileProcessor.kt b/app/src/main/java/com/tari/android/wallet/infrastructure/backup/BackupFileProcessor.kt index cb6c6da98..5935e15ba 100644 --- a/app/src/main/java/com/tari/android/wallet/infrastructure/backup/BackupFileProcessor.kt +++ b/app/src/main/java/com/tari/android/wallet/infrastructure/backup/BackupFileProcessor.kt @@ -35,7 +35,7 @@ package com.tari.android.wallet.infrastructure.backup import com.google.gson.Gson import com.orhanobut.logger.Logger import com.tari.android.wallet.data.WalletConfig -import com.tari.android.wallet.data.sharedPrefs.SharedPrefsRepository +import com.tari.android.wallet.data.sharedPrefs.security.SecurityPrefRepository import com.tari.android.wallet.extension.encrypt import com.tari.android.wallet.ffi.FFIError import com.tari.android.wallet.ffi.FFIWallet @@ -54,7 +54,7 @@ import javax.inject.Singleton @Singleton class BackupFileProcessor @Inject constructor( private val backupSettingsRepository: BackupSettingsRepository, - private val sharedPrefsRepository: SharedPrefsRepository, + private val securityPrefRepository: SecurityPrefRepository, private val walletConfig: WalletConfig, private val namingPolicy: BackupNamingPolicy, ) { @@ -88,7 +88,7 @@ class BackupFileProcessor @Inject constructor( logger.i("Partial files was generated") return Triple(outputFile, backupDate, mimeType) } else { - val passphrase = sharedPrefsRepository.databasePassphrase!! + val passphrase = securityPrefRepository.databasePassphrase!! val passphraseFile = File(walletConfig.getWalletTempDirPath(), namingPolicy.getPassphraseFileName()) passphraseFile.bufferedWriter().use { it.append(passphrase) } @@ -133,7 +133,7 @@ class BackupFileProcessor @Inject constructor( val passphraseFile = File(walletConfig.getWalletFilesDirPath(), namingPolicy.getPassphraseFileName()) val passphrase = passphraseFile.readText() - sharedPrefsRepository.databasePassphrase = passphrase + securityPrefRepository.databasePassphrase = passphrase unencryptedCompressedFile.delete() diff --git a/app/src/main/java/com/tari/android/wallet/infrastructure/bluetooth/TariBluetoothServer.kt b/app/src/main/java/com/tari/android/wallet/infrastructure/bluetooth/TariBluetoothServer.kt index 3d1f89061..a8cc28c27 100644 --- a/app/src/main/java/com/tari/android/wallet/infrastructure/bluetooth/TariBluetoothServer.kt +++ b/app/src/main/java/com/tari/android/wallet/infrastructure/bluetooth/TariBluetoothServer.kt @@ -9,7 +9,6 @@ import android.bluetooth.le.AdvertiseSettings import android.os.ParcelUuid import com.tari.android.wallet.application.deeplinks.DeepLink import com.tari.android.wallet.application.deeplinks.DeeplinkHandler -import com.tari.android.wallet.data.sharedPrefs.SharedPrefsRepository import com.tari.android.wallet.data.sharedPrefs.bluetooth.BluetoothServerState import com.tari.android.wallet.data.sharedPrefs.bluetooth.ShareSettingsRepository import com.tari.android.wallet.extension.addTo @@ -32,9 +31,7 @@ import javax.inject.Singleton class TariBluetoothServer @Inject constructor( private val shareSettingsRepository: ShareSettingsRepository, val deeplinkHandler: DeeplinkHandler, - val sharedPrefsRepository: SharedPrefsRepository -) : - TariBluetoothAdapter() { +) : TariBluetoothAdapter() { private var bluetoothGattServer: BluetoothGattServer? = null @@ -151,7 +148,7 @@ class TariBluetoothServer @Inject constructor( val data = deeplinkHandler.getDeeplink( DeepLink.UserProfile( sharedPrefsRepository.publicKeyHexString.orEmpty(), - ContactDto.normalizeAlias(sharedPrefsRepository.name.orEmpty(), myWalletAddress), + ContactDto.normalizeAlias((sharedPrefsRepository.name.orEmpty() + " " + sharedPrefsRepository.surname).trim(), myWalletAddress), ) ) logger.i("contactlessPayment: read: whole data: $data") diff --git a/app/src/main/java/com/tari/android/wallet/infrastructure/logging/LoggerAdapter.kt b/app/src/main/java/com/tari/android/wallet/infrastructure/logging/LoggerAdapter.kt index 0d0a04e04..f9ac61efa 100644 --- a/app/src/main/java/com/tari/android/wallet/infrastructure/logging/LoggerAdapter.kt +++ b/app/src/main/java/com/tari/android/wallet/infrastructure/logging/LoggerAdapter.kt @@ -4,17 +4,18 @@ import com.orhanobut.logger.AndroidLogAdapter import com.orhanobut.logger.Logger import com.tari.android.wallet.BuildConfig import com.tari.android.wallet.data.WalletConfig +import com.tari.android.wallet.data.sharedPrefs.sentry.SentryPrefRepository import javax.inject.Inject import javax.inject.Singleton @Singleton -class LoggerAdapter @Inject constructor(val walletConfig: WalletConfig) { +class LoggerAdapter @Inject constructor(val walletConfig: WalletConfig, private val sentryPrefRepository: SentryPrefRepository) { fun init() { Logger.addLogAdapter(AndroidLogAdapter()) Logger.addLogAdapter(FFIFileAdapter()) @Suppress("KotlinConstantConditions") if (BuildConfig.FLAVOR != "privacy") { - Logger.addLogAdapter(SentryLogAdapter(walletConfig)) + Logger.addLogAdapter(SentryLogAdapter(walletConfig, sentryPrefRepository)) } } } \ No newline at end of file diff --git a/app/src/main/java/com/tari/android/wallet/infrastructure/logging/SentryLogAdapter.kt b/app/src/main/java/com/tari/android/wallet/infrastructure/logging/SentryLogAdapter.kt index eb6d7adc0..5aae4d368 100644 --- a/app/src/main/java/com/tari/android/wallet/infrastructure/logging/SentryLogAdapter.kt +++ b/app/src/main/java/com/tari/android/wallet/infrastructure/logging/SentryLogAdapter.kt @@ -3,20 +3,30 @@ package com.tari.android.wallet.infrastructure.logging import com.orhanobut.logger.LogAdapter import com.orhanobut.logger.Logger import com.tari.android.wallet.data.WalletConfig +import com.tari.android.wallet.data.sharedPrefs.sentry.SentryPrefRepository import com.tari.android.wallet.util.WalletUtil -import io.sentry.* +import com.welie.blessed.BluetoothPeripheralManager +import io.sentry.Attachment +import io.sentry.Breadcrumb +import io.sentry.Hint +import io.sentry.Sentry +import io.sentry.SentryEvent +import io.sentry.SentryLevel import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Job import kotlinx.coroutines.launch -class SentryLogAdapter(val walletConfig: WalletConfig) : LogAdapter { +class SentryLogAdapter(val walletConfig: WalletConfig, + private val sentryPrefRepository: SentryPrefRepository) : LogAdapter { private var localScope = CoroutineScope(Job()) - override fun isLoggable(priority: Int, tag: String?): Boolean = true + override fun isLoggable(priority: Int, tag: String?): Boolean = sentryPrefRepository.isEnabled == true override fun log(priority: Int, tag: String?, message: String) { + if (tag == BluetoothPeripheralManager::class.java.simpleName) return + if (priority == Logger.ERROR) { localScope.launch(Dispatchers.IO) { try { diff --git a/app/src/main/java/com/tari/android/wallet/infrastructure/nfc/TariNFCAdapter.kt b/app/src/main/java/com/tari/android/wallet/infrastructure/nfc/TariNFCAdapter.kt deleted file mode 100644 index c2742e9dd..000000000 --- a/app/src/main/java/com/tari/android/wallet/infrastructure/nfc/TariNFCAdapter.kt +++ /dev/null @@ -1,135 +0,0 @@ -package com.tari.android.wallet.infrastructure.nfc - -import android.app.PendingIntent -import android.app.PendingIntent.FLAG_MUTABLE -import android.content.Intent -import android.content.IntentFilter -import android.nfc.NdefMessage -import android.nfc.NdefRecord -import android.nfc.NfcAdapter -import android.os.Build -import androidx.appcompat.app.AppCompatActivity -import com.tari.android.wallet.application.deeplinks.DeepLink -import com.tari.android.wallet.application.deeplinks.DeeplinkHandler -import com.tari.android.wallet.data.sharedPrefs.SharedPrefsRepository -import com.tari.android.wallet.ui.common.CommonViewModel -import com.tari.android.wallet.ui.fragment.home.HomeActivity -import java.nio.charset.Charset -import java.util.Locale -import javax.inject.Inject -import javax.inject.Singleton - - -@Singleton -class TariNFCAdapter @Inject constructor( - val deeplinkHandler: DeeplinkHandler, - val sharedPrefsRepository: SharedPrefsRepository -) : CommonViewModel() { - - val MIME_TEXT_PLAIN = "text/plain" - - var context: AppCompatActivity? = null - - var onSuccessSharing: () -> Unit = {} - var onFailedSharing: (String) -> Unit = {} - - var onReceived: (List) -> Unit = {} - - fun onNewIntent(intent: Intent) { - if (NfcAdapter.ACTION_NDEF_DISCOVERED == intent.action) { - intent.getParcelableArrayExtra(NfcAdapter.EXTRA_NDEF_MESSAGES)?.also { rawMessages -> - val messages: List = rawMessages.map { it as NdefMessage } - val first = messages.firstOrNull() ?: return - val actualMessage = first.records.firstOrNull()?.payload ?: return - val payload = String(actualMessage, Charsets.UTF_16) - doHandling(payload) - } - } - } - - private fun doHandling(string: String) { - val handled = runCatching { deeplinkHandler.handle(string) }.getOrNull() - - if (handled != null && handled is DeepLink.Contacts) { - onReceived.invoke(handled.contacts) - } - } - - fun stopSharing() { - val nfcAdapter = NfcAdapter.getDefaultAdapter(context!!) - nfcAdapter.setNdefPushMessageCallback(null, context) - nfcAdapter.setOnNdefPushCompleteCallback(null, context) - } - - fun startSharing(data: String) { - val adapter = NfcAdapter.getDefaultAdapter(context!!) ?: return - if (adapter.isEnabled.not()) { - showNFCSettings() - return - } - createTag(data) - } - - private fun createTag(data: String) { - val outBytes: ByteArray = data.toByteArray(Charsets.UTF_16) - val outRecord = NdefRecord.createMime(MIME_TEXT_PLAIN, outBytes) - - val nfcAdapter = NfcAdapter.getDefaultAdapter(context!!) - nfcAdapter.enableForegroundNdefPush(context, NdefMessage(outRecord)) - nfcAdapter.setOnNdefPushCompleteCallback({ onSuccessSharing() }, context) - nfcAdapter.setNdefPushMessageCallback({ NdefMessage(outRecord) }, context) - } - - fun enableForegroundDispatch(activity: AppCompatActivity) { - val adapter = NfcAdapter.getDefaultAdapter(activity) ?: return - - val intent = Intent(activity.applicationContext, HomeActivity::class.java) - intent.flags = Intent.FLAG_ACTIVITY_SINGLE_TOP - val pendingIntent = PendingIntent.getActivity(activity.applicationContext, 0, intent, FLAG_MUTABLE) - val filters = arrayOfNulls(1) - val techList = arrayOf>() - filters[0] = IntentFilter() - filters[0]!!.addAction(NfcAdapter.ACTION_NDEF_DISCOVERED) - filters[0]!!.addCategory(Intent.CATEGORY_DEFAULT) - try { - filters[0]!!.addDataType(MIME_TEXT_PLAIN) - } catch (ex: IntentFilter.MalformedMimeTypeException) { - throw RuntimeException("Check your MIME type") - } - adapter.enableForegroundDispatch(activity, pendingIntent, filters, techList) - } - - fun createTextRecord(payload: String, locale: Locale, encodeInUtf8: Boolean): NdefRecord { - val langBytes = locale.language.toByteArray(Charset.forName("US-ASCII")) - val utfEncoding = if (encodeInUtf8) Charset.forName("UTF-8") else Charset.forName("UTF-16") - val textBytes = payload.toByteArray(utfEncoding) - val utfBit: Int = if (encodeInUtf8) 0 else 1 shl 7 - val status = (utfBit + langBytes.size).toChar() - val data = ByteArray(1 + langBytes.size + textBytes.size) - data[0] = status.code.toByte() - System.arraycopy(langBytes, 0, data, 1, langBytes.size) - System.arraycopy(textBytes, 0, data, 1 + langBytes.size, textBytes.size) - return NdefRecord(NdefRecord.TNF_WELL_KNOWN, NdefRecord.RTD_TEXT, ByteArray(0), data) - } - - fun disableForegroundDispatch(activity: AppCompatActivity?) { - val adapter = NfcAdapter.getDefaultAdapter(activity) ?: return - adapter.disableForegroundDispatch(activity) - } - - fun isNFCAvailable(): Boolean { - val adapter = NfcAdapter.getDefaultAdapter(context!!) - return adapter != null && adapter.isEnabled - } - - fun isNFCSupported(): Boolean { - val adapter = NfcAdapter.getDefaultAdapter(context!!) - val sdk = Build.VERSION.SDK_INT - return adapter != null && sdk < Build.VERSION_CODES.Q - } - - fun showNFCSettings() { - val intent = Intent(android.provider.Settings.ACTION_NFC_SETTINGS) - context?.startActivity(intent) - } -} \ No newline at end of file diff --git a/app/src/main/java/com/tari/android/wallet/model/TariWalletAddress.kt b/app/src/main/java/com/tari/android/wallet/model/TariWalletAddress.kt index c34d0b9dc..482122eef 100644 --- a/app/src/main/java/com/tari/android/wallet/model/TariWalletAddress.kt +++ b/app/src/main/java/com/tari/android/wallet/model/TariWalletAddress.kt @@ -48,8 +48,8 @@ class TariWalletAddress() : Parcelable, Serializable { constructor(hexString: String, emojiId: String) : this() { // crunch fix for not crashing on action related to wallet address - if (hexString == "0000000000000000000000000000000000000000000000000000000000000026") { - this.hexString = "000000000000000000000000000000000000000000000000000000000000000026" + if (hexString == zeroHex) { + this.hexString = zero66Hex this.emojiId = "\uD83C\uDF00\uD83C\uDF00\uD83C\uDF00\uD83C\uDF00\uD83C\uDF00\uD83C\uDF00\uD83C\uDF00\uD83C\uDF00\uD83C\uDF00\uD83C\uDF00\uD83C\uDF00\uD83C\uDF00\uD83C\uDF00\uD83C\uDF00\uD83C\uDF00\uD83C\uDF00\uD83C\uDF00\uD83C\uDF00\uD83C\uDF00\uD83C\uDF00\uD83C\uDF00\uD83C\uDF00\uD83C\uDF00\uD83C\uDF00\uD83C\uDF00\uD83C\uDF00\uD83C\uDF00\uD83C\uDF00\uD83C\uDF00\uD83C\uDF00\uD83C\uDF00\uD83C\uDF00\uD83C\uDF00\uD83C\uDF57" } else { @@ -58,6 +58,8 @@ class TariWalletAddress() : Parcelable, Serializable { } } + fun isZeros(): Boolean = hexString == zeroHex || hexString == zero66Hex || hexString.all { it == '0' } + override fun equals(other: Any?): Boolean = (other is TariWalletAddress) && hexString == other.hexString override fun hashCode(): Int = hexString.hashCode() @@ -72,6 +74,9 @@ class TariWalletAddress() : Parcelable, Serializable { companion object CREATOR : Parcelable.Creator { + const val zeroHex = "0000000000000000000000000000000000000000000000000000000000000026" + const val zero66Hex = "000000000000000000000000000000000000000000000000000000000000000026" + override fun createFromParcel(parcel: Parcel): TariWalletAddress { return TariWalletAddress(parcel) } @@ -80,6 +85,7 @@ class TariWalletAddress() : Parcelable, Serializable { return Array(size) { TariWalletAddress() } } + fun validate(addressHex: String): Boolean = addressHex.length > 64 } override fun writeToParcel(parcel: Parcel, flags: Int) { @@ -97,5 +103,4 @@ class TariWalletAddress() : Parcelable, Serializable { } // endregion - } diff --git a/app/src/main/java/com/tari/android/wallet/service/service/TariWalletServiceStubImpl.kt b/app/src/main/java/com/tari/android/wallet/service/service/TariWalletServiceStubImpl.kt index ecc096ec9..435498209 100644 --- a/app/src/main/java/com/tari/android/wallet/service/service/TariWalletServiceStubImpl.kt +++ b/app/src/main/java/com/tari/android/wallet/service/service/TariWalletServiceStubImpl.kt @@ -149,6 +149,8 @@ class TariWalletServiceStubImpl( override fun cancelPendingTx(id: TxId, error: WalletError): Boolean = runMapping(error) { wallet.cancelPendingTx(id.value) } ?: false override fun addBaseNodePeer(baseNodePublicKey: String, baseNodeAddress: String, error: WalletError): Boolean = runMapping(error) { + Logger.t(this::class.simpleName).e("walletServiceStub:addBaseNodePeer:publicKeyHex: ${baseNodePublicKey}") + Logger.t(this::class.simpleName).e("walletServiceStub:addBaseNodePeer:address: ${baseNodeAddress}") val result = FFIPublicKey(HexString(baseNodePublicKey)).runWithDestroy { wallet.addBaseNodePeer(it, baseNodeAddress) } if (result) { walletServiceListener.baseNodeValidationStatusMap.clear() diff --git a/app/src/main/java/com/tari/android/wallet/tor/TorProxyControl.kt b/app/src/main/java/com/tari/android/wallet/tor/TorProxyControl.kt index 482b910e9..c6167f48a 100644 --- a/app/src/main/java/com/tari/android/wallet/tor/TorProxyControl.kt +++ b/app/src/main/java/com/tari/android/wallet/tor/TorProxyControl.kt @@ -72,6 +72,7 @@ class TorProxyControl(private val torConfig: TorConfig) { private fun checkTorStatus() { try { val bootstrapStatus = isTorRunning(controlConnection) + logger.i(bootstrapStatus.toString()) if (bootstrapStatus != null) { if (bootstrapStatus.progress == 100 && bootstrapStatus.summary == "Done") { updateState(TorProxyState.Running(bootstrapStatus)) @@ -87,6 +88,7 @@ class TorProxyControl(private val torConfig: TorConfig) { updateState(TorProxyState.Failed(Throwable("Tor not running"))) } } catch (throwable: Throwable) { + logger.i("tor state during fall: ${EventBus.torProxyState.publishSubject.value}") logger.e(throwable, "Tor proxy has failed") updateState(TorProxyState.Failed(throwable)) } @@ -99,6 +101,8 @@ class TorProxyControl(private val torConfig: TorConfig) { } private fun updateState(newState: TorProxyState) { + logger.i("tor update old state: ${EventBus.torProxyState.publishSubject.value}") + logger.i("tor update new state: $newState") if (newState != EventBus.torProxyState.publishSubject.value) { EventBus.torProxyState.post(newState) } diff --git a/app/src/main/java/com/tari/android/wallet/ui/common/CommonActivity.kt b/app/src/main/java/com/tari/android/wallet/ui/common/CommonActivity.kt index c6df1e339..6be51da48 100644 --- a/app/src/main/java/com/tari/android/wallet/ui/common/CommonActivity.kt +++ b/app/src/main/java/com/tari/android/wallet/ui/common/CommonActivity.kt @@ -1,5 +1,6 @@ package com.tari.android.wallet.ui.common +import android.app.Activity import android.content.Intent import android.content.res.Configuration.UI_MODE_NIGHT_MASK import android.content.res.Configuration.UI_MODE_NIGHT_YES @@ -121,6 +122,14 @@ abstract class CommonActivity : App super.onStart() } + fun launch(destination: Class) { + val intent = Intent(this, destination) + intent.flags = Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_NEW_TASK + this.intent.data?.let(intent::setData) + startActivity(intent) + finish() + } + override fun onResume() { super.onResume() @@ -144,7 +153,7 @@ abstract class CommonActivity : App override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) dialogManager.context = this - overridePendingTransition(R.anim.enter_from_right, R.anim.exit_to_left) + subscribeToCommon(connectionStateViewModel) } @@ -152,11 +161,6 @@ abstract class CommonActivity : App containerId = id } - override fun onBackPressed() { - super.onBackPressed() - overridePendingTransition(R.anim.enter_from_left, R.anim.exit_to_right) - } - fun addFragment(fragment: Fragment, bundle: Bundle? = null, isRoot: Boolean = false, withAnimation: Boolean = true) { bundle?.let { fragment.arguments = it } if (supportFragmentManager.isDestroyed) return @@ -172,7 +176,11 @@ abstract class CommonActivity : App } fun popUpTo(tag: String) { - while (supportFragmentManager.fragments.last().tag != tag && supportFragmentManager.backStackEntryCount > 0) { + viewModel.logger.e("popUpTo $tag") + viewModel.logger.e("popUpTo:last ${supportFragmentManager.fragments.last().tag}") + viewModel.logger.e("popUpTo:all ${supportFragmentManager.fragments.map { it.tag }.joinToString(", ")}") + viewModel.logger.e("popUpTo:all ${supportFragmentManager.fragments.map { it::class.java }.joinToString(", ")}") + while (supportFragmentManager.fragments.last()::class.java.simpleName != tag && supportFragmentManager.backStackEntryCount > 0) { supportFragmentManager.popBackStackImmediate() } } diff --git a/app/src/main/java/com/tari/android/wallet/ui/common/CommonViewModel.kt b/app/src/main/java/com/tari/android/wallet/ui/common/CommonViewModel.kt index af1334e6a..04c547af9 100644 --- a/app/src/main/java/com/tari/android/wallet/ui/common/CommonViewModel.kt +++ b/app/src/main/java/com/tari/android/wallet/ui/common/CommonViewModel.kt @@ -7,7 +7,9 @@ import com.orhanobut.logger.Logger import com.orhanobut.logger.Printer import com.tari.android.wallet.R import com.tari.android.wallet.application.WalletState +import com.tari.android.wallet.data.sharedPrefs.SharedPrefsRepository import com.tari.android.wallet.data.sharedPrefs.network.NetworkRepository +import com.tari.android.wallet.data.sharedPrefs.security.SecurityPrefRepository import com.tari.android.wallet.data.sharedPrefs.tariSettings.TariSettingsSharedRepository import com.tari.android.wallet.di.ApplicationComponent import com.tari.android.wallet.di.DiContainer @@ -68,13 +70,20 @@ open class CommonViewModel : ViewModel() { @Inject lateinit var tariNavigator: TariNavigator + @Inject + lateinit var sharedPrefsRepository: SharedPrefsRepository + + @Inject + lateinit var securityPrefRepository: SecurityPrefRepository + + private var authorizedAction: (() -> Unit)? = null + val logger: Printer get() = Logger.t(this::class.simpleName).t(LoggerTags.UI.name) val currentTheme = SingleLiveEvent() - protected val _backPressed = SingleLiveEvent() - val backPressed: LiveData = _backPressed + val backPressed = SingleLiveEvent() protected val _openLink = SingleLiveEvent() val openLink: LiveData = _openLink @@ -108,6 +117,10 @@ open class CommonViewModel : ViewModel() { logger.t(LoggerTags.Navigation.name).i(this::class.simpleName + " was started") + securityPrefRepository.updateNotifier.subscribe { + checkAuthorization() + }.addTo(compositeDisposable) + EventBus.walletState.publishSubject.filter { it is WalletState.Failed } .subscribe({ val exception = (it as WalletState.Failed).exception @@ -152,5 +165,19 @@ open class CommonViewModel : ViewModel() { modularDialog.postValue(modularArgs) } + fun runWithAuthorization(action: () -> Unit) { + authorizedAction = action + navigation.postValue(Navigation.FeatureAuth()) + } + + fun checkAuthorization() { + if (authorizedAction != null && securityPrefRepository.isFeatureAuthenticated) { + securityPrefRepository.isFeatureAuthenticated = false + backPressed.value = Unit + authorizedAction?.invoke() + authorizedAction = null + } + } + fun doOnBackground(action: suspend CoroutineScope.() -> Unit): Job = viewModelScope.launch { action() } } \ No newline at end of file diff --git a/app/src/main/java/com/tari/android/wallet/ui/common/gyphy/presentation/GlideGIFListener.kt b/app/src/main/java/com/tari/android/wallet/ui/common/gyphy/presentation/GlideGIFListener.kt index 2df6fca08..f191149cb 100644 --- a/app/src/main/java/com/tari/android/wallet/ui/common/gyphy/presentation/GlideGIFListener.kt +++ b/app/src/main/java/com/tari/android/wallet/ui/common/gyphy/presentation/GlideGIFListener.kt @@ -11,7 +11,7 @@ class GlideGIFListener(private val consumer: GIFStateConsumer) : RequestListener override fun onLoadFailed( e: GlideException?, model: Any?, - target: Target?, + target: Target, isFirstResource: Boolean ): Boolean { consumer.onErrorState() @@ -19,10 +19,10 @@ class GlideGIFListener(private val consumer: GIFStateConsumer) : RequestListener } override fun onResourceReady( - resource: GifDrawable?, - model: Any?, + resource: GifDrawable, + model: Any, target: Target?, - dataSource: DataSource?, + dataSource: DataSource, isFirstResource: Boolean ): Boolean { consumer.onResourceReady() diff --git a/app/src/main/java/com/tari/android/wallet/ui/common/gyphy/repository/GIFItem.kt b/app/src/main/java/com/tari/android/wallet/ui/common/gyphy/repository/GIFItem.kt index 8daf6d20d..93b62899b 100644 --- a/app/src/main/java/com/tari/android/wallet/ui/common/gyphy/repository/GIFItem.kt +++ b/app/src/main/java/com/tari/android/wallet/ui/common/gyphy/repository/GIFItem.kt @@ -55,4 +55,6 @@ data class GIFItem(val id: String, val embedUri: Uri, val uri: Uri) : Parcelable override fun newArray(size: Int): Array = arrayOfNulls(size) } + + override fun toString(): String = "GIFItem(id='$id', embedUri=$embedUri, uri=$uri)" } diff --git a/app/src/main/java/com/tari/android/wallet/ui/common/gyphy/repository/GiphyRESTRetrofitRepository.kt b/app/src/main/java/com/tari/android/wallet/ui/common/gyphy/repository/GiphyRESTRetrofitRepository.kt index abc45e443..3786d2323 100644 --- a/app/src/main/java/com/tari/android/wallet/ui/common/gyphy/repository/GiphyRESTRetrofitRepository.kt +++ b/app/src/main/java/com/tari/android/wallet/ui/common/gyphy/repository/GiphyRESTRetrofitRepository.kt @@ -26,13 +26,16 @@ class GiphyRESTRetrofitRepository(private val gateway: GiphyRESTGateway) : GIFRe } override fun getById(id: String): GIFItem { - val response: Response = gateway.getGIFByID(id).execute() + val request = gateway.getGIFByID(id) + val response: Response = request.execute() val body = response.body() return if (response.isSuccessful && body != null && body.meta.status in 200..299) body.data.let { GIFItem(it.id, Uri.parse(it.embedUrl), Uri.parse(it.images.fixedWidth.url)) } else { val exception = GIFSearchException(body?.meta?.message ?: response.message() ?: response.errorBody()?.string()) - logger.e(exception, "Get all was failed") + logger.e(exception.message.orEmpty()) + logger.e(exception.stackTraceToString()) + logger.e(exception, "Get by id was failed") throw exception } } diff --git a/app/src/main/java/com/tari/android/wallet/ui/component/clipboardController/WalletAddressViewModel.kt b/app/src/main/java/com/tari/android/wallet/ui/component/clipboardController/WalletAddressViewModel.kt index cb9a79173..c9e7c4285 100644 --- a/app/src/main/java/com/tari/android/wallet/ui/component/clipboardController/WalletAddressViewModel.kt +++ b/app/src/main/java/com/tari/android/wallet/ui/component/clipboardController/WalletAddressViewModel.kt @@ -43,7 +43,7 @@ class WalletAddressViewModel : CommonViewModel() { runCatching { checkForValidEmojiId(walletService, clipboardString) - discoveredWalletAddressFromClipboard.value = discoveredWalletAddress + discoveredWalletAddressFromClipboard.postValue(discoveredWalletAddress) } } } @@ -51,7 +51,7 @@ class WalletAddressViewModel : CommonViewModel() { fun checkFromQuery(walletService: TariWalletService, query: String) { doOnConnectedToWallet { checkForValidEmojiId(walletService, query) - discoveredWalletAddressFromQuery.value = discoveredWalletAddress + discoveredWalletAddressFromQuery.postValue(discoveredWalletAddress) } } diff --git a/app/src/main/java/com/tari/android/wallet/ui/fragment/auth/AuthActivity.kt b/app/src/main/java/com/tari/android/wallet/ui/fragment/auth/AuthActivity.kt index 11e3b949a..ce38714f7 100644 --- a/app/src/main/java/com/tari/android/wallet/ui/fragment/auth/AuthActivity.kt +++ b/app/src/main/java/com/tari/android/wallet/ui/fragment/auth/AuthActivity.kt @@ -32,115 +32,94 @@ */ package com.tari.android.wallet.ui.fragment.auth -import android.animation.AnimatorSet -import android.animation.ObjectAnimator -import android.animation.ValueAnimator import android.content.Intent import android.os.Bundle -import android.view.View +import androidx.activity.OnBackPressedCallback import androidx.activity.viewModels import androidx.appcompat.app.AlertDialog -import androidx.core.animation.addListener -import androidx.lifecycle.Lifecycle import androidx.lifecycle.lifecycleScope -import com.daasuu.ei.Ease -import com.daasuu.ei.EasingInterpolator +import com.tari.android.wallet.R import com.tari.android.wallet.R.string.auth_biometric_prompt import com.tari.android.wallet.R.string.auth_device_lock_code_prompt -import com.tari.android.wallet.R.string.auth_failed_desc -import com.tari.android.wallet.R.string.auth_failed_title import com.tari.android.wallet.R.string.auth_not_available_or_canceled_desc import com.tari.android.wallet.R.string.auth_not_available_or_canceled_title import com.tari.android.wallet.R.string.auth_title import com.tari.android.wallet.R.string.exit import com.tari.android.wallet.R.string.proceed +import com.tari.android.wallet.data.sharedPrefs.security.LoginAttemptDto import com.tari.android.wallet.databinding.ActivityAuthBinding import com.tari.android.wallet.extension.observe import com.tari.android.wallet.infrastructure.security.biometric.BiometricAuthenticationException import com.tari.android.wallet.ui.common.CommonActivity -import com.tari.android.wallet.ui.extension.addAnimatorListener -import com.tari.android.wallet.ui.extension.invisible import com.tari.android.wallet.ui.extension.setColor import com.tari.android.wallet.ui.extension.string import com.tari.android.wallet.ui.extension.visible import com.tari.android.wallet.ui.fragment.home.HomeActivity +import com.tari.android.wallet.ui.fragment.pinCode.EnterPinCodeFragment +import com.tari.android.wallet.ui.fragment.pinCode.PinCodeScreenBehavior import com.tari.android.wallet.ui.fragment.settings.allSettings.TariVersionModel -import com.tari.android.wallet.util.Build.MOCKED -import com.tari.android.wallet.util.Constants import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch -/** - * Initial activity class - authenticates the user. - * - * @author The Tari Development Team - */ class AuthActivity : CommonActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - ui = ActivityAuthBinding.inflate(layoutInflater).apply { setContentView(root) } val viewModel: AuthViewModel by viewModels() bindViewModel(viewModel) + onBackPressedDispatcher.addCallback(object : OnBackPressedCallback(true) { + override fun handleOnBackPressed() = Unit + }) + + ui = ActivityAuthBinding.inflate(layoutInflater).apply { setContentView(root) } + setupUi() observe(viewModel.goAuth) { - // call the animations - showTariText() viewModel.walletServiceLauncher.start() } + + doAuth() + } + + private fun setupPinCodeFragment() { + val pinCode = viewModel.securityPrefRepository.pinCode + if (pinCode != null) { + val pinCodeFragment = EnterPinCodeFragment.newInstance(PinCodeScreenBehavior.Auth, pinCode) + supportFragmentManager.beginTransaction() + .replace(R.id.fragment_container, pinCodeFragment) + .commit() + } } private fun setupUi() { - ui.progressBar.setColor(viewModel.paletteManager.getWhite(this)) - ui.progressBar.invisible() ui.networkInfoTextView.text = TariVersionModel(viewModel.networkRepository).versionInfo } - override fun onBackPressed() = Unit - - /** - * Hides the gem and displays Tari logo. - */ - private fun showTariText() { - // hide features to be shown after animation - ui.authAnimLottieAnimationView.alpha = 0f - ui.networkInfoTextView.alpha = 0f - ui.smallGemImageView.alpha = 0f + private fun doAuth() { + showBiometricAuth() - // define animations - val hideGemAnim = ValueAnimator.ofFloat(1f, 0f).apply { - addUpdateListener { valueAnimator: ValueAnimator -> - val alpha = valueAnimator.animatedValue as Float - ui.bigGemImageView.alpha = alpha - } - } - val showTariTextAnim = ValueAnimator.ofFloat(0f, 1f).apply { - addUpdateListener { valueAnimator: ValueAnimator -> - val alpha = valueAnimator.animatedValue as Float - ui.authAnimLottieAnimationView.alpha = alpha - ui.networkInfoTextView.alpha = alpha - ui.smallGemImageView.alpha = alpha + // check whether there's at least screen lock + if (viewModel.authService.isDeviceSecured) { + lifecycleScope.launch { + try { + setupPinCodeFragment() + } catch (e: BiometricAuthenticationException) { + viewModel.logger.e(e, "Authentication has failed") + } } - } - AnimatorSet().apply { - startDelay = Constants.UI.shortDurationMs - play(showTariTextAnim).after(hideGemAnim) - duration = Constants.UI.shortDurationMs - interpolator = EasingInterpolator(Ease.QUART_IN) - addListener(onEnd = { doAuth() }) - start() + } else { + displayAuthNotAvailableDialog() } } - private fun doAuth() { - // check whether there's at least screen lock + fun showBiometricAuth() { if (viewModel.authService.isDeviceSecured) { lifecycleScope.launch { try { - if (!MOCKED) { + if (viewModel.securityPrefRepository.biometricsAuth == true) { // prompt system authentication dialog viewModel.authService.authenticate( this@AuthActivity, @@ -149,33 +128,17 @@ class AuthActivity : CommonActivity() { if (viewModel.authService.isBiometricAuthAvailable) string(auth_biometric_prompt) else string(auth_device_lock_code_prompt) ) + proceedLogin() } - authSuccessful() } catch (e: BiometricAuthenticationException) { - authHasFailed() + viewModel.logger.e(e, "Authentication has failed") } } } else { - // local authentication not available displayAuthNotAvailableDialog() } } - /** - * Auth was successful. - */ - private fun authSuccessful() { - viewModel.sharedPrefsWrapper.isAuthenticated = true - playTariWalletAnim() - } - - /** - * Auth has failed. - */ - private fun authHasFailed() { - displayAuthFailedDialog() - } - /** * Auth not available on device, i.e. lock screen is disabled */ @@ -186,8 +149,8 @@ class AuthActivity : CommonActivity() { .setPositiveButton(getString(proceed)) { dialog, _ -> dialog.cancel() // user has chosen to proceed without authentication - viewModel.sharedPrefsWrapper.isAuthenticated = true - playTariWalletAnim() + viewModel.securityPrefRepository.isAuthenticated = true + proceedLogin() } // negative button text and action .setNegativeButton(getString(exit)) { _, _ -> finish() } @@ -196,58 +159,17 @@ class AuthActivity : CommonActivity() { dialog.show() } - private fun displayAuthFailedDialog() { - val state = lifecycle.currentState - if (state == Lifecycle.State.RESUMED || state == Lifecycle.State.STARTED) { - AlertDialog.Builder(this) - .setMessage(getString(auth_failed_desc)) - .setCancelable(false) - .setNegativeButton(getString(exit)) { _, _ -> finish() } - .run(AlertDialog.Builder::create) - .apply { setTitle(string(auth_failed_title)) } - .show() - } - } - - /** - * Plays Tari Wallet text anim. - */ - private fun playTariWalletAnim() { - ui.authAnimLottieAnimationView.addAnimatorListener(onEnd = { - ui.progressBar.alpha = 0f - ui.progressBar.visible() - ObjectAnimator.ofFloat(ui.progressBar, View.ALPHA, 0f, 1f).apply { - duration = Constants.UI.mediumDurationMs - start() - } - - proceedLogin() - }) - ui.authAnimLottieAnimationView.playAnimation() - - ValueAnimator.ofFloat(1f, 0f).apply { - duration = Constants.UI.mediumDurationMs - addUpdateListener { valueAnimator: ValueAnimator -> - val alpha = valueAnimator.animatedValue as Float - ui.smallGemImageView.alpha = alpha - ui.networkInfoTextView.alpha = alpha - } - startDelay = Constants.UI.CreateWallet.introductionBottomViewsFadeOutDelay - start() - } - } - private fun proceedLogin() { + viewModel.securityPrefRepository.saveAttempt(LoginAttemptDto(System.currentTimeMillis(), true)) lifecycleScope.launch(Dispatchers.Main) { + ui.loader.visible() + ui.progressBar.setColor(viewModel.paletteManager.getPurpleBrand(this@AuthActivity)) continueToHomeActivity() } } - private fun continueToHomeActivity() { - ObjectAnimator.ofFloat(ui.progressBar, View.ALPHA, 1f, 0f).apply { - duration = Constants.UI.shortDurationMs - start() - } + fun continueToHomeActivity() { + viewModel.securityPrefRepository.isAuthenticated = true // go to home activity Intent(this, HomeActivity::class.java).apply { @@ -257,5 +179,4 @@ class AuthActivity : CommonActivity() { } finish() } - } \ No newline at end of file diff --git a/app/src/main/java/com/tari/android/wallet/ui/fragment/auth/AuthViewModel.kt b/app/src/main/java/com/tari/android/wallet/ui/fragment/auth/AuthViewModel.kt index a9467a6e3..b697381c0 100644 --- a/app/src/main/java/com/tari/android/wallet/ui/fragment/auth/AuthViewModel.kt +++ b/app/src/main/java/com/tari/android/wallet/ui/fragment/auth/AuthViewModel.kt @@ -3,7 +3,6 @@ package com.tari.android.wallet.ui.fragment.auth import androidx.lifecycle.viewModelScope import com.tari.android.wallet.R import com.tari.android.wallet.application.MigrationManager -import com.tari.android.wallet.data.sharedPrefs.SharedPrefsRepository import com.tari.android.wallet.infrastructure.security.biometric.BiometricAuthenticationService import com.tari.android.wallet.service.service.WalletServiceLauncher import com.tari.android.wallet.ui.common.CommonViewModel @@ -21,9 +20,6 @@ import javax.inject.Inject class AuthViewModel : CommonViewModel() { - @Inject - lateinit var sharedPrefsWrapper: SharedPrefsRepository - @Inject lateinit var authService: BiometricAuthenticationService diff --git a/app/src/main/java/com/tari/android/wallet/ui/fragment/auth/FeatureAuthFragment.kt b/app/src/main/java/com/tari/android/wallet/ui/fragment/auth/FeatureAuthFragment.kt new file mode 100644 index 000000000..560291199 --- /dev/null +++ b/app/src/main/java/com/tari/android/wallet/ui/fragment/auth/FeatureAuthFragment.kt @@ -0,0 +1,166 @@ +/** + * Copyright 2020 The Tari Project + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the + * following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * 3. Neither the name of the copyright holder nor the names of + * its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE + * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.tari.android.wallet.ui.fragment.auth + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.appcompat.app.AlertDialog +import androidx.fragment.app.viewModels +import androidx.lifecycle.lifecycleScope +import com.tari.android.wallet.R +import com.tari.android.wallet.R.string.auth_biometric_prompt +import com.tari.android.wallet.R.string.auth_device_lock_code_prompt +import com.tari.android.wallet.R.string.auth_not_available_or_canceled_desc +import com.tari.android.wallet.R.string.auth_not_available_or_canceled_title +import com.tari.android.wallet.R.string.auth_title +import com.tari.android.wallet.R.string.exit +import com.tari.android.wallet.R.string.proceed +import com.tari.android.wallet.data.sharedPrefs.security.LoginAttemptDto +import com.tari.android.wallet.databinding.FragmentFeatureAuthBinding +import com.tari.android.wallet.extension.observe +import com.tari.android.wallet.infrastructure.security.biometric.BiometricAuthenticationException +import com.tari.android.wallet.ui.common.CommonFragment +import com.tari.android.wallet.ui.extension.setColor +import com.tari.android.wallet.ui.extension.string +import com.tari.android.wallet.ui.extension.visible +import com.tari.android.wallet.ui.fragment.pinCode.EnterPinCodeFragment +import com.tari.android.wallet.ui.fragment.pinCode.PinCodeScreenBehavior +import com.tari.android.wallet.ui.fragment.settings.allSettings.TariVersionModel +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch + +class FeatureAuthFragment : CommonFragment() { + + override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View = + FragmentFeatureAuthBinding.inflate(inflater, container, false).also { ui = it }.root + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + + val viewModel: AuthViewModel by viewModels() + bindViewModel(viewModel) + + setupUi() + + observe(viewModel.goAuth) { + viewModel.walletServiceLauncher.start() + } + + doAuth() + } + + private fun setupPinCodeFragment() { + val pinCode = viewModel.securityPrefRepository.pinCode + if (pinCode != null) { + val pinCodeFragment = EnterPinCodeFragment.newInstance(PinCodeScreenBehavior.FeatureAuth, pinCode) + childFragmentManager.beginTransaction() + .replace(R.id.fragment_container, pinCodeFragment) + .commit() + } + } + + private fun setupUi() { + ui.networkInfoTextView.text = TariVersionModel(viewModel.networkRepository).versionInfo + } + + private fun doAuth() { + // check whether there's at least screen lock + if (viewModel.authService.isDeviceSecured) { + try { + setupPinCodeFragment() + } catch (e: BiometricAuthenticationException) { + viewModel.logger.e(e, "Authentication has failed") + } + } else { + displayAuthNotAvailableDialog() + } + + showBiometricAuth() + } + + fun showBiometricAuth() { + if (viewModel.authService.isDeviceSecured) { + lifecycleScope.launch { + try { + if (viewModel.securityPrefRepository.biometricsAuth == true) { + // prompt system authentication dialog + viewModel.authService.authenticate( + this@FeatureAuthFragment, + title = string(auth_title), + subtitle = + if (viewModel.authService.isBiometricAuthAvailable) string(auth_biometric_prompt) + else string(auth_device_lock_code_prompt) + ) + authSuccessfully() + } + } catch (e: BiometricAuthenticationException) { + viewModel.logger.e(e, "Authentication has failed") + } + } + } else { + displayAuthNotAvailableDialog() + } + } + + /** + * Auth not available on device, i.e. lock screen is disabled + */ + private fun displayAuthNotAvailableDialog() { + val dialog = AlertDialog.Builder(requireContext()) + .setMessage(getString(auth_not_available_or_canceled_desc)) + .setCancelable(false) + .setPositiveButton(getString(proceed)) { dialog, _ -> + dialog.cancel() + // user has chosen to proceed without authentication + viewModel.securityPrefRepository.isAuthenticated = true + authSuccessfully() + } + // negative button text and action + .setNegativeButton(getString(exit)) { _, _ -> viewModel.backPressed.postValue(Unit) } + .create() + dialog.setTitle(getString(auth_not_available_or_canceled_title)) + dialog.show() + } + + fun authSuccessfully() { + viewModel.securityPrefRepository.saveAttempt(LoginAttemptDto(System.currentTimeMillis(), true)) + lifecycleScope.launch(Dispatchers.Main) { + ui.loader.visible() + ui.progressBar.setColor(viewModel.paletteManager.getPurpleBrand(requireContext())) + + viewModel.securityPrefRepository.isFeatureAuthenticated = true + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/tari/android/wallet/ui/fragment/biometrics/ChangeBiometricsFragment.kt b/app/src/main/java/com/tari/android/wallet/ui/fragment/biometrics/ChangeBiometricsFragment.kt new file mode 100644 index 000000000..e39fa7441 --- /dev/null +++ b/app/src/main/java/com/tari/android/wallet/ui/fragment/biometrics/ChangeBiometricsFragment.kt @@ -0,0 +1,76 @@ +package com.tari.android.wallet.ui.fragment.biometrics + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.fragment.app.viewModels +import androidx.lifecycle.lifecycleScope +import com.tari.android.wallet.R.string.auth_biometric_prompt +import com.tari.android.wallet.R.string.auth_device_lock_code_prompt +import com.tari.android.wallet.R.string.auth_title +import com.tari.android.wallet.databinding.FragmentChangeBiometricsBinding +import com.tari.android.wallet.extension.observe +import com.tari.android.wallet.infrastructure.security.biometric.BiometricAuthenticationException +import com.tari.android.wallet.ui.common.CommonFragment +import com.tari.android.wallet.ui.extension.string +import com.tari.android.wallet.util.TariBuild.MOCKED +import kotlinx.coroutines.launch + +class ChangeBiometricsFragment : CommonFragment() { + + override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View = + FragmentChangeBiometricsBinding.inflate(inflater, container, false).also { ui = it }.root + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + + initUI() + } + + private fun initUI() { + val viewModel: ChangeBiometricsViewModel by viewModels() + bindViewModel(viewModel) + + ui.loadingSwitchView.setOnCheckedChangeListener { + doAuth(it) + } + + observe(viewModel.state) { ui.loadingSwitchView.setState(it) } + } + + private fun doAuth(isTurningOn: Boolean = false) { + // check whether there's at least screen lock + if (viewModel.authService.isDeviceSecured) { + viewModel.startAuth(isTurningOn) + lifecycleScope.launch { + try { + if (!MOCKED) { + // prompt system authentication dialog + viewModel.authService.authenticate( + this@ChangeBiometricsFragment, + title = string(auth_title), + subtitle = + if (viewModel.authService.isBiometricAuthAvailable) string(auth_biometric_prompt) + else string(auth_device_lock_code_prompt) + ) + } + authSuccess(isTurningOn) + } catch (e: BiometricAuthenticationException) { + authFailed(isTurningOn) + } + } + } else { + authFailed(isTurningOn) + } + } + + private fun authSuccess(isChecked: Boolean) { + viewModel.authSuccessfully(isChecked) + viewModel.stopAuth(isChecked) + } + + private fun authFailed(isChecked: Boolean) { + viewModel.stopAuth(!isChecked) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/tari/android/wallet/ui/fragment/biometrics/ChangeBiometricsViewModel.kt b/app/src/main/java/com/tari/android/wallet/ui/fragment/biometrics/ChangeBiometricsViewModel.kt new file mode 100644 index 000000000..21af67720 --- /dev/null +++ b/app/src/main/java/com/tari/android/wallet/ui/fragment/biometrics/ChangeBiometricsViewModel.kt @@ -0,0 +1,32 @@ +package com.tari.android.wallet.ui.fragment.biometrics + +import androidx.lifecycle.MutableLiveData +import com.tari.android.wallet.infrastructure.security.biometric.BiometricAuthenticationService +import com.tari.android.wallet.ui.common.CommonViewModel +import com.tari.android.wallet.ui.component.loadingSwitch.TariLoadingSwitchState +import javax.inject.Inject + +class ChangeBiometricsViewModel : CommonViewModel() { + + @Inject + lateinit var authService: BiometricAuthenticationService + + val state: MutableLiveData = MutableLiveData() + + init { + component.inject(this) + state.value = TariLoadingSwitchState(isChecked = securityPrefRepository.biometricsAuth == true, false) + } + + fun startAuth(isChecked: Boolean) { + state.value = TariLoadingSwitchState(isChecked = isChecked, isLoading = true) + } + + fun authSuccessfully(newState: Boolean) { + securityPrefRepository.biometricsAuth = newState + } + + fun stopAuth(isChecked: Boolean) { + state.value = TariLoadingSwitchState(isChecked = isChecked, isLoading = false) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/tari/android/wallet/ui/fragment/chat_list/ChatListFragment.kt b/app/src/main/java/com/tari/android/wallet/ui/fragment/chat_list/ChatListFragment.kt new file mode 100644 index 000000000..eceda6deb --- /dev/null +++ b/app/src/main/java/com/tari/android/wallet/ui/fragment/chat_list/ChatListFragment.kt @@ -0,0 +1,63 @@ +package com.tari.android.wallet.ui.fragment.chat_list + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.fragment.app.viewModels +import androidx.recyclerview.widget.LinearLayoutManager +import com.tari.android.wallet.R +import com.tari.android.wallet.databinding.FragmentChatListBinding +import com.tari.android.wallet.extension.observe +import com.tari.android.wallet.ui.common.CommonFragment +import com.tari.android.wallet.ui.common.recyclerView.AdapterFactory +import com.tari.android.wallet.ui.common.recyclerView.CommonAdapter +import com.tari.android.wallet.ui.common.recyclerView.CommonViewHolderItem +import com.tari.android.wallet.ui.component.tari.toolbar.TariToolbarActionArg +import com.tari.android.wallet.ui.extension.setVisible +import com.tari.android.wallet.ui.fragment.chat_list.adapter.ChatItemViewHolder +import com.tari.android.wallet.ui.fragment.chat_list.adapter.ChatItemViewHolderItem +import com.tari.android.wallet.ui.fragment.home.navigation.Navigation + +class ChatListFragment : CommonFragment() { + + val adapter: CommonAdapter by lazy { AdapterFactory.generate(ChatItemViewHolder.getBuilder()) } + + override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View = + FragmentChatListBinding.inflate(inflater, container, false).also { ui = it }.root + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + + val viewModel: ChatListViewModel by viewModels() + bindViewModel(viewModel) + + initUI() + + subscribeUI() + } + + private fun initUI() = with(ui) { + list.layoutManager = LinearLayoutManager(requireContext()) + list.adapter = adapter + + adapter.setClickListener(CommonAdapter.ItemClickListener { + if (it is ChatItemViewHolderItem) { + viewModel.navigation.postValue(Navigation.ChatNavigation.ToChat(it.walletAddress, false)) + } + }) + + val right = TariToolbarActionArg(R.drawable.vector_chat_add) { + viewModel.navigation.postValue(Navigation.ChatNavigation.ToAddChat) + } + toolbar.setRightArgs(right) + } + + private fun subscribeUI() = with(viewModel) { + observe(list) { + adapter.submitList(it) + ui.list.setVisible(it.isNotEmpty()) + ui.emptyState.setVisible(it.isEmpty()) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/tari/android/wallet/ui/fragment/chat_list/ChatListViewModel.kt b/app/src/main/java/com/tari/android/wallet/ui/fragment/chat_list/ChatListViewModel.kt new file mode 100644 index 000000000..38079a990 --- /dev/null +++ b/app/src/main/java/com/tari/android/wallet/ui/fragment/chat_list/ChatListViewModel.kt @@ -0,0 +1,50 @@ +package com.tari.android.wallet.ui.fragment.chat_list + +import androidx.lifecycle.MediatorLiveData +import com.tari.android.wallet.ui.common.CommonViewModel +import com.tari.android.wallet.ui.common.recyclerView.CommonViewHolderItem +import com.tari.android.wallet.ui.fragment.chat_list.adapter.ChatItemViewHolderItem +import com.tari.android.wallet.ui.fragment.chat_list.data.ChatsRepository +import com.tari.android.wallet.ui.fragment.contact_book.data.ContactsRepository +import com.tari.android.wallet.util.extractEmojis +import javax.inject.Inject + +class ChatListViewModel : CommonViewModel() { + + init { + component.inject(this) + } + + @Inject + lateinit var chatsRepository: ChatsRepository + + @Inject + lateinit var contactsRepository: ContactsRepository + + val list = MediatorLiveData>() + + init { + component.inject(this) + + list.addSource(chatsRepository.list) { + list.postValue(it.map { + val firstEmoji = it.walletAddress.emojiId.extractEmojis().first() + val contact = contactsRepository.ffiBridge.getContactByAddress(it.walletAddress) + val lastMessage = it.messages.sortedBy { it.date }.lastOrNull() + val unreadCount = it.messages.count { it.isRead.not() } + ChatItemViewHolderItem( + it.walletAddress, + it.uuid, + firstEmoji, + contact.getPhoneDto()?.avatar.orEmpty(), + contact.contact.getAlias(), + it.walletAddress.emojiId, + lastMessage?.message.orEmpty(), + lastMessage?.date.orEmpty(), + unreadCount, + true + ) + }.toMutableList()) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/tari/android/wallet/ui/fragment/chat_list/adapter/ChatItemViewHolder.kt b/app/src/main/java/com/tari/android/wallet/ui/fragment/chat_list/adapter/ChatItemViewHolder.kt new file mode 100644 index 000000000..0dcbc47f9 --- /dev/null +++ b/app/src/main/java/com/tari/android/wallet/ui/fragment/chat_list/adapter/ChatItemViewHolder.kt @@ -0,0 +1,53 @@ +package com.tari.android.wallet.ui.fragment.chat_list.adapter + +import com.bumptech.glide.Glide +import com.tari.android.wallet.databinding.ItemChatItemBinding +import com.tari.android.wallet.ui.common.recyclerView.CommonViewHolder +import com.tari.android.wallet.ui.common.recyclerView.ViewHolderBuilder +import com.tari.android.wallet.ui.component.fullEmojiId.EmojiIdSummaryViewController +import com.tari.android.wallet.ui.extension.gone +import com.tari.android.wallet.ui.extension.setVisible +import com.tari.android.wallet.ui.extension.visible + +class ChatItemViewHolder(view: ItemChatItemBinding) : CommonViewHolder(view) { + + private val emojiIdSummaryController = EmojiIdSummaryViewController(ui.participantEmojiIdView) + + override fun bind(item: ChatItemViewHolderItem) { + super.bind(item) + + with(ui) { + onlineStatus.setVisible(item.isOnline) + unreadCountContainer.setVisible(item.unreadCount > 0) + unreadCount.text = item.unreadCount.toString() + date.text = item.dateMessage + message.text = item.subtitle + alias.text = item.alias + + if (item.avatar.isNotEmpty()) { + firstEmojiTextView.gone() + avatar.visible() + Glide.with(avatar).load(item.avatar).into(avatar) + } else { + firstEmojiTextView.visible() + avatar.gone() + firstEmojiTextView.text = item.firstEmoji + } + + if (item.alias.isEmpty()) { + participantEmojiIdView.root.visible() + alias.gone() + emojiIdSummaryController.display(item.emojiId) + } else { + participantEmojiIdView.root.gone() + alias.text = item.alias + alias.visible() + } + } + } + + companion object { + fun getBuilder(): ViewHolderBuilder = + ViewHolderBuilder(ItemChatItemBinding::inflate, ChatItemViewHolderItem::class.java) { ChatItemViewHolder(it as ItemChatItemBinding) } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/tari/android/wallet/ui/fragment/chat_list/adapter/ChatItemViewHolderItem.kt b/app/src/main/java/com/tari/android/wallet/ui/fragment/chat_list/adapter/ChatItemViewHolderItem.kt new file mode 100644 index 000000000..78b4f5d8d --- /dev/null +++ b/app/src/main/java/com/tari/android/wallet/ui/fragment/chat_list/adapter/ChatItemViewHolderItem.kt @@ -0,0 +1,20 @@ +package com.tari.android.wallet.ui.fragment.chat_list.adapter + +import com.tari.android.wallet.model.TariWalletAddress +import com.tari.android.wallet.ui.common.recyclerView.CommonViewHolderItem + +data class ChatItemViewHolderItem( + val walletAddress: TariWalletAddress, + val uuid: String, + val firstEmoji: String, + val avatar: String, + val alias: String, + val emojiId: String, + val subtitle: String, + val dateMessage: String, + val unreadCount: Int, + val isOnline: Boolean, +) : + CommonViewHolderItem() { + override val viewHolderUUID: String = "" +} \ No newline at end of file diff --git a/app/src/main/java/com/tari/android/wallet/ui/fragment/chat_list/addChat/AddChatFragment.kt b/app/src/main/java/com/tari/android/wallet/ui/fragment/chat_list/addChat/AddChatFragment.kt new file mode 100644 index 000000000..368f08e96 --- /dev/null +++ b/app/src/main/java/com/tari/android/wallet/ui/fragment/chat_list/addChat/AddChatFragment.kt @@ -0,0 +1,34 @@ +package com.tari.android.wallet.ui.fragment.chat_list.addChat + +import android.os.Bundle +import android.view.View +import com.tari.android.wallet.R +import com.tari.android.wallet.ui.extension.gone +import com.tari.android.wallet.ui.extension.string +import com.tari.android.wallet.ui.fragment.chat_list.data.ChatItemDto +import com.tari.android.wallet.ui.fragment.contact_book.contactSelection.ContactSelectionFragment +import com.tari.android.wallet.ui.fragment.home.navigation.Navigation +import java.util.UUID + +class AddChatFragment : ContactSelectionFragment() { + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + + ui.toolbar.ui.toolbarTitle.text = string(R.string.chat_add_chat) + ui.addFirstNameInput.gone() + + viewModel.additionalFilter = { it.contact.getFFIDto() != null } + } + + override fun goToNext() { + super.goToNext() + + val user = viewModel.getUserDto() + + val chatDto = ChatItemDto(UUID.randomUUID().toString(), listOf(), user.getFFIDto()!!.walletAddress) + viewModel.chatsRepository.addChat(chatDto) + + viewModel.navigation.postValue(Navigation.ChatNavigation.ToChat(user.getFFIDto()?.walletAddress!!, true)) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/tari/android/wallet/ui/fragment/chat_list/chat/ChatFragment.kt b/app/src/main/java/com/tari/android/wallet/ui/fragment/chat_list/chat/ChatFragment.kt new file mode 100644 index 000000000..728642d99 --- /dev/null +++ b/app/src/main/java/com/tari/android/wallet/ui/fragment/chat_list/chat/ChatFragment.kt @@ -0,0 +1,76 @@ +package com.tari.android.wallet.ui.fragment.chat_list.chat + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.fragment.app.viewModels +import androidx.recyclerview.widget.LinearLayoutManager +import com.tari.android.wallet.databinding.FragmentChatBinding +import com.tari.android.wallet.extension.observe +import com.tari.android.wallet.model.TariWalletAddress +import com.tari.android.wallet.ui.common.CommonFragment +import com.tari.android.wallet.ui.common.recyclerView.AdapterFactory +import com.tari.android.wallet.ui.common.recyclerView.CommonViewHolderItem +import com.tari.android.wallet.ui.extension.parcelable +import com.tari.android.wallet.ui.extension.setVisible +import com.tari.android.wallet.ui.fragment.chat_list.chat.cells.MessageCellViewHolder +import com.tari.android.wallet.ui.fragment.contact_book.data.contacts.ContactDto + +class ChatFragment : CommonFragment() { + + private val adapter = AdapterFactory.generate(MessageCellViewHolder.getBuilder()) + + override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View = + FragmentChatBinding.inflate(inflater, container, false).also { ui = it }.root + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + + val viewModel: ChatViewModel by viewModels() + bindViewModel(viewModel) + + val walletAddress = arguments?.parcelable(WALLET_ADDRESS)!! + viewModel.startWith(walletAddress) + + initUI() + observeUI() + } + + private fun initUI() { + ui.list.adapter = adapter + ui.list.layoutManager = LinearLayoutManager(requireContext()) + + ui.sendTariButton.setOnClickListener { + viewModel.sendMessage(ui.messageInput.ui.editText.text.toString()) + ui.messageInput.setText("") + } + + ui.attachButton.setOnClickListener { viewModel.showOptions() } + } + + private fun observeUI() = with(viewModel) { + observe(contact) { showContact(it) } + + observe(messages) { + adapter.update(it) + ui.emptyState.setVisible(it.isEmpty()) + ui.list.setVisible(it.isNotEmpty()) + } + } + + private fun showContact(contact: ContactDto) { + ui.toolbar.setText(contact.contact.getAlias()) + } + + companion object { + const val WALLET_ADDRESS = "Wallet address key" + + fun newInstance(walletAddress: TariWalletAddress) = ChatFragment().apply { + arguments = Bundle().apply { + putParcelable(WALLET_ADDRESS, walletAddress) + } + } + } +} + diff --git a/app/src/main/java/com/tari/android/wallet/ui/fragment/chat_list/chat/ChatViewModel.kt b/app/src/main/java/com/tari/android/wallet/ui/fragment/chat_list/chat/ChatViewModel.kt new file mode 100644 index 000000000..3442d8701 --- /dev/null +++ b/app/src/main/java/com/tari/android/wallet/ui/fragment/chat_list/chat/ChatViewModel.kt @@ -0,0 +1,85 @@ +package com.tari.android.wallet.ui.fragment.chat_list.chat + +import androidx.lifecycle.MutableLiveData +import com.tari.android.wallet.R +import com.tari.android.wallet.model.TariWalletAddress +import com.tari.android.wallet.ui.common.CommonViewModel +import com.tari.android.wallet.ui.common.recyclerView.CommonViewHolderItem +import com.tari.android.wallet.ui.dialog.modular.DialogArgs +import com.tari.android.wallet.ui.dialog.modular.ModularDialogArgs +import com.tari.android.wallet.ui.dialog.modular.modules.button.ButtonModule +import com.tari.android.wallet.ui.dialog.modular.modules.button.ButtonStyle +import com.tari.android.wallet.ui.dialog.modular.modules.head.HeadModule +import com.tari.android.wallet.ui.fragment.chat_list.chat.cells.MessagePresentationDto +import com.tari.android.wallet.ui.fragment.chat_list.data.ChatMessageItemDto +import com.tari.android.wallet.ui.fragment.chat_list.data.ChatsRepository +import com.tari.android.wallet.ui.fragment.contact_book.data.ContactsRepository +import com.tari.android.wallet.ui.fragment.contact_book.data.contacts.ContactDto +import com.tari.android.wallet.ui.fragment.home.navigation.Navigation +import java.util.UUID +import javax.inject.Inject + +class ChatViewModel : CommonViewModel() { + + @Inject + lateinit var contactsRepository: ContactsRepository + + @Inject + lateinit var chatRepository: ChatsRepository + + val userAddress = MutableLiveData() + + val contact = MutableLiveData() + + val messages = MutableLiveData>() + + init { + component.inject(this) + } + + + fun startWith(walletAddress: TariWalletAddress) { + userAddress.postValue(walletAddress) + + val contact = contactsRepository.ffiBridge.getContactByAddress(walletAddress) + this.contact.postValue(contact) + + val chat = chatRepository.getByWalletAddress(walletAddress) + + val list = mutableListOf() + + val messagesPresentation = chat?.messages.orEmpty().map { getMessage(it) } + list.addAll(messagesPresentation) + + this.messages.postValue(list) + } + + private fun getMessage(dto: ChatMessageItemDto): MessagePresentationDto = MessagePresentationDto(dto.message, dto.isMine) + + fun sendMessage(message: String) { + //todo send to backend + val list = messages.value.orEmpty().toMutableList() + val dto = ChatMessageItemDto(UUID.randomUUID().toString(), message, "date", true, false) + chatRepository.addMessage(userAddress.value!!, dto) + list.add(getMessage(dto)) + messages.postValue(list) + } + + fun showOptions() { + val args = ModularDialogArgs( + DialogArgs(), listOf( + HeadModule(resourceManager.getString(R.string.chat_options_title)), + ButtonModule(resourceManager.getString(R.string.send_tari), ButtonStyle.Normal) { + dismissDialog.postValue(Unit) + navigation.postValue(Navigation.ContactBookNavigation.ToSendTari(contact.value!!)) + }, + ButtonModule(resourceManager.getString(R.string.request_tari), ButtonStyle.Normal) { + dismissDialog.postValue(Unit) + navigation.postValue(Navigation.AllSettingsNavigation.ToRequestTari) + }, + ButtonModule(resourceManager.getString(R.string.common_cancel), ButtonStyle.Close) + ) + ) + modularDialog.postValue(args) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/tari/android/wallet/ui/fragment/chat_list/chat/cells/MessageCellViewHolder.kt b/app/src/main/java/com/tari/android/wallet/ui/fragment/chat_list/chat/cells/MessageCellViewHolder.kt new file mode 100644 index 000000000..dc82358b0 --- /dev/null +++ b/app/src/main/java/com/tari/android/wallet/ui/fragment/chat_list/chat/cells/MessageCellViewHolder.kt @@ -0,0 +1,20 @@ +package com.tari.android.wallet.ui.fragment.chat_list.chat.cells + +import com.tari.android.wallet.databinding.ItemChatItemBinding +import com.tari.android.wallet.ui.common.recyclerView.CommonViewHolder +import com.tari.android.wallet.ui.common.recyclerView.ViewHolderBuilder + +class MessageCellViewHolder(ui: ItemChatItemBinding) : CommonViewHolder(ui) { + + override fun bind(item: MessagePresentationDto) { + super.bind(item) + + //todo + } + + companion object { + fun getBuilder(): ViewHolderBuilder = ViewHolderBuilder(ItemChatItemBinding::inflate, MessagePresentationDto::class.java) { + MessageCellViewHolder(it as ItemChatItemBinding) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/tari/android/wallet/ui/fragment/chat_list/chat/cells/MessagePresentationDto.kt b/app/src/main/java/com/tari/android/wallet/ui/fragment/chat_list/chat/cells/MessagePresentationDto.kt new file mode 100644 index 000000000..458617819 --- /dev/null +++ b/app/src/main/java/com/tari/android/wallet/ui/fragment/chat_list/chat/cells/MessagePresentationDto.kt @@ -0,0 +1,7 @@ +package com.tari.android.wallet.ui.fragment.chat_list.chat.cells + +import com.tari.android.wallet.ui.common.recyclerView.CommonViewHolderItem + +data class MessagePresentationDto(val message: String, val isMine: Boolean) : CommonViewHolderItem() { + override val viewHolderUUID: String = "MessageCellViewHolder + $message + $isMine" +} \ No newline at end of file diff --git a/app/src/main/java/com/tari/android/wallet/ui/fragment/chat_list/data/ChatItemDto.kt b/app/src/main/java/com/tari/android/wallet/ui/fragment/chat_list/data/ChatItemDto.kt new file mode 100644 index 000000000..fc3262988 --- /dev/null +++ b/app/src/main/java/com/tari/android/wallet/ui/fragment/chat_list/data/ChatItemDto.kt @@ -0,0 +1,6 @@ +package com.tari.android.wallet.ui.fragment.chat_list.data + +import com.tari.android.wallet.model.TariWalletAddress +import java.io.Serializable + +class ChatItemDto(val uuid: String, val messages: List, val walletAddress: TariWalletAddress) : Serializable \ No newline at end of file diff --git a/app/src/main/java/com/tari/android/wallet/ui/fragment/chat_list/data/ChatList.kt b/app/src/main/java/com/tari/android/wallet/ui/fragment/chat_list/data/ChatList.kt new file mode 100644 index 000000000..a574eb44f --- /dev/null +++ b/app/src/main/java/com/tari/android/wallet/ui/fragment/chat_list/data/ChatList.kt @@ -0,0 +1,11 @@ +package com.tari.android.wallet.ui.fragment.chat_list.data + +import java.io.Serializable + +class ChatList() : ArrayList(), Serializable { + constructor(list: List) : this() { + this.addAll(list) + } +} + +fun ChatList?.orEmpty(): ChatList = this ?: ChatList() \ No newline at end of file diff --git a/app/src/main/java/com/tari/android/wallet/ui/fragment/chat_list/data/ChatMessageItemDto.kt b/app/src/main/java/com/tari/android/wallet/ui/fragment/chat_list/data/ChatMessageItemDto.kt new file mode 100644 index 000000000..10177c84f --- /dev/null +++ b/app/src/main/java/com/tari/android/wallet/ui/fragment/chat_list/data/ChatMessageItemDto.kt @@ -0,0 +1,3 @@ +package com.tari.android.wallet.ui.fragment.chat_list.data + +class ChatMessageItemDto(val uuid: String, val message: String, val date: String, val isMine: Boolean, val isRead: Boolean) \ No newline at end of file diff --git a/app/src/main/java/com/tari/android/wallet/ui/fragment/chat_list/data/ChatsPrefRepository.kt b/app/src/main/java/com/tari/android/wallet/ui/fragment/chat_list/data/ChatsPrefRepository.kt new file mode 100644 index 000000000..eb3928bd3 --- /dev/null +++ b/app/src/main/java/com/tari/android/wallet/ui/fragment/chat_list/data/ChatsPrefRepository.kt @@ -0,0 +1,52 @@ +package com.tari.android.wallet.ui.fragment.chat_list.data + +import android.content.SharedPreferences +import com.tari.android.wallet.data.repository.CommonRepository +import com.tari.android.wallet.data.sharedPrefs.delegates.SharedPrefGsonDelegate +import com.tari.android.wallet.data.sharedPrefs.network.NetworkRepository +import com.tari.android.wallet.data.sharedPrefs.network.formatKey +import javax.inject.Inject +import javax.inject.Singleton + +@Singleton +class ChatsPrefRepository @Inject constructor( + networkRepository: NetworkRepository, + val sharedPrefs: SharedPreferences +) : CommonRepository(networkRepository) { + + private var savedChats: ChatList? by SharedPrefGsonDelegate(sharedPrefs, this, formatKey(KEY_SAVED_CHATS), ChatList::class.java, ChatList()) + + fun getSavedChats(): List = savedChats.orEmpty().map { it } + + @Synchronized + fun saveChats(list: List) { + savedChats = ChatList(list.toList()) + } + + @Synchronized + fun addChat(chatItemDto: ChatItemDto) { + val list = savedChats?.toMutableList() ?: mutableListOf() + list.add(chatItemDto) + saveChats(list.toList()) + } + + fun saveMessage(chatItemDto: ChatItemDto?, messageItemDto: ChatMessageItemDto) { + val list = savedChats?.toMutableList() ?: mutableListOf() + val index = list.indexOfFirst { it.uuid == chatItemDto?.uuid } + if (index != -1) { + val chatItem = list[index] + val messages = chatItem.messages.toMutableList() + messages.add(messageItemDto) + list[index] = ChatItemDto(chatItem.uuid, messages.toList(), chatItem.walletAddress) + saveChats(list.toList()) + } + } + + fun clear() { + savedChats = null + } + + companion object { + const val KEY_SAVED_CHATS = "KEY_SAVED_CHATS" + } +} \ No newline at end of file diff --git a/app/src/main/java/com/tari/android/wallet/ui/fragment/chat_list/data/ChatsRepository.kt b/app/src/main/java/com/tari/android/wallet/ui/fragment/chat_list/data/ChatsRepository.kt new file mode 100644 index 000000000..e0e5748e9 --- /dev/null +++ b/app/src/main/java/com/tari/android/wallet/ui/fragment/chat_list/data/ChatsRepository.kt @@ -0,0 +1,124 @@ +package com.tari.android.wallet.ui.fragment.chat_list.data + +import androidx.lifecycle.MutableLiveData +import com.tari.android.wallet.extension.addTo +import com.tari.android.wallet.model.TariWalletAddress +import com.tari.android.wallet.ui.common.CommonViewModel +import com.tari.android.wallet.util.TariBuild +import java.util.UUID +import javax.inject.Inject +import javax.inject.Singleton + +@Singleton +class ChatsRepository @Inject constructor(private val chatsPrefRepository: ChatsPrefRepository) : CommonViewModel() { + + val list = MutableLiveData>() + + init { + component.inject(this) + + chatsPrefRepository.updateNotifier.subscribe { + updateList() + }.addTo(compositeDisposable) + } + + private fun updateList() { + val list = chatsPrefRepository.getSavedChats().toMutableList() + + if (TariBuild.MOCKED && list.isEmpty()) { + val mockedList = mutableListOf( + ChatItemDto( + UUID.randomUUID().toString(), + listOf( + ChatMessageItemDto( + UUID.randomUUID().toString(), + "first", + "2 days ago", + false, + false + ) + ), + TariBuild.mocked_wallet_address + ), + ChatItemDto( + UUID.randomUUID().toString(), + listOf( + ChatMessageItemDto( + UUID.randomUUID().toString(), + "second", + "2 days ago", + true, + false + ) + ), + TariBuild.mocked_wallet_address + ), + ChatItemDto( + UUID.randomUUID().toString(), + listOf( + ChatMessageItemDto( + UUID.randomUUID().toString(), + "third", + "2 days ago", + false, + true + ) + ), + TariBuild.mocked_wallet_address + ), + ChatItemDto( + UUID.randomUUID().toString(), + listOf( + ChatMessageItemDto( + UUID.randomUUID().toString(), + "fourth", + "2 days ago", + true, + true + ) + ), + TariBuild.mocked_wallet_address + ), + ChatItemDto( + UUID.randomUUID().toString(), + listOf( + ChatMessageItemDto( + UUID.randomUUID().toString(), + "fifth", + "2 days ago", + false, + false + ) + ), + TariBuild.mocked_wallet_address + ), + ChatItemDto( + UUID.randomUUID().toString(), + listOf( + ChatMessageItemDto( + UUID.randomUUID().toString(), + "sixth", + "2 days ago", + false, + false + ) + ), + TariBuild.mocked_wallet_address + ) + ) + + list.addAll(mockedList) + } + + this.list.postValue(list) + } + + fun getByUuid(uuid: String): ChatItemDto? = list.value?.find { it.uuid == uuid } + + fun getByWalletAddress(walletAddress: TariWalletAddress): ChatItemDto? = list.value?.find { it.walletAddress == walletAddress } + + fun addChat(chat: ChatItemDto) = chatsPrefRepository.addChat(chat) + + fun addMessage(walletAddress: TariWalletAddress, message: ChatMessageItemDto) = + chatsPrefRepository.saveMessage(getByWalletAddress(walletAddress), message) +} \ No newline at end of file diff --git a/app/src/main/java/com/tari/android/wallet/ui/fragment/contact_book/add/AddContactFragment.kt b/app/src/main/java/com/tari/android/wallet/ui/fragment/contact_book/add/AddContactFragment.kt index 93be17f81..89c80ee7c 100644 --- a/app/src/main/java/com/tari/android/wallet/ui/fragment/contact_book/add/AddContactFragment.kt +++ b/app/src/main/java/com/tari/android/wallet/ui/fragment/contact_book/add/AddContactFragment.kt @@ -15,7 +15,6 @@ class AddContactFragment : ContactSelectionFragment() { ui.toolbar.ui.toolbarTitle.text = string(R.string.contact_book_add_contact_title) ui.addFirstNameInput.visible() - ui.addSurnameInput.visible() viewModel.additionalFilter = { it.contact.getFFIDto() != null && it.contact.contact.getAlias().isEmpty() } } @@ -24,8 +23,10 @@ class AddContactFragment : ContactSelectionFragment() { super.goToNext() val user = viewModel.getUserDto() - val firstName = ui.addFirstNameInput.ui.editText.text.toString() - val surname = ui.addSurnameInput.ui.editText.text.toString() + val fullName = ui.addFirstNameInput.ui.editText.text.toString() + val split = fullName.split(" ") + val firstName = split.getOrNull(1).orEmpty().trim() + val surname = split.getOrNull(0).orEmpty().trim() viewModel.contactsRepository.updateContactInfo(user, firstName, surname, "") viewModel.navigation.postValue(Navigation.ContactBookNavigation.BackToContactBook()) diff --git a/app/src/main/java/com/tari/android/wallet/ui/fragment/contact_book/add/SelectUserContactFragment.kt b/app/src/main/java/com/tari/android/wallet/ui/fragment/contact_book/add/SelectUserContactFragment.kt index bd98f7001..c449d5806 100644 --- a/app/src/main/java/com/tari/android/wallet/ui/fragment/contact_book/add/SelectUserContactFragment.kt +++ b/app/src/main/java/com/tari/android/wallet/ui/fragment/contact_book/add/SelectUserContactFragment.kt @@ -27,7 +27,6 @@ class SelectUserContactFragment : ContactSelectionFragment() { ui.toolbar.ui.toolbarTitle.text = string(R.string.transaction_send_to) ui.addFirstNameInput.gone() - ui.addSurnameInput.gone() viewModel.isContactlessPayment.postValue(true) viewModel.additionalFilter = { it.contact.getFFIDto() != null } diff --git a/app/src/main/java/com/tari/android/wallet/ui/fragment/contact_book/contactSelection/ContactSelectionFragment.kt b/app/src/main/java/com/tari/android/wallet/ui/fragment/contact_book/contactSelection/ContactSelectionFragment.kt index ec4a32f87..9d94f06d7 100644 --- a/app/src/main/java/com/tari/android/wallet/ui/fragment/contact_book/contactSelection/ContactSelectionFragment.kt +++ b/app/src/main/java/com/tari/android/wallet/ui/fragment/contact_book/contactSelection/ContactSelectionFragment.kt @@ -113,8 +113,7 @@ open class ContactSelectionFragment : CommonFragment + val response = yatAdapter.searchTariYats(query) + response?.result?.entries?.firstOrNull()?.let { response -> walletService.getWalletAddressFromHexString(response.value.address)?.let { pubKey -> val yatUser = YatDto(query) foundYatUser.postValue(Optional.ofNullable(yatUser)) diff --git a/app/src/main/java/com/tari/android/wallet/ui/fragment/contact_book/data/ContactsRepository.kt b/app/src/main/java/com/tari/android/wallet/ui/fragment/contact_book/data/ContactsRepository.kt index ccaf7f0a3..dfb5b830d 100644 --- a/app/src/main/java/com/tari/android/wallet/ui/fragment/contact_book/data/ContactsRepository.kt +++ b/app/src/main/java/com/tari/android/wallet/ui/fragment/contact_book/data/ContactsRepository.kt @@ -188,7 +188,8 @@ class ContactsRepository @Inject constructor( private fun contactExists(contact: ContactDto): Boolean = this.publishSubject.value!!.any { it.uuid == contact.uuid } - private fun contactExistsByWalletAddress(contact: ContactDto): Boolean = this.publishSubject.value!!.any { it.contact.extractWalletAddress() == contact.contact.extractWalletAddress() } + private fun contactExistsByWalletAddress(contact: ContactDto): Boolean = + this.publishSubject.value!!.any { it.contact.extractWalletAddress() == contact.contact.extractWalletAddress() } @Synchronized private fun withListUpdate(silently: Boolean = false, updateAction: (list: MutableList) -> Unit) { @@ -235,11 +236,8 @@ class ContactsRepository @Inject constructor( fun getContactForTx(tx: Tx): ContactDto = getContactByAddress(tx.tariContact.walletAddress) fun getContactByAddress(address: TariWalletAddress): ContactDto = - this@ContactsRepository.publishSubject.value!!.firstOrNull { it.getFFIDto()?.walletAddress == address } ?: ContactDto( - FFIContactDto( - address - ) - ) + this@ContactsRepository.publishSubject.value!!.firstOrNull { it.getFFIDto()?.walletAddress == address } + ?: ContactDto(FFIContactDto(address)) private fun subscribeToActions() { EventBus.subscribe(this) { updateRecentUsedTime(it.tx.tariContact) } @@ -315,12 +313,13 @@ class ContactsRepository @Inject constructor( private fun onFFIContactAddedOrUpdated(contact: TariWalletAddress, alias: String, isFavorite: Boolean) { if (ffiContactExist(contact)) { - withFFIContact(contact) { - it.getFFIDto()?.let { ffiContactDto -> - ffiContactDto.setAlias(alias) - ffiContactDto.isFavorite = isFavorite - } - } + //turn off updated because of name erasing +// withFFIContact(contact) { +// it.getFFIDto()?.let { ffiContactDto -> +// ffiContactDto.setAlias(alias) +// ffiContactDto.isFavorite = isFavorite +// } +// } } else { withListUpdate { it.add(ContactDto(FFIContactDto(contact, alias, isFavorite))) diff --git a/app/src/main/java/com/tari/android/wallet/ui/fragment/contact_book/details/ContactDetailsViewModel.kt b/app/src/main/java/com/tari/android/wallet/ui/fragment/contact_book/details/ContactDetailsViewModel.kt index 4677d92fb..1a45fbabf 100644 --- a/app/src/main/java/com/tari/android/wallet/ui/fragment/contact_book/details/ContactDetailsViewModel.kt +++ b/app/src/main/java/com/tari/android/wallet/ui/fragment/contact_book/details/ContactDetailsViewModel.kt @@ -10,7 +10,6 @@ import com.tari.android.wallet.R.string.common_close import com.tari.android.wallet.R.string.common_confirm import com.tari.android.wallet.R.string.contact_book_add_contact_done_button import com.tari.android.wallet.R.string.contact_book_add_contact_first_name_hint -import com.tari.android.wallet.R.string.contact_book_add_contact_surname_hint import com.tari.android.wallet.R.string.contact_book_add_contact_yat_hint import com.tari.android.wallet.R.string.contact_book_contacts_book_unlink_message_firstLine import com.tari.android.wallet.R.string.contact_book_contacts_book_unlink_message_secondLine @@ -204,16 +203,13 @@ class ContactDetailsViewModel : CommonViewModel() { fun onEditClick() { val contact = contact.value!! - val name = contact.contact.firstName - val surname = contact.contact.surname + val name = (contact.contact.firstName + " " + contact.contact.surname).trim() val phoneDto = contact.getPhoneDto() val yatDto = contact.getYatDto() var saveAction: () -> Boolean = { false } - val nameModule = InputModule(name, resourceManager.getString(contact_book_add_contact_first_name_hint), true, false) { saveAction.invoke() } - val surnameModule = - InputModule(surname, resourceManager.getString(contact_book_add_contact_surname_hint), false, phoneDto == null) { saveAction.invoke() } + val nameModule = InputModule(name, resourceManager.getString(contact_book_add_contact_first_name_hint), true, true) { saveAction.invoke() } val yatModule = phoneDto?.let { YatInputModule(this::yatSearchAction, yatDto?.yat.orEmpty(), resourceManager.getString(contact_book_add_contact_yat_hint), false, true) { saveAction.invoke() @@ -225,11 +221,11 @@ class ContactDetailsViewModel : CommonViewModel() { rightButtonTitle = resourceManager.getString(contact_book_add_contact_done_button) ) { saveAction.invoke() } - val moduleList = mutableListOf(headModule, nameModule, surnameModule) + val moduleList = mutableListOf(headModule, nameModule) yatModule?.let { moduleList.add(it) } saveAction = { - saveDetails(nameModule.value, surnameModule.value, yatModule?.value ?: "") + saveDetails(nameModule.value,yatModule?.value ?: "") true } @@ -252,7 +248,10 @@ class ContactDetailsViewModel : CommonViewModel() { return searchingJob?.await() != null } - private fun saveDetails(name: String, surname: String = "", yat: String = "") { + private fun saveDetails(newName: String, yat: String = "") { + val split = newName.split(" ") + val name = split.getOrNull(0).orEmpty().trim() + val surname = split.getOrNull(1).orEmpty().trim() val contact = contact.value!! updatingJob = null this.contact.value = contactsRepository.updateContactInfo(contact, name, surname, yat) diff --git a/app/src/main/java/com/tari/android/wallet/ui/fragment/contact_book/root/ContactBookFragment.kt b/app/src/main/java/com/tari/android/wallet/ui/fragment/contact_book/root/ContactBookFragment.kt index 9aef1940a..6a2724792 100644 --- a/app/src/main/java/com/tari/android/wallet/ui/fragment/contact_book/root/ContactBookFragment.kt +++ b/app/src/main/java/com/tari/android/wallet/ui/fragment/contact_book/root/ContactBookFragment.kt @@ -71,6 +71,7 @@ class ContactBookFragment : CommonFragment doShareViaQrCode(deeplink) ShareType.LINK -> doShareViaLink(deeplink) - ShareType.NFC -> doShareViaNFC() ShareType.BLE -> doShareViaBLE() } } @@ -150,23 +141,6 @@ class ShareViewModel : CommonViewModel() { showShareSuccessDialog() } - private fun doShareViaNFC() { - if (!tariNFCAdapter.isNFCAvailable()) { - tariNFCAdapter.showNFCSettings() - return - } - val args = ModularDialogArgs( - DialogArgs(canceledOnTouchOutside = false, cancelable = false) { tariNFCAdapter.stopSharing() }, listOf( - IconModule(R.drawable.vector_sharing_via_nfc), - HeadModule(resourceManager.getString(R.string.share_via_nfc_title)), - BodyModule(resourceManager.getString(R.string.share_via_nfc_message)), - ButtonModule(resourceManager.getString(R.string.common_close), ButtonStyle.Close) - ) - ) - modularDialog.postValue(args) - tariNFCAdapter.startSharing(shareInfo.value.orEmpty()) - } - private fun doShareViaBLE() { permissionManager.runWithPermission(tariBluetoothServer.bluetoothPermissions) { startBLESharing() diff --git a/app/src/main/java/com/tari/android/wallet/ui/fragment/contact_book/root/action_menu/ActionMenuView.kt b/app/src/main/java/com/tari/android/wallet/ui/fragment/contact_book/root/action_menu/ActionMenuView.kt index 02c2e8dff..d8374d2dc 100644 --- a/app/src/main/java/com/tari/android/wallet/ui/fragment/contact_book/root/action_menu/ActionMenuView.kt +++ b/app/src/main/java/com/tari/android/wallet/ui/fragment/contact_book/root/action_menu/ActionMenuView.kt @@ -78,6 +78,7 @@ class ActionMenuView : CommonView { this.layoutParams = FrameLayout.LayoutParams(ConstraintLayout.LayoutParams.MATCH_PARENT, ConstraintLayout.LayoutParams.MATCH_PARENT) changeSideButton.setOnClickListener { changeSide() } this.closeButton.setOnClickListener { close() } + close() gone() } diff --git a/app/src/main/java/com/tari/android/wallet/ui/fragment/contact_book/root/share/ShareType.kt b/app/src/main/java/com/tari/android/wallet/ui/fragment/contact_book/root/share/ShareType.kt index 8ac5a578a..542f19f73 100644 --- a/app/src/main/java/com/tari/android/wallet/ui/fragment/contact_book/root/share/ShareType.kt +++ b/app/src/main/java/com/tari/android/wallet/ui/fragment/contact_book/root/share/ShareType.kt @@ -3,6 +3,5 @@ package com.tari.android.wallet.ui.fragment.contact_book.root.share enum class ShareType { QR_CODE, LINK, - NFC, BLE } \ No newline at end of file diff --git a/app/src/main/java/com/tari/android/wallet/ui/fragment/contact_book/transactionHistory/TransactionHistoryFragment.kt b/app/src/main/java/com/tari/android/wallet/ui/fragment/contact_book/transactionHistory/TransactionHistoryFragment.kt index fb7406b01..b821c67fa 100644 --- a/app/src/main/java/com/tari/android/wallet/ui/fragment/contact_book/transactionHistory/TransactionHistoryFragment.kt +++ b/app/src/main/java/com/tari/android/wallet/ui/fragment/contact_book/transactionHistory/TransactionHistoryFragment.kt @@ -55,6 +55,7 @@ class TransactionHistoryFragment : CommonFragment() var list = MediatorLiveData>() @@ -21,20 +27,28 @@ class TransactionHistoryViewModel : CommonViewModel() { init { component.inject(this) + list.addSource(contactRepository.publishSubject.toFlowable(BackpressureStrategy.LATEST).toLiveData()) { + updateList() + } + list.addSource(selectedContact) { updateList() } - list.addSource(transactionRepository.debouncedList) { updateList() } + list.addSource(transactionRepository.list) { updateList() } } private fun updateList() { - val filtered = transactionRepository.list.value?.filter { + val contact = selectedContact.value ?: return + val actualContact = contactRepository.getByUuid(contact.uuid) + if (contact != actualContact) selectedContact.postValue(actualContact) + + val filtered: MutableList = transactionRepository.list.value?.filter { if (it is TransactionItem) { it.tx.tariContact.walletAddress == selectedContact.value?.contact?.extractWalletAddress() } else { false } - } - list.postValue(filtered.orEmpty().toMutableList()) + }.orEmpty().map { (it as TransactionItem).copy(contact = actualContact) }.toMutableList() + list.postValue(filtered) } } diff --git a/app/src/main/java/com/tari/android/wallet/ui/fragment/home/HomeActivity.kt b/app/src/main/java/com/tari/android/wallet/ui/fragment/home/HomeActivity.kt index e8effbe6a..df2e5a3f1 100644 --- a/app/src/main/java/com/tari/android/wallet/ui/fragment/home/HomeActivity.kt +++ b/app/src/main/java/com/tari/android/wallet/ui/fragment/home/HomeActivity.kt @@ -34,9 +34,11 @@ package com.tari.android.wallet.ui.fragment.home import android.Manifest.permission.POST_NOTIFICATIONS import android.content.Intent +import android.content.res.Configuration import android.os.Build import android.os.Bundle import android.widget.ImageView +import androidx.activity.addCallback import androidx.activity.viewModels import androidx.fragment.app.Fragment import androidx.fragment.app.FragmentManager @@ -49,6 +51,7 @@ import com.tari.android.wallet.application.deeplinks.DeeplinkHandler import com.tari.android.wallet.application.deeplinks.DeeplinkViewModel import com.tari.android.wallet.data.sharedPrefs.SharedPrefsRepository import com.tari.android.wallet.data.sharedPrefs.network.NetworkRepository +import com.tari.android.wallet.data.sharedPrefs.security.SecurityPrefRepository import com.tari.android.wallet.data.sharedPrefs.tariSettings.TariSettingsSharedRepository import com.tari.android.wallet.databinding.ActivityHomeBinding import com.tari.android.wallet.di.DiContainer.appComponent @@ -70,20 +73,24 @@ import com.tari.android.wallet.ui.dialog.modular.modules.head.HeadModule import com.tari.android.wallet.ui.extension.parcelable import com.tari.android.wallet.ui.extension.setVisible import com.tari.android.wallet.ui.extension.string +import com.tari.android.wallet.ui.fragment.auth.AuthActivity +import com.tari.android.wallet.ui.fragment.chat_list.ChatListFragment import com.tari.android.wallet.ui.fragment.contact_book.root.ContactBookFragment import com.tari.android.wallet.ui.fragment.contact_book.root.action_menu.ContactBookActionMenuViewModel import com.tari.android.wallet.ui.fragment.home.navigation.Navigation +import com.tari.android.wallet.ui.fragment.home.navigation.TariNavigator.Companion.INDEX_CHAT import com.tari.android.wallet.ui.fragment.home.navigation.TariNavigator.Companion.INDEX_CONTACT_BOOK import com.tari.android.wallet.ui.fragment.home.navigation.TariNavigator.Companion.INDEX_HOME import com.tari.android.wallet.ui.fragment.home.navigation.TariNavigator.Companion.INDEX_SETTINGS -import com.tari.android.wallet.ui.fragment.home.navigation.TariNavigator.Companion.INDEX_STORE import com.tari.android.wallet.ui.fragment.home.navigation.TariNavigator.Companion.NO_SMOOTH_SCROLL import com.tari.android.wallet.ui.fragment.onboarding.activity.OnboardingFlowActivity import com.tari.android.wallet.ui.fragment.settings.allSettings.AllSettingsFragment +import com.tari.android.wallet.ui.fragment.settings.themeSelector.TariTheme import com.tari.android.wallet.ui.fragment.splash.SplashActivity import com.tari.android.wallet.ui.fragment.store.StoreFragment import com.tari.android.wallet.ui.fragment.tx.HomeFragment import com.tari.android.wallet.util.Constants +import com.tari.android.wallet.util.TariBuild import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.delay import kotlinx.coroutines.launch @@ -95,6 +102,9 @@ class HomeActivity : CommonActivity() { @Inject lateinit var sharedPrefsRepository: SharedPrefsRepository + @Inject + lateinit var securityPrefRepository: SecurityPrefRepository + @Inject lateinit var walletServiceLauncher: WalletServiceLauncher @@ -119,6 +129,23 @@ class HomeActivity : CommonActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) + appComponent.inject(this) + + onBackPressedDispatcher.addCallback { + if (ui.actionMenuView.onBackPressed()) { + return@addCallback + } + if (supportFragmentManager.backStackEntryCount > 0) { + supportFragmentManager.popBackStack() + } else { + if (ui.viewPager.currentItem == INDEX_HOME) { + finish() + } else { + ui.viewPager.setCurrentItem(INDEX_HOME, NO_SMOOTH_SCROLL) + enableNavigationView(ui.homeImageView) + } + } + } instance = WeakReference(this) @@ -131,16 +158,14 @@ class HomeActivity : CommonActivity() { subscribeToCommon(viewModel.shareViewModel.tariBluetoothClient) subscribeToCommon(viewModel.shareViewModel.deeplinkViewModel) subscribeToCommon(actionMenuViewModel) - viewModel.nfcAdapter.context = this viewModel.shareViewModel.tariBluetoothServer.init(this) viewModel.shareViewModel.tariBluetoothClient.init(this) setContainerId(R.id.nav_container) - overridePendingTransition(0, 0) - appComponent.inject(this) - if (!sharedPrefsRepository.isAuthenticated) { + + if (!securityPrefRepository.isAuthenticated) { val intent = Intent(this, SplashActivity::class.java) .apply { flags = Intent.FLAG_ACTIVITY_NEW_TASK } this.intent?.data?.let(intent::setData) @@ -149,6 +174,20 @@ class HomeActivity : CommonActivity() { return } ui = ActivityHomeBinding.inflate(layoutInflater).also { setContentView(it.root) } + + val buttonBg = when(tariSettingsRepository.currentTheme) { + TariTheme.AppBased -> { + when (resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK) { + Configuration.UI_MODE_NIGHT_YES -> R.drawable.vector_disable_able_gradient_button_bg_external_dark + else -> R.drawable.vector_disable_able_gradient_button_bg_external + } + } + null, + TariTheme.Light -> R.drawable.vector_disable_able_gradient_button_bg_external + else -> R.drawable.vector_disable_able_gradient_button_bg_external_dark + } + ui.sendButtonExternalContainer.setBackgroundResource(buttonBg) + if (savedInstanceState == null) { enableNavigationView(ui.homeImageView) viewModel.doOnConnected { @@ -178,12 +217,9 @@ class HomeActivity : CommonActivity() { override fun onResume() { super.onResume() - viewModel.nfcAdapter.enableForegroundDispatch(this) - } - - override fun onPause() { - super.onPause() - viewModel.nfcAdapter.disableForegroundDispatch(this) + if (!viewModel.securityPrefRepository.isAuthenticated) { + launch(AuthActivity::class.java) + } } private fun subscribeUI() = with(viewModel) { @@ -195,6 +231,7 @@ class HomeActivity : CommonActivity() { } } + @Deprecated("Deprecated in Java") override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { super.onActivityResult(requestCode, resultCode, data) viewModel.shareViewModel.tariBluetoothServer.handleActivityResult(requestCode, resultCode, data) @@ -204,8 +241,6 @@ class HomeActivity : CommonActivity() { override fun onNewIntent(intent: Intent) { super.onNewIntent(intent) - viewModel.nfcAdapter.onNewIntent(intent) - // onNewIntent might get called before onCreate, so we anticipate that here checkScreensDeeplink(intent) if (viewModel.serviceConnection.currentState.status == ServiceConnectionStatus.CONNECTED) { @@ -220,22 +255,6 @@ class HomeActivity : CommonActivity() { outState.putInt(KEY_PAGE, ui.viewPager.currentItem) } - override fun onBackPressed() { - if (ui.actionMenuView.onBackPressed()) { - return - } - if (supportFragmentManager.backStackEntryCount > 0) { - super.onBackPressed() - } else { - if (ui.viewPager.currentItem == INDEX_HOME) { - super.onBackPressed() - } else { - ui.viewPager.setCurrentItem(INDEX_HOME, NO_SMOOTH_SCROLL) - enableNavigationView(ui.homeImageView) - } - } - } - fun setBottomBarVisibility(isVisible: Boolean) { val postDelay = if (!isVisible) 0 else Constants.UI.shortDurationMs ui.bottomNavigationView.postDelayed({ @@ -251,20 +270,23 @@ class HomeActivity : CommonActivity() { private fun setupBottomNavigation() { enableNavigationView(ui.homeImageView) - ui.viewPager.adapter = HomeAdapter(supportFragmentManager, this.lifecycle) + ui.viewPager.adapter = if (TariBuild.isChat) HomeChatAdapter(supportFragmentManager, this.lifecycle) + else HomeStoreAdapter(supportFragmentManager, this.lifecycle) ui.viewPager.isUserInputEnabled = false ui.viewPager.offscreenPageLimit = 3 ui.homeView.setOnClickListener { ui.viewPager.setCurrentItem(INDEX_HOME, NO_SMOOTH_SCROLL) enableNavigationView(ui.homeImageView) } + ui.storeImageView.setImageResource(if (TariBuild.isChat) R.drawable.vector_home_book else R.drawable.vector_ttl_store_icon) ui.storeView.setOnClickListener { - ui.viewPager.setCurrentItem(INDEX_STORE, NO_SMOOTH_SCROLL) + ui.viewPager.setCurrentItem(INDEX_CONTACT_BOOK, NO_SMOOTH_SCROLL) enableNavigationView(ui.storeImageView) } - ui.walletInfoView.setOnClickListener { - ui.viewPager.setCurrentItem(INDEX_CONTACT_BOOK, NO_SMOOTH_SCROLL) - enableNavigationView(ui.walletInfoImageView) + ui.chatImageView.setImageResource(if (TariBuild.isChat) R.drawable.vector_home_chat else R.drawable.vector_home_book) + ui.chatView.setOnClickListener { + ui.viewPager.setCurrentItem(INDEX_CHAT, NO_SMOOTH_SCROLL) + enableNavigationView(ui.chatImageView) } ui.settingsView.setOnClickListener { ui.viewPager.setCurrentItem(INDEX_SETTINGS, NO_SMOOTH_SCROLL) @@ -275,8 +297,8 @@ class HomeActivity : CommonActivity() { private fun enableNavigationView(index: Int) { val view: ImageView = when (index) { INDEX_HOME -> ui.homeImageView - INDEX_STORE -> ui.storeImageView - INDEX_CONTACT_BOOK -> ui.walletInfoImageView + INDEX_CHAT -> ui.storeImageView + INDEX_CONTACT_BOOK -> ui.chatImageView INDEX_SETTINGS -> ui.settingsImageView else -> error("Unexpected index: $index") } @@ -284,7 +306,7 @@ class HomeActivity : CommonActivity() { } private fun enableNavigationView(view: ImageView) { - arrayOf(ui.homeImageView, ui.storeImageView, ui.walletInfoImageView, ui.settingsImageView).forEach { it.clearColorFilter() } + arrayOf(ui.homeImageView, ui.storeImageView, ui.chatImageView, ui.settingsImageView).forEach { it.clearColorFilter() } view.setColorFilter(viewModel.paletteManager.getPurpleBrand(this)) } @@ -355,7 +377,7 @@ class HomeActivity : CommonActivity() { fun willNotifyAboutNewTx(): Boolean = ui.viewPager.currentItem == INDEX_HOME private fun processIntentDeepLink(intent: Intent) { - deeplinkViewModel.tryToHandle(intent.data?.toString().orEmpty()) + deeplinkViewModel.tryToHandle(intent.data?.toString().orEmpty(), false) } override fun onDestroy() { @@ -364,11 +386,24 @@ class HomeActivity : CommonActivity() { viewModelStore.clear() } - class HomeAdapter(fm: FragmentManager, lifecycle: Lifecycle) : FragmentStateAdapter(fm, lifecycle) { + class HomeStoreAdapter(fm: FragmentManager, lifecycle: Lifecycle) : FragmentStateAdapter(fm, lifecycle) { + + override fun createFragment(position: Int): Fragment = when (position) { + INDEX_HOME -> HomeFragment() + INDEX_CHAT -> ContactBookFragment() + INDEX_CONTACT_BOOK -> StoreFragment.newInstance() + INDEX_SETTINGS -> AllSettingsFragment.newInstance() + else -> error("Unexpected position: $position") + } + + override fun getItemCount(): Int = 4 + } + + class HomeChatAdapter(fm: FragmentManager, lifecycle: Lifecycle) : FragmentStateAdapter(fm, lifecycle) { override fun createFragment(position: Int): Fragment = when (position) { INDEX_HOME -> HomeFragment() - INDEX_STORE -> StoreFragment.newInstance() + INDEX_CHAT -> ChatListFragment() INDEX_CONTACT_BOOK -> ContactBookFragment() INDEX_SETTINGS -> AllSettingsFragment.newInstance() else -> error("Unexpected position: $position") diff --git a/app/src/main/java/com/tari/android/wallet/ui/fragment/home/HomeViewModel.kt b/app/src/main/java/com/tari/android/wallet/ui/fragment/home/HomeViewModel.kt index ab4a5af01..7f2a249b6 100644 --- a/app/src/main/java/com/tari/android/wallet/ui/fragment/home/HomeViewModel.kt +++ b/app/src/main/java/com/tari/android/wallet/ui/fragment/home/HomeViewModel.kt @@ -1,6 +1,5 @@ package com.tari.android.wallet.ui.fragment.home -import com.tari.android.wallet.infrastructure.nfc.TariNFCAdapter import com.tari.android.wallet.ui.common.CommonViewModel import com.tari.android.wallet.ui.fragment.contact_book.data.ContactsRepository import com.tari.android.wallet.ui.fragment.contact_book.root.ShareViewModel @@ -15,9 +14,6 @@ class HomeViewModel: CommonViewModel() { @Inject lateinit var contactsRepository: ContactsRepository - @Inject - lateinit var nfcAdapter: TariNFCAdapter - val shareViewModel = ShareViewModel() init { diff --git a/app/src/main/java/com/tari/android/wallet/ui/fragment/home/homeTransactionHistory/HomeTransactionHistoryFragment.kt b/app/src/main/java/com/tari/android/wallet/ui/fragment/home/homeTransactionHistory/HomeTransactionHistoryFragment.kt index 19ae04bd9..8d59a222b 100644 --- a/app/src/main/java/com/tari/android/wallet/ui/fragment/home/homeTransactionHistory/HomeTransactionHistoryFragment.kt +++ b/app/src/main/java/com/tari/android/wallet/ui/fragment/home/homeTransactionHistory/HomeTransactionHistoryFragment.kt @@ -39,7 +39,10 @@ class HomeTransactionHistoryFragment : CommonFragment>() val searchText = MutableLiveData("") @@ -25,6 +31,10 @@ class HomeTransactionHistoryViewModel : CommonViewModel() { init { component.inject(this) + list.addSource(contactsRepository.publishSubject.toFlowable(BackpressureStrategy.LATEST).toLiveData()) { + updateList() + } + list.addSource(transactionRepository.list) { updateList() } list.addSource(searchText) { updateList() } diff --git a/app/src/main/java/com/tari/android/wallet/ui/fragment/home/navigation/Navigation.kt b/app/src/main/java/com/tari/android/wallet/ui/fragment/home/navigation/Navigation.kt index 372736f82..2b428a343 100644 --- a/app/src/main/java/com/tari/android/wallet/ui/fragment/home/navigation/Navigation.kt +++ b/app/src/main/java/com/tari/android/wallet/ui/fragment/home/navigation/Navigation.kt @@ -2,16 +2,22 @@ package com.tari.android.wallet.ui.fragment.home.navigation import com.tari.android.wallet.application.deeplinks.DeepLink import com.tari.android.wallet.model.MicroTari +import com.tari.android.wallet.model.TariWalletAddress import com.tari.android.wallet.model.Tx import com.tari.android.wallet.ui.fragment.contact_book.data.contacts.ContactDto import com.tari.android.wallet.ui.fragment.contact_book.data.contacts.YatDto +import com.tari.android.wallet.ui.fragment.pinCode.PinCodeScreenBehavior import com.tari.android.wallet.ui.fragment.send.common.TransactionData sealed class Navigation { - sealed class CustomBridgeNavigation : Navigation() { - object ScanQrCode : CustomBridgeNavigation() + class EnterPinCodeNavigation(val behavior: PinCodeScreenBehavior, val stashedPin: String? = null): Navigation() + + class ChangeBiometrics(): Navigation() + class FeatureAuth(): Navigation() + + sealed class CustomBridgeNavigation : Navigation() { object UploadQrCode : CustomBridgeNavigation() } @@ -46,7 +52,7 @@ sealed class Navigation { class ToTxDetails(val tx: Tx) : TxListNavigation() - object ToTTLStore : TxListNavigation() + object ToChat : TxListNavigation() object ToAllSettings : TxListNavigation() @@ -60,6 +66,12 @@ sealed class Navigation { class ToTransfer : TxListNavigation() } + sealed class ChatNavigation : Navigation() { + object ToAddChat : ChatNavigation() + + class ToChat(val walletAddress: TariWalletAddress, val isNew: Boolean) : ChatNavigation() + } + sealed class AddAmountNavigation : Navigation() { object OnAmountExceedsActualAvailableBalance : AddAmountNavigation() @@ -76,6 +88,7 @@ sealed class Navigation { sealed class AllSettingsNavigation : Navigation() { object ToMyProfile : AllSettingsNavigation() object ToBugReporting : AllSettingsNavigation() + object ToDataCollection : AllSettingsNavigation() object ToAbout : AllSettingsNavigation() object ToBackupSettings : AllSettingsNavigation() object ToDeleteWallet : AllSettingsNavigation() diff --git a/app/src/main/java/com/tari/android/wallet/ui/fragment/home/navigation/TariNavigator.kt b/app/src/main/java/com/tari/android/wallet/ui/fragment/home/navigation/TariNavigator.kt index 149683084..9c265bf15 100644 --- a/app/src/main/java/com/tari/android/wallet/ui/fragment/home/navigation/TariNavigator.kt +++ b/app/src/main/java/com/tari/android/wallet/ui/fragment/home/navigation/TariNavigator.kt @@ -31,7 +31,10 @@ import com.tari.android.wallet.ui.dialog.modular.modules.button.ButtonStyle import com.tari.android.wallet.ui.dialog.modular.modules.head.HeadModule import com.tari.android.wallet.ui.extension.showInternetConnectionErrorDialog import com.tari.android.wallet.ui.extension.string -import com.tari.android.wallet.ui.fragment.auth.AuthActivity +import com.tari.android.wallet.ui.fragment.auth.FeatureAuthFragment +import com.tari.android.wallet.ui.fragment.biometrics.ChangeBiometricsFragment +import com.tari.android.wallet.ui.fragment.chat_list.addChat.AddChatFragment +import com.tari.android.wallet.ui.fragment.chat_list.chat.ChatFragment import com.tari.android.wallet.ui.fragment.contact_book.add.AddContactFragment import com.tari.android.wallet.ui.fragment.contact_book.add.SelectUserContactFragment import com.tari.android.wallet.ui.fragment.contact_book.data.contacts.ContactDto @@ -45,9 +48,9 @@ import com.tari.android.wallet.ui.fragment.home.homeTransactionHistory.HomeTrans import com.tari.android.wallet.ui.fragment.home.navigation.Navigation.AllSettingsNavigation import com.tari.android.wallet.ui.fragment.home.navigation.Navigation.ContactBookNavigation import com.tari.android.wallet.ui.fragment.onboarding.activity.OnboardingFlowActivity +import com.tari.android.wallet.ui.fragment.onboarding.localAuth.LocalAuthFragment +import com.tari.android.wallet.ui.fragment.pinCode.EnterPinCodeFragment import com.tari.android.wallet.ui.fragment.profile.WalletInfoFragment -import com.tari.android.wallet.ui.fragment.qr.QRScannerActivity -import com.tari.android.wallet.ui.fragment.qr.QrScannerSource import com.tari.android.wallet.ui.fragment.restore.enterRestorationPassword.EnterRestorationPasswordFragment import com.tari.android.wallet.ui.fragment.restore.inputSeedWords.InputSeedWordsFragment import com.tari.android.wallet.ui.fragment.restore.walletRestoringFromSeedWords.WalletRestoringFromSeedWordsFragment @@ -58,6 +61,7 @@ import com.tari.android.wallet.ui.fragment.send.finalize.FinalizeSendTxFragment import com.tari.android.wallet.ui.fragment.send.finalize.TxFailureReason import com.tari.android.wallet.ui.fragment.send.requestTari.RequestTariFragment import com.tari.android.wallet.ui.fragment.send.transfer.TransferFragment +import com.tari.android.wallet.ui.fragment.settings.allSettings.AllSettingsFragment import com.tari.android.wallet.ui.fragment.settings.allSettings.about.TariAboutFragment import com.tari.android.wallet.ui.fragment.settings.backgroundService.BackgroundServiceSettingsFragment import com.tari.android.wallet.ui.fragment.settings.backup.backupOnboarding.BackupOnboardingFlowFragment @@ -69,6 +73,7 @@ import com.tari.android.wallet.ui.fragment.settings.backup.writeDownSeedWords.Wr import com.tari.android.wallet.ui.fragment.settings.baseNodeConfig.addBaseNode.AddCustomBaseNodeFragment import com.tari.android.wallet.ui.fragment.settings.baseNodeConfig.changeBaseNode.ChangeBaseNodeFragment import com.tari.android.wallet.ui.fragment.settings.bluetoothSettings.BluetoothSettingsFragment +import com.tari.android.wallet.ui.fragment.settings.dataCollection.DataCollectionFragment import com.tari.android.wallet.ui.fragment.settings.deleteWallet.DeleteWalletFragment import com.tari.android.wallet.ui.fragment.settings.logs.activity.DebugActivity import com.tari.android.wallet.ui.fragment.settings.logs.activity.DebugNavigation @@ -91,6 +96,9 @@ class TariNavigator @Inject constructor(val prefs: SharedPrefsRepository, val ta fun navigate(navigation: Navigation) { when (navigation) { + is Navigation.EnterPinCodeNavigation -> addFragment(EnterPinCodeFragment.newInstance(navigation.behavior, navigation.stashedPin)) + is Navigation.ChangeBiometrics -> addFragment(ChangeBiometricsFragment()) + is Navigation.FeatureAuth -> addFragment(FeatureAuthFragment()) is ContactBookNavigation.ToAddContact -> toAddContact() is ContactBookNavigation.ToContactDetails -> toContactDetails(navigation.contact) is ContactBookNavigation.ToRequestTari -> toRequestTariFromContact(navigation.contact) @@ -114,6 +122,7 @@ class TariNavigator @Inject constructor(val prefs: SharedPrefsRepository, val ta AllSettingsNavigation.ToDeleteWallet -> toDeleteWallet() AllSettingsNavigation.ToNetworkSelection -> toNetworkSelection() AllSettingsNavigation.ToTorBridges -> toTorBridges() + AllSettingsNavigation.ToDataCollection -> addFragment(DataCollectionFragment()) AllSettingsNavigation.ToThemeSelection -> toThemeSelection() AllSettingsNavigation.ToRequestTari -> addFragment(RequestTariFragment.newInstance()) Navigation.EnterRestorationPasswordNavigation.OnRestore -> onRestoreCompleted() @@ -128,7 +137,7 @@ class TariNavigator @Inject constructor(val prefs: SharedPrefsRepository, val ta Navigation.AddAmountNavigation.OnAmountExceedsActualAvailableBalance -> onAmountExceedsActualAvailableBalance() is Navigation.AddAmountNavigation.ContinueToAddNote -> continueToAddNote(navigation.transactionData) is Navigation.AddAmountNavigation.ContinueToFinalizing -> continueToFinalizeSendTx(navigation.transactionData) - Navigation.TxListNavigation.ToTTLStore -> toTTLStore() + Navigation.TxListNavigation.ToChat -> toChat() is Navigation.TxListNavigation.ToTxDetails -> toTxDetails(navigation.tx, null) is Navigation.TxListNavigation.ToSendTariToUser -> toSendTari(navigation.contact, navigation.amount) is Navigation.TxListNavigation.ToSendWithDeeplink -> toSendWithDeeplink(navigation.sendDeeplink) @@ -145,11 +154,16 @@ class TariNavigator @Inject constructor(val prefs: SharedPrefsRepository, val ta Navigation.BackupSettingsNavigation.ToConfirmPassword -> toConfirmPassword() Navigation.BackupSettingsNavigation.ToWalletBackupWithRecoveryPhrase -> toWalletBackupWithRecoveryPhrase() Navigation.BackupSettingsNavigation.ToLearnMore -> toBackupOnboardingFlow() - Navigation.CustomBridgeNavigation.ScanQrCode -> { - QRScannerActivity.startScanner(activity, QrScannerSource.TorBridges) + Navigation.CustomBridgeNavigation.UploadQrCode -> Unit + is Navigation.ChatNavigation.ToChat -> { + if (navigation.isNew) { + onBackPressed() + } + + addFragment(ChatFragment.newInstance(navigation.walletAddress)) } - Navigation.CustomBridgeNavigation.UploadQrCode -> Unit + Navigation.ChatNavigation.ToAddChat -> addFragment(AddChatFragment()) else -> Unit } } @@ -175,12 +189,14 @@ class TariNavigator @Inject constructor(val prefs: SharedPrefsRepository, val ta fun onRestoreCompleted() { // wallet restored, setup shared prefs accordingly prefs.onboardingCompleted = true - prefs.onboardingAuthSetupCompleted = true + prefs.onboardingStarted = true + prefs.onboardingAuthSetupStarted = true + prefs.onboardingAuthSetupCompleted = false prefs.onboardingDisplayedAtHome = true tariSettingsSharedRepository.isRestoredWallet = true activity.finish() - activity.startActivity(Intent(this.activity, AuthActivity::class.java).apply { + activity.startActivity(Intent(this.activity, OnboardingFlowActivity::class.java).apply { flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK }) } @@ -194,7 +210,7 @@ class TariNavigator @Inject constructor(val prefs: SharedPrefsRepository, val ta } }) - fun toTTLStore() = (activity as HomeActivity).ui.viewPager.setCurrentItem(INDEX_STORE, NO_SMOOTH_SCROLL) + fun toChat() = (activity as HomeActivity).ui.viewPager.setCurrentItem(INDEX_CHAT, NO_SMOOTH_SCROLL) fun toAllSettings() = (activity as HomeActivity).ui.viewPager.setCurrentItem(INDEX_SETTINGS, NO_SMOOTH_SCROLL) @@ -234,7 +250,7 @@ class TariNavigator @Inject constructor(val prefs: SharedPrefsRepository, val ta fun toSendTari(user: ContactDto, amount: MicroTari?) = sendToUser(user, amount) - fun toSendWithDeeplink(deeplink: DeepLink.Send) { + fun toSendWithDeeplink(deeplink: DeepLink.Send) { popUpTo(HomeFragment::class.java.simpleName) sendToUserByDeeplink(deeplink) } @@ -249,6 +265,16 @@ class TariNavigator @Inject constructor(val prefs: SharedPrefsRepository, val ta fun backToContactBook() = popUpTo(ContactBookFragment::class.java.simpleName) + fun backToAllSettings() = popUpTo(AllSettingsFragment::class.java.simpleName) + + fun backAfterAuth() { + if (activity is HomeActivity) { + popUpTo(AllSettingsFragment::class.java.simpleName) + } else { + popUpTo(LocalAuthFragment::class.java.simpleName) + } + } + fun toLinkContact(contact: ContactDto) = addFragment(ContactLinkFragment.createFragment(contact)) fun toContactTransactionHistory(contact: ContactDto) = addFragment(TransactionHistoryFragment.createFragment(contact)) @@ -371,7 +397,6 @@ class TariNavigator @Inject constructor(val prefs: SharedPrefsRepository, val ta //popup fragment private fun popUpTo(tag: String) = activity.popUpTo(tag) - companion object { const val PARAMETER_NOTE = "note" const val PARAMETER_AMOUNT = "amount" @@ -379,8 +404,8 @@ class TariNavigator @Inject constructor(val prefs: SharedPrefsRepository, val ta const val PARAMETER_CONTACT = "tari_contact_dto_args" const val INDEX_HOME = 0 - const val INDEX_STORE = 1 - const val INDEX_CONTACT_BOOK = 2 + const val INDEX_CONTACT_BOOK = 1 + const val INDEX_CHAT = 2 const val INDEX_SETTINGS = 3 const val NO_SMOOTH_SCROLL = false } diff --git a/app/src/main/java/com/tari/android/wallet/ui/fragment/onboarding/activity/OnboardingFlowActivity.kt b/app/src/main/java/com/tari/android/wallet/ui/fragment/onboarding/activity/OnboardingFlowActivity.kt index dbeb7b0e2..642f17ba5 100644 --- a/app/src/main/java/com/tari/android/wallet/ui/fragment/onboarding/activity/OnboardingFlowActivity.kt +++ b/app/src/main/java/com/tari/android/wallet/ui/fragment/onboarding/activity/OnboardingFlowActivity.kt @@ -34,6 +34,7 @@ package com.tari.android.wallet.ui.fragment.onboarding.activity import android.content.Intent import android.os.Bundle +import androidx.activity.addCallback import androidx.activity.viewModels import androidx.fragment.app.Fragment import androidx.lifecycle.lifecycleScope @@ -82,6 +83,12 @@ class OnboardingFlowActivity : CommonActivity 1) { + supportFragmentManager.popBackStack() + } + } + val viewModel: OnboardingFlowViewModel by viewModels() bindViewModel(viewModel) @@ -113,20 +120,11 @@ class OnboardingFlowActivity : CommonActivity 1) { - supportFragmentManager.popBackStack() - } - } - override fun continueToCreateWallet() { sharedPrefsWrapper.onboardingStarted = true supportFragmentManager.beginTransaction() diff --git a/app/src/main/java/com/tari/android/wallet/ui/fragment/onboarding/localAuth/LocalAuthFragment.kt b/app/src/main/java/com/tari/android/wallet/ui/fragment/onboarding/localAuth/LocalAuthFragment.kt index 9da1dc029..ed63e4607 100644 --- a/app/src/main/java/com/tari/android/wallet/ui/fragment/onboarding/localAuth/LocalAuthFragment.kt +++ b/app/src/main/java/com/tari/android/wallet/ui/fragment/onboarding/localAuth/LocalAuthFragment.kt @@ -39,40 +39,25 @@ import android.os.Bundle import android.view.LayoutInflater import android.view.View import android.view.ViewGroup -import androidx.appcompat.app.AlertDialog import androidx.fragment.app.viewModels import androidx.lifecycle.lifecycleScope import com.daasuu.ei.Ease import com.daasuu.ei.EasingInterpolator -import com.tari.android.wallet.R -import com.tari.android.wallet.R.dimen.auth_button_bottom_margin -import com.tari.android.wallet.R.string.auth_failed_desc -import com.tari.android.wallet.R.string.auth_failed_title -import com.tari.android.wallet.R.string.auth_not_available_or_canceled_desc -import com.tari.android.wallet.R.string.auth_not_available_or_canceled_title -import com.tari.android.wallet.R.string.auth_prompt_button_text -import com.tari.android.wallet.R.string.auth_prompt_button_touch_id_text -import com.tari.android.wallet.R.string.common_cancel -import com.tari.android.wallet.R.string.common_ok import com.tari.android.wallet.R.string.onboarding_auth_biometric_prompt -import com.tari.android.wallet.R.string.onboarding_auth_device_lock_code_prompt import com.tari.android.wallet.R.string.onboarding_auth_title -import com.tari.android.wallet.R.string.proceed import com.tari.android.wallet.application.WalletState import com.tari.android.wallet.databinding.FragmentLocalAuthBinding import com.tari.android.wallet.event.EventBus import com.tari.android.wallet.extension.addTo -import com.tari.android.wallet.extension.observeOnLoad +import com.tari.android.wallet.extension.observe import com.tari.android.wallet.infrastructure.security.biometric.BiometricAuthenticationException -import com.tari.android.wallet.infrastructure.security.biometric.BiometricAuthenticationType import com.tari.android.wallet.ui.common.CommonFragment -import com.tari.android.wallet.ui.extension.animateClick -import com.tari.android.wallet.ui.extension.dimenPx import com.tari.android.wallet.ui.extension.doOnGlobalLayout -import com.tari.android.wallet.ui.extension.invisible +import com.tari.android.wallet.ui.extension.setOnThrottledClickListener +import com.tari.android.wallet.ui.extension.setVisible import com.tari.android.wallet.ui.extension.string -import com.tari.android.wallet.ui.extension.temporarilyDisableClick -import com.tari.android.wallet.ui.extension.visible +import com.tari.android.wallet.ui.fragment.home.navigation.Navigation +import com.tari.android.wallet.ui.fragment.pinCode.PinCodeScreenBehavior import com.tari.android.wallet.util.Constants.UI.Auth import kotlinx.coroutines.launch @@ -93,28 +78,23 @@ class LocalAuthFragment : CommonFragment val alpha = valueAnimator.animatedValue as Float ui.smallGemImageView.alpha = alpha - ui.authTypeImageView.alpha = alpha + ui.authTypeBiometrics.alpha = alpha + ui.authTypePasscode.alpha = alpha ui.authDescTextView.alpha = alpha - ui.enableAuthButtonContainerView.alpha = alpha } startDelay = Auth.viewFadeAnimDelayMs } AnimatorSet().apply { - playTogether(titleTextAnim, buttonContainerViewAnim, fadeInAnim) + playTogether(titleTextAnim, fadeInAnim) interpolator = EasingInterpolator(Ease.QUINT_IN) startDelay = Auth.localAuthAnimDurationMs duration = Auth.localAuthAnimDurationMs @@ -138,70 +118,26 @@ class LocalAuthFragment : CommonFragment - // user has chosen to proceed without authentication - viewModel.sharedPrefsWrapper.isAuthenticated = true - dialog.cancel() - authSuccess() - } - .setNegativeButton(string(common_cancel), null) - .create().apply { - setTitle(string(auth_not_available_or_canceled_title)) - show() - } - } - - private fun authSuccess() { - // check if the wallet is ready & switch to wait mode if not & start listening - ui.progressBarContainerView.visible() - ui.enableAuthButton.invisible() - + private fun proceedToMain() { EventBus.walletState.publishSubject .filter { it == WalletState.Running } .subscribe { - viewModel.sharedPrefsWrapper.isAuthenticated = true - viewModel.sharedPrefsWrapper.onboardingAuthSetupCompleted = true + viewModel.securityPrefRepository.isAuthenticated = true + viewModel.sharedPrefsRepository.onboardingAuthSetupCompleted = true viewModel.backupManager.backupNow() + viewModel.navigation.postValue(Navigation.EnterPinCodeNavigation(PinCodeScreenBehavior.CreateConfirm)) (requireActivity() as? LocalAuthListener)?.onAuthSuccess() }.addTo(viewModel.compositeDisposable) } diff --git a/app/src/main/java/com/tari/android/wallet/ui/fragment/onboarding/localAuth/LocalAuthViewModel.kt b/app/src/main/java/com/tari/android/wallet/ui/fragment/onboarding/localAuth/LocalAuthViewModel.kt index d6181340b..378b7aea8 100644 --- a/app/src/main/java/com/tari/android/wallet/ui/fragment/onboarding/localAuth/LocalAuthViewModel.kt +++ b/app/src/main/java/com/tari/android/wallet/ui/fragment/onboarding/localAuth/LocalAuthViewModel.kt @@ -1,30 +1,43 @@ package com.tari.android.wallet.ui.fragment.onboarding.localAuth -import androidx.lifecycle.LiveData import androidx.lifecycle.MutableLiveData -import com.tari.android.wallet.data.sharedPrefs.SharedPrefsRepository +import com.tari.android.wallet.extension.addTo import com.tari.android.wallet.infrastructure.backup.BackupManager import com.tari.android.wallet.infrastructure.security.biometric.BiometricAuthenticationService -import com.tari.android.wallet.infrastructure.security.biometric.BiometricAuthenticationType import com.tari.android.wallet.ui.common.CommonViewModel import javax.inject.Inject class LocalAuthViewModel : CommonViewModel() { - @Inject - lateinit var sharedPrefsWrapper: SharedPrefsRepository - @Inject lateinit var authService: BiometricAuthenticationService @Inject lateinit var backupManager: BackupManager - private val _authType = MutableLiveData() - val authType: LiveData = _authType + val secureState = MutableLiveData(SecureState(true, false, false)) + init { component.inject(this) - _authType.value = authService.authType + sharedPrefsRepository.onboardingAuthSetupStarted = true + updateState() + + securityPrefRepository.updateNotifier.subscribe { + updateState() + }.addTo(compositeDisposable) + } + + fun updateState() { + secureState.value = SecureState( + authService.isBiometricAuthAvailable, + securityPrefRepository.pinCode != null, + securityPrefRepository.biometricsAuth == true + ) + } + + fun securedWithBiometrics() { + securityPrefRepository.biometricsAuth = true + secureState.value = secureState.value!!.copy(biometricsSecured = true) } } \ No newline at end of file diff --git a/app/src/main/java/com/tari/android/wallet/ui/fragment/onboarding/localAuth/SecureState.kt b/app/src/main/java/com/tari/android/wallet/ui/fragment/onboarding/localAuth/SecureState.kt new file mode 100644 index 000000000..83aa00108 --- /dev/null +++ b/app/src/main/java/com/tari/android/wallet/ui/fragment/onboarding/localAuth/SecureState.kt @@ -0,0 +1,3 @@ +package com.tari.android.wallet.ui.fragment.onboarding.localAuth + +data class SecureState(val biometricsAvailable: Boolean, val pinCodeSecured: Boolean, val biometricsSecured: Boolean) \ No newline at end of file diff --git a/app/src/main/java/com/tari/android/wallet/ui/fragment/pinCode/EnterPinCodeFragment.kt b/app/src/main/java/com/tari/android/wallet/ui/fragment/pinCode/EnterPinCodeFragment.kt new file mode 100644 index 000000000..14379bfc0 --- /dev/null +++ b/app/src/main/java/com/tari/android/wallet/ui/fragment/pinCode/EnterPinCodeFragment.kt @@ -0,0 +1,216 @@ +package com.tari.android.wallet.ui.fragment.pinCode + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.fragment.app.viewModels +import androidx.lifecycle.lifecycleScope +import com.tari.android.wallet.R +import com.tari.android.wallet.databinding.FragmentEnterPincodeBinding +import com.tari.android.wallet.extension.addTo +import com.tari.android.wallet.extension.observe +import com.tari.android.wallet.ui.common.CommonFragment +import com.tari.android.wallet.ui.extension.gone +import com.tari.android.wallet.ui.extension.invisible +import com.tari.android.wallet.ui.extension.setOnThrottledClickListener +import com.tari.android.wallet.ui.extension.setVisible +import com.tari.android.wallet.ui.extension.visible +import com.tari.android.wallet.ui.fragment.auth.AuthActivity +import com.tari.android.wallet.ui.fragment.auth.FeatureAuthFragment +import io.reactivex.Observable +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import java.time.Duration +import java.time.LocalDateTime +import java.util.concurrent.TimeUnit + +class EnterPinCodeFragment : CommonFragment() { + + override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?) = + FragmentEnterPincodeBinding.inflate(inflater, container, false).also { ui = it }.root + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + + val viewModel: EnterPinCodeViewModel by viewModels() + bindViewModel(viewModel) + + val behavior = arguments?.getInt(PIN_CODE_EXTRA_KEY)?.let { PinCodeScreenBehavior.values()[it] } ?: PinCodeScreenBehavior.CreateConfirm + val stashedPinCode = arguments?.getString(PIN_CODE_STASHED_KEY) + viewModel.init(behavior, stashedPinCode) + + initUI() + ui.continueBtn.setOnThrottledClickListener { viewModel.doMainAction() } + observeUI() + } + + override fun onStart() { + super.onStart() + viewModel.currentNums.value = "" + } + + private fun initUI() = with(ui.numpad) { + initNumpad() + biometricAuth.setOnClickListener { + (requireActivity() as? AuthActivity)?.showBiometricAuth() + requireActivity().supportFragmentManager.fragments.firstOrNull { it is FeatureAuthFragment }?.let { + (it as? FeatureAuthFragment)?.showBiometricAuth() + } + } + decimalPointButton.gone() + } + + private fun observeUI() = with(viewModel) { + observe(behavior) { + val isVisible = + (it == PinCodeScreenBehavior.Auth || it == PinCodeScreenBehavior.FeatureAuth) && viewModel.securityPrefRepository.biometricsAuth == true + ui.numpad.biometricAuth.setVisible(isVisible) + if (isVisible) { + ui.numpad.decimalPointButton.gone() + } else { + ui.numpad.decimalPointButton.invisible() + } + } + + observe(subtitleText) { + ui.subtitle.text = it + ui.toolbar.ui.toolbarTitle.text = it + } + + observe(toolbarIsVisible) { ui.toolbar.setVisible(it) } + + observe(buttonIsVisible) { ui.continueBtn.setVisible(it) } + + observe(buttonTitle) { ui.continueBtn.text = it } + + observe(currentNums) { + val length = it.length + ui.pinCode1Dot.setVisible(length > 0) + ui.pinCode2Dot.setVisible(length > 1) + ui.pinCode3Dot.setVisible(length > 2) + ui.pinCode4Dot.setVisible(length > 3) + ui.pinCode5Dot.setVisible(length > 4) + ui.pinCode6Dot.setVisible(length > 5) + ui.continueBtn.isEnabled = length >= 6 + if (length < 6) { + viewModel.errorMessage.postValue("") + } + } + + observe(errorMessage) { + setState(it.isNotEmpty()) + ui.errorMessage.text = it + ui.errorMessage.setVisible(it.isNotEmpty()) + } + + observe(successfullyAuth) { + (requireActivity() as? AuthActivity)?.continueToHomeActivity() + requireActivity().supportFragmentManager.fragments.firstOrNull { it is FeatureAuthFragment }?.let { + (it as? FeatureAuthFragment)?.authSuccessfully() + } + } + + observe(nextEnterTime) { + val nextEnterTime = viewModel.nextEnterTime.value + val now = LocalDateTime.now() + val isLater = now.isBefore(nextEnterTime) + setSecurityState(isLater) + ui.errorMessageCountdown.setVisible(isLater) + ui.errorMessage.setVisible(!isLater) + startCountdown(nextEnterTime) + } + } + + private fun startCountdown(dateTime: LocalDateTime?) { + if (dateTime != null && dateTime.isAfter(LocalDateTime.now())) { + updateText(Duration.between(LocalDateTime.now(), dateTime)) + Observable.timer(1, TimeUnit.SECONDS) + .repeat() + .map { Duration.between(LocalDateTime.now(), dateTime) } + .takeWhile { + val shouldTake = LocalDateTime.now().isBefore(dateTime) + if (!shouldTake) { + viewModel.nextEnterTime.postValue(LocalDateTime.now()) + } + shouldTake + } + .subscribe { lifecycleScope.launch(Dispatchers.Main) { updateText(it) } } + .addTo(viewModel.compositeDisposable) + } + } + + private fun updateText(duration: Duration) { + val seconds = duration.seconds + val minutes = seconds / 60 + val hours = minutes / 60 + var timeText = "" + if (hours > 0) { + timeText += "${hours}h " + } + if (minutes > 0) { + timeText += "${minutes % 60}m " + } + timeText += "${seconds % 60}s" + val errorText = if (!duration.isNegative) "Too many attempts. You should wait $timeText" else "" + ui.errorMessageCountdown.text = errorText + ui.errorMessageCountdown.visible() + ui.errorMessage.invisible() + } + + private fun setSecurityState(isDisabled: Boolean) = with(ui) { + pinCodeContainer.setVisible(!isDisabled) + subtitle.setVisible(!isDisabled) + for (index in 0 until numpad.root.childCount) { + numpad.root.getChildAt(index).alpha = if (isDisabled) 0.5f else 1f + } + if (isDisabled) clearNumpad() else initNumpad() + numpad.biometricAuth.alpha = 1f + } + + private fun setState(isError: Boolean) { + val backResource = if (isError) R.drawable.vector_pin_input_dot_error else R.drawable.vector_pin_input_dot_normal + val allPinCodeViews = listOf(ui.pinCode1Dot, ui.pinCode2Dot, ui.pinCode3Dot, ui.pinCode4Dot, ui.pinCode5Dot, ui.pinCode6Dot) + allPinCodeViews.forEach { it.setBackgroundResource(backResource) } + } + + private fun initNumpad() = with(ui.numpad) { + pad0Button.setOnClickListener { viewModel.addNum("0") } + pad1Button.setOnClickListener { viewModel.addNum("1") } + pad2Button.setOnClickListener { viewModel.addNum("2") } + pad3Button.setOnClickListener { viewModel.addNum("3") } + pad4Button.setOnClickListener { viewModel.addNum("4") } + pad5Button.setOnClickListener { viewModel.addNum("5") } + pad6Button.setOnClickListener { viewModel.addNum("6") } + pad7Button.setOnClickListener { viewModel.addNum("7") } + pad8Button.setOnClickListener { viewModel.addNum("8") } + pad9Button.setOnClickListener { viewModel.addNum("9") } + deleteButton.setOnClickListener { viewModel.removeLast() } + } + + private fun clearNumpad() = with(ui.numpad) { + pad0Button.setOnClickListener { } + pad1Button.setOnClickListener { } + pad2Button.setOnClickListener { } + pad3Button.setOnClickListener { } + pad4Button.setOnClickListener { } + pad5Button.setOnClickListener { } + pad6Button.setOnClickListener { } + pad7Button.setOnClickListener { } + pad8Button.setOnClickListener { } + pad9Button.setOnClickListener { } + deleteButton.setOnClickListener { } + } + + companion object { + const val PIN_CODE_EXTRA_KEY = "pin_code_extra_key" + const val PIN_CODE_STASHED_KEY = "pin_code_stashed_key" + + fun newInstance(behavior: PinCodeScreenBehavior, pinCodeStashed: String? = null) = EnterPinCodeFragment().apply { + arguments = Bundle().apply { + putInt(PIN_CODE_EXTRA_KEY, behavior.ordinal) + putString(PIN_CODE_STASHED_KEY, pinCodeStashed) + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/tari/android/wallet/ui/fragment/pinCode/EnterPinCodeViewModel.kt b/app/src/main/java/com/tari/android/wallet/ui/fragment/pinCode/EnterPinCodeViewModel.kt new file mode 100644 index 000000000..d7f6b903b --- /dev/null +++ b/app/src/main/java/com/tari/android/wallet/ui/fragment/pinCode/EnterPinCodeViewModel.kt @@ -0,0 +1,154 @@ +package com.tari.android.wallet.ui.fragment.pinCode + +import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.map +import com.tari.android.wallet.R +import com.tari.android.wallet.data.sharedPrefs.security.LoginAttemptDto +import com.tari.android.wallet.extension.addTo +import com.tari.android.wallet.ui.common.CommonViewModel +import com.tari.android.wallet.ui.common.SingleLiveEvent +import com.tari.android.wallet.ui.fragment.home.navigation.Navigation +import java.time.LocalDateTime +import java.time.OffsetDateTime + +class EnterPinCodeViewModel : CommonViewModel() { + + val behavior = MutableLiveData() + + val stashedPin = MutableLiveData() + + val subtitleText = behavior.map { + val id = when (it) { + PinCodeScreenBehavior.Create -> R.string.pin_code_subtitle_create + PinCodeScreenBehavior.CreateConfirm -> R.string.pin_code_subtitle_create_confirm + PinCodeScreenBehavior.ChangeNew -> R.string.pin_code_subtitle_change_new + PinCodeScreenBehavior.ChangeNewConfirm -> R.string.pin_code_subtitle_change_new_confirm + PinCodeScreenBehavior.Auth -> R.string.pin_code_subtitle_auth + PinCodeScreenBehavior.FeatureAuth -> R.string.pin_code_subtitle_feature_auth + else -> R.string.common_continue + } + resourceManager.getString(id) + } + + val toolbarIsVisible = behavior.map { + it != PinCodeScreenBehavior.Auth && it != PinCodeScreenBehavior.FeatureAuth + } + + val buttonIsVisible = behavior.map { + false + } + + val currentNums = MutableLiveData("") + + val errorMessage = MutableLiveData("") + + val buttonTitle = behavior.map { + val id = when (it) { + PinCodeScreenBehavior.Create -> R.string.common_continue + PinCodeScreenBehavior.CreateConfirm -> R.string.common_confirm + PinCodeScreenBehavior.ChangeNew -> R.string.common_continue + PinCodeScreenBehavior.ChangeNewConfirm -> R.string.common_confirm + PinCodeScreenBehavior.Auth -> R.string.common_confirm + else -> R.string.common_continue + } + resourceManager.getString(id) + } + + val successfullyAuth = SingleLiveEvent() + + val nextEnterTime = MutableLiveData() + + init { + component.inject(this) + + doFraudLogic() + securityPrefRepository.updateNotifier.subscribe { doFraudLogic() }.addTo(compositeDisposable) + } + + private fun doFraudLogic() { + val attempts = securityPrefRepository.attempts.orEmpty() + val lastTimeInMills = attempts.lastOrNull()?.timeInMills + val localDateTime = LocalDateTime.ofEpochSecond((lastTimeInMills ?: 0) / 1000, 0, OffsetDateTime.now().offset) + val nextDateTime = when (attempts.size) { + in 0..3 -> LocalDateTime.now().minusMinutes(1) + 4 -> localDateTime.plusMinutes(1) + 5 -> localDateTime.plusMinutes(5) + 6 -> localDateTime.plusMinutes(60) + else -> localDateTime.plusMinutes(60 * 5) + } + nextEnterTime.postValue(nextDateTime) + } + + fun init(behavior: PinCodeScreenBehavior, stashedPin: String? = null) { + this.behavior.value = behavior + this.stashedPin.value = stashedPin + } + + fun addNum(num: String) { + if ((currentNums.value?.length ?: 0) >= 6) { + if (errorMessage.value.orEmpty().isNotEmpty()) { + errorMessage.value = "" + currentNums.value = num + } + return + } + currentNums.value += num + if (currentNums.value.orEmpty().length == 6) { + doMainAction() + } + } + + fun removeLast() { + currentNums.value = currentNums.value?.dropLast(1) + } + + fun doMainAction() { + when (behavior.value) { + PinCodeScreenBehavior.Create -> createPinCode() + PinCodeScreenBehavior.CreateConfirm -> createPinCodeConfirm() + PinCodeScreenBehavior.ChangeNew -> changeNewPinCode() + PinCodeScreenBehavior.ChangeNewConfirm -> changeNewPinCodeConfirm() + PinCodeScreenBehavior.Auth -> authPinCode() + PinCodeScreenBehavior.FeatureAuth -> authPinCode() + null -> Unit + } + } + + private fun createPinCode() { + navigation.postValue(Navigation.EnterPinCodeNavigation(PinCodeScreenBehavior.CreateConfirm, currentNums.value)) + } + + private fun createPinCodeConfirm() { + if (currentNums.value != stashedPin.value) { + backPressed.postValue(Unit) + return + } + securityPrefRepository.pinCode = currentNums.value.orEmpty() + tariNavigator.backAfterAuth() + } + + private fun changeNewPinCode() { + navigation.postValue(Navigation.EnterPinCodeNavigation(PinCodeScreenBehavior.ChangeNewConfirm, currentNums.value)) + } + + private fun changeNewPinCodeConfirm() { + if (currentNums.value != stashedPin.value) { + backPressed.postValue(Unit) + return + } + securityPrefRepository.pinCode = currentNums.value.orEmpty() + tariNavigator.backAfterAuth() + } + + private fun authPinCode() { + val isSuccessfully = currentNums.value == securityPrefRepository.pinCode + + securityPrefRepository.saveAttempt(LoginAttemptDto(System.currentTimeMillis(), isSuccessfully)) + + if (!isSuccessfully) { + errorMessage.value = resourceManager.getString(R.string.pin_code_error_message) + return + } + successfullyAuth.postValue(Unit) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/tari/android/wallet/ui/fragment/pinCode/PinCodeScreenBehavior.kt b/app/src/main/java/com/tari/android/wallet/ui/fragment/pinCode/PinCodeScreenBehavior.kt new file mode 100644 index 000000000..ccbeb6249 --- /dev/null +++ b/app/src/main/java/com/tari/android/wallet/ui/fragment/pinCode/PinCodeScreenBehavior.kt @@ -0,0 +1,10 @@ +package com.tari.android.wallet.ui.fragment.pinCode + +enum class PinCodeScreenBehavior() { + Create, + CreateConfirm, + ChangeNew, + ChangeNewConfirm, + Auth, + FeatureAuth, +} \ No newline at end of file diff --git a/app/src/main/java/com/tari/android/wallet/ui/fragment/profile/RoundButtonWithIconView.kt b/app/src/main/java/com/tari/android/wallet/ui/fragment/profile/RoundButtonWithIconView.kt new file mode 100644 index 000000000..88e0e634d --- /dev/null +++ b/app/src/main/java/com/tari/android/wallet/ui/fragment/profile/RoundButtonWithIconView.kt @@ -0,0 +1,53 @@ +package com.tari.android.wallet.ui.fragment.profile + +import android.content.Context +import android.util.AttributeSet +import android.view.LayoutInflater +import android.widget.FrameLayout +import android.widget.LinearLayout +import androidx.core.view.updateLayoutParams +import com.tari.android.wallet.R +import com.tari.android.wallet.databinding.ViewShareOptionBinding +import com.tari.android.wallet.ui.common.domain.PaletteManager +import com.tari.android.wallet.ui.extension.setOnThrottledClickListener + +class RoundButtonWithIconView @JvmOverloads constructor( + context: Context, + attrs: AttributeSet? = null, + defStyle: Int = 0, + defStyleRes: Int = 0 +) : FrameLayout(context, attrs, defStyle, defStyleRes) { + + val ui = ViewShareOptionBinding.inflate(LayoutInflater.from(context), this, true) + + init { + layoutParams = LinearLayout.LayoutParams(0, LinearLayout.LayoutParams.WRAP_CONTENT).apply { + weight = 1f + } + } + + fun setArgs(text: String, icon: Int, action: () -> Unit, size: Size = Size.Medium, isSelected: Boolean = false) { + this.setOnThrottledClickListener { action() } + ui.icon.setImageResource(icon) + val padding = context.resources.getDimensionPixelSize(size.padding) + ui.optionBackground.setPadding(padding, padding, padding, padding) + ui.optionBackground.updateLayoutParams { + val backSize = context.resources.getDimensionPixelSize(size.value) + height = backSize + width = backSize + } + ui.text.text = text + val paletteManager = PaletteManager() + val textColor = if (isSelected) paletteManager.getTextHeading(context) else paletteManager.getTextBody(context) + ui.text.setTextColor(textColor) + val backgroundColor = if (isSelected) paletteManager.getPurpleBrand(context) else paletteManager.getBackgroundPrimary(context) + ui.optionBackground.updateBack(backColor = backgroundColor) + val iconColor = if (isSelected) paletteManager.getBackgroundPrimary(context) else paletteManager.getTextHeading(context) + ui.icon.setColorFilter(iconColor) + } + + enum class Size(val value: Int, val padding: Int) { + Big(R.dimen.contact_book_share_button_size_big, R.dimen.contact_book_share_button_padding_big), + Medium(R.dimen.contact_book_share_button_size_medium, R.dimen.contact_book_share_button_padding_medium) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/tari/android/wallet/ui/fragment/profile/WalletInfoFragment.kt b/app/src/main/java/com/tari/android/wallet/ui/fragment/profile/WalletInfoFragment.kt index ffdc97ebd..f0fcf651b 100644 --- a/app/src/main/java/com/tari/android/wallet/ui/fragment/profile/WalletInfoFragment.kt +++ b/app/src/main/java/com/tari/android/wallet/ui/fragment/profile/WalletInfoFragment.kt @@ -56,6 +56,7 @@ import com.tari.android.wallet.ui.extension.temporarilyDisableClick import com.tari.android.wallet.ui.fragment.contact_book.root.share.ShareOptionArgs import com.tari.android.wallet.ui.fragment.contact_book.root.share.ShareOptionView import com.tari.android.wallet.ui.fragment.contact_book.root.share.ShareType +import com.tari.android.wallet.ui.fragment.home.navigation.Navigation class WalletInfoFragment : CommonFragment() { @@ -110,19 +111,24 @@ class WalletInfoFragment : CommonFragment = MutableLiveData() val emojiId: LiveData = _emojiId @@ -117,24 +113,21 @@ class WalletInfoViewModel : CommonViewModel() { } fun showEditAliasDialog() { - val name = sharedPrefsWrapper.name.orEmpty() - val surname = sharedPrefsWrapper.surname.orEmpty() + val name = (sharedPrefsWrapper.name.orEmpty() + " " + sharedPrefsWrapper.surname.orEmpty()).trim() var saveAction: () -> Boolean = { false } val nameModule = InputModule(name, resourceManager.getString(R.string.contact_book_add_contact_first_name_hint), true, false) { saveAction.invoke() } - val surnameModule = - InputModule(surname, resourceManager.getString(R.string.contact_book_add_contact_surname_hint), false, true) { saveAction.invoke() } val headModule = HeadModule( resourceManager.getString(R.string.wallet_info_alias_edit_title), rightButtonTitle = resourceManager.getString(R.string.contact_book_add_contact_done_button) ) { saveAction.invoke() } - val moduleList = mutableListOf(headModule, nameModule, surnameModule) + val moduleList = mutableListOf(headModule, nameModule) saveAction = { - saveDetails(nameModule.value, surnameModule.value) + saveDetails(nameModule.value) true } @@ -146,10 +139,11 @@ class WalletInfoViewModel : CommonViewModel() { navigation.postValue(Navigation.AllSettingsNavigation.ToRequestTari) } - private fun saveDetails(name: String, surname: String) { - sharedPrefsWrapper.name = name - sharedPrefsWrapper.surname = surname - alias.postValue("$name $surname") + private fun saveDetails(name: String) { + val split = name.split(" ") + sharedPrefsWrapper.name = split.getOrNull(0).orEmpty().trim() + sharedPrefsWrapper.surname = split.getOrNull(1).orEmpty().trim() + alias.postValue(name) dismissDialog.postValue(Unit) } } \ No newline at end of file diff --git a/app/src/main/java/com/tari/android/wallet/ui/fragment/qr/QRScannerActivity.kt b/app/src/main/java/com/tari/android/wallet/ui/fragment/qr/QRScannerActivity.kt index 3ba726c62..84f79fac0 100644 --- a/app/src/main/java/com/tari/android/wallet/ui/fragment/qr/QRScannerActivity.kt +++ b/app/src/main/java/com/tari/android/wallet/ui/fragment/qr/QRScannerActivity.kt @@ -99,7 +99,7 @@ class QRScannerActivity : CommonActivity, grantResults: IntArray) { diff --git a/app/src/main/java/com/tari/android/wallet/ui/fragment/qr/QRScannerViewModel.kt b/app/src/main/java/com/tari/android/wallet/ui/fragment/qr/QRScannerViewModel.kt index 53778a898..14f233423 100644 --- a/app/src/main/java/com/tari/android/wallet/ui/fragment/qr/QRScannerViewModel.kt +++ b/app/src/main/java/com/tari/android/wallet/ui/fragment/qr/QRScannerViewModel.kt @@ -44,7 +44,7 @@ class QRScannerViewModel : CommonViewModel() { } fun onAlternativeApply() { - _backPressed.postValue(Unit) + backPressed.postValue(Unit) executeWithDelay(deeplinkViewModel) { deeplinkViewModel.executeRawDeeplink(scannedDeeplink.value!!) } @@ -86,6 +86,7 @@ class QRScannerViewModel : CommonViewModel() { is DeepLink.UserProfile -> navigateBack(deepLink) is DeepLink.Contacts, + is DeepLink.TorBridges, is DeepLink.AddBaseNode -> setAlternativeText(deepLink) } } @@ -96,6 +97,7 @@ class QRScannerViewModel : CommonViewModel() { is DeepLink.Send, is DeepLink.Contacts, + is DeepLink.TorBridges, is DeepLink.AddBaseNode -> setAlternativeText(deepLink) } } @@ -106,6 +108,7 @@ class QRScannerViewModel : CommonViewModel() { is DeepLink.UserProfile, is DeepLink.Contacts -> navigateBack(deepLink) + is DeepLink.TorBridges, is DeepLink.AddBaseNode -> setAlternativeText(deepLink) } } @@ -114,9 +117,10 @@ class QRScannerViewModel : CommonViewModel() { when (deepLink) { is DeepLink.Send, is DeepLink.UserProfile, + is DeepLink.AddBaseNode, is DeepLink.Contacts -> setAlternativeText(deepLink) - is DeepLink.AddBaseNode -> navigateBack(deepLink) + is DeepLink.TorBridges -> navigateBack(deepLink) } } @@ -134,7 +138,7 @@ class QRScannerViewModel : CommonViewModel() { is DeepLink.UserProfile -> resourceManager.getString(R.string.qr_code_scanner_labels_actions_profile) is DeepLink.Contacts -> resourceManager.getString(R.string.qr_code_scanner_labels_actions_contacts) is DeepLink.AddBaseNode -> resourceManager.getString(R.string.qr_code_scanner_labels_actions_base_node_add) -// is DeepLink.TorBridge -> resourceManager.getString(R.string.qr_code_scanner_labels_actions_transaction_send) + is DeepLink.TorBridges -> resourceManager.getString(R.string.qr_code_scanner_labels_actions_tor_bridges) } alternativeText.postValue(text) } @@ -145,5 +149,6 @@ class QRScannerViewModel : CommonViewModel() { fun onRetry() { proceedScan.postValue(Unit) + scanError.postValue(false) } } \ No newline at end of file diff --git a/app/src/main/java/com/tari/android/wallet/ui/fragment/restore/activity/WalletRestoreActivity.kt b/app/src/main/java/com/tari/android/wallet/ui/fragment/restore/activity/WalletRestoreActivity.kt index 33b18ef4c..18584278a 100644 --- a/app/src/main/java/com/tari/android/wallet/ui/fragment/restore/activity/WalletRestoreActivity.kt +++ b/app/src/main/java/com/tari/android/wallet/ui/fragment/restore/activity/WalletRestoreActivity.kt @@ -70,11 +70,16 @@ class WalletRestoreActivity : CommonActivity = _state fun onBack() { - _backPressed.postValue(Unit) + backPressed.postValue(Unit) viewModelScope.launch(Dispatchers.IO) { backupManager.signOut() } @@ -105,7 +105,7 @@ class EnterRestorationPasswordViewModel : CommonViewModel() { description = message, cancelable = false, canceledOnTouchOutside = false, - onClose = { _backPressed.call() }) + onClose = { backPressed.call() }) modularDialog.postValue(args.getModular(resourceManager)) } } \ No newline at end of file diff --git a/app/src/main/java/com/tari/android/wallet/ui/fragment/restore/walletRestoringFromSeedWords/WalletRestoringFromSeedWordsViewModel.kt b/app/src/main/java/com/tari/android/wallet/ui/fragment/restore/walletRestoringFromSeedWords/WalletRestoringFromSeedWordsViewModel.kt index d1f60e6e4..d7be6b66b 100644 --- a/app/src/main/java/com/tari/android/wallet/ui/fragment/restore/walletRestoringFromSeedWords/WalletRestoringFromSeedWordsViewModel.kt +++ b/app/src/main/java/com/tari/android/wallet/ui/fragment/restore/walletRestoringFromSeedWords/WalletRestoringFromSeedWordsViewModel.kt @@ -5,7 +5,6 @@ import androidx.lifecycle.MutableLiveData import androidx.lifecycle.viewModelScope import com.tari.android.wallet.R import com.tari.android.wallet.application.baseNodes.BaseNodes -import com.tari.android.wallet.data.sharedPrefs.SharedPrefsRepository import com.tari.android.wallet.data.sharedPrefs.baseNode.BaseNodeDto import com.tari.android.wallet.event.EventBus import com.tari.android.wallet.extension.addTo @@ -31,9 +30,6 @@ class WalletRestoringFromSeedWordsViewModel : CommonViewModel() { @Inject lateinit var seedPhraseRepository: SeedPhraseRepository - @Inject - lateinit var sharedPrefsRepository: SharedPrefsRepository - @Inject lateinit var walletServiceLauncher: WalletServiceLauncher diff --git a/app/src/main/java/com/tari/android/wallet/ui/fragment/send/addAmount/AddAmountFragment.kt b/app/src/main/java/com/tari/android/wallet/ui/fragment/send/addAmount/AddAmountFragment.kt index 464c6c0e0..56c9dfd39 100644 --- a/app/src/main/java/com/tari/android/wallet/ui/fragment/send/addAmount/AddAmountFragment.kt +++ b/app/src/main/java/com/tari/android/wallet/ui/fragment/send/addAmount/AddAmountFragment.kt @@ -228,13 +228,13 @@ class AddAmountFragment : CommonFragment balanceInfo.availableBalance) { + if (amount > balanceInfo.availableBalance && !TariBuild.MOCKED) { lifecycleScope.launch(Dispatchers.Main) { actualBalanceExceeded() } } else { lifecycleScope.launch(Dispatchers.Main) { - if (fee > amount) { + if (fee > amount && !TariBuild.MOCKED) { val args = ErrorDialogArgs( string(error_fee_more_than_amount_title), string(error_fee_more_than_amount_description) @@ -292,7 +292,7 @@ class AddAmountFragment : CommonFragment availableBalance) { + if (!TariBuild.MOCKED && (keyboardController.currentAmount + viewModel.selectedFeeData?.calculatedFee!!) > availableBalance) { showErrorState() } else { showSuccessState() diff --git a/app/src/main/java/com/tari/android/wallet/ui/fragment/send/addAmount/keyboard/KeyboardController.kt b/app/src/main/java/com/tari/android/wallet/ui/fragment/send/addAmount/keyboard/KeyboardController.kt index a3c776cc3..7070d8e8f 100644 --- a/app/src/main/java/com/tari/android/wallet/ui/fragment/send/addAmount/keyboard/KeyboardController.kt +++ b/app/src/main/java/com/tari/android/wallet/ui/fragment/send/addAmount/keyboard/KeyboardController.kt @@ -14,7 +14,16 @@ import com.tari.android.wallet.R import com.tari.android.wallet.databinding.ViewInputAmountBinding import com.tari.android.wallet.databinding.ViewNumpadBinding import com.tari.android.wallet.model.MicroTari -import com.tari.android.wallet.ui.extension.* +import com.tari.android.wallet.ui.extension.dimen +import com.tari.android.wallet.ui.extension.dimenPx +import com.tari.android.wallet.ui.extension.getFirstChild +import com.tari.android.wallet.ui.extension.getLastChild +import com.tari.android.wallet.ui.extension.setLayoutSize +import com.tari.android.wallet.ui.extension.setLayoutWidth +import com.tari.android.wallet.ui.extension.setStartMargin +import com.tari.android.wallet.ui.extension.setTextSizePx +import com.tari.android.wallet.ui.extension.setTopMargin +import com.tari.android.wallet.ui.extension.setWidthAndHeightToMeasured import com.tari.android.wallet.util.Constants import com.tari.android.wallet.util.WalletUtil import java.math.BigInteger @@ -95,14 +104,14 @@ class KeyboardController { if (isFirstLaunch && startAmount != Double.MIN_VALUE) { val handler = Handler(Looper.getMainLooper()) isFirstLaunch = false - startAmount.toString().withIndex().forEach { (index, char) -> - handler.postDelayed({ + handler.post { + startAmount.toString().withIndex().forEach { (index, char) -> if (Character.isDigit(char)) { onDigitOrSeparatorClicked(char.toString()) } else { onDigitOrSeparatorClicked(decimalSeparator) } - }, (index + 1) * Constants.UI.AddAmount.numPadDigitEnterAnimDurationMs * 2) + } } handler.postDelayed( this::setActionBindings, @@ -200,7 +209,6 @@ class KeyboardController { * Digit or separator clicked. */ private fun onDigitOrSeparatorClicked(digit: String) { - if (digitAnimIsRunning) return // check if entering first digit var enteringFirstDigit = false if (elements.size == 1 && elements[0].first == "0") { diff --git a/app/src/main/java/com/tari/android/wallet/ui/fragment/send/addNote/AddNoteFragment.kt b/app/src/main/java/com/tari/android/wallet/ui/fragment/send/addNote/AddNoteFragment.kt index 6a9921f86..29b4acb68 100644 --- a/app/src/main/java/com/tari/android/wallet/ui/fragment/send/addNote/AddNoteFragment.kt +++ b/app/src/main/java/com/tari/android/wallet/ui/fragment/send/addNote/AddNoteFragment.kt @@ -59,8 +59,9 @@ import com.daasuu.ei.Ease import com.daasuu.ei.EasingInterpolator import com.giphy.sdk.core.models.Media import com.tari.android.wallet.R -import com.tari.android.wallet.R.color.* -import com.tari.android.wallet.R.dimen.* +import com.tari.android.wallet.R.dimen.add_note_gif_inner_margin +import com.tari.android.wallet.R.dimen.add_note_slide_button_left_margin +import com.tari.android.wallet.R.dimen.add_note_slide_button_width import com.tari.android.wallet.databinding.FragmentAddNoteBinding import com.tari.android.wallet.event.EventBus import com.tari.android.wallet.extension.observe @@ -71,11 +72,26 @@ import com.tari.android.wallet.ui.common.CommonFragment import com.tari.android.wallet.ui.common.gyphy.repository.GIFItem import com.tari.android.wallet.ui.component.fullEmojiId.EmojiIdSummaryViewController import com.tari.android.wallet.ui.component.fullEmojiId.FullEmojiIdViewController -import com.tari.android.wallet.ui.extension.* +import com.tari.android.wallet.ui.extension.dimen +import com.tari.android.wallet.ui.extension.dimenPx +import com.tari.android.wallet.ui.extension.getStartMargin +import com.tari.android.wallet.ui.extension.gone +import com.tari.android.wallet.ui.extension.hideKeyboard +import com.tari.android.wallet.ui.extension.invisible +import com.tari.android.wallet.ui.extension.parcelable +import com.tari.android.wallet.ui.extension.postDelayed +import com.tari.android.wallet.ui.extension.setStartMargin +import com.tari.android.wallet.ui.extension.showInternetConnectionErrorDialog +import com.tari.android.wallet.ui.extension.temporarilyDisableClick +import com.tari.android.wallet.ui.extension.visible import com.tari.android.wallet.ui.fragment.contact_book.data.contacts.ContactDto import com.tari.android.wallet.ui.fragment.home.navigation.TariNavigator.Companion.PARAMETER_NOTE import com.tari.android.wallet.ui.fragment.home.navigation.TariNavigator.Companion.PARAMETER_TRANSACTION -import com.tari.android.wallet.ui.fragment.send.addNote.gif.* +import com.tari.android.wallet.ui.fragment.send.addNote.gif.ChooseGIFDialogFragment +import com.tari.android.wallet.ui.fragment.send.addNote.gif.GIFContainer +import com.tari.android.wallet.ui.fragment.send.addNote.gif.GIFThumbnailAdapter +import com.tari.android.wallet.ui.fragment.send.addNote.gif.HorizontalInnerMarginDecoration +import com.tari.android.wallet.ui.fragment.send.addNote.gif.ThumbnailGIFsViewModel import com.tari.android.wallet.ui.fragment.send.addNote.gif.ThumbnailGIFsViewModel.Companion.REQUEST_CODE_GIF import com.tari.android.wallet.ui.fragment.send.common.TransactionData import com.tari.android.wallet.util.Constants @@ -128,6 +144,7 @@ class AddNoteFragment : CommonFragment } } + @Deprecated("Deprecated in Java") override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { super.onActivityResult(requestCode, resultCode, data) if (requestCode == REQUEST_CODE_GIF) { diff --git a/app/src/main/java/com/tari/android/wallet/ui/fragment/settings/allSettings/AllSettingsViewModel.kt b/app/src/main/java/com/tari/android/wallet/ui/fragment/settings/allSettings/AllSettingsViewModel.kt index 6573b17e1..f0ea57b2d 100644 --- a/app/src/main/java/com/tari/android/wallet/ui/fragment/settings/allSettings/AllSettingsViewModel.kt +++ b/app/src/main/java/com/tari/android/wallet/ui/fragment/settings/allSettings/AllSettingsViewModel.kt @@ -8,9 +8,12 @@ import com.tari.android.wallet.R.drawable.vector_all_settings_background_service import com.tari.android.wallet.R.drawable.vector_all_settings_backup_options_icon import com.tari.android.wallet.R.drawable.vector_all_settings_bluetooth import com.tari.android.wallet.R.drawable.vector_all_settings_bridge_configuration_icon +import com.tari.android.wallet.R.drawable.vector_all_settings_cart import com.tari.android.wallet.R.drawable.vector_all_settings_contribute_to_tari_icon +import com.tari.android.wallet.R.drawable.vector_all_settings_data_collection import com.tari.android.wallet.R.drawable.vector_all_settings_delete_button_icon import com.tari.android.wallet.R.drawable.vector_all_settings_disclaimer_icon +import com.tari.android.wallet.R.drawable.vector_all_settings_passcode import com.tari.android.wallet.R.drawable.vector_all_settings_privacy_policy_icon import com.tari.android.wallet.R.drawable.vector_all_settings_report_bug_icon import com.tari.android.wallet.R.drawable.vector_all_settings_select_base_node_icon @@ -19,14 +22,19 @@ import com.tari.android.wallet.R.drawable.vector_all_settings_select_theme_icon import com.tari.android.wallet.R.drawable.vector_all_settings_user_agreement_icon import com.tari.android.wallet.R.drawable.vector_all_settings_visit_tari_icon import com.tari.android.wallet.R.drawable.vector_all_settings_yat_icon +import com.tari.android.wallet.R.drawable.vector_fingerprint import com.tari.android.wallet.R.string.all_settings_advanced_settings_label import com.tari.android.wallet.R.string.all_settings_background_service +import com.tari.android.wallet.R.string.all_settings_biometrics import com.tari.android.wallet.R.string.all_settings_bluetooth_settings import com.tari.android.wallet.R.string.all_settings_bridge_configuration import com.tari.android.wallet.R.string.all_settings_connect_yats import com.tari.android.wallet.R.string.all_settings_contribute +import com.tari.android.wallet.R.string.all_settings_create_pin_code +import com.tari.android.wallet.R.string.all_settings_data_collection import com.tari.android.wallet.R.string.all_settings_delete_wallet import com.tari.android.wallet.R.string.all_settings_disclaimer +import com.tari.android.wallet.R.string.all_settings_pin_code import com.tari.android.wallet.R.string.all_settings_privacy_policy import com.tari.android.wallet.R.string.all_settings_report_a_bug import com.tari.android.wallet.R.string.all_settings_secondary_settings_label @@ -34,6 +42,7 @@ import com.tari.android.wallet.R.string.all_settings_security_label import com.tari.android.wallet.R.string.all_settings_select_base_node import com.tari.android.wallet.R.string.all_settings_select_network import com.tari.android.wallet.R.string.all_settings_select_theme +import com.tari.android.wallet.R.string.all_settings_store import com.tari.android.wallet.R.string.all_settings_user_agreement import com.tari.android.wallet.R.string.all_settings_version_text_copy_title import com.tari.android.wallet.R.string.all_settings_version_text_copy_toast_message @@ -47,9 +56,11 @@ import com.tari.android.wallet.R.string.github_repo_url import com.tari.android.wallet.R.string.privacy_policy_url import com.tari.android.wallet.R.string.tari_about_title import com.tari.android.wallet.R.string.tari_url +import com.tari.android.wallet.R.string.ttl_store_url import com.tari.android.wallet.R.string.user_agreement_url import com.tari.android.wallet.data.sharedPrefs.SharedPrefsRepository import com.tari.android.wallet.event.EventBus +import com.tari.android.wallet.extension.addTo import com.tari.android.wallet.infrastructure.backup.BackupManager import com.tari.android.wallet.infrastructure.backup.BackupState import com.tari.android.wallet.infrastructure.backup.BackupsState @@ -59,7 +70,9 @@ import com.tari.android.wallet.ui.common.SingleLiveEvent import com.tari.android.wallet.ui.common.recyclerView.CommonViewHolderItem import com.tari.android.wallet.ui.common.recyclerView.items.DividerViewHolderItem import com.tari.android.wallet.ui.dialog.error.ErrorDialogArgs +import com.tari.android.wallet.ui.fragment.home.navigation.Navigation import com.tari.android.wallet.ui.fragment.home.navigation.Navigation.AllSettingsNavigation +import com.tari.android.wallet.ui.fragment.pinCode.PinCodeScreenBehavior import com.tari.android.wallet.ui.fragment.settings.allSettings.PresentationBackupState.BackupStateStatus.InProgress import com.tari.android.wallet.ui.fragment.settings.allSettings.PresentationBackupState.BackupStateStatus.Success import com.tari.android.wallet.ui.fragment.settings.allSettings.PresentationBackupState.BackupStateStatus.Warning @@ -72,6 +85,7 @@ import com.tari.android.wallet.ui.fragment.settings.allSettings.version.Settings import com.tari.android.wallet.ui.fragment.settings.backup.data.BackupSettingsRepository import com.tari.android.wallet.ui.fragment.settings.userAutorization.BiometricAuthenticationViewModel import com.tari.android.wallet.yat.YatAdapter +import com.tari.android.wallet.yat.YatSharedRepository import javax.inject.Inject class AllSettingsViewModel : CommonViewModel() { @@ -79,7 +93,7 @@ class AllSettingsViewModel : CommonViewModel() { lateinit var authenticationViewModel: BiometricAuthenticationViewModel private val backupOption = SettingsBackupOptionViewHolderItem(leftIconId = vector_all_settings_backup_options_icon) { - authenticationViewModel.requireAuthorization { navigation.postValue(AllSettingsNavigation.ToBackupSettings) } + runWithAuthorization { navigation.postValue(AllSettingsNavigation.ToBackupSettings) } } @Inject @@ -94,6 +108,9 @@ class AllSettingsViewModel : CommonViewModel() { @Inject lateinit var settingsRepository: SharedPrefsRepository + @Inject + lateinit var yatSharedPrefsRepository: YatSharedRepository + private val _openYatOnboarding = SingleLiveEvent() val openYatOnboarding: LiveData = _openYatOnboarding @@ -104,6 +121,8 @@ class AllSettingsViewModel : CommonViewModel() { component.inject(this) initOptions() EventBus.backupState.subscribe(this) { backupState -> onBackupStateChanged(backupState) } + + settingsRepository.updateNotifier.subscribe { initOptions() }.addTo(compositeDisposable) } private fun initOptions() { @@ -114,9 +133,10 @@ class AllSettingsViewModel : CommonViewModel() { ) val alias = settingsRepository.name.orEmpty() + " " + settingsRepository.surname.orEmpty() + val pinCode = securityPrefRepository.pinCode val allOptions = mutableListOf( - MyProfileViewHolderItem(settingsRepository.emojiId.orEmpty(), alias) { + MyProfileViewHolderItem(settingsRepository.emojiId.orEmpty(), yatSharedPrefsRepository.connectedYat.orEmpty(), alias) { navigation.postValue(AllSettingsNavigation.ToMyProfile) }, DividerViewHolderItem(), @@ -126,7 +146,34 @@ class AllSettingsViewModel : CommonViewModel() { SettingsTitleViewHolderItem(resourceManager.getString(all_settings_security_label)), backupOption, DividerViewHolderItem(), + ButtonViewDto(resourceManager.getString(all_settings_data_collection), vector_all_settings_data_collection) { + navigation.postValue(AllSettingsNavigation.ToDataCollection) + }, + DividerViewHolderItem(), + if (pinCode != null) { + ButtonViewDto(resourceManager.getString(all_settings_pin_code), vector_all_settings_passcode) { + runWithAuthorization { + navigation.postValue(Navigation.EnterPinCodeNavigation(PinCodeScreenBehavior.ChangeNew)) + } + } + } else { + ButtonViewDto(resourceManager.getString(all_settings_create_pin_code), vector_all_settings_passcode) { + runWithAuthorization { + navigation.postValue(Navigation.EnterPinCodeNavigation(PinCodeScreenBehavior.Create)) + } + } + }, + DividerViewHolderItem(), + ButtonViewDto(resourceManager.getString(all_settings_biometrics), vector_fingerprint) { + runWithAuthorization { + navigation.postValue(Navigation.ChangeBiometrics()) + } + }, SettingsTitleViewHolderItem(resourceManager.getString(all_settings_secondary_settings_label)), + ButtonViewDto(resourceManager.getString(all_settings_store), vector_all_settings_cart) { + _openLink.postValue(resourceManager.getString(ttl_store_url)) + }, + DividerViewHolderItem(), ButtonViewDto(resourceManager.getString(tari_about_title), vector_all_settings_about_icon) { navigation.postValue(AllSettingsNavigation.ToAbout) }, @@ -189,7 +236,7 @@ class AllSettingsViewModel : CommonViewModel() { DividerViewHolderItem(), SettingsVersionViewHolderItem(versionText) { _copyToClipboard.postValue(versionArgs) } ) - _allSettingsOptions.value = allOptions + _allSettingsOptions.postValue(allOptions) } private fun onBackupStateChanged(backupState: BackupsState?) { diff --git a/app/src/main/java/com/tari/android/wallet/ui/fragment/settings/allSettings/myProfile/MyProfileViewHolder.kt b/app/src/main/java/com/tari/android/wallet/ui/fragment/settings/allSettings/myProfile/MyProfileViewHolder.kt index 56e537a13..0f3e626d7 100644 --- a/app/src/main/java/com/tari/android/wallet/ui/fragment/settings/allSettings/myProfile/MyProfileViewHolder.kt +++ b/app/src/main/java/com/tari/android/wallet/ui/fragment/settings/allSettings/myProfile/MyProfileViewHolder.kt @@ -11,12 +11,15 @@ class MyProfileViewHolder(view: ItemMyProfileBinding) : CommonViewHolder(view) { private val emojiIdSummaryController = EmojiIdSummaryViewController(ui.participantEmojiIdView) + private val yatController = EmojiIdSummaryViewController(ui.participantYatIdView) override fun bind(item: MyProfileViewHolderItem) { super.bind(item) ui.firstEmojiTextView.text = item.emojiId.extractEmojis()[0] ui.alias.text = item.alias ui.alias.setVisible(item.alias.isNotBlank()) + ui.participantYatIdView.root.setVisible(item.yat.isNotBlank()) + yatController.display(item.yat) emojiIdSummaryController.display(item.emojiId) ui.root.setOnClickListener { item.action.invoke() } } diff --git a/app/src/main/java/com/tari/android/wallet/ui/fragment/settings/allSettings/myProfile/MyProfileViewHolderItem.kt b/app/src/main/java/com/tari/android/wallet/ui/fragment/settings/allSettings/myProfile/MyProfileViewHolderItem.kt index 8849ce1f9..8711ef406 100644 --- a/app/src/main/java/com/tari/android/wallet/ui/fragment/settings/allSettings/myProfile/MyProfileViewHolderItem.kt +++ b/app/src/main/java/com/tari/android/wallet/ui/fragment/settings/allSettings/myProfile/MyProfileViewHolderItem.kt @@ -2,6 +2,6 @@ package com.tari.android.wallet.ui.fragment.settings.allSettings.myProfile import com.tari.android.wallet.ui.common.recyclerView.CommonViewHolderItem -class MyProfileViewHolderItem(val emojiId: String, val alias: String, val action: () -> Unit) : CommonViewHolderItem() { +class MyProfileViewHolderItem(val emojiId: String, val yat: String, val alias: String, val action: () -> Unit) : CommonViewHolderItem() { override val viewHolderUUID: String = "MyProfileViewHolderItem$emojiId" } \ No newline at end of file diff --git a/app/src/main/java/com/tari/android/wallet/ui/fragment/settings/backup/backupSettings/BackupSettingsFragment.kt b/app/src/main/java/com/tari/android/wallet/ui/fragment/settings/backup/backupSettings/BackupSettingsFragment.kt index 38c2a3b56..570affaf1 100644 --- a/app/src/main/java/com/tari/android/wallet/ui/fragment/settings/backup/backupSettings/BackupSettingsFragment.kt +++ b/app/src/main/java/com/tari/android/wallet/ui/fragment/settings/backup/backupSettings/BackupSettingsFragment.kt @@ -73,6 +73,7 @@ class BackupSettingsFragment : CommonFragment) : Iterable { val length get() = seedWords.size - private fun sorted(): SeedPhrase = SeedPhrase(if (Build.MOCKED) seedWords else seedWords.sorted()) + private fun sorted(): SeedPhrase = SeedPhrase(if (TariBuild.MOCKED) seedWords else seedWords.sorted()) fun consistsOf(result: List): Boolean = seedWords == result diff --git a/app/src/main/java/com/tari/android/wallet/ui/fragment/settings/baseNodeConfig/addBaseNode/AddCustomBaseNodeViewModel.kt b/app/src/main/java/com/tari/android/wallet/ui/fragment/settings/baseNodeConfig/addBaseNode/AddCustomBaseNodeViewModel.kt index dff9ab090..96f2f4cbf 100644 --- a/app/src/main/java/com/tari/android/wallet/ui/fragment/settings/baseNodeConfig/addBaseNode/AddCustomBaseNodeViewModel.kt +++ b/app/src/main/java/com/tari/android/wallet/ui/fragment/settings/baseNodeConfig/addBaseNode/AddCustomBaseNodeViewModel.kt @@ -58,7 +58,7 @@ class AddCustomBaseNodeViewModel : CommonViewModel() { val baseNodeDto = BaseNodeDto(name, publicKeyHex, address, true) baseNodeSharedRepository.addUserBaseNode(baseNodeDto) baseNodes.setBaseNode(baseNodeDto) - _backPressed.postValue(Unit) + backPressed.postValue(Unit) } catch (e: Throwable) { baseNodes.setNextBaseNode() addBaseNodePeerFailed() diff --git a/app/src/main/java/com/tari/android/wallet/ui/fragment/settings/baseNodeConfig/changeBaseNode/ChangeBaseNodeViewModel.kt b/app/src/main/java/com/tari/android/wallet/ui/fragment/settings/baseNodeConfig/changeBaseNode/ChangeBaseNodeViewModel.kt index 4ce5f86ef..ca8180698 100644 --- a/app/src/main/java/com/tari/android/wallet/ui/fragment/settings/baseNodeConfig/changeBaseNode/ChangeBaseNodeViewModel.kt +++ b/app/src/main/java/com/tari/android/wallet/ui/fragment/settings/baseNodeConfig/changeBaseNode/ChangeBaseNodeViewModel.kt @@ -43,7 +43,7 @@ class ChangeBaseNodeViewModel : CommonViewModel() { fun selectBaseNode(baseNodeDto: BaseNodeDto) { baseNodes.setBaseNode(baseNodeDto) - _backPressed.postValue(Unit) + backPressed.postValue(Unit) } fun showQrCode(baseNodeDto: BaseNodeDto) { diff --git a/app/src/main/java/com/tari/android/wallet/ui/fragment/settings/bugReporting/BugsReportingViewModel.kt b/app/src/main/java/com/tari/android/wallet/ui/fragment/settings/bugReporting/BugsReportingViewModel.kt index 0a061cdc7..8162b0bcb 100644 --- a/app/src/main/java/com/tari/android/wallet/ui/fragment/settings/bugReporting/BugsReportingViewModel.kt +++ b/app/src/main/java/com/tari/android/wallet/ui/fragment/settings/bugReporting/BugsReportingViewModel.kt @@ -17,7 +17,7 @@ class BugsReportingViewModel : CommonViewModel() { fun send(name: String, email: String, bugDescription: String) = viewModelScope.launch { bugReportingService.share(name, email, bugDescription) - _backPressed.postValue(Unit) + backPressed.postValue(Unit) } } diff --git a/app/src/main/java/com/tari/android/wallet/ui/fragment/settings/dataCollection/DataCollectionFragment.kt b/app/src/main/java/com/tari/android/wallet/ui/fragment/settings/dataCollection/DataCollectionFragment.kt new file mode 100644 index 000000000..f6498dd54 --- /dev/null +++ b/app/src/main/java/com/tari/android/wallet/ui/fragment/settings/dataCollection/DataCollectionFragment.kt @@ -0,0 +1,33 @@ +package com.tari.android.wallet.ui.fragment.settings.dataCollection + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.fragment.app.viewModels +import com.tari.android.wallet.databinding.FragmentDataCollectionBinding +import com.tari.android.wallet.extension.observe +import com.tari.android.wallet.ui.common.CommonFragment +import com.tari.android.wallet.ui.component.loadingSwitch.TariLoadingSwitchState + +class DataCollectionFragment : CommonFragment() { + + override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View = + FragmentDataCollectionBinding.inflate(inflater, container, false).also { ui = it }.root + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + + val viewModel: DataCollectionViewModel by viewModels() + bindViewModel(viewModel) + + ui.loadingSwitchView.setOnCheckedChangeListener { + viewModel.updateState(it) + } + + observe(viewModel.state) { + ui.loadingSwitchView.setState(TariLoadingSwitchState(it, false)) + } + } +} + diff --git a/app/src/main/java/com/tari/android/wallet/ui/fragment/settings/dataCollection/DataCollectionViewModel.kt b/app/src/main/java/com/tari/android/wallet/ui/fragment/settings/dataCollection/DataCollectionViewModel.kt new file mode 100644 index 000000000..e8fa8ee4a --- /dev/null +++ b/app/src/main/java/com/tari/android/wallet/ui/fragment/settings/dataCollection/DataCollectionViewModel.kt @@ -0,0 +1,24 @@ +package com.tari.android.wallet.ui.fragment.settings.dataCollection + +import androidx.lifecycle.MutableLiveData +import com.tari.android.wallet.data.sharedPrefs.sentry.SentryPrefRepository +import com.tari.android.wallet.ui.common.CommonViewModel +import javax.inject.Inject + +class DataCollectionViewModel : CommonViewModel() { + + @Inject + lateinit var sentryPrefRepository: SentryPrefRepository + + val state: MutableLiveData = MutableLiveData() + + init { + component.inject(this) + state.value = sentryPrefRepository.isEnabled == true + } + + fun updateState(state: Boolean) { + sentryPrefRepository.isEnabled = state + this.state.value = state + } +} \ No newline at end of file diff --git a/app/src/main/java/com/tari/android/wallet/ui/fragment/settings/logs/logs/LogsViewModel.kt b/app/src/main/java/com/tari/android/wallet/ui/fragment/settings/logs/logs/LogsViewModel.kt index 6f53ec365..3db4c9393 100644 --- a/app/src/main/java/com/tari/android/wallet/ui/fragment/settings/logs/logs/LogsViewModel.kt +++ b/app/src/main/java/com/tari/android/wallet/ui/fragment/settings/logs/logs/LogsViewModel.kt @@ -50,7 +50,7 @@ class LogsViewModel : CommonViewModel() { resourceManager.getString(R.string.common_error_title), resourceManager.getString(R.string.debug_logs_cant_open_file), ) { - _backPressed.postValue(Unit) + backPressed.postValue(Unit) } modularDialog.postValue(errorArgs.getModular(resourceManager)) logger.e(e, "Out of memory on reading big log file") diff --git a/app/src/main/java/com/tari/android/wallet/ui/fragment/settings/networkSelection/NetworkSelectionViewModel.kt b/app/src/main/java/com/tari/android/wallet/ui/fragment/settings/networkSelection/NetworkSelectionViewModel.kt index d4ae7a627..b14d7991e 100644 --- a/app/src/main/java/com/tari/android/wallet/ui/fragment/settings/networkSelection/NetworkSelectionViewModel.kt +++ b/app/src/main/java/com/tari/android/wallet/ui/fragment/settings/networkSelection/NetworkSelectionViewModel.kt @@ -46,7 +46,7 @@ class NetworkSelectionViewModel : CommonViewModel() { fun selectNetwork(networkViewHolderItem: NetworkViewHolderItem) { if (networkViewHolderItem.network.network == networkRepository.currentNetwork!!.network) { - _backPressed.postValue(Unit) + backPressed.postValue(Unit) return } diff --git a/app/src/main/java/com/tari/android/wallet/ui/fragment/settings/torBridges/TorBridgesSelectionFragment.kt b/app/src/main/java/com/tari/android/wallet/ui/fragment/settings/torBridges/TorBridgesSelectionFragment.kt index bcea99de7..92d73fa7a 100644 --- a/app/src/main/java/com/tari/android/wallet/ui/fragment/settings/torBridges/TorBridgesSelectionFragment.kt +++ b/app/src/main/java/com/tari/android/wallet/ui/fragment/settings/torBridges/TorBridgesSelectionFragment.kt @@ -44,6 +44,10 @@ class TorBridgesSelectionFragment : CommonFragment { torBridgeItem.isSelected = !torBridgeItem.isSelected } @@ -86,6 +93,19 @@ class TorBridgesSelectionViewModel : CommonViewModel() { _torBridges.postValue(_torBridges.value!!.map { it.deepCopy() as TorBridgeViewHolderItem }.toMutableList()) } + fun showBridgeQrCode(torBridgeItem: TorBridgeViewHolderItem) { + if (torBridgeItem !is TorBridgeViewHolderItem.Bridge) return + val data = deeplinkHandler.getDeeplink(DeepLink.TorBridges(listOf(torBridgeItem.bridgeConfiguration))) + val args = ModularDialogArgs( + DialogArgs(true, canceledOnTouchOutside = true), listOf( + HeadModule(resourceManager.getString(R.string.share_via_qr_code_title)), + ShareQrCodeModule(data), + ButtonModule(resourceManager.getString(R.string.common_close), ButtonStyle.Close) + ) + ) + modularDialog.postValue(args) + } + fun connect() { val selectedBridges = _torBridges.value.orEmpty().filter { it.isSelected } if (selectedBridges.any { it is TorBridgeViewHolderItem.Empty }) { @@ -124,6 +144,7 @@ class TorBridgesSelectionViewModel : CommonViewModel() { ) { stopConnecting() } modularDialog.postValue(errorArgs.getModular(resourceManager)) } + is TorProxyState.Running -> { if (it.bootstrapStatus.progress == 100) { EventBus.torProxyState.unsubscribe(this) @@ -142,8 +163,8 @@ class TorBridgesSelectionViewModel : CommonViewModel() { BodyModule(description), ButtonModule(resourceManager.getString(R.string.common_confirm), ButtonStyle.Normal) { dismissDialog.postValue(Unit) - _backPressed.postValue(Unit) - }, + backPressed.postValue(Unit) + }, ) ) modularDialog.postValue(args) @@ -162,6 +183,7 @@ class TorBridgesSelectionViewModel : CommonViewModel() { _loadingDialog.postValue(nextArgs) } } + else -> Unit } } diff --git a/app/src/main/java/com/tari/android/wallet/ui/fragment/settings/torBridges/customBridges/CustomTorBridgesFragment.kt b/app/src/main/java/com/tari/android/wallet/ui/fragment/settings/torBridges/customBridges/CustomTorBridgesFragment.kt index 7cb53a671..6fde6b0d7 100644 --- a/app/src/main/java/com/tari/android/wallet/ui/fragment/settings/torBridges/customBridges/CustomTorBridgesFragment.kt +++ b/app/src/main/java/com/tari/android/wallet/ui/fragment/settings/torBridges/customBridges/CustomTorBridgesFragment.kt @@ -7,13 +7,14 @@ import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import androidx.fragment.app.viewModels -import com.google.gson.Gson import com.tari.android.wallet.R import com.tari.android.wallet.databinding.FragmentCustomTorBridgesBinding +import com.tari.android.wallet.extension.observe import com.tari.android.wallet.ui.common.CommonFragment import com.tari.android.wallet.ui.component.tari.toolbar.TariToolbarActionArg import com.tari.android.wallet.ui.extension.setOnThrottledClickListener import com.tari.android.wallet.ui.fragment.qr.QRScannerActivity +import com.tari.android.wallet.ui.fragment.qr.QrScannerSource class CustomTorBridgesFragment : CommonFragment() { @@ -31,21 +32,23 @@ class CustomTorBridgesFragment : CommonFragment() } \ No newline at end of file diff --git a/app/src/main/java/com/tari/android/wallet/ui/fragment/settings/torBridges/customBridges/CustomTorBridgesViewModel.kt b/app/src/main/java/com/tari/android/wallet/ui/fragment/settings/torBridges/customBridges/CustomTorBridgesViewModel.kt index 4049c9e17..3ef3445bd 100644 --- a/app/src/main/java/com/tari/android/wallet/ui/fragment/settings/torBridges/customBridges/CustomTorBridgesViewModel.kt +++ b/app/src/main/java/com/tari/android/wallet/ui/fragment/settings/torBridges/customBridges/CustomTorBridgesViewModel.kt @@ -1,10 +1,12 @@ package com.tari.android.wallet.ui.fragment.settings.torBridges.customBridges import com.tari.android.wallet.R -import com.tari.android.wallet.application.WalletManager +import com.tari.android.wallet.application.deeplinks.DeepLink +import com.tari.android.wallet.application.deeplinks.DeeplinkHandler import com.tari.android.wallet.data.sharedPrefs.tor.TorBridgeConfiguration import com.tari.android.wallet.data.sharedPrefs.tor.TorSharedRepository import com.tari.android.wallet.ui.common.CommonViewModel +import com.tari.android.wallet.ui.common.SingleLiveEvent import com.tari.android.wallet.ui.dialog.error.ErrorDialogArgs import com.tari.android.wallet.ui.fragment.home.navigation.Navigation import javax.inject.Inject @@ -15,7 +17,9 @@ class CustomTorBridgesViewModel : CommonViewModel() { lateinit var torSharedRepository: TorSharedRepository @Inject - lateinit var walletManager: WalletManager + lateinit var deeplinkHandler: DeeplinkHandler + + var text = SingleLiveEvent() init { component.inject(this) @@ -23,8 +27,6 @@ class CustomTorBridgesViewModel : CommonViewModel() { fun openRequestPage() = _openLink.postValue(resourceManager.getString(R.string.tor_bridges_url)) - fun navigateToScanQr() = navigation.postValue(Navigation.CustomBridgeNavigation.ScanQrCode) - fun navigateToUploadQr() = navigation.postValue(Navigation.CustomBridgeNavigation.UploadQrCode) fun connect(inputStr: String) { @@ -61,7 +63,15 @@ class CustomTorBridgesViewModel : CommonViewModel() { } newBridges.forEach { torSharedRepository.addTorBridgeConfiguration(it) } - _backPressed.postValue(Unit) + backPressed.postValue(Unit) + } + + fun handleQrCode(input: String) { + val deeplink = deeplinkHandler.handle(input) + if (deeplink is DeepLink.TorBridges) { + val text = deeplinkHandler.getDeeplink(deeplink) + this.text.postValue(text) + } } private fun incorrectFormat() { diff --git a/app/src/main/java/com/tari/android/wallet/ui/fragment/splash/SplashActivity.kt b/app/src/main/java/com/tari/android/wallet/ui/fragment/splash/SplashActivity.kt index 102830973..c463b42b9 100644 --- a/app/src/main/java/com/tari/android/wallet/ui/fragment/splash/SplashActivity.kt +++ b/app/src/main/java/com/tari/android/wallet/ui/fragment/splash/SplashActivity.kt @@ -35,9 +35,11 @@ package com.tari.android.wallet.ui.fragment.splash import android.app.Activity import android.content.Intent import android.os.Bundle +import androidx.activity.addCallback import androidx.appcompat.app.AppCompatActivity import com.tari.android.wallet.data.WalletConfig import com.tari.android.wallet.data.sharedPrefs.SharedPrefsRepository +import com.tari.android.wallet.data.sharedPrefs.security.SecurityPrefRepository import com.tari.android.wallet.di.DiContainer.appComponent import com.tari.android.wallet.service.service.WalletServiceLauncher import com.tari.android.wallet.ui.fragment.auth.AuthActivity @@ -58,6 +60,9 @@ class SplashActivity : AppCompatActivity() { @Inject lateinit var sharedPrefsRepository: SharedPrefsRepository + @Inject + lateinit var securityPrefRepository: SecurityPrefRepository + @Inject lateinit var walletServiceLauncher: WalletServiceLauncher @@ -65,6 +70,8 @@ class SplashActivity : AppCompatActivity() { appComponent.inject(this) super.onCreate(savedInstanceState) + onBackPressedDispatcher.addCallback { } + if (sharedPrefsRepository.checkIfIsDataCleared()) { walletServiceLauncher.stopAndDelete() } @@ -75,6 +82,10 @@ class SplashActivity : AppCompatActivity() { WalletUtil.clearWalletFiles(walletConfig.getWalletFilesDirPath()) sharedPrefsRepository.clear() } + if (securityPrefRepository.pinCode == null) { + launch(OnboardingFlowActivity::class.java) + return + } launch(if (exists) AuthActivity::class.java else OnboardingFlowActivity::class.java) } @@ -85,6 +96,4 @@ class SplashActivity : AppCompatActivity() { startActivity(intent) finish() } - - override fun onBackPressed() = Unit } diff --git a/app/src/main/java/com/tari/android/wallet/ui/fragment/tx/HomeFragment.kt b/app/src/main/java/com/tari/android/wallet/ui/fragment/tx/HomeFragment.kt index 9907b1a00..b127f1a9a 100644 --- a/app/src/main/java/com/tari/android/wallet/ui/fragment/tx/HomeFragment.kt +++ b/app/src/main/java/com/tari/android/wallet/ui/fragment/tx/HomeFragment.kt @@ -32,13 +32,20 @@ */ package com.tari.android.wallet.ui.fragment.tx -import android.animation.* import android.annotation.SuppressLint -import android.os.* -import android.view.* +import android.app.Activity +import android.content.Intent +import android.os.Build +import android.os.Bundle +import android.os.Handler +import android.os.Looper +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup import androidx.fragment.app.viewModels import androidx.recyclerview.widget.LinearLayoutManager import com.tari.android.wallet.R +import com.tari.android.wallet.application.deeplinks.DeeplinkViewModel import com.tari.android.wallet.databinding.FragmentHomeBinding import com.tari.android.wallet.event.EventBus import com.tari.android.wallet.extension.observe @@ -49,7 +56,7 @@ import com.tari.android.wallet.ui.common.recyclerView.AdapterFactory import com.tari.android.wallet.ui.common.recyclerView.CommonAdapter import com.tari.android.wallet.ui.common.recyclerView.CommonViewHolderItem import com.tari.android.wallet.ui.component.networkStateIndicator.ConnectionIndicatorViewModel -import com.tari.android.wallet.ui.extension.* +import com.tari.android.wallet.ui.extension.setVisible import com.tari.android.wallet.ui.fragment.home.navigation.Navigation import com.tari.android.wallet.ui.fragment.qr.QRScannerActivity import com.tari.android.wallet.ui.fragment.qr.QrScannerSource @@ -70,6 +77,8 @@ class HomeFragment : CommonFragment( // This listener is used only to animate the visibility of the scroll depth gradient view. private lateinit var balanceViewController: BalanceViewController + private val deeplinkViewModel: DeeplinkViewModel by viewModels() + override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View = FragmentHomeBinding.inflate(inflater, container, false).also { ui = it }.root @@ -82,6 +91,7 @@ class HomeFragment : CommonFragment( viewModel.serviceConnection.reconnectToService() subscribeVM(viewModel.stagedWalletSecurityManager) + subscribeVM(deeplinkViewModel) checkPermission() setupUI() @@ -99,6 +109,7 @@ class HomeFragment : CommonFragment( observe(txList) { ui.transactionsRecyclerView.setVisible(it.isNotEmpty()) + ui.viewAllTxsButton.setVisible(it.isNotEmpty()) ui.emptyState.setVisible(it.isEmpty()) adapter.update(it) adapter.notifyDataSetChanged() @@ -166,6 +177,14 @@ class HomeFragment : CommonFragment( } } + @Deprecated("Deprecated in Java") + override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { + if (requestCode == QRScannerActivity.REQUEST_QR_SCANNER && resultCode == Activity.RESULT_OK && data != null) { + val qrData = data.getStringExtra(QRScannerActivity.EXTRA_QR_DATA) ?: return + deeplinkViewModel.tryToHandle(qrData) + } + } + private fun updateBalanceInfoUI(restart: Boolean) { val balanceInfo = viewModel.balanceInfo.value!! diff --git a/app/src/main/java/com/tari/android/wallet/ui/fragment/tx/HomeFragmentViewModel.kt b/app/src/main/java/com/tari/android/wallet/ui/fragment/tx/HomeFragmentViewModel.kt index 8a706e8d9..4e701df57 100644 --- a/app/src/main/java/com/tari/android/wallet/ui/fragment/tx/HomeFragmentViewModel.kt +++ b/app/src/main/java/com/tari/android/wallet/ui/fragment/tx/HomeFragmentViewModel.kt @@ -6,12 +6,14 @@ import androidx.lifecycle.MediatorLiveData import androidx.lifecycle.MutableLiveData import androidx.lifecycle.toLiveData import androidx.lifecycle.viewModelScope +import com.tari.android.wallet.R import com.tari.android.wallet.R.string.error_no_connection_description import com.tari.android.wallet.R.string.error_no_connection_title import com.tari.android.wallet.R.string.error_node_unreachable_description import com.tari.android.wallet.R.string.error_node_unreachable_title import com.tari.android.wallet.application.securityStage.StagedWalletSecurityManager import com.tari.android.wallet.data.sharedPrefs.SharedPrefsRepository +import com.tari.android.wallet.data.sharedPrefs.sentry.SentryPrefRepository import com.tari.android.wallet.event.Event import com.tari.android.wallet.event.EventBus import com.tari.android.wallet.extension.addTo @@ -22,6 +24,12 @@ import com.tari.android.wallet.ui.common.CommonViewModel import com.tari.android.wallet.ui.common.SingleLiveEvent import com.tari.android.wallet.ui.common.recyclerView.CommonViewHolderItem import com.tari.android.wallet.ui.dialog.error.ErrorDialogArgs +import com.tari.android.wallet.ui.dialog.modular.DialogArgs +import com.tari.android.wallet.ui.dialog.modular.ModularDialogArgs +import com.tari.android.wallet.ui.dialog.modular.modules.body.BodyModule +import com.tari.android.wallet.ui.dialog.modular.modules.button.ButtonModule +import com.tari.android.wallet.ui.dialog.modular.modules.button.ButtonStyle +import com.tari.android.wallet.ui.dialog.modular.modules.head.HeadModule import com.tari.android.wallet.ui.fragment.contact_book.data.ContactsRepository import com.tari.android.wallet.ui.fragment.home.navigation.Navigation import com.tari.android.wallet.ui.fragment.send.finalize.TxFailureReason @@ -51,6 +59,9 @@ class HomeFragmentViewModel : CommonViewModel() { @Inject lateinit var transactionRepository: TransactionRepository + @Inject + lateinit var sentryPrefRepository: SentryPrefRepository + val stagedWalletSecurityManager = StagedWalletSecurityManager() private val _balanceInfo = MutableLiveData() @@ -77,8 +88,11 @@ class HomeFragmentViewModel : CommonViewModel() { val emojies = sharedPrefsWrapper.emojiId.orEmpty().extractEmojis() emojiMedium.postValue(emojies.take(3).joinToString("")) emoji.postValue(emojies.take(1).joinToString("")) + + checkForDataConsent() } + private fun updateList() { val list = transactionRepository.list.value ?: return txList.postValue(list.filterIsInstance().sortedBy { it.tx.timestamp }.takeLast(amountOfTransactions).toMutableList()) @@ -90,6 +104,25 @@ class HomeFragmentViewModel : CommonViewModel() { } } + private fun checkForDataConsent() { + if (sentryPrefRepository.isEnabled == null) { + sentryPrefRepository.isEnabled = false + val args = ModularDialogArgs(DialogArgs(cancelable = false, canceledOnTouchOutside = false), listOf( + HeadModule(resourceManager.getString(R.string.data_collection_dialog_title)), + BodyModule(resourceManager.getString(R.string.data_collection_dialog_description)), + ButtonModule(resourceManager.getString(R.string.data_collection_dialog_positive), ButtonStyle.Normal) { + sentryPrefRepository.isEnabled = true + dismissDialog.postValue(Unit) + }, + ButtonModule(resourceManager.getString(R.string.data_collection_dialog_negative), ButtonStyle.Close) { + sentryPrefRepository.isEnabled = false + dismissDialog.postValue(Unit) + } + )) + modularDialog.postValue(args) + } + } + private fun onServiceConnected() { subscribeToEventBus() diff --git a/app/src/main/java/com/tari/android/wallet/ui/fragment/tx/TransactionRepository.kt b/app/src/main/java/com/tari/android/wallet/ui/fragment/tx/TransactionRepository.kt index 051bf56b6..b8f702d90 100644 --- a/app/src/main/java/com/tari/android/wallet/ui/fragment/tx/TransactionRepository.kt +++ b/app/src/main/java/com/tari/android/wallet/ui/fragment/tx/TransactionRepository.kt @@ -18,7 +18,6 @@ import com.tari.android.wallet.model.MicroTari import com.tari.android.wallet.model.PendingInboundTx import com.tari.android.wallet.model.PendingOutboundTx import com.tari.android.wallet.model.TariContact -import com.tari.android.wallet.model.TariWalletAddress import com.tari.android.wallet.model.Tx import com.tari.android.wallet.model.TxId import com.tari.android.wallet.model.TxStatus @@ -30,7 +29,8 @@ import com.tari.android.wallet.ui.common.recyclerView.CommonViewHolderItem import com.tari.android.wallet.ui.common.recyclerView.items.TitleViewHolderItem import com.tari.android.wallet.ui.fragment.contact_book.data.ContactsRepository import com.tari.android.wallet.ui.fragment.tx.adapter.TransactionItem -import com.tari.android.wallet.util.Build.MOCKED +import com.tari.android.wallet.util.TariBuild +import com.tari.android.wallet.util.TariBuild.MOCKED import io.reactivex.BackpressureStrategy import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch @@ -175,10 +175,8 @@ class TransactionRepository @Inject constructor() : CommonViewModel() { if (MOCKED) { - val emojiId = - "\uD83C\uDFB9\uD83C\uDFA4\uD83C\uDF20\uD83C\uDFAA\uD83D\uDC16\uD83C\uDF5A\uD83D\uDE08\uD83C\uDF73\uD83C\uDFED\uD83D\uDC2F\uD83D\uDC29\uD83D\uDC33\uD83D\uDC2D\uD83D\uDC35\uD83D\uDC11\uD83C\uDF4E\uD83D\uDE02\uD83C\uDFB3\uD83C\uDF34\uD83C\uDF6D\uD83D\uDC0D\uD83C\uDF1F\uD83D\uDCBC\uD83C\uDFB9\uD83D\uDC3A\uD83D\uDC79\uD83C\uDF77\uD83D\uDC3B\uD83D\uDEAB\uD83D\uDE92\uD83D\uDCB3\uD83C\uDFAE\uD83D\uDD2A" - val hex = "5A4A0A4F7427E33469858088838A721FE1560C316F09C55A8EA6388FFBF1C152DA" val title = TitleViewHolderItem("Mocked Transactions", true) + val messageGiphy = " https://giphy.com/embed/5885nYOgBHdCw" val item = TransactionItem( CompletedTx().apply { @@ -186,9 +184,10 @@ class TransactionRepository @Inject constructor() : CommonViewModel() { status = TxStatus.MINED_CONFIRMED amount = MicroTari(BigInteger.valueOf(100000)) fee = MicroTari(BigInteger.valueOf(1000)) + message = messageGiphy timestamp = BigInteger.valueOf(System.currentTimeMillis()) id = BigInteger.valueOf(1) - tariContact = TariContact(TariWalletAddress(hex, emojiId), "test1") + tariContact = TariContact(TariBuild.moched_zero_contact, "test1") }, contactsRepository.ffiBridge.getContactForTx(CompletedTx()), 0, @@ -203,8 +202,8 @@ class TransactionRepository @Inject constructor() : CommonViewModel() { fee = MicroTari(BigInteger.valueOf(1000)) timestamp = BigInteger.valueOf(System.currentTimeMillis()) id = BigInteger.valueOf(1) - message = "message" - tariContact = TariContact(TariWalletAddress(hex, emojiId), "test2") + message = messageGiphy + tariContact = TariContact(TariBuild.moched_zero_contact, "test2") } val item2 = TransactionItem( tx2, @@ -217,11 +216,12 @@ class TransactionRepository @Inject constructor() : CommonViewModel() { val tx3 = CompletedTx().apply { direction = Tx.Direction.INBOUND status = TxStatus.MINED_CONFIRMED + message = messageGiphy amount = MicroTari(BigInteger.valueOf(111000)) fee = MicroTari(BigInteger.valueOf(1000)) timestamp = BigInteger.valueOf(System.currentTimeMillis()) id = BigInteger.valueOf(1) - tariContact = TariContact(TariWalletAddress(hex, emojiId), "test3") + tariContact = TariContact(TariBuild.mocked_wallet_address, "test3") } val item3 = TransactionItem( diff --git a/app/src/main/java/com/tari/android/wallet/ui/fragment/tx/WaveView.kt b/app/src/main/java/com/tari/android/wallet/ui/fragment/tx/WaveView.kt index 662091abc..34199105f 100644 --- a/app/src/main/java/com/tari/android/wallet/ui/fragment/tx/WaveView.kt +++ b/app/src/main/java/com/tari/android/wallet/ui/fragment/tx/WaveView.kt @@ -56,11 +56,11 @@ class WaveView : View { waveLength = width * 1.5F } - override fun onDraw(canvas: Canvas?) { + override fun onDraw(canvas: Canvas) { super.onDraw(canvas) doWave() - canvas?.drawPath(path, paint) + canvas.drawPath(path, paint) } private fun doWave() { diff --git a/app/src/main/java/com/tari/android/wallet/ui/fragment/tx/adapter/TransactionItem.kt b/app/src/main/java/com/tari/android/wallet/ui/fragment/tx/adapter/TransactionItem.kt index c487a8c9c..71a92364a 100644 --- a/app/src/main/java/com/tari/android/wallet/ui/fragment/tx/adapter/TransactionItem.kt +++ b/app/src/main/java/com/tari/android/wallet/ui/fragment/tx/adapter/TransactionItem.kt @@ -16,7 +16,7 @@ data class TransactionItem( override val viewHolderUUID: String = "TransactionItem" + tx.id - override fun hashCode(): Int = HashcodeUtils.generate(tx.id, contact?.contact, position, requiredConfirmationCount, contact?.contact?.getAlias()) + override fun hashCode(): Int = HashcodeUtils.generate(tx.id, contact?.contact?.getAlias(), contact?.contact, position, requiredConfirmationCount, contact?.contact?.getAlias()) override fun equals(other: Any?): Boolean { return if (other is TransactionItem) { diff --git a/app/src/main/java/com/tari/android/wallet/ui/fragment/tx/adapter/TxListHomeViewHolder.kt b/app/src/main/java/com/tari/android/wallet/ui/fragment/tx/adapter/TxListHomeViewHolder.kt index 5c85c9235..2102903e8 100644 --- a/app/src/main/java/com/tari/android/wallet/ui/fragment/tx/adapter/TxListHomeViewHolder.kt +++ b/app/src/main/java/com/tari/android/wallet/ui/fragment/tx/adapter/TxListHomeViewHolder.kt @@ -53,8 +53,8 @@ class TxListHomeViewHolder(view: ItemHomeTxListBinding) : CommonViewHolder { - val alias = contact.contact.getAlias() + contact != null && contact.contact.getAlias().isNotEmpty() || txUser.walletAddress.isZeros() -> { + val alias = contact?.contact?.getAlias().orEmpty().ifBlank { itemView.context.getString(R.string.unknown_source) } val fullText = when (tx.direction) { Tx.Direction.INBOUND -> string(R.string.tx_list_sent_a_payment, alias) Tx.Direction.OUTBOUND -> string(R.string.tx_list_you_paid_with_alias, alias) diff --git a/app/src/main/java/com/tari/android/wallet/ui/fragment/tx/adapter/TxListViewHolder.kt b/app/src/main/java/com/tari/android/wallet/ui/fragment/tx/adapter/TxListViewHolder.kt index 6aa15c728..960ef98d1 100644 --- a/app/src/main/java/com/tari/android/wallet/ui/fragment/tx/adapter/TxListViewHolder.kt +++ b/app/src/main/java/com/tari/android/wallet/ui/fragment/tx/adapter/TxListViewHolder.kt @@ -91,8 +91,8 @@ class TxListViewHolder(view: ItemTxListBinding) : CommonViewHolder { - val alias = contact.contact.getAlias() + contact != null && contact.contact.getAlias().isNotEmpty() || txUser.walletAddress.isZeros() -> { + val alias = contact?.contact?.getAlias().orEmpty().ifBlank { itemView.context.getString(R.string.unknown_source) } val fullText = when (tx.direction) { Tx.Direction.INBOUND -> string(R.string.tx_list_sent_a_payment, alias) Tx.Direction.OUTBOUND -> string(R.string.tx_list_you_paid_with_alias, alias) diff --git a/app/src/main/java/com/tari/android/wallet/ui/fragment/tx/details/TxDetailsFragment.kt b/app/src/main/java/com/tari/android/wallet/ui/fragment/tx/details/TxDetailsFragment.kt index af28b176a..0e7803aca 100644 --- a/app/src/main/java/com/tari/android/wallet/ui/fragment/tx/details/TxDetailsFragment.kt +++ b/app/src/main/java/com/tari/android/wallet/ui/fragment/tx/details/TxDetailsFragment.kt @@ -39,17 +39,45 @@ import android.view.View import android.view.ViewGroup import androidx.fragment.app.viewModels import com.bumptech.glide.Glide -import com.tari.android.wallet.R.color.* import com.tari.android.wallet.R.dimen.add_amount_element_text_size import com.tari.android.wallet.R.dimen.add_amount_gem_size -import com.tari.android.wallet.R.string.* +import com.tari.android.wallet.R.string.common_are_you_sure +import com.tari.android.wallet.R.string.common_from +import com.tari.android.wallet.R.string.common_to +import com.tari.android.wallet.R.string.tx_detail_add_contact +import com.tari.android.wallet.R.string.tx_detail_completing_final_processing +import com.tari.android.wallet.R.string.tx_detail_edit +import com.tari.android.wallet.R.string.tx_detail_fee_tooltip_desc +import com.tari.android.wallet.R.string.tx_detail_fee_tooltip_transaction_fee +import com.tari.android.wallet.R.string.tx_detail_payment_cancelled +import com.tari.android.wallet.R.string.tx_detail_payment_received +import com.tari.android.wallet.R.string.tx_detail_payment_sent +import com.tari.android.wallet.R.string.tx_detail_pending_payment_received +import com.tari.android.wallet.R.string.tx_detail_waiting_for_recipient +import com.tari.android.wallet.R.string.tx_detail_waiting_for_sender_to_complete +import com.tari.android.wallet.R.string.tx_details_cancel_dialog_cancel +import com.tari.android.wallet.R.string.tx_details_cancel_dialog_description +import com.tari.android.wallet.R.string.tx_details_cancel_dialog_not_cancel +import com.tari.android.wallet.R.string.tx_details_fee_value +import com.tari.android.wallet.R.string.tx_list_you_received_one_side_payment import com.tari.android.wallet.databinding.FragmentTxDetailsBinding import com.tari.android.wallet.extension.observe import com.tari.android.wallet.extension.txFormattedDate -import com.tari.android.wallet.model.* +import com.tari.android.wallet.model.CancelledTx +import com.tari.android.wallet.model.CompletedTx +import com.tari.android.wallet.model.MicroTari +import com.tari.android.wallet.model.PendingOutboundTx +import com.tari.android.wallet.model.Tx import com.tari.android.wallet.model.Tx.Direction.INBOUND import com.tari.android.wallet.model.Tx.Direction.OUTBOUND -import com.tari.android.wallet.model.TxStatus.* +import com.tari.android.wallet.model.TxId +import com.tari.android.wallet.model.TxNote +import com.tari.android.wallet.model.TxStatus.COINBASE +import com.tari.android.wallet.model.TxStatus.FAUX_CONFIRMED +import com.tari.android.wallet.model.TxStatus.FAUX_UNCONFIRMED +import com.tari.android.wallet.model.TxStatus.IMPORTED +import com.tari.android.wallet.model.TxStatus.MINED_CONFIRMED +import com.tari.android.wallet.model.TxStatus.PENDING import com.tari.android.wallet.ui.animation.collapseAndHideAnimation import com.tari.android.wallet.ui.common.CommonFragment import com.tari.android.wallet.ui.component.fullEmojiId.EmojiIdSummaryViewController @@ -62,13 +90,25 @@ import com.tari.android.wallet.ui.dialog.modular.modules.button.ButtonModule import com.tari.android.wallet.ui.dialog.modular.modules.button.ButtonStyle import com.tari.android.wallet.ui.dialog.modular.modules.head.HeadModule import com.tari.android.wallet.ui.dialog.tooltipDialog.TooltipDialogArgs -import com.tari.android.wallet.ui.extension.* +import com.tari.android.wallet.ui.extension.dimen +import com.tari.android.wallet.ui.extension.getFirstChild +import com.tari.android.wallet.ui.extension.getLastChild +import com.tari.android.wallet.ui.extension.gone +import com.tari.android.wallet.ui.extension.hideKeyboard +import com.tari.android.wallet.ui.extension.invisible +import com.tari.android.wallet.ui.extension.parcelable +import com.tari.android.wallet.ui.extension.setLayoutSize +import com.tari.android.wallet.ui.extension.setTextSizePx +import com.tari.android.wallet.ui.extension.setVisible +import com.tari.android.wallet.ui.extension.string +import com.tari.android.wallet.ui.extension.temporarilyDisableClick +import com.tari.android.wallet.ui.extension.visible import com.tari.android.wallet.ui.fragment.contact_book.data.contacts.ContactDto import com.tari.android.wallet.ui.fragment.tx.details.gif.GIFView import com.tari.android.wallet.ui.fragment.tx.details.gif.GIFViewModel import com.tari.android.wallet.ui.fragment.tx.details.gif.TxState import com.tari.android.wallet.util.WalletUtil -import java.util.* +import java.util.Date /** * Activity class - Transaction detail. @@ -92,6 +132,8 @@ class TxDetailsFragment : CommonFragment Boolean = { false } val nameModule = InputModule(name, resourceManager.getString(R.string.contact_book_add_contact_first_name_hint), true, false) { saveAction.invoke() } - val surnameModule = - InputModule(surname, resourceManager.getString(R.string.contact_book_add_contact_surname_hint), false, phoneDto == null) { saveAction.invoke() } val headModule = HeadModule( resourceManager.getString(R.string.contact_book_details_edit_title), rightButtonTitle = resourceManager.getString(R.string.contact_book_add_contact_done_button) ) { saveAction.invoke() } - val moduleList = mutableListOf(headModule, nameModule, surnameModule) + val moduleList = mutableListOf(headModule, nameModule) saveAction = { - saveDetails(nameModule.value, surnameModule.value) + saveDetails(nameModule.value) true } @@ -195,7 +191,10 @@ class TxDetailsViewModel : CommonViewModel() { _inputDialog.postValue(args) } - private fun saveDetails(name: String, surname: String = "") { + private fun saveDetails(newName: String) { + val split = newName.split(" ") + val name = split.getOrNull(0).orEmpty().trim() + val surname = split.getOrNull(1).orEmpty().trim() val contact = contact.value!! this.contact.value = contactsRepository.updateContactInfo(contact, name, surname, contact.getYatDto()?.yat.orEmpty()) dismissDialog.postValue(Unit) diff --git a/app/src/main/java/com/tari/android/wallet/ui/fragment/tx/details/gif/GIFView.kt b/app/src/main/java/com/tari/android/wallet/ui/fragment/tx/details/gif/GIFView.kt index 616c59a13..6aac4fae9 100644 --- a/app/src/main/java/com/tari/android/wallet/ui/fragment/tx/details/gif/GIFView.kt +++ b/app/src/main/java/com/tari/android/wallet/ui/fragment/tx/details/gif/GIFView.kt @@ -99,7 +99,7 @@ class GIFView( override fun onLoadFailed( e: GlideException?, model: Any?, - target: Target?, + target: Target, isFirstResource: Boolean ): Boolean { onFailure() @@ -107,10 +107,10 @@ class GIFView( } override fun onResourceReady( - resource: GifDrawable?, - model: Any?, + resource: GifDrawable, + model: Any, target: Target?, - dataSource: DataSource?, + dataSource: DataSource, isFirstResource: Boolean ): Boolean { hideDownloadingUI() diff --git a/app/src/main/java/com/tari/android/wallet/ui/fragment/utxos/list/UtxosListFragment.kt b/app/src/main/java/com/tari/android/wallet/ui/fragment/utxos/list/UtxosListFragment.kt index 6672a0480..1342831ac 100644 --- a/app/src/main/java/com/tari/android/wallet/ui/fragment/utxos/list/UtxosListFragment.kt +++ b/app/src/main/java/com/tari/android/wallet/ui/fragment/utxos/list/UtxosListFragment.kt @@ -14,6 +14,7 @@ import com.tari.android.wallet.extension.observeOnLoad import com.tari.android.wallet.ui.common.CommonFragment import com.tari.android.wallet.ui.common.recyclerView.CommonAdapter import com.tari.android.wallet.ui.extension.gone +import com.tari.android.wallet.ui.extension.hideKeyboard import com.tari.android.wallet.ui.extension.setVisible import com.tari.android.wallet.ui.extension.visible import com.tari.android.wallet.ui.fragment.utxos.list.adapters.UtxosListAdapter @@ -43,6 +44,8 @@ class UtxosListFragment : CommonFragment>>>>>> cb06ea1bc7c0b86add6505cb8e8235be91f6dd50 +} \ No newline at end of file diff --git a/app/src/main/java/com/tari/android/wallet/yat/YatAdapter.kt b/app/src/main/java/com/tari/android/wallet/yat/YatAdapter.kt index fece16d14..ebafbc166 100644 --- a/app/src/main/java/com/tari/android/wallet/yat/YatAdapter.kt +++ b/app/src/main/java/com/tari/android/wallet/yat/YatAdapter.kt @@ -33,7 +33,7 @@ class YatAdapter( fun initYat(application: Application) { val config = YatConfiguration(BuildConfig.YAT_ORGANIZATION_RETURN_URL, BuildConfig.YAT_ORGANIZATION_NAME, BuildConfig.YAT_ORGANIZATION_KEY) - YatIntegration.setup(application, config, YatIntegration.ColorMode.LIGHT, this, environment = YatIntegration.Environment.SandBox) + YatIntegration.setup(application, config, YatIntegration.ColorMode.LIGHT, this, environment = YatIntegration.Environment.Production) } fun searchTariYats(query: String): PaymentAddressResponse? = diff --git a/app/src/main/res/drawable-xhdpi/tari_fingerprint.png b/app/src/main/res/drawable-xhdpi/tari_fingerprint.png deleted file mode 100644 index f1baa7350..000000000 Binary files a/app/src/main/res/drawable-xhdpi/tari_fingerprint.png and /dev/null differ diff --git a/app/src/main/res/drawable-xhdpi/tari_numpad.png b/app/src/main/res/drawable-xhdpi/tari_numpad.png deleted file mode 100644 index a4b9f36d3..000000000 Binary files a/app/src/main/res/drawable-xhdpi/tari_numpad.png and /dev/null differ diff --git a/app/src/main/res/drawable-xxhdpi/tari_fingerprint.png b/app/src/main/res/drawable-xxhdpi/tari_fingerprint.png deleted file mode 100644 index 483ddfbe4..000000000 Binary files a/app/src/main/res/drawable-xxhdpi/tari_fingerprint.png and /dev/null differ diff --git a/app/src/main/res/drawable-xxhdpi/tari_numpad.png b/app/src/main/res/drawable-xxhdpi/tari_numpad.png deleted file mode 100644 index 4c977499c..000000000 Binary files a/app/src/main/res/drawable-xxhdpi/tari_numpad.png and /dev/null differ diff --git a/app/src/main/res/drawable-xxxhdpi/tari_fingerprint.png b/app/src/main/res/drawable-xxxhdpi/tari_fingerprint.png deleted file mode 100644 index 278583923..000000000 Binary files a/app/src/main/res/drawable-xxxhdpi/tari_fingerprint.png and /dev/null differ diff --git a/app/src/main/res/drawable-xxxhdpi/tari_numpad.png b/app/src/main/res/drawable-xxxhdpi/tari_numpad.png deleted file mode 100644 index 2e837bfbb..000000000 Binary files a/app/src/main/res/drawable-xxxhdpi/tari_numpad.png and /dev/null differ diff --git a/app/src/main/res/drawable/vector_all_settings_bluetooth.xml b/app/src/main/res/drawable/vector_all_settings_bluetooth.xml index eeeadecad..9b3fd82e8 100644 --- a/app/src/main/res/drawable/vector_all_settings_bluetooth.xml +++ b/app/src/main/res/drawable/vector_all_settings_bluetooth.xml @@ -1,10 +1,14 @@ - + + + + diff --git a/app/src/main/res/drawable/vector_all_settings_card.xml b/app/src/main/res/drawable/vector_all_settings_card.xml new file mode 100644 index 000000000..17a2c01b8 --- /dev/null +++ b/app/src/main/res/drawable/vector_all_settings_card.xml @@ -0,0 +1,15 @@ + + + + + diff --git a/app/src/main/res/drawable/vector_all_settings_cart.xml b/app/src/main/res/drawable/vector_all_settings_cart.xml new file mode 100644 index 000000000..90ae7eb8d --- /dev/null +++ b/app/src/main/res/drawable/vector_all_settings_cart.xml @@ -0,0 +1,15 @@ + + + + + diff --git a/app/src/main/res/drawable/vector_all_settings_data_collection.xml b/app/src/main/res/drawable/vector_all_settings_data_collection.xml new file mode 100644 index 000000000..992e1d05f --- /dev/null +++ b/app/src/main/res/drawable/vector_all_settings_data_collection.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/vector_all_settings_passcode.xml b/app/src/main/res/drawable/vector_all_settings_passcode.xml new file mode 100644 index 000000000..23315e409 --- /dev/null +++ b/app/src/main/res/drawable/vector_all_settings_passcode.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/drawable/vector_chat_add.xml b/app/src/main/res/drawable/vector_chat_add.xml new file mode 100644 index 000000000..3cceafa7d --- /dev/null +++ b/app/src/main/res/drawable/vector_chat_add.xml @@ -0,0 +1,20 @@ + + + + + + + + diff --git a/app/src/main/res/drawable/vector_chat_empty_list.xml b/app/src/main/res/drawable/vector_chat_empty_list.xml new file mode 100644 index 000000000..dd74a30d7 --- /dev/null +++ b/app/src/main/res/drawable/vector_chat_empty_list.xml @@ -0,0 +1,11 @@ + + + diff --git a/app/src/main/res/drawable/vector_chat_empty_messages.xml b/app/src/main/res/drawable/vector_chat_empty_messages.xml new file mode 100644 index 000000000..183474c4f --- /dev/null +++ b/app/src/main/res/drawable/vector_chat_empty_messages.xml @@ -0,0 +1,26 @@ + + + + + + diff --git a/app/src/main/res/drawable/vector_chat_input_attach.xml b/app/src/main/res/drawable/vector_chat_input_attach.xml new file mode 100644 index 000000000..a4a2fefe6 --- /dev/null +++ b/app/src/main/res/drawable/vector_chat_input_attach.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/vector_chat_input_send.xml b/app/src/main/res/drawable/vector_chat_input_send.xml new file mode 100644 index 000000000..e7177d556 --- /dev/null +++ b/app/src/main/res/drawable/vector_chat_input_send.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/vector_chat_unread_count.xml b/app/src/main/res/drawable/vector_chat_unread_count.xml new file mode 100644 index 000000000..b597d4872 --- /dev/null +++ b/app/src/main/res/drawable/vector_chat_unread_count.xml @@ -0,0 +1,6 @@ + + + + + diff --git a/app/src/main/res/drawable/vector_disable_able_gradient_button_bg_external_dark.xml b/app/src/main/res/drawable/vector_disable_able_gradient_button_bg_external_dark.xml new file mode 100644 index 000000000..49bd3d5f5 --- /dev/null +++ b/app/src/main/res/drawable/vector_disable_able_gradient_button_bg_external_dark.xml @@ -0,0 +1,10 @@ + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/vector_fingerprint.xml b/app/src/main/res/drawable/vector_fingerprint.xml new file mode 100644 index 000000000..dcc26f7a2 --- /dev/null +++ b/app/src/main/res/drawable/vector_fingerprint.xml @@ -0,0 +1,10 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/vector_home_book.xml b/app/src/main/res/drawable/vector_home_book.xml new file mode 100644 index 000000000..617523c44 --- /dev/null +++ b/app/src/main/res/drawable/vector_home_book.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/drawable/vector_home_chat.xml b/app/src/main/res/drawable/vector_home_chat.xml new file mode 100644 index 000000000..d9166c284 --- /dev/null +++ b/app/src/main/res/drawable/vector_home_chat.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/drawable/vector_home_home.xml b/app/src/main/res/drawable/vector_home_home.xml new file mode 100644 index 000000000..0859731df --- /dev/null +++ b/app/src/main/res/drawable/vector_home_home.xml @@ -0,0 +1,12 @@ + + + + diff --git a/app/src/main/res/drawable/vector_home_menu.xml b/app/src/main/res/drawable/vector_home_menu.xml new file mode 100644 index 000000000..9502a91ef --- /dev/null +++ b/app/src/main/res/drawable/vector_home_menu.xml @@ -0,0 +1,18 @@ + + + + + diff --git a/app/src/main/res/drawable/vector_pin_input_background.xml b/app/src/main/res/drawable/vector_pin_input_background.xml new file mode 100644 index 000000000..fce539ef3 --- /dev/null +++ b/app/src/main/res/drawable/vector_pin_input_background.xml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/vector_pin_input_dot_error.xml b/app/src/main/res/drawable/vector_pin_input_dot_error.xml new file mode 100644 index 000000000..6c2eefb8b --- /dev/null +++ b/app/src/main/res/drawable/vector_pin_input_dot_error.xml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/vector_pin_input_dot_normal.xml b/app/src/main/res/drawable/vector_pin_input_dot_normal.xml new file mode 100644 index 000000000..f6111ed5b --- /dev/null +++ b/app/src/main/res/drawable/vector_pin_input_dot_normal.xml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/vector_wallet_wallet.xml b/app/src/main/res/drawable/vector_wallet_wallet.xml new file mode 100644 index 000000000..a8dd386ff --- /dev/null +++ b/app/src/main/res/drawable/vector_wallet_wallet.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/drawable/vector_wallet_yat.xml b/app/src/main/res/drawable/vector_wallet_yat.xml new file mode 100644 index 000000000..7a8676072 --- /dev/null +++ b/app/src/main/res/drawable/vector_wallet_yat.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/layout/activity_auth.xml b/app/src/main/res/layout/activity_auth.xml index 0293a2b2c..cfaa55542 100644 --- a/app/src/main/res/layout/activity_auth.xml +++ b/app/src/main/res/layout/activity_auth.xml @@ -1,66 +1,46 @@ - + android:layout_height="match_parent"> - - - - - + - - - + + + + + - + - diff --git a/app/src/main/res/layout/activity_home.xml b/app/src/main/res/layout/activity_home.xml index 4bbf78298..222bf3f52 100644 --- a/app/src/main/res/layout/activity_home.xml +++ b/app/src/main/res/layout/activity_home.xml @@ -58,7 +58,7 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center" - android:src="@drawable/vector_home_icon" /> + android:src="@drawable/vector_home_home" /> + android:src="@drawable/vector_home_book" /> + android:src="@drawable/vector_home_chat" /> + android:src="@drawable/vector_home_menu" /> @@ -113,12 +113,12 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_chat.xml b/app/src/main/res/layout/fragment_chat.xml new file mode 100644 index 000000000..7c1f8c7a9 --- /dev/null +++ b/app/src/main/res/layout/fragment_chat.xml @@ -0,0 +1,132 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_chat_list.xml b/app/src/main/res/layout/fragment_chat_list.xml new file mode 100644 index 000000000..281e5ec09 --- /dev/null +++ b/app/src/main/res/layout/fragment_chat_list.xml @@ -0,0 +1,95 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_contact_book_root.xml b/app/src/main/res/layout/fragment_contact_book_root.xml index aa2c8d674..276f5aa41 100644 --- a/app/src/main/res/layout/fragment_contact_book_root.xml +++ b/app/src/main/res/layout/fragment_contact_book_root.xml @@ -163,11 +163,13 @@ + app:queryBackground="@null" + tools:text="Search"/> diff --git a/app/src/main/res/layout/fragment_contacts_selection.xml b/app/src/main/res/layout/fragment_contacts_selection.xml index cc5a02c0a..99932c2b9 100644 --- a/app/src/main/res/layout/fragment_contacts_selection.xml +++ b/app/src/main/res/layout/fragment_contacts_selection.xml @@ -33,15 +33,6 @@ android:visibility="gone" app:hintText="@string/contact_book_add_contact_first_name_hint" /> - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_enter_pincode.xml b/app/src/main/res/layout/fragment_enter_pincode.xml new file mode 100644 index 000000000..4b767a7c9 --- /dev/null +++ b/app/src/main/res/layout/fragment_enter_pincode.xml @@ -0,0 +1,243 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_feature_auth.xml b/app/src/main/res/layout/fragment_feature_auth.xml new file mode 100644 index 000000000..0cdf4f9dc --- /dev/null +++ b/app/src/main/res/layout/fragment_feature_auth.xml @@ -0,0 +1,57 @@ + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/fragment_home.xml b/app/src/main/res/layout/fragment_home.xml index cfbecfe2c..4e256b03a 100644 --- a/app/src/main/res/layout/fragment_home.xml +++ b/app/src/main/res/layout/fragment_home.xml @@ -195,14 +195,6 @@ android:layout_marginTop="-4dp" android:orientation="horizontal"> - - diff --git a/app/src/main/res/layout/fragment_local_auth.xml b/app/src/main/res/layout/fragment_local_auth.xml index b6565f0b6..99fcd3f7a 100644 --- a/app/src/main/res/layout/fragment_local_auth.xml +++ b/app/src/main/res/layout/fragment_local_auth.xml @@ -2,116 +2,154 @@ + + + android:layout_height="wrap_content" + android:layout_centerInParent="true" + android:translationY="-10dp" + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintTop_toTopOf="parent"> - - - + android:layout_marginTop="50dp" + android:clipChildren="false" + android:clipToPadding="false" + android:orientation="horizontal" + android:padding="24dp"> + android:src="@drawable/vector_all_settings_passcode" + android:tint="?attr/palette_brand_purple" + tools:alpha="1" /> - - - + android:src="@drawable/vector_fingerprint" + android:tint="?attr/palette_brand_purple" + tools:alpha="1" /> - + + + - + android:layout_height="wrap_content" + android:layout_below="@id/prompt_text_view" + android:layout_gravity="center" + android:layout_marginHorizontal="@dimen/common_horizontal_margin" + android:alpha="0" + android:gravity="center" + android:letterSpacing="-0.02" + android:lineSpacingExtra="10dp" + android:text="@string/auth_prompt_desc" + android:textColor="?attr/palette_text_body" + android:textSize="14sp" + android:visibility="visible" + app:customFont="medium" /> - + - - + - + + + - - \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_tx_details.xml b/app/src/main/res/layout/fragment_tx_details.xml index fa176ff29..5e50416f5 100644 --- a/app/src/main/res/layout/fragment_tx_details.xml +++ b/app/src/main/res/layout/fragment_tx_details.xml @@ -172,10 +172,11 @@ android:gravity="center_horizontal" android:orientation="vertical"> - @@ -184,21 +185,27 @@ android:id="@+id/amount_gem_image_view" android:layout_width="@dimen/add_amount_gem_size" android:layout_height="@dimen/add_amount_gem_size" - android:layout_centerVertical="true" + android:layout_gravity="center_vertical" android:layout_marginEnd="6dp" - android:layout_toStartOf="@id/amount_text_view" android:src="@drawable/vector_gem" /> - + tools:text="150.000000000000000" /> + + + + android:layout_height="match_parent" + tools:visibility="gone" /> diff --git a/app/src/main/res/layout/fragment_wallet_info.xml b/app/src/main/res/layout/fragment_wallet_info.xml index e5329ed82..b93d63aa8 100644 --- a/app/src/main/res/layout/fragment_wallet_info.xml +++ b/app/src/main/res/layout/fragment_wallet_info.xml @@ -66,36 +66,88 @@ android:layout_gravity="center" /> + app:customFont="black" /> + + + + + + + + + + + + + app:customFont="medium" /> - - diff --git a/app/src/main/res/layout/item_chat_cell.xml b/app/src/main/res/layout/item_chat_cell.xml new file mode 100644 index 000000000..7db992acf --- /dev/null +++ b/app/src/main/res/layout/item_chat_cell.xml @@ -0,0 +1,204 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/item_chat_item.xml b/app/src/main/res/layout/item_chat_item.xml new file mode 100644 index 000000000..d2de840ff --- /dev/null +++ b/app/src/main/res/layout/item_chat_item.xml @@ -0,0 +1,235 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/item_home_tx_list.xml b/app/src/main/res/layout/item_home_tx_list.xml index f7a8e70c4..058b4080b 100644 --- a/app/src/main/res/layout/item_home_tx_list.xml +++ b/app/src/main/res/layout/item_home_tx_list.xml @@ -4,7 +4,7 @@ xmlns:tools="http://schemas.android.com/tools" android:id="@+id/root_view" android:layout_width="match_parent" - android:layout_height="60dp" + android:layout_height="wrap_content" android:layout_marginTop="10dp" android:background="@drawable/tari_home_tx_bg" android:clickable="true" @@ -16,6 +16,7 @@ android:layout_height="wrap_content" android:layout_gravity="center_vertical" android:orientation="vertical" + android:paddingVertical="16dp" android:paddingHorizontal="10dp"> + + + app:customFont="heavy" + tools:visibility="gone" /> + + + 80dp 22dp + 46dp + 12dp + 14dp + diff --git a/app/src/main/res/values/light_colors_palette.xml b/app/src/main/res/values/light_colors_palette.xml index 1ff8785b1..72bec2989 100644 --- a/app/src/main/res/values/light_colors_palette.xml +++ b/app/src/main/res/values/light_colors_palette.xml @@ -3,7 +3,7 @@ #FFFFFF #F6F6F6 - #ECECEC + #EEEEF0 #E1E1E1 @@ -48,7 +48,7 @@ #FFFFFF - #F6F6F6 + #F5F5F7 #000000 diff --git a/app/src/main/res/values/palette_styles.xml b/app/src/main/res/values/palette_styles.xml index 57a24963c..99e29787e 100644 --- a/app/src/main/res/values/palette_styles.xml +++ b/app/src/main/res/values/palette_styles.xml @@ -3,6 +3,7 @@ + +