From 1c0eb997a7262468c43859e9f5ae90bcbd1d0b21 Mon Sep 17 00:00:00 2001 From: Devin Bileck <603793+devinbileck@users.noreply.github.com> Date: Fri, 22 Dec 2023 21:33:41 -0800 Subject: [PATCH 01/95] ci: Update GitHub workflows to use JDK 17 --- .github/workflows/ci.yaml | 18 +++++++++--------- .github/workflows/master.yaml | 6 +++--- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index d3942ec..4c701ba 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -17,11 +17,11 @@ jobs: - name: Checkout the code uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 - - name: Set up JDK 11 + - name: Set up JDK 17 uses: actions/setup-java@99b8673ff64fbf99d8d325f52d9a5bdedb8483e9 with: - java-version: '11' - distribution: 'temurin' + java-version: '17' + distribution: 'corretto' cache: gradle - name: Clear gradle cache @@ -76,11 +76,11 @@ jobs: - name: Checkout the code uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 - - name: Set up JDK 11 + - name: Set up JDK 17 uses: actions/setup-java@99b8673ff64fbf99d8d325f52d9a5bdedb8483e9 with: - java-version: '11' - distribution: 'temurin' + java-version: '17' + distribution: 'corretto' cache: gradle - name: Clear gradle cache @@ -123,11 +123,11 @@ jobs: - name: Checkout the code uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 - - name: Set up JDK 11 + - name: Set up JDK 17 uses: actions/setup-java@99b8673ff64fbf99d8d325f52d9a5bdedb8483e9 with: - java-version: '11' - distribution: 'temurin' + java-version: '17' + distribution: 'corretto' cache: gradle - name: Clear gradle cache diff --git a/.github/workflows/master.yaml b/.github/workflows/master.yaml index b62bed1..e51e9f4 100644 --- a/.github/workflows/master.yaml +++ b/.github/workflows/master.yaml @@ -34,11 +34,11 @@ jobs: GOOGLE_SERVICES_JSON: ${{ secrets.GOOGLE_SERVICES_JSON }} run: echo $GOOGLE_SERVICES_JSON > app/google-services.json - - name: Set up JDK 11 + - name: Set up JDK 17 uses: actions/setup-java@99b8673ff64fbf99d8d325f52d9a5bdedb8483e9 with: - java-version: '11' - distribution: 'temurin' + java-version: '17' + distribution: 'corretto' cache: gradle - name: Clear gradle cache From 8a591411d61c510e2dbe076f338c8f9aa8f3668b Mon Sep 17 00:00:00 2001 From: Devin Bileck <603793+devinbileck@users.noreply.github.com> Date: Fri, 22 Dec 2023 21:35:30 -0800 Subject: [PATCH 02/95] build: Update gradle version --- gradle.properties | 2 ++ gradle/wrapper/gradle-wrapper.properties | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/gradle.properties b/gradle.properties index 8de5058..704d68f 100644 --- a/gradle.properties +++ b/gradle.properties @@ -7,6 +7,8 @@ # Specifies the JVM arguments used for the daemon process. # The setting is particularly useful for tweaking memory settings. android.enableJetifier=true +android.nonFinalResIds=false +android.nonTransitiveRClass=false android.useAndroidX=true org.gradle.jvmargs=-Xmx1536m # When configured, Gradle will run in incubating parallel mode. diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 4d21c6c..4d8d9fa 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ #Mon Mar 28 15:32:07 PDT 2022 distributionBase=GRADLE_USER_HOME -distributionUrl=https\://services.gradle.org/distributions/gradle-7.3.3-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.2-bin.zip distributionPath=wrapper/dists zipStorePath=wrapper/dists zipStoreBase=GRADLE_USER_HOME From a8fe772810cb4a484dfc6db47a403715725fd6c6 Mon Sep 17 00:00:00 2001 From: Devin Bileck <603793+devinbileck@users.noreply.github.com> Date: Fri, 22 Dec 2023 21:40:44 -0800 Subject: [PATCH 03/95] refactor: Move FirebaseMock to a separate testCommon module The latest version of Gradle does not seem to like having duplicate source sets between androidTest and test for a common directory within the same module. --- app/build.gradle | 9 +--- settings.gradle | 1 + testCommon/.gitignore | 1 + testCommon/build.gradle | 46 +++++++++++++++++++ testCommon/consumer-rules.pro | 0 testCommon/proguard-rules.pro | 21 +++++++++ .../android/testCommon}/mocks/FirebaseMock.kt | 4 +- 7 files changed, 72 insertions(+), 10 deletions(-) create mode 100644 testCommon/.gitignore create mode 100644 testCommon/build.gradle create mode 100644 testCommon/consumer-rules.pro create mode 100644 testCommon/proguard-rules.pro rename {app/src/testCommon/java/bisq/android => testCommon/src/main/java/bisq/android/testCommon}/mocks/FirebaseMock.kt (98%) diff --git a/app/build.gradle b/app/build.gradle index 4d72ee4..0c2c060 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -81,14 +81,6 @@ android { } } } - sourceSets { - androidTest { - java.srcDirs += "$projectDir/src/testCommon/java" - } - test { - java.srcDirs += "$projectDir/src/testCommon/java" - } - } } dependencies { @@ -135,6 +127,7 @@ dependencies { testImplementation 'org.powermock:powermock:1.6.5' testImplementation 'org.powermock:powermock-module-junit4:2.0.2' testImplementation 'org.powermock:powermock-api-mockito2:2.0.2' + testImplementation(project(":testCommon")) androidTestImplementation "junit:junit:4.13.2" androidTestImplementation "androidx.test:core-ktx:1.4.0" diff --git a/settings.gradle b/settings.gradle index e7b4def..b0bf188 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1 +1,2 @@ include ':app' +include ':testCommon' diff --git a/testCommon/.gitignore b/testCommon/.gitignore new file mode 100644 index 0000000..42afabf --- /dev/null +++ b/testCommon/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/testCommon/build.gradle b/testCommon/build.gradle new file mode 100644 index 0000000..c1eaca3 --- /dev/null +++ b/testCommon/build.gradle @@ -0,0 +1,46 @@ +apply plugin: 'com.android.library' +apply plugin: 'kotlin-android' +apply plugin: 'org.jlleitschuh.gradle.ktlint' +apply plugin: 'io.gitlab.arturbosch.detekt' + +repositories { + google() + mavenCentral() +} + +android { + namespace 'bisq.android.testCommon' + compileSdk 34 + + defaultConfig { + minSdk 23 + + testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" + consumerProguardFiles "consumer-rules.pro" + } + + buildTypes { + release { + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' + } + } + + compileOptions { + sourceCompatibility JavaVersion.VERSION_17 + targetCompatibility JavaVersion.VERSION_17 + } + + kotlinOptions { + jvmTarget = JavaVersion.VERSION_17 + } +} + +dependencies { + detektPlugins("io.gitlab.arturbosch.detekt:detekt-formatting:1.20.0") + + implementation platform('com.google.firebase:firebase-bom:32.6.0') + implementation 'com.google.firebase:firebase-messaging:23.4.0' + + implementation "io.mockk:mockk:1.12.3" +} diff --git a/testCommon/consumer-rules.pro b/testCommon/consumer-rules.pro new file mode 100644 index 0000000..e69de29 diff --git a/testCommon/proguard-rules.pro b/testCommon/proguard-rules.pro new file mode 100644 index 0000000..481bb43 --- /dev/null +++ b/testCommon/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile \ No newline at end of file diff --git a/app/src/testCommon/java/bisq/android/mocks/FirebaseMock.kt b/testCommon/src/main/java/bisq/android/testCommon/mocks/FirebaseMock.kt similarity index 98% rename from app/src/testCommon/java/bisq/android/mocks/FirebaseMock.kt rename to testCommon/src/main/java/bisq/android/testCommon/mocks/FirebaseMock.kt index 70a09b1..88540aa 100644 --- a/app/src/testCommon/java/bisq/android/mocks/FirebaseMock.kt +++ b/testCommon/src/main/java/bisq/android/testCommon/mocks/FirebaseMock.kt @@ -15,7 +15,7 @@ * along with Bisq. If not, see . */ -package bisq.android.mocks +package bisq.android.testCommon.mocks import com.google.android.gms.common.ConnectionResult import com.google.android.gms.common.GoogleApiAvailability @@ -65,7 +65,7 @@ object FirebaseMock { fun mockFirebaseNotInitialized() { mockkStatic("com.google.firebase.messaging.FirebaseMessaging") - every { FirebaseMessaging.getInstance() } throws IllegalStateException() + every { FirebaseMessaging.getInstance() } throws IllegalStateException("") } fun mockGooglePlayServicesAvailable() { From 01dd064cac950e2cea27e717b8060ccfdc284d69 Mon Sep 17 00:00:00 2001 From: Devin Bileck <603793+devinbileck@users.noreply.github.com> Date: Fri, 22 Dec 2023 21:42:52 -0800 Subject: [PATCH 04/95] build: Update to latest dependency versions --- app/build.gradle | 64 ++++++++++++++++++++++++++---------------------- build.gradle | 15 ++++++------ 2 files changed, 43 insertions(+), 36 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 0c2c060..c041919 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -87,35 +87,37 @@ dependencies { implementation 'androidx.test.uiautomator:uiautomator:2.2.0' detektPlugins("io.gitlab.arturbosch.detekt:detekt-formatting:1.20.0") - implementation fileTree(include: ['*.jar'], dir: 'libs') + implementation "androidx.core:core-ktx:1.12.0" - implementation 'androidx.appcompat:appcompat:1.4.2' + implementation 'androidx.preference:preference-ktx:1.2.1' + + implementation 'androidx.appcompat:appcompat:1.6.1' implementation 'androidx.constraintlayout:constraintlayout:2.1.4' implementation 'androidx.legacy:legacy-support-v4:1.0.0' - implementation 'androidx.media:media:1.6.0' - implementation 'androidx.recyclerview:recyclerview:1.2.1' + implementation 'androidx.media:media:1.7.0' + implementation 'androidx.recyclerview:recyclerview:1.3.2' - implementation 'com.google.android.material:material:1.6.1' + implementation 'com.google.android.material:material:1.11.0' implementation 'com.google.zxing:core:3.4.1' - implementation platform('com.google.firebase:firebase-bom:30.1.0') - implementation 'com.google.firebase:firebase-analytics-ktx:21.0.0' - implementation 'com.google.firebase:firebase-messaging:23.0.5' + implementation platform('com.google.firebase:firebase-bom:32.6.0') + implementation 'com.google.firebase:firebase-analytics-ktx:21.5.0' + implementation 'com.google.firebase:firebase-messaging:23.4.0' - implementation 'com.google.code.gson:gson:2.9.0' + implementation 'com.google.code.gson:gson:2.10' - implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.6.0' + implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.7.1' - implementation 'androidx.room:room-ktx:2.4.2' - implementation 'androidx.room:room-runtime:2.4.2' - annotationProcessor 'androidx.room:room-compiler:2.4.2' - kapt 'androidx.room:room-compiler:2.4.2' + implementation 'androidx.room:room-ktx:2.6.1' + implementation 'androidx.room:room-runtime:2.6.1' + annotationProcessor 'androidx.room:room-compiler:2.6.1' + kapt 'androidx.room:room-compiler:2.6.1' implementation 'androidx.lifecycle:lifecycle-extensions:2.2.0' - implementation 'androidx.lifecycle:lifecycle-livedata-ktx:2.4.1' - implementation "androidx.lifecycle:lifecycle-common-java8:2.4.1" - implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.4.1" + implementation 'androidx.lifecycle:lifecycle-livedata-ktx:2.6.2' + implementation "androidx.lifecycle:lifecycle-common-java8:2.6.2" + implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.6.2" implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version" @@ -123,23 +125,27 @@ dependencies { testImplementation 'junit:junit:4.13.2' testImplementation 'org.hamcrest:hamcrest:2.2' - testImplementation "io.mockk:mockk:1.12.3" + testImplementation 'io.mockk:mockk:1.12.3' testImplementation 'org.powermock:powermock:1.6.5' testImplementation 'org.powermock:powermock-module-junit4:2.0.2' testImplementation 'org.powermock:powermock-api-mockito2:2.0.2' testImplementation(project(":testCommon")) - androidTestImplementation "junit:junit:4.13.2" - androidTestImplementation "androidx.test:core-ktx:1.4.0" - androidTestImplementation 'androidx.test.ext:junit:1.1.3' - androidTestImplementation "androidx.test.ext:junit-ktx:1.1.3" - androidTestImplementation "androidx.test.espresso:espresso-core:3.4.0" - androidTestImplementation "androidx.test.espresso:espresso-intents:3.4.0" - androidTestImplementation "androidx.test.espresso:espresso-contrib:3.4.0" - androidTestImplementation "androidx.test.uiautomator:uiautomator:2.2.0" - androidTestImplementation "org.jetbrains.kotlinx:kotlinx-coroutines-test:1.6.0" - androidTestImplementation "io.mockk:mockk-android:1.12.3" + androidTestImplementation 'junit:junit:4.13.2' + androidTestImplementation "androidx.test:core-ktx:1.5.0" + androidTestImplementation 'androidx.test.ext:junit:1.1.5' + androidTestImplementation 'androidx.test.ext:junit-ktx:1.1.5' + androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1' + androidTestImplementation 'androidx.test.espresso:espresso-intents:3.5.1' + androidTestImplementation 'androidx.test.espresso:espresso-contrib:3.5.1' + androidTestImplementation 'androidx.test.uiautomator:uiautomator:2.2.0' + androidTestImplementation 'org.jetbrains.kotlinx:kotlinx-coroutines-test:1.7.1' androidTestImplementation 'org.awaitility:awaitility-kotlin:4.2.0' + androidTestImplementation 'io.mockk:mockk-android:1.12.3' + androidTestImplementation(project(":testCommon")) { + exclude group: "io.mockk", module: "mockk" + } + androidTestImplementation 'org.objenesis:objenesis:3.3' - androidTestUtil 'androidx.test:orchestrator:1.4.1' + androidTestUtil 'androidx.test:orchestrator:1.4.2' } diff --git a/build.gradle b/build.gradle index 95fd7f1..ea31f22 100644 --- a/build.gradle +++ b/build.gradle @@ -1,21 +1,22 @@ buildscript { - ext.kotlin_version = '1.6.21' + ext.kotlin_version = '1.9.10' repositories { google() mavenCentral() } dependencies { - classpath 'com.android.tools.build:gradle:7.2.1' - classpath 'com.google.gms:google-services:4.3.10' + classpath 'com.android.tools.build:gradle:8.2.0' + classpath 'com.google.gms:google-services:4.4.0' classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" - classpath "org.jlleitschuh.gradle:ktlint-gradle:10.3.0" + classpath "org.jlleitschuh.gradle:ktlint-gradle:12.0.3" } } plugins { - id "org.jlleitschuh.gradle.ktlint" version "10.3.0" - id "io.gitlab.arturbosch.detekt" version "1.20.0" - id "io.snyk.gradle.plugin.snykplugin" version "0.4" + id "org.jlleitschuh.gradle.ktlint" version "12.0.3" + id "io.gitlab.arturbosch.detekt" version "1.23.4" + id "io.snyk.gradle.plugin.snykplugin" version "0.5" + id 'org.jetbrains.kotlin.android' version '1.9.0' apply false } repositories { From 141eb9e7f91f3af40798d077ceee8445930cb2ce Mon Sep 17 00:00:00 2001 From: Devin Bileck <603793+devinbileck@users.noreply.github.com> Date: Fri, 22 Dec 2023 21:44:50 -0800 Subject: [PATCH 05/95] build: Bump compile, min, and target Android SDK versions --- .github/workflows/ci.yaml | 2 +- app/build.gradle | 7 ++++--- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 4c701ba..586cfc1 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -118,7 +118,7 @@ jobs: strategy: fail-fast: true matrix: - api-level: [ 21, 32 ] + api-level: [ 23, 34 ] steps: - name: Checkout the code uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 diff --git a/app/build.gradle b/app/build.gradle index c041919..de2f794 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -43,13 +43,14 @@ def getVersionName = { -> android { namespace 'bisq.android' - compileSdkVersion 32 + compileSdk 34 + defaultConfig { applicationId "com.joachimneumann.bisq" versionCode getVersionCode() versionName getVersionName() - minSdkVersion 21 - targetSdkVersion 32 + minSdkVersion 23 + targetSdkVersion 34 multiDexEnabled true setProperty("archivesBaseName", "bisq") From 9fa824d3c82d88191839b5e6f8ccacbb58c6a886 Mon Sep 17 00:00:00 2001 From: Devin Bileck <603793+devinbileck@users.noreply.github.com> Date: Fri, 22 Dec 2023 21:45:22 -0800 Subject: [PATCH 06/95] build: Cleanup gradle build files --- app/build.gradle | 21 +++++++++++++++++++-- build.gradle | 13 +++++++++++-- 2 files changed, 30 insertions(+), 4 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index de2f794..4200310 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -13,8 +13,8 @@ repositories { // This is primarily to be able to run unit and instrumented tests in GitHub workflows, // since google-services.json is unavailable and will cause the GoogleServices task to fail if (!project.file('google-services.json').exists()) { - android.applicationVariants.all { variant -> - def googleTask = tasks.findByName("process${variant.name.capitalize()}GoogleServices") + android.applicationVariants.configureEach { variant -> + def googleTask = tasks.named("process${variant.name.capitalize()}GoogleServices") googleTask.enabled = false } } @@ -61,10 +61,18 @@ android { // that the app's state is completely cleared between tests. testInstrumentationRunnerArguments clearPackageData: 'true' } + testOptions { animationsDisabled = true execution 'ANDROIDX_TEST_ORCHESTRATOR' + + packagingOptions { + jniLibs { + useLegacyPackaging true + } + } } + buildTypes { debug { minifyEnabled false @@ -82,6 +90,15 @@ android { } } } + + compileOptions { + sourceCompatibility = JavaVersion.VERSION_17 + targetCompatibility = JavaVersion.VERSION_17 + } + + kotlinOptions { + jvmTarget = JavaVersion.VERSION_17.toString() + } } dependencies { diff --git a/build.gradle b/build.gradle index ea31f22..c9fe182 100644 --- a/build.gradle +++ b/build.gradle @@ -23,7 +23,7 @@ repositories { mavenCentral() } -task clean(type: Delete) { +tasks.register('clean', Delete) { delete rootProject.buildDir } @@ -31,7 +31,7 @@ allprojects { project -> dependencyLocking { lockAllConfigurations() } - task resolveAndLockAll { + tasks.register('resolveAndLockAll') { doFirst { assert gradle.startParameter.writeDependencyLocks } @@ -44,6 +44,15 @@ allprojects { project -> } } +subprojects { + tasks.withType(Test).configureEach { + jvmArgs = jvmArgs + [ + '--add-opens=java.base/java.lang=ALL-UNNAMED', + '--add-opens=java.base/java.util=ALL-UNNAMED' + ] + } +} + detekt { buildUponDefaultConfig = true allRules = false From 5cbe2c0610e46802375e90b67e8fed6ed7e04895 Mon Sep 17 00:00:00 2001 From: Devin Bileck <603793+devinbileck@users.noreply.github.com> Date: Fri, 22 Dec 2023 21:46:10 -0800 Subject: [PATCH 07/95] chore: Specify nuisance ktlint rules --- .editorconfig | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/.editorconfig b/.editorconfig index 6d83688..eab196d 100644 --- a/.editorconfig +++ b/.editorconfig @@ -11,3 +11,13 @@ insert_final_newline = true [.idea/codeStyles/*.xml] indent_size = 2 insert_final_newline = false + +[*.{kt,kts}] +max_line_length = 120 +ktlint_code_style = android_studio +ktlint_standard_import-ordering = disabled +ktlint_class_signature_rule_force_multiline_when_parameter_count_greater_or_equal_than = unset +ktlint_function_signature_rule_force_multiline_when_parameter_count_greater_or_equal_than = unset +ktlint_standard_trailing-comma-on-call-site = disabled +ktlint_standard_trailing-comma-on-declaration-site = disabled +ktlint_standard_function-signature = disabled From d0099306c0cef514957f85e36bbd85f136700c8c Mon Sep 17 00:00:00 2001 From: Devin Bileck <603793+devinbileck@users.noreply.github.com> Date: Fri, 22 Dec 2023 21:54:48 -0800 Subject: [PATCH 08/95] minor: UI improvements - Refine layout, content, and colors of all UI screens - Implemented an action menu in notification table screen - Reworked settings screen - Implemented dark mode with dark/light mode selection --- .../screens/NotificationDetailScreen.kt | 13 +- .../screens/NotificationTableScreen.kt | 18 +- .../bisq/android/screens/PairingScanScreen.kt | 3 +- .../bisq/android/screens/PairingSendScreen.kt | 3 +- .../android/screens/PairingSuccessScreen.kt | 3 +- .../java/bisq/android/screens/Screen.kt | 3 +- .../bisq/android/screens/SettingsScreen.kt | 23 ++- .../bisq/android/screens/WelcomeScreen.kt | 7 +- .../bisq/android/screens/dialogs/Dialog.kt | 5 +- .../android/screens/dialogs/PromptDialog.kt | 4 +- .../screens/dialogs/ThemePromptDialog.kt | 36 ++++ .../android/screens/elements/ButtonElement.kt | 5 +- .../screens/elements/ClickableElement.kt | 22 ++ .../bisq/android/screens/elements/Element.kt | 9 +- .../android/screens/elements/ElementById.kt | 28 +++ .../android/screens/elements/ElementByText.kt | 28 +++ .../screens/elements/MenuItemElement.kt | 35 ++++ .../screens/elements/PreferenceElement.kt | 31 +++ .../screens/elements/RecyclerViewElement.kt | 71 +++---- .../screens/elements/SelectionElement.kt | 31 +++ .../android/screens/elements/TextElement.kt | 3 +- .../android/tests/NotificationDetailTest.kt | 19 +- .../android/tests/NotificationTableTest.kt | 91 +++++++-- .../bisq/android/tests/PairingScanTest.kt | 1 - .../bisq/android/tests/PairingSendTest.kt | 4 +- .../bisq/android/tests/PairingSuccessTest.kt | 1 - .../java/bisq/android/tests/SettingsTest.kt | 191 ++++++++++++++---- .../java/bisq/android/tests/WelcomeTest.kt | 59 ++++-- app/src/main/AndroidManifest.xml | 47 +---- app/src/main/java/bisq/android/Application.kt | 8 + .../main/java/bisq/android/ui/BaseActivity.kt | 21 -- .../java/bisq/android/ui/DialogBuilder.kt | 6 +- .../bisq/android/ui/PairedBaseActivity.kt | 4 +- .../java/bisq/android/ui/ThemeProvider.kt | 57 ++++++ app/src/main/java/bisq/android/ui/UiUtil.kt | 48 +++++ .../ui/notification/NotificationAdapter.kt | 29 ++- .../NotificationDetailActivity.kt | 17 +- .../notification/NotificationTableActivity.kt | 105 ++++++++-- .../android/ui/pairing/PairingScanActivity.kt | 25 +-- .../android/ui/pairing/PairingSendActivity.kt | 10 +- .../ui/pairing/PairingSuccessActivity.kt | 6 +- .../android/ui/settings/SettingsActivity.kt | 153 -------------- .../android/ui/settings/SettingsFragment.kt | 149 ++++++++++++++ .../android/ui/welcome/WelcomeActivity.kt | 13 +- app/src/main/res/drawable/bisq_logo_green.xml | 36 ++++ .../res/drawable/circular_progressbar.xml | 2 - app/src/main/res/drawable/ic_bisq_mark.xml | 12 ++ .../main/res/drawable/ic_info_white_24.xml | 9 + .../main/res/drawable/ic_link_white_24.xml | 10 + .../drawable/ic_qr_code_scanner_white_24.xml | 9 + .../main/res/drawable/ic_restart_white_24.xml | 9 + .../main/res/drawable/ic_theme_white_24.xml | 9 + .../main/res/drawable/rounded_corner_gray.xml | 15 -- .../main/res/drawable/rounded_corner_pink.xml | 15 -- .../layout/activity_notification_detail.xml | 65 ++++-- .../layout/activity_notification_table.xml | 16 +- .../main/res/layout/activity_pairing_scan.xml | 37 ++-- .../main/res/layout/activity_pairing_send.xml | 28 +-- .../res/layout/activity_pairing_success.xml | 62 +++--- app/src/main/res/layout/activity_settings.xml | 125 +----------- app/src/main/res/layout/activity_welcome.xml | 48 +++-- app/src/main/res/layout/notification_cell.xml | 19 +- app/src/main/res/menu/menu.xml | 24 ++- app/src/main/res/values-night/colors.xml | 32 +++ app/src/main/res/values/colors.xml | 31 ++- app/src/main/res/values/strings.xml | 44 +++- app/src/main/res/values/styles.xml | 62 +++++- app/src/main/res/values/theme_res.xml | 13 ++ app/src/main/res/xml/settings.xml | 39 ++++ images/notification_list.png | Bin 43182 -> 85913 bytes images/offer_taken_details.png | Bin 22671 -> 54768 bytes images/pairing_success.png | Bin 25841 -> 86560 bytes images/scan_pairing_code.png | Bin 62706 -> 122336 bytes images/settings.png | Bin 25876 -> 67124 bytes images/welcome.png | Bin 48453 -> 80048 bytes 75 files changed, 1503 insertions(+), 713 deletions(-) create mode 100644 app/src/androidTest/java/bisq/android/screens/dialogs/ThemePromptDialog.kt create mode 100644 app/src/androidTest/java/bisq/android/screens/elements/ClickableElement.kt create mode 100644 app/src/androidTest/java/bisq/android/screens/elements/ElementById.kt create mode 100644 app/src/androidTest/java/bisq/android/screens/elements/ElementByText.kt create mode 100644 app/src/androidTest/java/bisq/android/screens/elements/MenuItemElement.kt create mode 100644 app/src/androidTest/java/bisq/android/screens/elements/PreferenceElement.kt create mode 100644 app/src/androidTest/java/bisq/android/screens/elements/SelectionElement.kt create mode 100644 app/src/main/java/bisq/android/ui/ThemeProvider.kt create mode 100644 app/src/main/java/bisq/android/ui/UiUtil.kt create mode 100644 app/src/main/java/bisq/android/ui/settings/SettingsFragment.kt create mode 100644 app/src/main/res/drawable/bisq_logo_green.xml create mode 100644 app/src/main/res/drawable/ic_bisq_mark.xml create mode 100644 app/src/main/res/drawable/ic_info_white_24.xml create mode 100644 app/src/main/res/drawable/ic_link_white_24.xml create mode 100644 app/src/main/res/drawable/ic_qr_code_scanner_white_24.xml create mode 100644 app/src/main/res/drawable/ic_restart_white_24.xml create mode 100644 app/src/main/res/drawable/ic_theme_white_24.xml delete mode 100644 app/src/main/res/drawable/rounded_corner_gray.xml delete mode 100644 app/src/main/res/drawable/rounded_corner_pink.xml create mode 100644 app/src/main/res/values-night/colors.xml create mode 100644 app/src/main/res/values/theme_res.xml create mode 100644 app/src/main/res/xml/settings.xml diff --git a/app/src/androidTest/java/bisq/android/screens/NotificationDetailScreen.kt b/app/src/androidTest/java/bisq/android/screens/NotificationDetailScreen.kt index 86996eb..4757001 100644 --- a/app/src/androidTest/java/bisq/android/screens/NotificationDetailScreen.kt +++ b/app/src/androidTest/java/bisq/android/screens/NotificationDetailScreen.kt @@ -18,13 +18,14 @@ package bisq.android.screens import bisq.android.R +import bisq.android.screens.elements.ButtonElement import bisq.android.screens.elements.TextElement class NotificationDetailScreen : Screen() { - - val title = TextElement(R.id.detail_title) - val message = TextElement(R.id.detail_message) - val action = TextElement(R.id.detail_action) - val eventTime = TextElement(R.id.detail_event_time) - val receivedTime = TextElement(R.id.detail_received_time) + val title = TextElement(R.id.notification_detail_title) + val message = TextElement(R.id.notification_detail_message) + val action = TextElement(R.id.notification_detail_action) + val eventTime = TextElement(R.id.notification_detail_event_time) + val receivedTime = TextElement(R.id.notification_detail_received_time) + val deleteButton = ButtonElement(R.id.notification_delete_button) } diff --git a/app/src/androidTest/java/bisq/android/screens/NotificationTableScreen.kt b/app/src/androidTest/java/bisq/android/screens/NotificationTableScreen.kt index 9e06383..ba448d1 100644 --- a/app/src/androidTest/java/bisq/android/screens/NotificationTableScreen.kt +++ b/app/src/androidTest/java/bisq/android/screens/NotificationTableScreen.kt @@ -18,11 +18,21 @@ package bisq.android.screens import bisq.android.R -import bisq.android.screens.elements.ButtonElement +import bisq.android.screens.dialogs.ChoicePromptDialog +import bisq.android.screens.elements.MenuItemElement import bisq.android.screens.elements.RecyclerViewElement class NotificationTableScreen : Screen() { - - val settingsButton = ButtonElement(R.id.action_settings) - val notificationRecylerView = RecyclerViewElement(R.id.notification_recycler_view) + val addExampleNotificationsMenuItem = + MenuItemElement(applicationContext.resources.getString(R.string.button_add_example_notifications)) + val markAllAsReadMenuItem = + MenuItemElement(applicationContext.resources.getString(R.string.button_mark_as_read)) + val deleteAllMenuItem = + MenuItemElement(applicationContext.resources.getString(R.string.button_delete_notifications)) + val settingsMenuItem = + MenuItemElement(applicationContext.resources.getString(R.string.settings)) + val notificationRecylerView = RecyclerViewElement(R.id.notification_table_recycler_view) + val alertDialogDeleteAll = ChoicePromptDialog( + applicationContext.resources.getString(R.string.delete_all_notifications_confirmation) + ) } diff --git a/app/src/androidTest/java/bisq/android/screens/PairingScanScreen.kt b/app/src/androidTest/java/bisq/android/screens/PairingScanScreen.kt index d3f0518..8b7def2 100644 --- a/app/src/androidTest/java/bisq/android/screens/PairingScanScreen.kt +++ b/app/src/androidTest/java/bisq/android/screens/PairingScanScreen.kt @@ -21,6 +21,5 @@ import bisq.android.R import bisq.android.screens.elements.ButtonElement class PairingScanScreen : Screen() { - - val noWebcamButton = ButtonElement(R.id.noWebcamButton) + val noWebcamButton = ButtonElement(R.id.pairing_scan_no_webcam_button) } diff --git a/app/src/androidTest/java/bisq/android/screens/PairingSendScreen.kt b/app/src/androidTest/java/bisq/android/screens/PairingSendScreen.kt index e54dbcf..4349b5c 100644 --- a/app/src/androidTest/java/bisq/android/screens/PairingSendScreen.kt +++ b/app/src/androidTest/java/bisq/android/screens/PairingSendScreen.kt @@ -21,6 +21,5 @@ import bisq.android.R import bisq.android.screens.elements.ButtonElement class PairingSendScreen : Screen() { - - val sendPairingTokenButton = ButtonElement(R.id.sendPairingTokenButton) + val sendPairingTokenButton = ButtonElement(R.id.pairing_send_pairing_token_button) } diff --git a/app/src/androidTest/java/bisq/android/screens/PairingSuccessScreen.kt b/app/src/androidTest/java/bisq/android/screens/PairingSuccessScreen.kt index f0b4ba3..7047d8e 100644 --- a/app/src/androidTest/java/bisq/android/screens/PairingSuccessScreen.kt +++ b/app/src/androidTest/java/bisq/android/screens/PairingSuccessScreen.kt @@ -21,6 +21,5 @@ import bisq.android.R import bisq.android.screens.elements.ButtonElement class PairingSuccessScreen : Screen() { - - val pairingCompleteButton = ButtonElement(R.id.pairing_complete_button) + val pairingCompleteButton = ButtonElement(R.id.pairing_scan_pairing_complete_button) } diff --git a/app/src/androidTest/java/bisq/android/screens/Screen.kt b/app/src/androidTest/java/bisq/android/screens/Screen.kt index f9d484d..ace9050 100644 --- a/app/src/androidTest/java/bisq/android/screens/Screen.kt +++ b/app/src/androidTest/java/bisq/android/screens/Screen.kt @@ -20,7 +20,6 @@ package bisq.android.screens import android.content.Context import androidx.test.core.app.ApplicationProvider -open class Screen { - +abstract class Screen { protected val applicationContext: Context = ApplicationProvider.getApplicationContext() } diff --git a/app/src/androidTest/java/bisq/android/screens/SettingsScreen.kt b/app/src/androidTest/java/bisq/android/screens/SettingsScreen.kt index 9283a8f..2085dca 100644 --- a/app/src/androidTest/java/bisq/android/screens/SettingsScreen.kt +++ b/app/src/androidTest/java/bisq/android/screens/SettingsScreen.kt @@ -21,20 +21,23 @@ import bisq.android.BISQ_MOBILE_URL import bisq.android.BISQ_NETWORK_URL import bisq.android.R import bisq.android.screens.dialogs.ChoicePromptDialog -import bisq.android.screens.elements.ButtonElement +import bisq.android.screens.dialogs.ThemePromptDialog +import bisq.android.screens.elements.PreferenceElement class SettingsScreen : Screen() { - - val aboutBisqButton = ButtonElement(R.id.settingsAboutBisqButton) - val aboutAppButton = ButtonElement(R.id.settingsAboutAppButton) + val themePreference = PreferenceElement(applicationContext.getString(R.string.theme)) + val themePromptDialog = ThemePromptDialog() + val resetPairingPreference = PreferenceElement(applicationContext.getString(R.string.reset_pairing)) + val alertDialogResetPairing = ChoicePromptDialog( + applicationContext.resources.getString(R.string.register_again_confirmation) + ) + val scanPairingTokenPreference = PreferenceElement(applicationContext.getString(R.string.scan_pairing_token)) + val aboutBisqPreference = PreferenceElement(applicationContext.getString(R.string.about_bisq)) + val aboutAppPreference = PreferenceElement(applicationContext.getString(R.string.about_this_app)) val alertDialogLoadBisqNetworkUrl = ChoicePromptDialog( - applicationContext.resources.getString(R.string.load_web_page_text, BISQ_NETWORK_URL) + applicationContext.resources.getString(R.string.load_web_page_confirmation, BISQ_NETWORK_URL) ) val alertDialogLoadBisqMobileUrl = ChoicePromptDialog( - applicationContext.resources.getString(R.string.load_web_page_text, BISQ_MOBILE_URL) + applicationContext.resources.getString(R.string.load_web_page_confirmation, BISQ_MOBILE_URL) ) - val resetButton = ButtonElement(R.id.settingsRegisterAgainButton) - val deleteNotificationsButton = ButtonElement(R.id.settingsDeleteAllNotificationsButton) - val markAsReadButton = ButtonElement(R.id.settingsMarkAsReadButton) - val addExampleNotificationsButton = ButtonElement(R.id.settingsAddExampleButton) } diff --git a/app/src/androidTest/java/bisq/android/screens/WelcomeScreen.kt b/app/src/androidTest/java/bisq/android/screens/WelcomeScreen.kt index 641298c..993f66f 100644 --- a/app/src/androidTest/java/bisq/android/screens/WelcomeScreen.kt +++ b/app/src/androidTest/java/bisq/android/screens/WelcomeScreen.kt @@ -24,9 +24,8 @@ import bisq.android.screens.dialogs.PromptDialog import bisq.android.screens.elements.ButtonElement class WelcomeScreen : Screen() { - - val pairButton = ButtonElement(R.id.pairButton) - val learnMoreButton = ButtonElement(R.id.learnMoreButton) + val pairButton = ButtonElement(R.id.welcome_pair_button) + val learnMoreButton = ButtonElement(R.id.welcome_learn_more_button) val alertDialogGooglePlayServicesUnavailable = PromptDialog( applicationContext.resources.getString(R.string.google_play_services_unavailable) ) @@ -34,6 +33,6 @@ class WelcomeScreen : Screen() { applicationContext.resources.getString(R.string.cannot_retrieve_fcm_token) ) val alertDialogLoadBisqMobileUrl = ChoicePromptDialog( - applicationContext.resources.getString(R.string.load_web_page_text, BISQ_MOBILE_URL) + applicationContext.resources.getString(R.string.load_web_page_confirmation, BISQ_MOBILE_URL) ) } diff --git a/app/src/androidTest/java/bisq/android/screens/dialogs/Dialog.kt b/app/src/androidTest/java/bisq/android/screens/dialogs/Dialog.kt index 9b0793d..592b955 100644 --- a/app/src/androidTest/java/bisq/android/screens/dialogs/Dialog.kt +++ b/app/src/androidTest/java/bisq/android/screens/dialogs/Dialog.kt @@ -17,6 +17,8 @@ package bisq.android.screens.dialogs +import android.content.Context +import androidx.test.core.app.ApplicationProvider import androidx.test.espresso.Espresso.onView import androidx.test.espresso.NoMatchingViewException import androidx.test.espresso.assertion.ViewAssertions.matches @@ -24,7 +26,8 @@ import androidx.test.espresso.matcher.ViewMatchers.Visibility import androidx.test.espresso.matcher.ViewMatchers.withEffectiveVisibility import androidx.test.espresso.matcher.ViewMatchers.withText -open class Dialog(private val message: String) { +abstract class Dialog(private val message: String) { + protected val applicationContext: Context = ApplicationProvider.getApplicationContext() fun isDisplayed(): Boolean { return try { diff --git a/app/src/androidTest/java/bisq/android/screens/dialogs/PromptDialog.kt b/app/src/androidTest/java/bisq/android/screens/dialogs/PromptDialog.kt index ca91e8a..482a7b6 100644 --- a/app/src/androidTest/java/bisq/android/screens/dialogs/PromptDialog.kt +++ b/app/src/androidTest/java/bisq/android/screens/dialogs/PromptDialog.kt @@ -20,6 +20,6 @@ package bisq.android.screens.dialogs import android.R import bisq.android.screens.elements.ButtonElement -class PromptDialog(message: String) : Dialog(message) { - val button = ButtonElement(R.id.button1) +open class PromptDialog(message: String) : Dialog(message) { + val dismissButton = ButtonElement(R.id.button1) } diff --git a/app/src/androidTest/java/bisq/android/screens/dialogs/ThemePromptDialog.kt b/app/src/androidTest/java/bisq/android/screens/dialogs/ThemePromptDialog.kt new file mode 100644 index 0000000..0d99ff0 --- /dev/null +++ b/app/src/androidTest/java/bisq/android/screens/dialogs/ThemePromptDialog.kt @@ -0,0 +1,36 @@ +/* + * This file is part of Bisq. + * + * Bisq is free software: you can redistribute it and/or modify it + * under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or (at + * your option) any later version. + * + * Bisq is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public + * License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with Bisq. If not, see . + */ + +package bisq.android.screens.dialogs + +import android.content.Context +import androidx.test.core.app.ApplicationProvider +import bisq.android.R +import bisq.android.screens.elements.ButtonElement +import bisq.android.screens.elements.SelectionElement + +class ThemePromptDialog : PromptDialog(message) { + val darkThemeSelection = SelectionElement(applicationContext.getString(R.string.dark_theme)) + val lightThemeSelection = SelectionElement(applicationContext.getString(R.string.light_theme)) + val systemThemeSelection = SelectionElement(applicationContext.getString(R.string.system_theme)) + val cancelButton = ButtonElement(android.R.id.button2) + + companion object { + private val applicationContext: Context = ApplicationProvider.getApplicationContext() + val message: String = applicationContext.getString(R.string.theme) + } +} diff --git a/app/src/androidTest/java/bisq/android/screens/elements/ButtonElement.kt b/app/src/androidTest/java/bisq/android/screens/elements/ButtonElement.kt index 3d9b6d3..e13b2da 100644 --- a/app/src/androidTest/java/bisq/android/screens/elements/ButtonElement.kt +++ b/app/src/androidTest/java/bisq/android/screens/elements/ButtonElement.kt @@ -22,9 +22,8 @@ import androidx.test.espresso.action.ViewActions import androidx.test.espresso.assertion.ViewAssertions import androidx.test.espresso.matcher.ViewMatchers -class ButtonElement(private val id: Int) : Element(id) { - - fun click() { +class ButtonElement(private val id: Int) : ElementById(id), ClickableElement { + override fun click() { Espresso.onView(ViewMatchers.withId(id)) .check(ViewAssertions.matches(ViewMatchers.isDisplayed())) .perform(ViewActions.click()) diff --git a/app/src/androidTest/java/bisq/android/screens/elements/ClickableElement.kt b/app/src/androidTest/java/bisq/android/screens/elements/ClickableElement.kt new file mode 100644 index 0000000..075e95c --- /dev/null +++ b/app/src/androidTest/java/bisq/android/screens/elements/ClickableElement.kt @@ -0,0 +1,22 @@ +/* + * This file is part of Bisq. + * + * Bisq is free software: you can redistribute it and/or modify it + * under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or (at + * your option) any later version. + * + * Bisq is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public + * License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with Bisq. If not, see . + */ + +package bisq.android.screens.elements + +interface ClickableElement { + fun click() +} diff --git a/app/src/androidTest/java/bisq/android/screens/elements/Element.kt b/app/src/androidTest/java/bisq/android/screens/elements/Element.kt index d6ed21d..cec1a60 100644 --- a/app/src/androidTest/java/bisq/android/screens/elements/Element.kt +++ b/app/src/androidTest/java/bisq/android/screens/elements/Element.kt @@ -17,15 +17,18 @@ package bisq.android.screens.elements +import android.view.View import androidx.test.espresso.Espresso import androidx.test.espresso.assertion.ViewAssertions import androidx.test.espresso.matcher.ViewMatchers +import org.hamcrest.Matcher -open class Element(private val id: Int) { +abstract class Element { + abstract fun getViewMatcher(): Matcher? fun isDisplayed(): Boolean { try { - Espresso.onView(ViewMatchers.withId(id)) + Espresso.onView(getViewMatcher()) .check(ViewAssertions.matches(ViewMatchers.isDisplayed())) } catch (e: AssertionError) { return false @@ -35,7 +38,7 @@ open class Element(private val id: Int) { fun isEnabled(): Boolean { try { - Espresso.onView(ViewMatchers.withId(id)) + Espresso.onView(getViewMatcher()) .check(ViewAssertions.matches(ViewMatchers.isDisplayed())) .check(ViewAssertions.matches(ViewMatchers.isEnabled())) } catch (e: AssertionError) { diff --git a/app/src/androidTest/java/bisq/android/screens/elements/ElementById.kt b/app/src/androidTest/java/bisq/android/screens/elements/ElementById.kt new file mode 100644 index 0000000..73d1087 --- /dev/null +++ b/app/src/androidTest/java/bisq/android/screens/elements/ElementById.kt @@ -0,0 +1,28 @@ +/* + * This file is part of Bisq. + * + * Bisq is free software: you can redistribute it and/or modify it + * under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or (at + * your option) any later version. + * + * Bisq is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public + * License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with Bisq. If not, see . + */ + +package bisq.android.screens.elements + +import android.view.View +import androidx.test.espresso.matcher.ViewMatchers +import org.hamcrest.Matcher + +abstract class ElementById(private val id: Int) : Element() { + override fun getViewMatcher(): Matcher? { + return ViewMatchers.withId(id) + } +} diff --git a/app/src/androidTest/java/bisq/android/screens/elements/ElementByText.kt b/app/src/androidTest/java/bisq/android/screens/elements/ElementByText.kt new file mode 100644 index 0000000..57eebc7 --- /dev/null +++ b/app/src/androidTest/java/bisq/android/screens/elements/ElementByText.kt @@ -0,0 +1,28 @@ +/* + * This file is part of Bisq. + * + * Bisq is free software: you can redistribute it and/or modify it + * under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or (at + * your option) any later version. + * + * Bisq is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public + * License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with Bisq. If not, see . + */ + +package bisq.android.screens.elements + +import android.view.View +import androidx.test.espresso.matcher.ViewMatchers +import org.hamcrest.Matcher + +abstract class ElementByText(private val text: String) : Element() { + override fun getViewMatcher(): Matcher? { + return ViewMatchers.withText(text) + } +} diff --git a/app/src/androidTest/java/bisq/android/screens/elements/MenuItemElement.kt b/app/src/androidTest/java/bisq/android/screens/elements/MenuItemElement.kt new file mode 100644 index 0000000..b5282e6 --- /dev/null +++ b/app/src/androidTest/java/bisq/android/screens/elements/MenuItemElement.kt @@ -0,0 +1,35 @@ +/* + * This file is part of Bisq. + * + * Bisq is free software: you can redistribute it and/or modify it + * under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or (at + * your option) any later version. + * + * Bisq is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public + * License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with Bisq. If not, see . + */ + +package bisq.android.screens.elements + +import androidx.test.espresso.Espresso +import androidx.test.espresso.action.ViewActions +import androidx.test.espresso.assertion.ViewAssertions +import androidx.test.espresso.matcher.RootMatchers.isPlatformPopup +import androidx.test.espresso.matcher.ViewMatchers +import androidx.test.platform.app.InstrumentationRegistry.getInstrumentation + +class MenuItemElement(private val text: String) : ElementByText(text), ClickableElement { + override fun click() { + Espresso.openActionBarOverflowOrOptionsMenu(getInstrumentation().targetContext) + Espresso.onView(ViewMatchers.withText(text)) + .inRoot(isPlatformPopup()) + .check(ViewAssertions.matches(ViewMatchers.isDisplayed())) + .perform(ViewActions.click()) + } +} diff --git a/app/src/androidTest/java/bisq/android/screens/elements/PreferenceElement.kt b/app/src/androidTest/java/bisq/android/screens/elements/PreferenceElement.kt new file mode 100644 index 0000000..cb8765d --- /dev/null +++ b/app/src/androidTest/java/bisq/android/screens/elements/PreferenceElement.kt @@ -0,0 +1,31 @@ +/* + * This file is part of Bisq. + * + * Bisq is free software: you can redistribute it and/or modify it + * under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or (at + * your option) any later version. + * + * Bisq is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public + * License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with Bisq. If not, see . + */ + +package bisq.android.screens.elements + +import androidx.test.espresso.Espresso +import androidx.test.espresso.action.ViewActions +import androidx.test.espresso.assertion.ViewAssertions +import androidx.test.espresso.matcher.ViewMatchers + +class PreferenceElement(private val text: String) : ElementByText(text), ClickableElement { + override fun click() { + Espresso.onView(ViewMatchers.withText(text)) + .check(ViewAssertions.matches(ViewMatchers.isDisplayed())) + .perform(ViewActions.click()) + } +} diff --git a/app/src/androidTest/java/bisq/android/screens/elements/RecyclerViewElement.kt b/app/src/androidTest/java/bisq/android/screens/elements/RecyclerViewElement.kt index 791ab66..1ab3635 100644 --- a/app/src/androidTest/java/bisq/android/screens/elements/RecyclerViewElement.kt +++ b/app/src/androidTest/java/bisq/android/screens/elements/RecyclerViewElement.kt @@ -20,8 +20,9 @@ package bisq.android.screens.elements import android.view.View import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView +import androidx.recyclerview.widget.RecyclerView.ViewHolder import androidx.test.espresso.Espresso.onView -import androidx.test.espresso.action.GeneralLocation +import androidx.test.espresso.action.CoordinatesProvider import androidx.test.espresso.action.GeneralSwipeAction import androidx.test.espresso.action.Press import androidx.test.espresso.action.Swipe @@ -31,44 +32,49 @@ import androidx.test.espresso.contrib.RecyclerViewActions.actionOnItemAtPosition import androidx.test.espresso.contrib.RecyclerViewActions.scrollToPosition import androidx.test.espresso.matcher.ViewMatchers import androidx.test.espresso.matcher.ViewMatchers.withId -import bisq.android.ui.notification.NotificationAdapter import org.hamcrest.CoreMatchers.allOf import org.hamcrest.Description import org.hamcrest.Matcher import org.hamcrest.TypeSafeMatcher -class RecyclerViewElement(private val id: Int) : Element(id) { - +class RecyclerViewElement(private val id: Int) : ElementById(id) { fun scrollToPosition(position: Int) { onView(withId(id)).perform( - scrollToPosition(position) + scrollToPosition(position) ) } fun clickAtPosition(position: Int) { onView(withId(id)).perform( - scrollToPosition(position), - actionOnItemAtPosition(position, click()) + scrollToPosition(position), + actionOnItemAtPosition(position, click()) ) } - fun swipeToDeleteAtPosition(position: Int) { + fun swipeRightToLeftAtPosition(position: Int) { + val from = CoordinatesProvider { it.getPointCoordinatesOfView(0.75f, 0.5f) } + val to = CoordinatesProvider { it.getPointCoordinatesOfView(0f, 0.5f) } + val swipeAction = GeneralSwipeAction(Swipe.FAST, from, to, Press.FINGER) onView(withId(id)).perform( - scrollToPosition(position), - actionOnItemAtPosition( - position, - GeneralSwipeAction( - Swipe.FAST, GeneralLocation.BOTTOM_RIGHT, GeneralLocation.BOTTOM_LEFT, - Press.FINGER - ) - ) + scrollToPosition(position), + actionOnItemAtPosition(position, swipeAction) ) } + private fun View.getPointCoordinatesOfView(xPercent: Float, yPercent: Float): FloatArray { + val xy = IntArray(2).apply { getLocationOnScreen(this) } + val x = xy[0] + (width - 1) * xPercent + val y = xy[1] + (height - 1) * yPercent + return floatArrayOf(x, y) + } + fun getItemCount(): Int { val count = intArrayOf(0) val matcher: Matcher = object : TypeSafeMatcher() { - override fun describeTo(description: Description?) {} + override fun describeTo(description: Description?) { + // Intentionally left empty + } + override fun matchesSafely(item: View?): Boolean { count[0] = (item as RecyclerView).adapter!!.itemCount return true @@ -81,7 +87,10 @@ class RecyclerViewElement(private val id: Int) : Element(id) { fun getScrollPosition(): Int { val position = intArrayOf(0) val matcher: Matcher = object : TypeSafeMatcher() { - override fun describeTo(description: Description?) {} + override fun describeTo(description: Description?) { + // Intentionally left empty + } + override fun matchesSafely(item: View?): Boolean { position[0] = ((item as RecyclerView).layoutManager!! as LinearLayoutManager) .findFirstVisibleItemPosition() @@ -92,34 +101,20 @@ class RecyclerViewElement(private val id: Int) : Element(id) { return position[0] } - fun getContentAtPosition(position: Int): String { - var content = String() + fun getContentAtPosition(position: Int): ViewHolder { + var viewHolder: ViewHolder? = null val matcher: Matcher = object : TypeSafeMatcher() { - override fun describeTo(description: Description?) {} - override fun matchesSafely(item: View?): Boolean { - val viewHolder = (item as RecyclerView).findViewHolderForAdapterPosition(position) - ?: return false // has no item on such position - content = - (viewHolder as NotificationAdapter.NotificationViewHolder).title.text.toString() - return true + override fun describeTo(description: Description?) { + // Intentionally left empty } - } - onView(allOf(withId(id), ViewMatchers.isDisplayed())).check(matches(matcher)) - return content - } - fun getReadStateAtPosition(position: Int): Boolean { - var readState = false - val matcher: Matcher = object : TypeSafeMatcher() { - override fun describeTo(description: Description?) {} override fun matchesSafely(item: View?): Boolean { - val viewHolder = (item as RecyclerView).findViewHolderForAdapterPosition(position) + viewHolder = (item as RecyclerView).findViewHolderForAdapterPosition(position) ?: return false // has no item on such position - readState = (viewHolder as NotificationAdapter.NotificationViewHolder).read return true } } onView(allOf(withId(id), ViewMatchers.isDisplayed())).check(matches(matcher)) - return readState + return viewHolder!! } } diff --git a/app/src/androidTest/java/bisq/android/screens/elements/SelectionElement.kt b/app/src/androidTest/java/bisq/android/screens/elements/SelectionElement.kt new file mode 100644 index 0000000..e471444 --- /dev/null +++ b/app/src/androidTest/java/bisq/android/screens/elements/SelectionElement.kt @@ -0,0 +1,31 @@ +/* + * This file is part of Bisq. + * + * Bisq is free software: you can redistribute it and/or modify it + * under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or (at + * your option) any later version. + * + * Bisq is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public + * License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with Bisq. If not, see . + */ + +package bisq.android.screens.elements + +import androidx.test.espresso.Espresso +import androidx.test.espresso.action.ViewActions +import androidx.test.espresso.assertion.ViewAssertions +import androidx.test.espresso.matcher.ViewMatchers + +class SelectionElement(private val text: String) : ElementByText(text), ClickableElement { + override fun click() { + Espresso.onView(ViewMatchers.withText(text)) + .check(ViewAssertions.matches(ViewMatchers.isDisplayed())) + .perform(ViewActions.click()) + } +} diff --git a/app/src/androidTest/java/bisq/android/screens/elements/TextElement.kt b/app/src/androidTest/java/bisq/android/screens/elements/TextElement.kt index 8fa3002..2f2a706 100644 --- a/app/src/androidTest/java/bisq/android/screens/elements/TextElement.kt +++ b/app/src/androidTest/java/bisq/android/screens/elements/TextElement.kt @@ -26,8 +26,7 @@ import androidx.test.espresso.matcher.ViewMatchers import androidx.test.espresso.matcher.ViewMatchers.isAssignableFrom import org.hamcrest.Matcher -class TextElement(private val id: Int) : Element(id) { - +class TextElement(private val id: Int) : ElementById(id) { fun getText(): String { var text = String() Espresso.onView(ViewMatchers.withId(id)).perform(object : ViewAction { diff --git a/app/src/androidTest/java/bisq/android/tests/NotificationDetailTest.kt b/app/src/androidTest/java/bisq/android/tests/NotificationDetailTest.kt index b049cb7..e56b090 100644 --- a/app/src/androidTest/java/bisq/android/tests/NotificationDetailTest.kt +++ b/app/src/androidTest/java/bisq/android/tests/NotificationDetailTest.kt @@ -32,7 +32,6 @@ import org.junit.runner.RunWith @RunWith(AndroidJUnit4::class) class NotificationDetailTest : BaseTest() { - @Before override fun setup() { super.setup() @@ -42,11 +41,10 @@ class NotificationDetailTest : BaseTest() { @Test fun notificationDetailsArePopulatedCorrectly() { ActivityScenario.launch(NotificationTableActivity::class.java).use { - notificationTableScreen.settingsButton.click() - settingsScreen.addExampleNotificationsButton.click() + notificationTableScreen.addExampleNotificationsMenuItem.click() notificationTableScreen.notificationRecylerView.clickAtPosition(2) Intents.intended(IntentMatchers.hasComponent(NotificationDetailActivity::class.java.name)) - assertEquals("(example) Dispute message", notificationDetailScreen.title.getText()) + assertEquals("Dispute message", notificationDetailScreen.title.getText()) assertEquals( "You received a dispute message for trade with ID 34059340", notificationDetailScreen.message.getText() @@ -62,4 +60,17 @@ class NotificationDetailTest : BaseTest() { ) } } + + @Test + fun clickDeleteButtonDeletesNotification() { + ActivityScenario.launch(NotificationTableActivity::class.java).use { + notificationTableScreen.addExampleNotificationsMenuItem.click() + val countBeforeSwipe = notificationTableScreen.notificationRecylerView.getItemCount() + notificationTableScreen.notificationRecylerView.clickAtPosition(0) + Intents.intended(IntentMatchers.hasComponent(NotificationDetailActivity::class.java.name)) + notificationDetailScreen.deleteButton.click() + val countAfterDelete = notificationTableScreen.notificationRecylerView.getItemCount() + assertEquals(countBeforeSwipe - 1, countAfterDelete) + } + } } diff --git a/app/src/androidTest/java/bisq/android/tests/NotificationTableTest.kt b/app/src/androidTest/java/bisq/android/tests/NotificationTableTest.kt index d7c8146..07a61a0 100644 --- a/app/src/androidTest/java/bisq/android/tests/NotificationTableTest.kt +++ b/app/src/androidTest/java/bisq/android/tests/NotificationTableTest.kt @@ -22,8 +22,11 @@ import androidx.test.espresso.Espresso.pressBack import androidx.test.espresso.intent.Intents import androidx.test.espresso.intent.matcher.IntentMatchers import androidx.test.ext.junit.runners.AndroidJUnit4 +import bisq.android.ui.notification.NotificationAdapter import bisq.android.ui.notification.NotificationDetailActivity import bisq.android.ui.notification.NotificationTableActivity +import bisq.android.ui.settings.SettingsActivity +import org.junit.Assert import org.junit.Assert.assertEquals import org.junit.Before import org.junit.Test @@ -31,7 +34,6 @@ import org.junit.runner.RunWith @RunWith(AndroidJUnit4::class) class NotificationTableTest : BaseTest() { - @Before override fun setup() { super.setup() @@ -41,8 +43,7 @@ class NotificationTableTest : BaseTest() { @Test fun clickNotificationLoadsNotificationDetailActivity() { ActivityScenario.launch(NotificationTableActivity::class.java).use { - notificationTableScreen.settingsButton.click() - settingsScreen.addExampleNotificationsButton.click() + notificationTableScreen.addExampleNotificationsMenuItem.click() notificationTableScreen.notificationRecylerView.clickAtPosition(0) Intents.intended(IntentMatchers.hasComponent(NotificationDetailActivity::class.java.name)) } @@ -51,15 +52,13 @@ class NotificationTableTest : BaseTest() { @Test fun viewedNotificationIsMarkedAsRead() { ActivityScenario.launch(NotificationTableActivity::class.java).use { - notificationTableScreen.settingsButton.click() - settingsScreen.addExampleNotificationsButton.click() - var readState = - notificationTableScreen.notificationRecylerView.getReadStateAtPosition(0) + notificationTableScreen.addExampleNotificationsMenuItem.click() + var readState = getContentAtPosition(0).read assertEquals(false, readState) notificationTableScreen.notificationRecylerView.clickAtPosition(0) Intents.intended(IntentMatchers.hasComponent(NotificationDetailActivity::class.java.name)) pressBack() - readState = notificationTableScreen.notificationRecylerView.getReadStateAtPosition(0) + readState = getContentAtPosition(0).read assertEquals(true, readState) } } @@ -67,10 +66,9 @@ class NotificationTableTest : BaseTest() { @Test fun swipeToDeleteNotificationDeletesNotification() { ActivityScenario.launch(NotificationTableActivity::class.java).use { - notificationTableScreen.settingsButton.click() - settingsScreen.addExampleNotificationsButton.click() + notificationTableScreen.addExampleNotificationsMenuItem.click() val countBeforeSwipe = notificationTableScreen.notificationRecylerView.getItemCount() - notificationTableScreen.notificationRecylerView.swipeToDeleteAtPosition(0) + notificationTableScreen.notificationRecylerView.swipeRightToLeftAtPosition(0) val countAfterSwipe = notificationTableScreen.notificationRecylerView.getItemCount() assertEquals(countBeforeSwipe - 1, countAfterSwipe) } @@ -80,8 +78,7 @@ class NotificationTableTest : BaseTest() { fun scrollPositionIsRetainedWhenNavigatingBack() { ActivityScenario.launch(NotificationTableActivity::class.java).use { for (counter in 1..5) { - notificationTableScreen.settingsButton.click() - settingsScreen.addExampleNotificationsButton.click() + notificationTableScreen.addExampleNotificationsMenuItem.click() } notificationTableScreen.notificationRecylerView.scrollToPosition( notificationTableScreen.notificationRecylerView.getItemCount() - 1 @@ -99,4 +96,72 @@ class NotificationTableTest : BaseTest() { ) } } + + @Test + fun clickDeleteAllNotificationsMenuItemAndNotAcceptingConfirmationDoesNotDeleteNotifications() { + ActivityScenario.launch(NotificationTableActivity::class.java).use { + notificationTableScreen.addExampleNotificationsMenuItem.click() + + val itemCount = notificationTableScreen.notificationRecylerView.getItemCount() + + Assert.assertTrue(itemCount > 0) + + notificationTableScreen.deleteAllMenuItem.click() + Assert.assertTrue(notificationTableScreen.alertDialogDeleteAll.isDisplayed()) + + notificationTableScreen.alertDialogDeleteAll.negativeButton.click() + + assertEquals(itemCount, notificationTableScreen.notificationRecylerView.getItemCount()) + } + } + + @Test + fun clickDeleteAllNotificationsMenuItemAndAcceptingConfirmationDeletesAllNotifications() { + ActivityScenario.launch(NotificationTableActivity::class.java).use { + notificationTableScreen.addExampleNotificationsMenuItem.click() + + Assert.assertTrue(notificationTableScreen.notificationRecylerView.getItemCount() > 0) + + notificationTableScreen.deleteAllMenuItem.click() + Assert.assertTrue(notificationTableScreen.alertDialogDeleteAll.isDisplayed()) + + notificationTableScreen.alertDialogDeleteAll.positiveButton.click() + + Assert.assertTrue(notificationTableScreen.notificationRecylerView.getItemCount() == 0) + } + } + + @Test + fun clickMarkAsReadMenuItemMarksAllNotificationsAsRead() { + ActivityScenario.launch(NotificationTableActivity::class.java).use { + notificationTableScreen.addExampleNotificationsMenuItem.click() + + val count = notificationTableScreen.notificationRecylerView.getItemCount() + for (position in 0 until count - 1) { + val readState = getContentAtPosition(position).read + assertEquals(false, readState) + } + + notificationTableScreen.markAllAsReadMenuItem.click() + + for (position in 0 until count - 1) { + val readState = getContentAtPosition(position).read + assertEquals(true, readState) + } + } + } + + @Test + fun clickSettingsMenuItemLoadsSettingsActivity() { + ActivityScenario.launch(NotificationTableActivity::class.java).use { + notificationTableScreen.settingsMenuItem.click() + Intents.intended(IntentMatchers.hasComponent(SettingsActivity::class.java.name)) + } + } + + private fun getContentAtPosition(position: Int): NotificationAdapter.NotificationViewHolder { + val viewHolder = + notificationTableScreen.notificationRecylerView.getContentAtPosition(position) + return viewHolder as NotificationAdapter.NotificationViewHolder + } } diff --git a/app/src/androidTest/java/bisq/android/tests/PairingScanTest.kt b/app/src/androidTest/java/bisq/android/tests/PairingScanTest.kt index 2fe56b7..6d21f8a 100644 --- a/app/src/androidTest/java/bisq/android/tests/PairingScanTest.kt +++ b/app/src/androidTest/java/bisq/android/tests/PairingScanTest.kt @@ -28,7 +28,6 @@ import org.junit.runner.RunWith @RunWith(AndroidJUnit4::class) class PairingScanTest : BaseTest() { - @Test fun clickNoWebcamButtonLoadsPairingSendActivity() { ActivityScenario.launch(PairingScanActivity::class.java).use { diff --git a/app/src/androidTest/java/bisq/android/tests/PairingSendTest.kt b/app/src/androidTest/java/bisq/android/tests/PairingSendTest.kt index a052809..6214a43 100644 --- a/app/src/androidTest/java/bisq/android/tests/PairingSendTest.kt +++ b/app/src/androidTest/java/bisq/android/tests/PairingSendTest.kt @@ -31,11 +31,11 @@ import org.junit.runner.RunWith @RunWith(AndroidJUnit4::class) class PairingSendTest : BaseTest() { - @Test fun clickSendPairingTokenButton() { Device.instance.newToken( - "fnWtGaJGSByKiPwT71O3Lo:APA91bGU05lvoKxvz3Y0fnFHytSveA_juVjq2QMY3_H9URqDsEpLHGbLSFBN3wY7YdHDD3w52GECwRWuKGBJm1O1f5fJhVvcr1rJxo94aDjoWwsnkVp-ecWwh5YY_MQ6LRqbWzumCeX_" + "fnWtGaJGSByKiPwT71O3Lo:APA91bGU05lvoKxvz3Y0fnFHytSveA_juVjq2QMY3_H9URqDsEp" + + "LHGbLSFBN3wY7YdHDD3w52GECwRWuKGBJm1O1f5fJhVvcr1rJxo94aDjoWwsnkVp-ecWwh5YY_MQ6LRqbWzumCeX_" ) ActivityScenario.launch(PairingSendActivity::class.java).use { pairingSendScreen.sendPairingTokenButton.click() diff --git a/app/src/androidTest/java/bisq/android/tests/PairingSuccessTest.kt b/app/src/androidTest/java/bisq/android/tests/PairingSuccessTest.kt index c4f3c6f..12cd3de 100644 --- a/app/src/androidTest/java/bisq/android/tests/PairingSuccessTest.kt +++ b/app/src/androidTest/java/bisq/android/tests/PairingSuccessTest.kt @@ -29,7 +29,6 @@ import org.junit.runner.RunWith @RunWith(AndroidJUnit4::class) class PairingSuccessTest : BaseTest() { - @Before override fun setup() { super.setup() diff --git a/app/src/androidTest/java/bisq/android/tests/SettingsTest.kt b/app/src/androidTest/java/bisq/android/tests/SettingsTest.kt index 2756cdd..5b5429e 100644 --- a/app/src/androidTest/java/bisq/android/tests/SettingsTest.kt +++ b/app/src/androidTest/java/bisq/android/tests/SettingsTest.kt @@ -19,28 +19,31 @@ package bisq.android.tests import android.app.Activity import android.app.Instrumentation +import android.app.UiModeManager import android.content.Intent -import android.os.Build +import androidx.appcompat.app.AppCompatDelegate import androidx.test.core.app.ActivityScenario import androidx.test.espresso.intent.Intents.intended import androidx.test.espresso.intent.Intents.intending import androidx.test.espresso.intent.matcher.IntentMatchers import androidx.test.ext.junit.runners.AndroidJUnit4 -import androidx.test.filters.SdkSuppress import bisq.android.BISQ_MOBILE_URL import bisq.android.BISQ_NETWORK_URL -import bisq.android.mocks.FirebaseMock import bisq.android.model.Device import bisq.android.model.DeviceStatus import bisq.android.services.BisqFirebaseMessagingService.Companion.isFirebaseMessagingInitialized -import bisq.android.ui.notification.NotificationTableActivity +import bisq.android.testCommon.mocks.FirebaseMock +import bisq.android.ui.pairing.PairingScanActivity import bisq.android.ui.settings.SettingsActivity import bisq.android.ui.welcome.WelcomeActivity +import junit.framework.AssertionFailedError import org.awaitility.Durations.TEN_SECONDS import org.awaitility.kotlin.atMost import org.awaitility.kotlin.await import org.awaitility.kotlin.untilNotNull +import org.hamcrest.core.AllOf import org.junit.After +import org.junit.Assert import org.junit.Assert.assertEquals import org.junit.Assert.assertNotEquals import org.junit.Assert.assertNotNull @@ -51,7 +54,6 @@ import org.junit.runner.RunWith @RunWith(AndroidJUnit4::class) class SettingsTest : BaseTest() { - @Before override fun setup() { super.setup() @@ -65,15 +67,85 @@ class SettingsTest : BaseTest() { } @Test - @SdkSuppress(minSdkVersion = Build.VERSION_CODES.P) - fun clickResetButtonWipesPairingAndLoadsWelcomeScreen() { + fun clickThemePromptsToChangeTheme() { + ActivityScenario.launch(SettingsActivity::class.java).use { + settingsScreen.themePreference.click() + assertTrue(settingsScreen.themePromptDialog.isDisplayed()) + + settingsScreen.themePromptDialog.cancelButton.click() + intended(IntentMatchers.hasComponent(SettingsActivity::class.java.name)) + } + } + + @Test + fun clickDarkThemeChangesToDarkTheme() { + ActivityScenario.launch(SettingsActivity::class.java).use { + settingsScreen.themePreference.click() + assertTrue(settingsScreen.themePromptDialog.isDisplayed()) + + settingsScreen.themePromptDialog.darkThemeSelection.click() + + assertEquals(UiModeManager.MODE_NIGHT_YES, AppCompatDelegate.getDefaultNightMode()) + } + } + + @Test + fun clickLightThemeChangesToLightTheme() { + ActivityScenario.launch(SettingsActivity::class.java).use { + settingsScreen.themePreference.click() + assertTrue(settingsScreen.themePromptDialog.isDisplayed()) + + settingsScreen.themePromptDialog.lightThemeSelection.click() + + assertEquals(UiModeManager.MODE_NIGHT_NO, AppCompatDelegate.getDefaultNightMode()) + } + } + + @Test + fun clickSystemThemeChangesToSystemTheme() { + ActivityScenario.launch(SettingsActivity::class.java).use { + settingsScreen.themePreference.click() + assertTrue(settingsScreen.themePromptDialog.isDisplayed()) + + settingsScreen.themePromptDialog.systemThemeSelection.click() + + assertEquals(AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM, AppCompatDelegate.getDefaultNightMode()) + } + } + + @Test + fun clickResetPairingAndNotAcceptingConfirmationDoesNotWipePairing() { + if (!isFirebaseMessagingInitialized()) { + FirebaseMock.mockFirebaseTokenSuccessful() + } + val key = Device.instance.key + val token = Device.instance.token + ActivityScenario.launch(SettingsActivity::class.java).use { + settingsScreen.resetPairingPreference.click() + assertTrue(settingsScreen.alertDialogResetPairing.isDisplayed()) + + settingsScreen.alertDialogResetPairing.negativeButton.click() + + intended(IntentMatchers.hasComponent(SettingsActivity::class.java.name)) + assertEquals(key, Device.instance.key) + assertEquals(token, Device.instance.token) + assertEquals(DeviceStatus.PAIRED, Device.instance.status) + } + } + + @Test + fun clickResetPairingAndAcceptingConfirmationWipesPairingAndLoadsWelcomeScreen() { if (!isFirebaseMessagingInitialized()) { FirebaseMock.mockFirebaseTokenSuccessful() } val key = Device.instance.key val token = Device.instance.token ActivityScenario.launch(SettingsActivity::class.java).use { - settingsScreen.resetButton.click() + settingsScreen.resetPairingPreference.click() + assertTrue(settingsScreen.alertDialogResetPairing.isDisplayed()) + + settingsScreen.alertDialogResetPairing.positiveButton.click() + intended(IntentMatchers.hasComponent(WelcomeActivity::class.java.name)) await atMost TEN_SECONDS untilNotNull { Device.instance.key } assertNotNull(Device.instance.key) @@ -85,66 +157,103 @@ class SettingsTest : BaseTest() { } @Test - fun clickDeleteAllNotificationsButtonDeletesAllNotifications() { - ActivityScenario.launch(NotificationTableActivity::class.java).use { - notificationTableScreen.settingsButton.click() - settingsScreen.addExampleNotificationsButton.click() - assertTrue(notificationTableScreen.notificationRecylerView.getItemCount() > 0) - notificationTableScreen.settingsButton.click() - settingsScreen.deleteNotificationsButton.click() - assertTrue(notificationTableScreen.notificationRecylerView.getItemCount() == 0) + fun clickScanPairingTokenLoadsPairingScanActivity() { + if (!isFirebaseMessagingInitialized()) { + FirebaseMock.mockFirebaseTokenSuccessful() + } + ActivityScenario.launch(SettingsActivity::class.java).use { + settingsScreen.scanPairingTokenPreference.click() + intended(IntentMatchers.hasComponent(PairingScanActivity::class.java.name)) } } @Test - fun clickMarkAsReadButtonMarksAllNotificationsAsRead() { - ActivityScenario.launch(NotificationTableActivity::class.java).use { - notificationTableScreen.settingsButton.click() - settingsScreen.addExampleNotificationsButton.click() - - val count = notificationTableScreen.notificationRecylerView.getItemCount() - for (position in 0 until count - 1) { - val readState = - notificationTableScreen.notificationRecylerView.getReadStateAtPosition(position) - assertEquals(false, readState) - } + fun clickAboutBisqAndNotAcceptingConfirmationDoesNotLoadBisqNetworkWebpage() { + ActivityScenario.launch(SettingsActivity::class.java).use { + settingsScreen.aboutBisqPreference.click() + assertTrue(settingsScreen.alertDialogLoadBisqNetworkUrl.isDisplayed()) - notificationTableScreen.settingsButton.click() - settingsScreen.markAsReadButton.click() + settingsScreen.alertDialogLoadBisqNetworkUrl.negativeButton.click() - for (position in 0 until count - 1) { - val readState = - notificationTableScreen.notificationRecylerView.getReadStateAtPosition(position) - assertEquals(true, readState) + try { + val expectedIntent = AllOf.allOf( + IntentMatchers.hasAction(Intent.ACTION_VIEW), + IntentMatchers.hasData(BISQ_NETWORK_URL) + ) + intending(expectedIntent).respondWith(Instrumentation.ActivityResult(0, null)) + intended(expectedIntent) + } catch (e: AssertionFailedError) { + // We want the assertion to fail, since trying to negate the intended + // doesn't seem to work + return } + Assert.fail("Loaded web page after clicking cancel") } } @Test - fun clickAboutBisqButtonLoadsBisqNetworkWebpage() { + fun clickAboutBisqAndAcceptingConfirmationLoadsBisqNetworkWebpage() { ActivityScenario.launch(SettingsActivity::class.java).use { intending(IntentMatchers.hasAction(Intent.ACTION_VIEW)).respondWith( Instrumentation.ActivityResult(Activity.RESULT_OK, Intent()) ) - settingsScreen.aboutBisqButton.click() + + settingsScreen.aboutBisqPreference.click() assertTrue(settingsScreen.alertDialogLoadBisqNetworkUrl.isDisplayed()) + settingsScreen.alertDialogLoadBisqNetworkUrl.positiveButton.click() - intended(IntentMatchers.hasAction(Intent.ACTION_VIEW)) - intended(IntentMatchers.hasData(BISQ_NETWORK_URL)) + + val expectedIntent = AllOf.allOf( + IntentMatchers.hasAction(Intent.ACTION_VIEW), + IntentMatchers.hasData(BISQ_NETWORK_URL) + ) + intending(expectedIntent).respondWith(Instrumentation.ActivityResult(0, null)) + intended(expectedIntent) + } + } + + @Test + fun clickAboutAppAndNotAcceptingConfirmationDoesNotLoadBisqMobileWebpage() { + ActivityScenario.launch(SettingsActivity::class.java).use { + settingsScreen.aboutAppPreference.click() + assertTrue(settingsScreen.alertDialogLoadBisqMobileUrl.isDisplayed()) + + settingsScreen.alertDialogLoadBisqMobileUrl.negativeButton.click() + + try { + val expectedIntent = AllOf.allOf( + IntentMatchers.hasAction(Intent.ACTION_VIEW), + IntentMatchers.hasData(BISQ_MOBILE_URL) + ) + intending(expectedIntent).respondWith(Instrumentation.ActivityResult(0, null)) + intended(expectedIntent) + } catch (e: AssertionFailedError) { + // We want the assertion to fail, since trying to negate the intended + // doesn't seem to work + return + } + Assert.fail("Loaded web page after clicking cancel") } } @Test - fun clickAboutAppButtonLoadsBisqMobileWebpage() { + fun clickAboutAppAndAcceptingConfirmationLoadsBisqMobileWebpage() { ActivityScenario.launch(SettingsActivity::class.java).use { intending(IntentMatchers.hasAction(Intent.ACTION_VIEW)).respondWith( Instrumentation.ActivityResult(Activity.RESULT_OK, Intent()) ) - settingsScreen.aboutAppButton.click() + + settingsScreen.aboutAppPreference.click() assertTrue(settingsScreen.alertDialogLoadBisqMobileUrl.isDisplayed()) + settingsScreen.alertDialogLoadBisqMobileUrl.positiveButton.click() - intended(IntentMatchers.hasAction(Intent.ACTION_VIEW)) - intended(IntentMatchers.hasData(BISQ_MOBILE_URL)) + + val expectedIntent = AllOf.allOf( + IntentMatchers.hasAction(Intent.ACTION_VIEW), + IntentMatchers.hasData(BISQ_MOBILE_URL) + ) + intending(expectedIntent).respondWith(Instrumentation.ActivityResult(0, null)) + intended(expectedIntent) } } } diff --git a/app/src/androidTest/java/bisq/android/tests/WelcomeTest.kt b/app/src/androidTest/java/bisq/android/tests/WelcomeTest.kt index ec625bd..a7297c6 100644 --- a/app/src/androidTest/java/bisq/android/tests/WelcomeTest.kt +++ b/app/src/androidTest/java/bisq/android/tests/WelcomeTest.kt @@ -20,7 +20,6 @@ package bisq.android.tests import android.app.Activity import android.app.Instrumentation import android.content.Intent -import android.os.Build import androidx.test.core.app.ActivityScenario import androidx.test.espresso.intent.Intents.intended import androidx.test.espresso.intent.Intents.intending @@ -28,23 +27,24 @@ import androidx.test.espresso.intent.matcher.IntentMatchers.hasAction import androidx.test.espresso.intent.matcher.IntentMatchers.hasComponent import androidx.test.espresso.intent.matcher.IntentMatchers.hasData import androidx.test.ext.junit.runners.AndroidJUnit4 -import androidx.test.filters.SdkSuppress import bisq.android.BISQ_MOBILE_URL -import bisq.android.mocks.FirebaseMock import bisq.android.model.Device +import bisq.android.testCommon.mocks.FirebaseMock import bisq.android.ui.pairing.PairingScanActivity import bisq.android.ui.welcome.WelcomeActivity +import junit.framework.AssertionFailedError +import org.hamcrest.core.AllOf.allOf import org.junit.After import org.junit.Assert.assertEquals import org.junit.Assert.assertFalse import org.junit.Assert.assertTrue +import org.junit.Assert.fail import org.junit.Before import org.junit.Test import org.junit.runner.RunWith @RunWith(AndroidJUnit4::class) class WelcomeTest : BaseTest() { - @Before override fun setup() { super.setup() @@ -59,14 +59,13 @@ class WelcomeTest : BaseTest() { } @Test - @SdkSuppress(minSdkVersion = Build.VERSION_CODES.P) fun testClickPairButtonWhenGooglePlayServicesUnavailableShowsPrompt() { FirebaseMock.mockGooglePlayServicesNotAvailable() ActivityScenario.launch(WelcomeActivity::class.java).use { welcomeScreen.pairButton.click() assertTrue(welcomeScreen.alertDialogGooglePlayServicesUnavailable.isDisplayed()) - welcomeScreen.alertDialogGooglePlayServicesUnavailable.button.click() + welcomeScreen.alertDialogGooglePlayServicesUnavailable.dismissButton.click() assertFalse(welcomeScreen.alertDialogGooglePlayServicesUnavailable.isDisplayed()) welcomeScreen.pairButton.click() @@ -75,7 +74,6 @@ class WelcomeTest : BaseTest() { } @Test - @SdkSuppress(minSdkVersion = Build.VERSION_CODES.P) fun clickPairButtonAfterReceivingFcmTokenLoadsPairingScanActivity() { FirebaseMock.mockFirebaseTokenSuccessful() ActivityScenario.launch(WelcomeActivity::class.java).use { @@ -85,7 +83,6 @@ class WelcomeTest : BaseTest() { } @Test - @SdkSuppress(minSdkVersion = Build.VERSION_CODES.P) fun clickPairButtonAfterFailingToReceiveFcmTokenShowsPromptToRetryFetchingToken() { FirebaseMock.mockFirebaseTokenUnsuccessful() ActivityScenario.launch(WelcomeActivity::class.java).use { @@ -95,7 +92,6 @@ class WelcomeTest : BaseTest() { } @Test - @SdkSuppress(minSdkVersion = Build.VERSION_CODES.P) fun clickCancelOnTokenFailurePromptAllowsClickingPairButtonAgain() { FirebaseMock.mockFirebaseTokenUnsuccessful() ActivityScenario.launch(WelcomeActivity::class.java).use { @@ -111,7 +107,6 @@ class WelcomeTest : BaseTest() { } @Test - @SdkSuppress(minSdkVersion = Build.VERSION_CODES.P) fun clickTryAgainOnTokenFailurePromptRetriesFetchingToken() { FirebaseMock.mockFirebaseTokenUnsuccessful() ActivityScenario.launch(WelcomeActivity::class.java).use { @@ -131,8 +126,7 @@ class WelcomeTest : BaseTest() { } @Test - @SdkSuppress(minSdkVersion = Build.VERSION_CODES.P) - fun clickLearnMoreButtonLoadsBisqMobileWebpage() { + fun clickLearnMoreButtonAndNotAcceptingConfirmationDoesNotLoadBisqMobileWebpage() { FirebaseMock.mockFirebaseTokenSuccessful() intending(hasAction(Intent.ACTION_VIEW)).respondWith( Instrumentation.ActivityResult(Activity.RESULT_OK, Intent()) @@ -141,9 +135,46 @@ class WelcomeTest : BaseTest() { welcomeScreen.learnMoreButton.click() assertTrue(welcomeScreen.alertDialogLoadBisqMobileUrl.isDisplayed()) + welcomeScreen.alertDialogLoadBisqMobileUrl.negativeButton.click() + + try { + val expectedIntent = allOf( + hasAction(Intent.ACTION_VIEW), + hasData(BISQ_MOBILE_URL) + ) + intending(expectedIntent).respondWith(Instrumentation.ActivityResult(0, null)) + intended(expectedIntent) + } catch (e: AssertionFailedError) { + // We want the assertion to fail, since trying to negate the intended + // doesn't seem to work + return + } + fail("Loaded web page after clicking cancel") + } + } + + @Test + fun clickLearnMoreButtonAndAcceptingConfirmationLoadsBisqMobileWebpage() { + FirebaseMock.mockFirebaseTokenSuccessful() + intending(hasAction(Intent.ACTION_VIEW)).respondWith( + Instrumentation.ActivityResult(Activity.RESULT_OK, Intent()) + ) + ActivityScenario.launch(WelcomeActivity::class.java).use { + intending(hasAction(Intent.ACTION_VIEW)).respondWith( + Instrumentation.ActivityResult(Activity.RESULT_OK, Intent()) + ) + + welcomeScreen.learnMoreButton.click() + assertTrue(welcomeScreen.alertDialogLoadBisqMobileUrl.isDisplayed()) + welcomeScreen.alertDialogLoadBisqMobileUrl.positiveButton.click() - intended(hasAction(Intent.ACTION_VIEW)) - intended(hasData(BISQ_MOBILE_URL)) + + val expectedIntent = allOf( + hasAction(Intent.ACTION_VIEW), + hasData(BISQ_MOBILE_URL) + ) + intending(expectedIntent).respondWith(Instrumentation.ActivityResult(0, null)) + intended(expectedIntent) } } } diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 47b28cb..0f0b2e2 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -6,53 +6,26 @@ android:name=".Application" android:allowBackup="true" android:icon="@mipmap/ic_launcher" - android:label="Bisq" + android:label="@string/app_name" android:roundIcon="@mipmap/ic_launcher" android:supportsRtl="true" - android:theme="@style/BisqMaterialTheme"> + android:theme="@style/BisqMaterialTheme" + tools:targetApi="34"> + android:exported="true"> - - - - - - + + + + + + Activity.bind(@IdRes res: Int): T { - @Suppress("UNCHECKED_CAST") return findViewById(res) } @@ -121,20 +116,4 @@ open class BaseActivity : AppCompatActivity() { Log.e(TAG, "Unable to play notification tone", e) } } - - protected fun loadWebPage(uri: String) { - DialogBuilder.choicePrompt( - this, getString(R.string.warning), getString(R.string.load_web_page_text, uri), - getString(R.string.yes), getString(R.string.no), - { _, _ -> - try { - startActivity(Intent(Intent.ACTION_VIEW, Uri.parse(uri))) - } catch (ignored: ActivityNotFoundException) { - Toast.makeText( - this, getString(R.string.cannot_launch_browser), Toast.LENGTH_LONG - ).show() - } - } - ).show() - } } diff --git a/app/src/main/java/bisq/android/ui/DialogBuilder.kt b/app/src/main/java/bisq/android/ui/DialogBuilder.kt index a04d0e1..d5ac9d6 100644 --- a/app/src/main/java/bisq/android/ui/DialogBuilder.kt +++ b/app/src/main/java/bisq/android/ui/DialogBuilder.kt @@ -43,7 +43,8 @@ object DialogBuilder { builder.setPositiveButton(positiveButtonText, positiveActionListener) if (negativeActionListener != null) { builder.setNegativeButton( - negativeButtonText, negativeActionListener + negativeButtonText, + negativeActionListener ) } else { builder.setNegativeButton( @@ -78,7 +79,8 @@ object DialogBuilder { builder.setCancelable(false) if (actionListener != null) { builder.setPositiveButton( - buttonText, actionListener + buttonText, + actionListener ) } else { builder.setPositiveButton( diff --git a/app/src/main/java/bisq/android/ui/PairedBaseActivity.kt b/app/src/main/java/bisq/android/ui/PairedBaseActivity.kt index e887c83..3ec907a 100644 --- a/app/src/main/java/bisq/android/ui/PairedBaseActivity.kt +++ b/app/src/main/java/bisq/android/ui/PairedBaseActivity.kt @@ -36,7 +36,9 @@ open class PairedBaseActivity : BaseActivity() { this.runOnUiThread { playTone() Toast.makeText( - this, toastMessage, Toast.LENGTH_LONG + this, + toastMessage, + Toast.LENGTH_LONG ).show() startActivity(Intent(Intent(this, WelcomeActivity::class.java))) } diff --git a/app/src/main/java/bisq/android/ui/ThemeProvider.kt b/app/src/main/java/bisq/android/ui/ThemeProvider.kt new file mode 100644 index 0000000..3ed8f02 --- /dev/null +++ b/app/src/main/java/bisq/android/ui/ThemeProvider.kt @@ -0,0 +1,57 @@ +/* + * This file is part of Bisq. + * + * Bisq is free software: you can redistribute it and/or modify it + * under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or (at + * your option) any later version. + * + * Bisq is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public + * License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with Bisq. If not, see . + */ + +package bisq.android.ui + +import android.app.UiModeManager +import android.content.Context +import androidx.appcompat.app.AppCompatDelegate +import androidx.preference.PreferenceManager +import bisq.android.R +import java.security.InvalidParameterException + +class ThemeProvider(private val context: Context) { + fun getThemeFromPreferences(): Int { + val sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context) + val selectedTheme = sharedPreferences.getString( + context.getString(R.string.theme_preferences_key), + context.getString(R.string.system_theme_preference_value) + ) + + return selectedTheme?.let { + getTheme(it) + } ?: AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM + } + + fun getThemeDescriptionForPreference(preferenceValue: String?): String = + when (preferenceValue) { + context.getString(R.string.dark_theme_preference_value) -> + context.getString(R.string.dark_theme_description) + + context.getString(R.string.light_theme_preference_value) -> + context.getString(R.string.light_theme_description) + + else -> context.getString(R.string.system_theme_description) + } + + fun getTheme(selectedTheme: String): Int = when (selectedTheme) { + context.getString(R.string.dark_theme_preference_value) -> UiModeManager.MODE_NIGHT_YES + context.getString(R.string.light_theme_preference_value) -> UiModeManager.MODE_NIGHT_NO + context.getString(R.string.system_theme_preference_value) -> AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM + else -> throw InvalidParameterException("Theme not defined for $selectedTheme") + } +} diff --git a/app/src/main/java/bisq/android/ui/UiUtil.kt b/app/src/main/java/bisq/android/ui/UiUtil.kt new file mode 100644 index 0000000..0f3d910 --- /dev/null +++ b/app/src/main/java/bisq/android/ui/UiUtil.kt @@ -0,0 +1,48 @@ +/* + * This file is part of Bisq. + * + * Bisq is free software: you can redistribute it and/or modify it + * under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or (at + * your option) any later version. + * + * Bisq is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public + * License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with Bisq. If not, see . + */ + +package bisq.android.ui + +import android.content.ActivityNotFoundException +import android.content.Context +import android.content.Intent +import android.net.Uri +import android.widget.Toast +import bisq.android.R + +object UiUtil { + fun loadWebPage(context: Context, uri: String) { + DialogBuilder.choicePrompt( + context, + context.getString(R.string.confirm), + context.getString(R.string.load_web_page_confirmation, uri), + context.getString(R.string.yes), + context.getString(R.string.no), + { _, _ -> + try { + context.startActivity(Intent(Intent.ACTION_VIEW, Uri.parse(uri))) + } catch (ignored: ActivityNotFoundException) { + Toast.makeText( + context, + context.getString(R.string.cannot_launch_browser), + Toast.LENGTH_LONG + ).show() + } + } + ).show() + } +} diff --git a/app/src/main/java/bisq/android/ui/notification/NotificationAdapter.kt b/app/src/main/java/bisq/android/ui/notification/NotificationAdapter.kt index 900596b..9cdc295 100644 --- a/app/src/main/java/bisq/android/ui/notification/NotificationAdapter.kt +++ b/app/src/main/java/bisq/android/ui/notification/NotificationAdapter.kt @@ -36,7 +36,7 @@ class NotificationAdapter( companion object { var iconTypeface: Typeface? = null - var iconTextSize: Float = 33F + var iconTextSize: Float = 28F } override fun onBindViewHolder(holder: NotificationViewHolder, position: Int) { @@ -63,16 +63,33 @@ class NotificationAdapter( ) holder.title.setTextColor( ContextCompat.getColor( - holder.icon.context, - R.color.read_title + holder.title.context, + R.color.read_notification + ) + ) + holder.time.setTextColor( + ContextCompat.getColor( + holder.time.context, + R.color.read_notification ) ) } else { - holder.icon.setTextColor(ContextCompat.getColor(holder.icon.context, R.color.primary)) - holder.title.setTextColor( + holder.icon.setTextColor( ContextCompat.getColor( holder.icon.context, - R.color.unread_title + R.color.primary + ) + ) + holder.title.setTextColor( + ContextCompat.getColor( + holder.title.context, + R.color.unread_notification + ) + ) + holder.time.setTextColor( + ContextCompat.getColor( + holder.time.context, + R.color.unread_notification ) ) } diff --git a/app/src/main/java/bisq/android/ui/notification/NotificationDetailActivity.kt b/app/src/main/java/bisq/android/ui/notification/NotificationDetailActivity.kt index e8ae5b4..9324a35 100644 --- a/app/src/main/java/bisq/android/ui/notification/NotificationDetailActivity.kt +++ b/app/src/main/java/bisq/android/ui/notification/NotificationDetailActivity.kt @@ -19,6 +19,7 @@ package bisq.android.ui.notification import android.os.Bundle import android.view.View +import android.widget.Button import android.widget.TextView import androidx.lifecycle.ViewModelProvider import bisq.android.R @@ -34,6 +35,7 @@ class NotificationDetailActivity : PairedBaseActivity() { private lateinit var action: TextView private lateinit var eventTime: TextView private lateinit var receivedTime: TextView + private lateinit var deleteButton: Button override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) @@ -56,11 +58,16 @@ class NotificationDetailActivity : PairedBaseActivity() { private fun initView() { setContentView(R.layout.activity_notification_detail) - title = bind(R.id.detail_title) - message = bind(R.id.detail_message) - action = bind(R.id.detail_action) - eventTime = bind(R.id.detail_event_time) - receivedTime = bind(R.id.detail_received_time) + title = bind(R.id.notification_detail_title) + message = bind(R.id.notification_detail_message) + action = bind(R.id.notification_detail_action) + eventTime = bind(R.id.notification_detail_event_time) + receivedTime = bind(R.id.notification_detail_received_time) + deleteButton = bind(R.id.notification_delete_button) + deleteButton.setOnClickListener { + getNotification()?.let { notification -> viewModel.delete(notification) } + finish() + } } private fun updateView(notification: BisqNotification) { diff --git a/app/src/main/java/bisq/android/ui/notification/NotificationTableActivity.kt b/app/src/main/java/bisq/android/ui/notification/NotificationTableActivity.kt index 53840b9..b398267 100644 --- a/app/src/main/java/bisq/android/ui/notification/NotificationTableActivity.kt +++ b/app/src/main/java/bisq/android/ui/notification/NotificationTableActivity.kt @@ -30,14 +30,19 @@ import android.view.MenuItem import android.view.View import androidx.appcompat.widget.Toolbar import androidx.core.content.ContextCompat +import androidx.core.view.MenuCompat import androidx.lifecycle.ViewModelProvider import androidx.recyclerview.widget.ItemTouchHelper import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView import bisq.android.R import bisq.android.database.BisqNotification +import bisq.android.model.Device +import bisq.android.model.NotificationType +import bisq.android.ui.DialogBuilder import bisq.android.ui.PairedBaseActivity import bisq.android.ui.settings.SettingsActivity +import java.util.Date @Suppress("TooManyFunctions") class NotificationTableActivity : PairedBaseActivity() { @@ -69,10 +74,10 @@ class NotificationTableActivity : PairedBaseActivity() { private fun initView() { setContentView(R.layout.activity_notification_table) - toolbar = bind(R.id.bisq_toolbar) + toolbar = bind(R.id.notification_table_toolbar) setSupportActionBar(toolbar) - recyclerView = bind(R.id.notification_recycler_view) + recyclerView = bind(R.id.notification_table_recycler_view) recyclerView.layoutManager = LinearLayoutManager(this) val swipeHandler = object : SwipeToDeleteCallback(this) { @@ -105,22 +110,50 @@ class NotificationTableActivity : PairedBaseActivity() { } } - override fun onBackPressed() { - val a = Intent(Intent.ACTION_MAIN) - a.addCategory(Intent.CATEGORY_HOME) - a.flags = Intent.FLAG_ACTIVITY_NEW_TASK - startActivity(a) - } - override fun onCreateOptionsMenu(menu: Menu): Boolean { menuInflater.inflate(R.menu.menu, menu) + MenuCompat.setGroupDividerEnabled(menu, true) + if (Device.instance.isEmulator()) { + menu.setGroupVisible(R.id.debug, true) + } else { + menu.setGroupVisible(R.id.debug, false) + } + viewModel.bisqNotifications.observe(this) { bisqNotifications -> + if (bisqNotifications.isEmpty()) { + menu.setGroupEnabled(R.id.notifications, false) + } else { + menu.setGroupEnabled(R.id.notifications, true) + } + } return true } override fun onOptionsItemSelected(item: MenuItem): Boolean { - val id = item.itemId - if (id == R.id.action_settings) { - startActivity(Intent(this, SettingsActivity::class.java)) + when (item.itemId) { + R.id.action_add_example_notifications -> { + addExampleNotifications() + } + + R.id.action_mark_all_read -> { + viewModel.markAllAsRead() + } + + R.id.action_delete_all -> { + DialogBuilder.choicePrompt( + this, + getString(R.string.confirm), + getString(R.string.delete_all_notifications_confirmation), + getString(R.string.yes), + getString(R.string.no), + { _, _ -> + viewModel.nukeTable() + } + ).show() + } + + R.id.action_settings -> { + startActivity(Intent(this, SettingsActivity::class.java)) + } } return true } @@ -139,6 +172,54 @@ class NotificationTableActivity : PairedBaseActivity() { scrollFirstVisibleItemPosition = llm.findFirstVisibleItemPosition() scrollLastVisibleItemPosition = llm.findLastVisibleItemPosition() } + + @Suppress("MagicNumber") + private fun addExampleNotifications() { + for (counter in 1..5) { + val now = Date() + val bisqNotification = BisqNotification() + bisqNotification.receivedDate = now.time + counter * 1000 + bisqNotification.sentDate = bisqNotification.receivedDate - 1000 * 30 + when (counter) { + 1 -> { + bisqNotification.type = NotificationType.TRADE.name + bisqNotification.title = "Trade confirmed" + bisqNotification.message = "The trade with ID 38765384 is confirmed." + } + + 2 -> { + bisqNotification.type = NotificationType.OFFER.name + bisqNotification.title = "Offer taken" + bisqNotification.message = "Your offer with ID 39847534 was taken" + } + + 3 -> { + bisqNotification.type = NotificationType.DISPUTE.name + bisqNotification.title = "Dispute message" + bisqNotification.actionRequired = "Please contact the arbitrator" + bisqNotification.message = + "You received a dispute message for trade with ID 34059340" + bisqNotification.txId = "34059340" + } + + 4 -> { + bisqNotification.type = NotificationType.PRICE.name + bisqNotification.title = "Price alert for United States Dollar" + bisqNotification.message = "Your price alert got triggered. The current" + + " United States Dollar price is 35351.08 BTC/USD" + } + + 5 -> { + bisqNotification.type = NotificationType.MARKET.name + bisqNotification.title = "New offer" + bisqNotification.message = "A new offer offer with price 36000 USD" + + " (1% above market price) and payment method Zelle was published to" + + " the Bisq offerbook.\nThe offer ID is 34534" + } + } + viewModel.insert(bisqNotification) + } + } } abstract class SwipeToDeleteCallback(context: NotificationTableActivity) : diff --git a/app/src/main/java/bisq/android/ui/pairing/PairingScanActivity.kt b/app/src/main/java/bisq/android/ui/pairing/PairingScanActivity.kt index 578972b..133f04e 100644 --- a/app/src/main/java/bisq/android/ui/pairing/PairingScanActivity.kt +++ b/app/src/main/java/bisq/android/ui/pairing/PairingScanActivity.kt @@ -36,7 +36,7 @@ import bisq.android.util.QrUtil class PairingScanActivity : UnpairedBaseActivity() { private lateinit var qrImage: ImageView - private lateinit var qrText: TextView + private lateinit var qrPlaceholderText: TextView private lateinit var noWebcamButton: Button private lateinit var simulatePairingButton: Button @@ -55,21 +55,21 @@ class PairingScanActivity : UnpairedBaseActivity() { private fun initView() { setContentView(R.layout.activity_pairing_scan) - qrImage = this.bind(R.id.qrImageView) + qrImage = this.bind(R.id.pairing_scan_qr_image) - qrText = this.bind(R.id.qrTextView) + qrPlaceholderText = this.bind(R.id.pairing_scan_qr_placeholder) - noWebcamButton = bind(R.id.noWebcamButton) + noWebcamButton = bind(R.id.pairing_scan_no_webcam_button) noWebcamButton.setOnClickListener { - onNoWebcamButtonClick() + onNoWebcam() } - simulatePairingButton = bind(R.id.simulatePairingButton) - if (Device.instance.isEmulator()) { + simulatePairingButton = bind(R.id.pairing_scan_simulate_pairing_button) + if (Device.instance.isEmulator() && Device.instance.status == DeviceStatus.UNPAIRED) { simulatePairingButton.visibility = View.VISIBLE } simulatePairingButton.setOnClickListener { - onSimulatePairingButtonClick() + onSimulatePairing() } mainHandler.post { @@ -80,21 +80,22 @@ class PairingScanActivity : UnpairedBaseActivity() { try { val bmp = QrUtil.createQrImage(Device.instance.pairingToken()!!) qrImage.setImageBitmap(bmp) - qrText.visibility = View.INVISIBLE + qrPlaceholderText.visibility = View.INVISIBLE } catch (ignored: Exception) { Toast.makeText( - this, getString(R.string.cannot_generate_qr_code), + this, + getString(R.string.cannot_generate_qr_code), Toast.LENGTH_LONG ).show() } } } - private fun onNoWebcamButtonClick() { + private fun onNoWebcam() { startActivity(Intent(Intent(this, PairingSendActivity::class.java))) } - private fun onSimulatePairingButtonClick() { + private fun onSimulatePairing() { Device.instance.status = DeviceStatus.PAIRED Device.instance.saveToPreferences(this) pairingConfirmed() diff --git a/app/src/main/java/bisq/android/ui/pairing/PairingSendActivity.kt b/app/src/main/java/bisq/android/ui/pairing/PairingSendActivity.kt index a09dcb0..6f3365c 100644 --- a/app/src/main/java/bisq/android/ui/pairing/PairingSendActivity.kt +++ b/app/src/main/java/bisq/android/ui/pairing/PairingSendActivity.kt @@ -38,19 +38,19 @@ class PairingSendActivity : UnpairedBaseActivity() { private fun initView() { setContentView(R.layout.activity_pairing_send) - sendPairingTokenInstructions = bind(R.id.sendPairingTokenInstructions) + sendPairingTokenInstructions = bind(R.id.pairing_send_pairing_token_instructions) - sendPairingTokenButton = bind(R.id.sendPairingTokenButton) + sendPairingTokenButton = bind(R.id.pairing_send_pairing_token_button) sendPairingTokenButton.setOnClickListener { - onSendPairingTokenButtonClick() + onSendPairingToken() } } - private fun onSendPairingTokenButtonClick() { + private fun onSendPairingToken() { val intent = Intent(Intent.ACTION_SEND) intent.type = "text/html" intent.putExtra(Intent.EXTRA_SUBJECT, getString(R.string.send_pairing_subject)) intent.putExtra(Intent.EXTRA_TEXT, Device.instance.pairingToken()) - startActivity(Intent.createChooser(intent, getString(R.string.send_pairing_token))) + startActivity(Intent.createChooser(intent, getString(R.string.scan_pairing_token))) } } diff --git a/app/src/main/java/bisq/android/ui/pairing/PairingSuccessActivity.kt b/app/src/main/java/bisq/android/ui/pairing/PairingSuccessActivity.kt index 45ae8ca..836c1c4 100644 --- a/app/src/main/java/bisq/android/ui/pairing/PairingSuccessActivity.kt +++ b/app/src/main/java/bisq/android/ui/pairing/PairingSuccessActivity.kt @@ -36,13 +36,13 @@ class PairingSuccessActivity : PairedBaseActivity() { private fun initView() { setContentView(R.layout.activity_pairing_success) - pairingCompleteButton = bind(R.id.pairing_complete_button) + pairingCompleteButton = bind(R.id.pairing_scan_pairing_complete_button) pairingCompleteButton.setOnClickListener { - onPairingCompleteButtonClick() + onPairingComplete() } } - private fun onPairingCompleteButtonClick() { + private fun onPairingComplete() { startActivity(Intent(Intent(this, NotificationTableActivity::class.java))) } } diff --git a/app/src/main/java/bisq/android/ui/settings/SettingsActivity.kt b/app/src/main/java/bisq/android/ui/settings/SettingsActivity.kt index a7710c7..4992b60 100644 --- a/app/src/main/java/bisq/android/ui/settings/SettingsActivity.kt +++ b/app/src/main/java/bisq/android/ui/settings/SettingsActivity.kt @@ -17,171 +17,18 @@ package bisq.android.ui.settings -import android.content.Intent import android.os.Bundle -import android.view.View -import android.widget.Button -import android.widget.TextView -import androidx.lifecycle.ViewModelProvider -import bisq.android.Application -import bisq.android.BISQ_MOBILE_URL -import bisq.android.BISQ_NETWORK_URL import bisq.android.R -import bisq.android.database.BisqNotification -import bisq.android.model.Device -import bisq.android.model.DeviceStatus -import bisq.android.model.NotificationType -import bisq.android.services.BisqFirebaseMessagingService.Companion.refreshFcmToken import bisq.android.ui.PairedBaseActivity -import bisq.android.ui.notification.NotificationViewModel -import bisq.android.ui.welcome.WelcomeActivity -import bisq.android.util.TextUtil.truncateSensitiveText -import java.util.Date class SettingsActivity : PairedBaseActivity() { - private lateinit var viewModel: NotificationViewModel - private lateinit var registerAgainButton: Button - private lateinit var deleteAllNotificationsButton: Button - private lateinit var markAllAsReadButton: Button - private lateinit var addExampleNotificationsButton: Button - private lateinit var aboutBisqButton: Button - private lateinit var aboutAppButton: Button - private lateinit var versionTextView: TextView - private lateinit var keyTextView: TextView - private lateinit var tokenTextView: TextView - override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - viewModel = ViewModelProvider(this)[NotificationViewModel::class.java] initView() } private fun initView() { setContentView(R.layout.activity_settings) - - registerAgainButton = bind(R.id.settingsRegisterAgainButton) - registerAgainButton.setOnClickListener { - onRegisterAgainButtonClick() - } - - deleteAllNotificationsButton = bind(R.id.settingsDeleteAllNotificationsButton) - deleteAllNotificationsButton.setOnClickListener { - onDeleteAllNotificationsButtonClick() - } - - markAllAsReadButton = bind(R.id.settingsMarkAsReadButton) - markAllAsReadButton.setOnClickListener { - onMarkAllAsReadButtonClick() - } - - addExampleNotificationsButton = bind(R.id.settingsAddExampleButton) - if (Device.instance.isEmulator()) { - addExampleNotificationsButton.visibility = View.VISIBLE - } - addExampleNotificationsButton.setOnClickListener { - onAddExampleNotificationsButtonClick() - } - - aboutBisqButton = bind(R.id.settingsAboutBisqButton) - aboutBisqButton.setOnClickListener { - onAboutBisqButtonClick() - } - - aboutAppButton = bind(R.id.settingsAboutAppButton) - aboutAppButton.setOnClickListener { - onAboutAppButtonClick() - } - - versionTextView = bind(R.id.settingsVersionTextView) - versionTextView.text = getString(R.string.version, Application.getAppVersion()) - - keyTextView = bind(R.id.settingsKeyTextView) - keyTextView.text = getString( - R.string.key, - truncateSensitiveText(Device.instance.key) - ) - - tokenTextView = bind(R.id.settingsTokenTextView) - tokenTextView.text = getString( - R.string.token, - truncateSensitiveText(Device.instance.token) - ) - } - - private fun onAboutAppButtonClick() { - loadWebPage(BISQ_MOBILE_URL) - } - - private fun onAboutBisqButtonClick() { - loadWebPage(BISQ_NETWORK_URL) - } - - private fun onAddExampleNotificationsButtonClick() { - addExampleNotifications() - finish() - } - - private fun onMarkAllAsReadButtonClick() { - viewModel.markAllAsRead() - finish() - } - - private fun onDeleteAllNotificationsButtonClick() { - viewModel.nukeTable() - finish() - } - - private fun onRegisterAgainButtonClick() { - Device.instance.reset() - Device.instance.clearPreferences(this) - viewModel.nukeTable() - Device.instance.status = DeviceStatus.ERASED - refreshFcmToken() - startActivity(Intent(this, WelcomeActivity::class.java)) - } - - @Suppress("MagicNumber") - private fun addExampleNotifications() { - for (counter in 1..5) { - val now = Date() - val bisqNotification = BisqNotification() - bisqNotification.receivedDate = now.time + counter * 1000 - bisqNotification.sentDate = bisqNotification.receivedDate - 1000 * 30 - when (counter) { - 1 -> { - bisqNotification.type = NotificationType.TRADE.name - bisqNotification.title = "(example) Trade confirmed" - bisqNotification.message = "The trade with ID 38765384 is confirmed." - } - 2 -> { - bisqNotification.type = NotificationType.OFFER.name - bisqNotification.title = "(example) Offer taken" - bisqNotification.message = "Your offer with ID 39847534 was taken" - } - 3 -> { - bisqNotification.type = NotificationType.DISPUTE.name - bisqNotification.title = "(example) Dispute message" - bisqNotification.actionRequired = "Please contact the arbitrator" - bisqNotification.message = - "You received a dispute message for trade with ID 34059340" - bisqNotification.txId = "34059340" - } - 4 -> { - bisqNotification.type = NotificationType.PRICE.name - bisqNotification.title = "(example) Price alert for United States Dollar" - bisqNotification.message = "Your price alert got triggered. The current" + - " United States Dollar price is 35351.08 BTC/USD" - } - 5 -> { - bisqNotification.type = NotificationType.MARKET.name - bisqNotification.title = "(example) New offer" - bisqNotification.message = "A new offer offer with price 36000 USD" + - " (1% above market price) and payment method Zelle was published to" + - " the Bisq offerbook.\nThe offer ID is 34534" - } - } - viewModel.insert(bisqNotification) - } } } diff --git a/app/src/main/java/bisq/android/ui/settings/SettingsFragment.kt b/app/src/main/java/bisq/android/ui/settings/SettingsFragment.kt new file mode 100644 index 0000000..5e93ef8 --- /dev/null +++ b/app/src/main/java/bisq/android/ui/settings/SettingsFragment.kt @@ -0,0 +1,149 @@ +/* + * This file is part of Bisq. + * + * Bisq is free software: you can redistribute it and/or modify it + * under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or (at + * your option) any later version. + * + * Bisq is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public + * License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with Bisq. If not, see . + */ + +package bisq.android.ui.settings + +import android.content.Context +import android.content.Intent +import android.os.Bundle +import androidx.appcompat.app.AppCompatDelegate +import androidx.lifecycle.ViewModelProvider +import androidx.preference.ListPreference +import androidx.preference.Preference +import androidx.preference.PreferenceFragmentCompat +import bisq.android.Application +import bisq.android.BISQ_MOBILE_URL +import bisq.android.BISQ_NETWORK_URL +import bisq.android.R +import bisq.android.model.Device +import bisq.android.model.DeviceStatus +import bisq.android.services.BisqFirebaseMessagingService +import bisq.android.ui.DialogBuilder +import bisq.android.ui.ThemeProvider +import bisq.android.ui.UiUtil.loadWebPage +import bisq.android.ui.notification.NotificationViewModel +import bisq.android.ui.pairing.PairingScanActivity +import bisq.android.ui.welcome.WelcomeActivity + +@Suppress("TooManyFunctions") +class SettingsFragment : PreferenceFragmentCompat() { + private val themeProvider by lazy { ThemeProvider(requireContext()) } + private val themePreference by lazy { + findPreference(getString(R.string.theme_preferences_key)) + } + private val resetPairingPreference by lazy { + findPreference(getString(R.string.reset_pairing_preferences_key)) + } + private val scanPairingTokenPreference by lazy { + findPreference(getString(R.string.scan_pairing_token_preferences_key)) + } + private val aboutBisqPreference by lazy { + findPreference(getString(R.string.about_bisq_preferences_key)) + } + private val aboutAppPreference by lazy { + findPreference(getString(R.string.about_app_preferences_key)) + } + private val versionPreference by lazy { + findPreference(getString(R.string.version_preferences_key)) + } + + private lateinit var viewModel: NotificationViewModel + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + viewModel = ViewModelProvider(this)[NotificationViewModel::class.java] + } + + override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) { + setPreferencesFromResource(R.xml.settings, rootKey) + setThemePreference() + setResetPairingPreference() + setScanPairingTokenPreference() + setAboutBisqPreference() + setAboutAppPreference() + setVersionPreference() + } + + private fun setThemePreference() { + themePreference?.onPreferenceChangeListener = + Preference.OnPreferenceChangeListener { _, newValue -> + if (newValue is String) { + val theme = themeProvider.getTheme(newValue) + AppCompatDelegate.setDefaultNightMode(theme) + } + true + } + themePreference?.summaryProvider = getThemeSummaryProvider() + } + + private fun getThemeSummaryProvider() = + Preference.SummaryProvider { preference -> + themeProvider.getThemeDescriptionForPreference(preference.value) + } + + private fun setResetPairingPreference() { + resetPairingPreference?.setOnPreferenceClickListener { + this.context?.let { context -> + onResetPairing(context) + } + true + } + } + + private fun onResetPairing(context: Context) { + DialogBuilder.choicePrompt( + context, + getString(R.string.confirm), + getString(R.string.register_again_confirmation), + getString(R.string.yes), + getString(R.string.no), + { _, _ -> + Device.instance.reset() + Device.instance.clearPreferences(context) + viewModel.nukeTable() + Device.instance.status = DeviceStatus.ERASED + BisqFirebaseMessagingService.refreshFcmToken() + startActivity(Intent(context, WelcomeActivity::class.java)) + } + ).show() + } + + private fun setScanPairingTokenPreference() { + scanPairingTokenPreference?.setOnPreferenceClickListener { + startActivity(Intent(Intent(context, PairingScanActivity::class.java))) + true + } + } + + private fun setAboutBisqPreference() { + aboutBisqPreference?.setOnPreferenceClickListener { + this.context?.let { context -> loadWebPage(context, BISQ_NETWORK_URL) } + true + } + } + + private fun setAboutAppPreference() { + aboutAppPreference?.setOnPreferenceClickListener { + this.context?.let { context -> loadWebPage(context, BISQ_MOBILE_URL) } + true + } + } + + private fun setVersionPreference() { + versionPreference?.title = getString(R.string.version, Application.getAppVersion()) + } +} diff --git a/app/src/main/java/bisq/android/ui/welcome/WelcomeActivity.kt b/app/src/main/java/bisq/android/ui/welcome/WelcomeActivity.kt index 92664de..0f95d09 100644 --- a/app/src/main/java/bisq/android/ui/welcome/WelcomeActivity.kt +++ b/app/src/main/java/bisq/android/ui/welcome/WelcomeActivity.kt @@ -36,6 +36,7 @@ import bisq.android.services.BisqFirebaseMessagingService import bisq.android.services.BisqFirebaseMessagingService.Companion.isGooglePlayServicesAvailable import bisq.android.services.BisqFirebaseMessagingService.Companion.isTokenBeingFetched import bisq.android.ui.DialogBuilder +import bisq.android.ui.UiUtil.loadWebPage import bisq.android.ui.UnpairedBaseActivity import bisq.android.ui.notification.NotificationTableActivity import bisq.android.ui.pairing.PairingScanActivity @@ -107,7 +108,7 @@ class WelcomeActivity : UnpairedBaseActivity() { private fun initView() { setContentView(R.layout.activity_welcome) - pairButton = bind(R.id.pairButton) + pairButton = bind(R.id.welcome_pair_button) if (isGooglePlayServicesAvailable(this)) { pairButton.setOnClickListener { maybeProceedToPairingScanActivity() @@ -118,12 +119,12 @@ class WelcomeActivity : UnpairedBaseActivity() { } } - learnMoreButton = bind(R.id.learnMoreButton) + learnMoreButton = bind(R.id.welcome_learn_more_button) learnMoreButton.setOnClickListener { - loadWebPage(BISQ_MOBILE_URL) + loadWebPage(this, BISQ_MOBILE_URL) } - progressBar = bind(R.id.circularProgressbar) + progressBar = bind(R.id.welcome_circular_progressbar) progressBar.progressDrawable = ContextCompat.getDrawable(this, R.drawable.circular_progressbar) Thread { @@ -198,13 +199,13 @@ class WelcomeActivity : UnpairedBaseActivity() { val extras = intent.extras if (extras != null) { Log.i(TAG, "Processing opened notification") - val notificationMessage = extras.get("encrypted") + val notificationMessage = extras.getString("encrypted") if (notificationMessage != null) { Log.i(TAG, "Broadcasting " + getString(R.string.notification_receiver_action)) Intent().also { broadcastIntent -> broadcastIntent.action = getString(R.string.notification_receiver_action) broadcastIntent.flags = Intent.FLAG_INCLUDE_STOPPED_PACKAGES - broadcastIntent.putExtra("encrypted", notificationMessage as String) + broadcastIntent.putExtra("encrypted", notificationMessage) sendBroadcast(broadcastIntent) } } diff --git a/app/src/main/res/drawable/bisq_logo_green.xml b/app/src/main/res/drawable/bisq_logo_green.xml new file mode 100644 index 0000000..2e270fb --- /dev/null +++ b/app/src/main/res/drawable/bisq_logo_green.xml @@ -0,0 +1,36 @@ + + + + + + + diff --git a/app/src/main/res/drawable/circular_progressbar.xml b/app/src/main/res/drawable/circular_progressbar.xml index 1c7f798..beeeffc 100644 --- a/app/src/main/res/drawable/circular_progressbar.xml +++ b/app/src/main/res/drawable/circular_progressbar.xml @@ -1,7 +1,5 @@ android:layout_width="wrap_content" android:layout_height="wrap_content"> diff --git a/app/src/main/res/drawable/ic_bisq_mark.xml b/app/src/main/res/drawable/ic_bisq_mark.xml new file mode 100644 index 0000000..a1257a3 --- /dev/null +++ b/app/src/main/res/drawable/ic_bisq_mark.xml @@ -0,0 +1,12 @@ + + + diff --git a/app/src/main/res/drawable/ic_info_white_24.xml b/app/src/main/res/drawable/ic_info_white_24.xml new file mode 100644 index 0000000..0988477 --- /dev/null +++ b/app/src/main/res/drawable/ic_info_white_24.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_link_white_24.xml b/app/src/main/res/drawable/ic_link_white_24.xml new file mode 100644 index 0000000..3c69538 --- /dev/null +++ b/app/src/main/res/drawable/ic_link_white_24.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/drawable/ic_qr_code_scanner_white_24.xml b/app/src/main/res/drawable/ic_qr_code_scanner_white_24.xml new file mode 100644 index 0000000..a2c3c83 --- /dev/null +++ b/app/src/main/res/drawable/ic_qr_code_scanner_white_24.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_restart_white_24.xml b/app/src/main/res/drawable/ic_restart_white_24.xml new file mode 100644 index 0000000..e17d695 --- /dev/null +++ b/app/src/main/res/drawable/ic_restart_white_24.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_theme_white_24.xml b/app/src/main/res/drawable/ic_theme_white_24.xml new file mode 100644 index 0000000..2e4e12a --- /dev/null +++ b/app/src/main/res/drawable/ic_theme_white_24.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/rounded_corner_gray.xml b/app/src/main/res/drawable/rounded_corner_gray.xml deleted file mode 100644 index 38966cc..0000000 --- a/app/src/main/res/drawable/rounded_corner_gray.xml +++ /dev/null @@ -1,15 +0,0 @@ - - - - - - - - - \ No newline at end of file diff --git a/app/src/main/res/drawable/rounded_corner_pink.xml b/app/src/main/res/drawable/rounded_corner_pink.xml deleted file mode 100644 index c4ceda0..0000000 --- a/app/src/main/res/drawable/rounded_corner_pink.xml +++ /dev/null @@ -1,15 +0,0 @@ - - - - - - - - - \ No newline at end of file diff --git a/app/src/main/res/layout/activity_notification_detail.xml b/app/src/main/res/layout/activity_notification_detail.xml index f0e695b..98f9042 100644 --- a/app/src/main/res/layout/activity_notification_detail.xml +++ b/app/src/main/res/layout/activity_notification_detail.xml @@ -3,10 +3,11 @@ xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" - android:layout_height="match_parent"> + android:layout_height="match_parent" + android:background="@color/background"> + tools:ignore="HardcodedText" /> + app:layout_constraintTop_toBottomOf="@+id/notification_detail_action" + tools:ignore="HardcodedText" /> + app:layout_constraintTop_toBottomOf="@+id/notification_detail_event_time" + tools:ignore="HardcodedText" /> + +