Compare commits

...

13 Commits

Author SHA1 Message Date
Adam Brown
28c9b0cf4b including screenshot test output to showcase comparisons -
these should not be merged directly into the repo but instead pulled in via git lfs
2022-09-01 16:40:48 +01:00
Adam Brown
bc6a0a842f add screenshot tests around the combined server selection 2022-09-01 16:40:43 +01:00
Adam Brown
51888e176c switching button to material button as the colorPrimary isn't working with the default button 2022-09-01 16:40:37 +01:00
Adam Brown
d403d16731 extracting inline fragment view logic to a controller 2022-09-01 16:40:15 +01:00
Adam Brown
92c615af31 adding room settings example 2022-09-01 16:33:53 +01:00
Adam Brown
a8190cf9c1 adding paparazzi screenshot testing lib setup 2022-09-01 16:33:23 +01:00
Adam Brown
c773eda3d6 enabling build caching by default to improve build times 2022-09-01 16:26:12 +01:00
Adam Brown
0e633bad46 split commit to make fdroid changes to avoid files being seen as new 2022-09-01 16:26:06 +01:00
Adam Brown
d689778470 lifting fdroid and gplay variants to the application module 2022-09-01 16:25:57 +01:00
Adam Brown
59cb6a03c8 split commit to make debug changes to avoid files being seen as new! 2022-09-01 16:24:41 +01:00
Adam Brown
4e9618fd0d lifting debug build type to the application module 2022-09-01 16:24:33 +01:00
Adam Brown
b1035a808e lifting the release build type to the application module 2022-09-01 16:24:16 +01:00
Adam Brown
0193695aa3 lifting nightly to the application module 2022-09-01 16:24:07 +01:00
95 changed files with 370 additions and 148 deletions

View File

@ -33,6 +33,7 @@ buildscript {
classpath "org.jetbrains.dokka:dokka-gradle-plugin:1.7.10" classpath "org.jetbrains.dokka:dokka-gradle-plugin:1.7.10"
classpath "org.jetbrains.kotlinx:kotlinx-knit:0.4.0" classpath "org.jetbrains.kotlinx:kotlinx-knit:0.4.0"
classpath 'com.jakewharton:butterknife-gradle-plugin:10.2.3' classpath 'com.jakewharton:butterknife-gradle-plugin:10.2.3'
classpath 'app.cash.paparazzi:paparazzi-gradle-plugin:1.0.0'
// NOTE: Do not place your application dependencies here; they belong // NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files // in the individual module build.gradle files
} }

View File

@ -42,6 +42,7 @@ ext.groups = [
regex: [ regex: [
], ],
group: [ group: [
'app.cash.paparazzi',
'ch.qos.logback', 'ch.qos.logback',
'com.adevinta.android', 'com.adevinta.android',
'com.airbnb.android', 'com.airbnb.android',
@ -145,11 +146,14 @@ ext.groups = [
'it.unimi.dsi', 'it.unimi.dsi',
'jakarta.activation', 'jakarta.activation',
'jakarta.xml.bind', 'jakarta.xml.bind',
'javax.activation',
'javax.annotation', 'javax.annotation',
'javax.inject', 'javax.inject',
'javax.xml.bind',
'jline', 'jline',
'jp.wasabeef', 'jp.wasabeef',
'junit', 'junit',
'kxml2',
'me.saket', 'me.saket',
'net.bytebuddy', 'net.bytebuddy',
'net.java', 'net.java',
@ -178,11 +182,13 @@ ext.groups = [
'org.hamcrest', 'org.hamcrest',
'org.jacoco', 'org.jacoco',
'org.java-websocket', 'org.java-websocket',
'org.jcodec',
'org.jetbrains', 'org.jetbrains',
'org.jetbrains.dokka', 'org.jetbrains.dokka',
'org.jetbrains.intellij.deps', 'org.jetbrains.intellij.deps',
'org.jetbrains.kotlin', 'org.jetbrains.kotlin',
'org.jetbrains.kotlinx', 'org.jetbrains.kotlinx',
'org.jetbrains.trove4j',
'org.json', 'org.json',
'org.jsoup', 'org.jsoup',
'org.junit', 'org.junit',

View File

@ -12,10 +12,12 @@ org.gradle.jvmargs=-Xmx4g -Xms512M -XX:MaxPermSize=2048m -XX:MaxMetaspaceSize=1g
org.gradle.configureondemand=true org.gradle.configureondemand=true
org.gradle.parallel=true org.gradle.parallel=true
org.gradle.vfs.watch=true org.gradle.vfs.watch=true
org.gradle.caching=true
# Android Settings # Android Settings
android.enableJetifier=true android.enableJetifier=true
android.useAndroidX=true android.useAndroidX=true
android.jetifier.ignorelist=android-base-common,common
#Project Settings #Project Settings
# Change debugPrivateData to true for debugging # Change debugPrivateData to true for debugging

View File

@ -341,6 +341,13 @@ android {
] ]
} }
sourceSets {
// Add sourceSets for `release` version when building `nightly`
nightly {
java.srcDirs += "src/release/java"
}
}
buildFeatures { buildFeatures {
viewBinding true viewBinding true
} }
@ -349,11 +356,39 @@ android {
dependencies { dependencies {
implementation project(':vector') implementation project(':vector')
implementation project(':vector-config') implementation project(':vector-config')
debugImplementation project(':library:ui-styles')
implementation libs.dagger.hilt implementation libs.dagger.hilt
implementation 'androidx.multidex:multidex:2.0.1' implementation 'androidx.multidex:multidex:2.0.1'
implementation "androidx.sharetarget:sharetarget:1.1.0" implementation "androidx.sharetarget:sharetarget:1.1.0"
// Flipper, debug builds only
debugImplementation(libs.flipper.flipper) {
exclude group: 'com.facebook.fbjni', module: 'fbjni'
}
debugImplementation(libs.flipper.flipperNetworkPlugin) {
exclude group: 'com.facebook.fbjni', module: 'fbjni'
}
debugImplementation 'com.facebook.soloader:soloader:0.10.4'
debugImplementation "com.kgurgul.flipper:flipper-realm-android:2.2.0"
gplayImplementation "com.google.android.gms:play-services-location:16.0.0"
// UnifiedPush gplay flavor only
gplayImplementation('com.github.UnifiedPush:android-embedded_fcm_distributor:2.1.2') {
exclude group: 'com.google.firebase', module: 'firebase-core'
exclude group: 'com.google.firebase', module: 'firebase-analytics'
exclude group: 'com.google.firebase', module: 'firebase-measurement-connector'
}
// Nightly
// API-only library
gplayImplementation libs.google.appdistributionApi
// Full SDK implementation
gplayImplementation libs.google.appdistribution
// OSS License, gplay flavor only
gplayImplementation 'com.google.android.gms:play-services-oss-licenses:17.0.0'
kapt libs.dagger.hiltCompiler kapt libs.dagger.hiltCompiler
kapt libs.airbnb.epoxyProcessor
androidTestImplementation libs.androidx.testCore androidTestImplementation libs.androidx.testCore
androidTestImplementation libs.androidx.testRunner androidTestImplementation libs.androidx.testRunner
@ -378,5 +413,6 @@ dependencies {
androidTestImplementation libs.androidx.fragmentTesting androidTestImplementation libs.androidx.fragmentTesting
androidTestImplementation "org.jetbrains.kotlin:kotlin-reflect:1.7.10" androidTestImplementation "org.jetbrains.kotlin:kotlin-reflect:1.7.10"
debugImplementation libs.androidx.fragmentTesting debugImplementation libs.androidx.fragmentTesting
debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.9.1'
} }

View File

@ -0,0 +1,22 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<application>
<activity android:name="im.vector.app.features.debug.TestLinkifyActivity" />
<activity android:name="im.vector.app.features.debug.DebugPermissionActivity" />
<activity android:name="im.vector.app.features.debug.analytics.DebugAnalyticsActivity" />
<activity android:name="im.vector.app.features.debug.settings.DebugPrivateSettingsActivity" />
<activity android:name="im.vector.app.features.debug.sas.DebugSasEmojiActivity" />
<activity android:name="im.vector.app.features.debug.features.DebugFeaturesSettingsActivity" />
<activity android:name="im.vector.app.features.debug.DebugMenuActivity" />
<activity android:name="im.vector.app.features.debug.leak.DebugMemoryLeaksActivity" />
<activity
android:name="com.facebook.flipper.android.diagnostics.FlipperDiagnosticActivity"
android:exported="true" />
<!-- Used for UI tests to display the BiometricPrompt. It's normal that it appears as an error. -->
<activity android:exported="false" android:name=".features.pin.lockscreen.tests.LockScreenTestActivity" />
</application>
</manifest>

View File

@ -34,13 +34,13 @@ import im.vector.app.core.utils.PERMISSIONS_FOR_TAKING_PHOTO
import im.vector.app.core.utils.checkPermissions import im.vector.app.core.utils.checkPermissions
import im.vector.app.core.utils.registerForPermissionsResult import im.vector.app.core.utils.registerForPermissionsResult
import im.vector.app.core.utils.toast import im.vector.app.core.utils.toast
import im.vector.app.databinding.ActivityDebugMenuBinding
import im.vector.app.features.debug.analytics.DebugAnalyticsActivity import im.vector.app.features.debug.analytics.DebugAnalyticsActivity
import im.vector.app.features.debug.features.DebugFeaturesSettingsActivity import im.vector.app.features.debug.features.DebugFeaturesSettingsActivity
import im.vector.app.features.debug.leak.DebugMemoryLeaksActivity import im.vector.app.features.debug.leak.DebugMemoryLeaksActivity
import im.vector.app.features.debug.sas.DebugSasEmojiActivity import im.vector.app.features.debug.sas.DebugSasEmojiActivity
import im.vector.app.features.debug.settings.DebugPrivateSettingsActivity import im.vector.app.features.debug.settings.DebugPrivateSettingsActivity
import im.vector.app.features.qrcode.QrCodeScannerActivity import im.vector.app.features.qrcode.QrCodeScannerActivity
import im.vector.application.databinding.ActivityDebugMenuBinding
import im.vector.lib.ui.styles.debug.DebugMaterialThemeDarkDefaultActivity import im.vector.lib.ui.styles.debug.DebugMaterialThemeDarkDefaultActivity
import im.vector.lib.ui.styles.debug.DebugMaterialThemeDarkTestActivity import im.vector.lib.ui.styles.debug.DebugMaterialThemeDarkTestActivity
import im.vector.lib.ui.styles.debug.DebugMaterialThemeDarkVectorActivity import im.vector.lib.ui.styles.debug.DebugMaterialThemeDarkVectorActivity

View File

@ -23,13 +23,13 @@ import android.widget.Toast
import androidx.core.app.ActivityCompat import androidx.core.app.ActivityCompat
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import dagger.hilt.android.AndroidEntryPoint import dagger.hilt.android.AndroidEntryPoint
import im.vector.app.R
import im.vector.app.core.platform.VectorBaseActivity import im.vector.app.core.platform.VectorBaseActivity
import im.vector.app.core.utils.checkPermissions import im.vector.app.core.utils.checkPermissions
import im.vector.app.core.utils.onPermissionDeniedDialog import im.vector.app.core.utils.onPermissionDeniedDialog
import im.vector.app.core.utils.onPermissionDeniedSnackbar import im.vector.app.core.utils.onPermissionDeniedSnackbar
import im.vector.app.core.utils.registerForPermissionsResult import im.vector.app.core.utils.registerForPermissionsResult
import im.vector.app.databinding.ActivityDebugPermissionBinding import im.vector.application.R
import im.vector.application.databinding.ActivityDebugPermissionBinding
import timber.log.Timber import timber.log.Timber
@AndroidEntryPoint @AndroidEntryPoint

View File

@ -20,9 +20,9 @@ import android.os.Bundle
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.ViewGroup import android.view.ViewGroup
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import im.vector.app.R import im.vector.application.R
import im.vector.app.databinding.ActivityTestLinkifyBinding import im.vector.application.databinding.ActivityTestLinkifyBinding
import im.vector.app.databinding.ItemTestLinkifyBinding import im.vector.application.databinding.ItemTestLinkifyBinding
class TestLinkifyActivity : AppCompatActivity() { class TestLinkifyActivity : AppCompatActivity() {

View File

@ -25,7 +25,7 @@ import com.airbnb.mvrx.withState
import im.vector.app.core.epoxy.onClick import im.vector.app.core.epoxy.onClick
import im.vector.app.core.extensions.toOnOff import im.vector.app.core.extensions.toOnOff
import im.vector.app.core.platform.VectorBaseFragment import im.vector.app.core.platform.VectorBaseFragment
import im.vector.app.databinding.FragmentDebugAnalyticsBinding import im.vector.application.databinding.FragmentDebugAnalyticsBinding
import me.gujun.android.span.span import me.gujun.android.span.span
class DebugAnalyticsFragment : VectorBaseFragment<FragmentDebugAnalyticsBinding>() { class DebugAnalyticsFragment : VectorBaseFragment<FragmentDebugAnalyticsBinding>() {

View File

@ -23,9 +23,9 @@ import android.widget.Spinner
import android.widget.TextView import android.widget.TextView
import com.airbnb.epoxy.EpoxyAttribute import com.airbnb.epoxy.EpoxyAttribute
import com.airbnb.epoxy.EpoxyModelClass import com.airbnb.epoxy.EpoxyModelClass
import im.vector.app.R
import im.vector.app.core.epoxy.VectorEpoxyHolder import im.vector.app.core.epoxy.VectorEpoxyHolder
import im.vector.app.core.epoxy.VectorEpoxyModel import im.vector.app.core.epoxy.VectorEpoxyModel
import im.vector.application.R
@EpoxyModelClass @EpoxyModelClass
abstract class BooleanFeatureItem : VectorEpoxyModel<BooleanFeatureItem.Holder>(R.layout.item_feature) { abstract class BooleanFeatureItem : VectorEpoxyModel<BooleanFeatureItem.Holder>(R.layout.item_feature) {
@ -70,8 +70,8 @@ abstract class BooleanFeatureItem : VectorEpoxyModel<BooleanFeatureItem.Holder>(
} }
class Holder : VectorEpoxyHolder() { class Holder : VectorEpoxyHolder() {
val label by bind<TextView>(im.vector.app.R.id.feature_label) val label by bind<TextView>(R.id.feature_label)
val optionsSpinner by bind<Spinner>(im.vector.app.R.id.feature_options) val optionsSpinner by bind<Spinner>(R.id.feature_options)
} }
interface Listener { interface Listener {

View File

@ -66,13 +66,13 @@ class DebugVectorOverrides(private val context: Context) : VectorOverrides {
suspend fun setHomeserverCapabilities(block: HomeserverCapabilitiesOverride.() -> HomeserverCapabilitiesOverride) { suspend fun setHomeserverCapabilities(block: HomeserverCapabilitiesOverride.() -> HomeserverCapabilitiesOverride) {
val capabilitiesOverride = block(forceHomeserverCapabilities.firstOrNull() ?: HomeserverCapabilitiesOverride(null, null)) val capabilitiesOverride = block(forceHomeserverCapabilities.firstOrNull() ?: HomeserverCapabilitiesOverride(null, null))
context.dataStore.edit { settings -> context.dataStore.edit { settings ->
when (capabilitiesOverride.canChangeDisplayName) { when (val canChangeDisplayName = capabilitiesOverride.canChangeDisplayName) {
null -> settings.remove(forceCanChangeDisplayName) null -> settings.remove(forceCanChangeDisplayName)
else -> settings[forceCanChangeDisplayName] = capabilitiesOverride.canChangeDisplayName else -> settings[forceCanChangeDisplayName] = canChangeDisplayName
} }
when (capabilitiesOverride.canChangeAvatar) { when (val canChangeAvatar = capabilitiesOverride.canChangeAvatar) {
null -> settings.remove(forceCanChangeAvatar) null -> settings.remove(forceCanChangeAvatar)
else -> settings[forceCanChangeAvatar] = capabilitiesOverride.canChangeAvatar else -> settings[forceCanChangeAvatar] = canChangeAvatar
} }
} }
} }

View File

@ -23,9 +23,9 @@ import android.widget.Spinner
import android.widget.TextView import android.widget.TextView
import com.airbnb.epoxy.EpoxyAttribute import com.airbnb.epoxy.EpoxyAttribute
import com.airbnb.epoxy.EpoxyModelClass import com.airbnb.epoxy.EpoxyModelClass
import im.vector.app.R
import im.vector.app.core.epoxy.VectorEpoxyHolder import im.vector.app.core.epoxy.VectorEpoxyHolder
import im.vector.app.core.epoxy.VectorEpoxyModel import im.vector.app.core.epoxy.VectorEpoxyModel
import im.vector.application.R
@EpoxyModelClass @EpoxyModelClass
abstract class EnumFeatureItem : VectorEpoxyModel<EnumFeatureItem.Holder>(R.layout.item_feature) { abstract class EnumFeatureItem : VectorEpoxyModel<EnumFeatureItem.Holder>(R.layout.item_feature) {
@ -70,8 +70,8 @@ abstract class EnumFeatureItem : VectorEpoxyModel<EnumFeatureItem.Holder>(R.layo
} }
class Holder : VectorEpoxyHolder() { class Holder : VectorEpoxyHolder() {
val label by bind<TextView>(im.vector.app.R.id.feature_label) val label by bind<TextView>(R.id.feature_label)
val optionsSpinner by bind<Spinner>(im.vector.app.R.id.feature_options) val optionsSpinner by bind<Spinner>(R.id.feature_options)
} }
interface Listener { interface Listener {

View File

@ -25,7 +25,7 @@ import com.airbnb.mvrx.withState
import dagger.hilt.android.AndroidEntryPoint import dagger.hilt.android.AndroidEntryPoint
import im.vector.app.core.epoxy.onClick import im.vector.app.core.epoxy.onClick
import im.vector.app.core.platform.VectorBaseFragment import im.vector.app.core.platform.VectorBaseFragment
import im.vector.app.databinding.FragmentDebugMemoryLeaksBinding import im.vector.application.databinding.FragmentDebugMemoryLeaksBinding
@AndroidEntryPoint @AndroidEntryPoint
class DebugMemoryLeaksFragment : class DebugMemoryLeaksFragment :

View File

@ -1,5 +1,5 @@
/* /*
* Copyright (c) 2021 New Vector Ltd * Copyright (c) 2022 New Vector Ltd
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.

View File

@ -21,9 +21,9 @@ import android.widget.TextView
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import com.airbnb.epoxy.EpoxyAttribute import com.airbnb.epoxy.EpoxyAttribute
import com.airbnb.epoxy.EpoxyModelClass import com.airbnb.epoxy.EpoxyModelClass
import im.vector.app.R
import im.vector.app.core.epoxy.VectorEpoxyHolder import im.vector.app.core.epoxy.VectorEpoxyHolder
import im.vector.app.core.epoxy.VectorEpoxyModel import im.vector.app.core.epoxy.VectorEpoxyModel
import im.vector.application.R
import me.gujun.android.span.image import me.gujun.android.span.image
import me.gujun.android.span.span import me.gujun.android.span.span
import org.matrix.android.sdk.api.session.crypto.verification.EmojiRepresentation import org.matrix.android.sdk.api.session.crypto.verification.EmojiRepresentation

View File

@ -23,7 +23,7 @@ import android.view.ViewGroup
import com.airbnb.mvrx.fragmentViewModel import com.airbnb.mvrx.fragmentViewModel
import com.airbnb.mvrx.withState import com.airbnb.mvrx.withState
import im.vector.app.core.platform.VectorBaseFragment import im.vector.app.core.platform.VectorBaseFragment
import im.vector.app.databinding.FragmentDebugPrivateSettingsBinding import im.vector.application.databinding.FragmentDebugPrivateSettingsBinding
class DebugPrivateSettingsFragment : VectorBaseFragment<FragmentDebugPrivateSettingsBinding>() { class DebugPrivateSettingsFragment : VectorBaseFragment<FragmentDebugPrivateSettingsBinding>() {

View File

@ -24,7 +24,7 @@ import android.view.View
import android.widget.AdapterView import android.widget.AdapterView
import android.widget.ArrayAdapter import android.widget.ArrayAdapter
import android.widget.LinearLayout import android.widget.LinearLayout
import im.vector.app.databinding.ViewBooleanDropdownBinding import im.vector.application.databinding.ViewBooleanDropdownBinding
class OverrideDropdownView @JvmOverloads constructor( class OverrideDropdownView @JvmOverloads constructor(
context: Context, context: Context,

View File

@ -1,7 +1,6 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android" <manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools">
package="im.vector.app">
<uses-permission android:name="android.permission.WAKE_LOCK" /> <uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" /> <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
@ -15,7 +14,7 @@
<application> <application>
<receiver <receiver
android:name=".fdroid.receiver.OnApplicationUpgradeOrRebootReceiver" android:name="im.vector.app.fdroid.receiver.OnApplicationUpgradeOrRebootReceiver"
android:exported="false"> android:exported="false">
<intent-filter> <intent-filter>
<action android:name="android.intent.action.MY_PACKAGE_REPLACED" /> <action android:name="android.intent.action.MY_PACKAGE_REPLACED" />
@ -24,12 +23,12 @@
</receiver> </receiver>
<receiver <receiver
android:name=".fdroid.receiver.AlarmSyncBroadcastReceiver" android:name="im.vector.app.fdroid.receiver.AlarmSyncBroadcastReceiver"
android:enabled="true" android:enabled="true"
android:exported="false" /> android:exported="false" />
<receiver <receiver
android:name=".fdroid.receiver.KeepInternalDistributor" android:name="im.vector.app.fdroid.receiver.KeepInternalDistributor"
android:enabled="true" android:enabled="true"
android:exported="false"> android:exported="false">
<intent-filter> <intent-filter>
@ -43,7 +42,7 @@
</receiver> </receiver>
<service <service
android:name=".fdroid.service.GuardAndroidService" android:name="im.vector.app.fdroid.service.GuardAndroidService"
android:exported="false" android:exported="false"
tools:ignore="Instantiatable" /> tools:ignore="Instantiatable" />

View File

@ -3,6 +3,7 @@ apply plugin: 'kotlin-android'
apply plugin: 'kotlin-parcelize' apply plugin: 'kotlin-parcelize'
apply plugin: 'kotlin-kapt' apply plugin: 'kotlin-kapt'
apply plugin: 'dagger.hilt.android.plugin' apply plugin: 'dagger.hilt.android.plugin'
apply plugin: 'app.cash.paparazzi'
if (project.hasProperty("coverage")) { if (project.hasProperty("coverage")) {
apply plugin: 'jacoco' apply plugin: 'jacoco'
@ -66,30 +67,6 @@ android {
testCoverageEnabled = coverage.enableTestCoverage testCoverageEnabled = coverage.enableTestCoverage
} }
} }
nightly {
initWith release
matchingFallbacks = ['release']
}
release
}
flavorDimensions "store"
productFlavors {
gplay {
dimension "store"
isDefault = true
buildConfigField "String", "SHORT_FLAVOR_DESCRIPTION", "\"G\""
buildConfigField "String", "FLAVOR_DESCRIPTION", "\"GooglePlay\""
}
fdroid {
dimension "store"
buildConfigField "String", "SHORT_FLAVOR_DESCRIPTION", "\"F\""
buildConfigField "String", "FLAVOR_DESCRIPTION", "\"FDroid\""
}
} }
compileOptions { compileOptions {
@ -118,10 +95,6 @@ android {
test { test {
java.srcDirs += "src/sharedTest/java" java.srcDirs += "src/sharedTest/java"
} }
// Add sourceSets for `release` version when building `nightly`
nightly {
java.srcDirs += "src/release/java"
}
} }
buildFeatures { buildFeatures {
@ -190,12 +163,6 @@ dependencies {
// Snap Helper https://github.com/rubensousa/GravitySnapHelper // Snap Helper https://github.com/rubensousa/GravitySnapHelper
api 'com.github.rubensousa:gravitysnaphelper:2.2.2' api 'com.github.rubensousa:gravitysnaphelper:2.2.2'
// Nightly
// API-only library
gplayImplementation libs.google.appdistributionApi
// Full SDK implementation
gplayImplementation libs.google.appdistribution
// Work // Work
api libs.androidx.work api libs.androidx.work
@ -211,7 +178,7 @@ dependencies {
// UI // UI
implementation 'com.amulyakhare:com.amulyakhare.textdrawable:1.0.1' implementation 'com.amulyakhare:com.amulyakhare.textdrawable:1.0.1'
implementation libs.google.material implementation libs.google.material
implementation 'me.gujun.android:span:1.7' api 'me.gujun.android:span:1.7'
implementation libs.markwon.core implementation libs.markwon.core
implementation libs.markwon.extLatex implementation libs.markwon.extLatex
implementation libs.markwon.inlineParser implementation libs.markwon.inlineParser
@ -263,15 +230,6 @@ dependencies {
// UnifiedPush // UnifiedPush
implementation 'com.github.UnifiedPush:android-connector:2.0.1' implementation 'com.github.UnifiedPush:android-connector:2.0.1'
// UnifiedPush gplay flavor only
gplayImplementation('com.github.UnifiedPush:android-embedded_fcm_distributor:2.1.2') {
exclude group: 'com.google.firebase', module: 'firebase-core'
exclude group: 'com.google.firebase', module: 'firebase-analytics'
exclude group: 'com.google.firebase', module: 'firebase-measurement-connector'
}
// OSS License, gplay flavor only
gplayImplementation 'com.google.android.gms:play-services-oss-licenses:17.0.0'
implementation "androidx.emoji2:emoji2:1.1.0" implementation "androidx.emoji2:emoji2:1.1.0"
@ -307,14 +265,12 @@ dependencies {
implementation 'commons-codec:commons-codec:1.15' implementation 'commons-codec:commons-codec:1.15'
// MapTiler // MapTiler
fdroidApi(libs.maplibre.androidSdk) { api(libs.maplibre.androidSdk) {
exclude group: 'com.google.android.gms', module: 'play-services-location' exclude group: 'com.google.android.gms', module: 'play-services-location'
} }
fdroidApi(libs.maplibre.pluginAnnotation) { api(libs.maplibre.pluginAnnotation) {
exclude group: 'com.google.android.gms', module: 'play-services-location' exclude group: 'com.google.android.gms', module: 'play-services-location'
} }
gplayApi libs.maplibre.androidSdk
gplayApi libs.maplibre.pluginAnnotation
// TESTS // TESTS
testImplementation libs.tests.junit testImplementation libs.tests.junit
@ -327,19 +283,6 @@ dependencies {
exclude group: "org.jetbrains.kotlinx", module: "kotlinx-coroutines-debug" exclude group: "org.jetbrains.kotlinx", module: "kotlinx-coroutines-debug"
} }
// Flipper, debug builds only
debugImplementation(libs.flipper.flipper) {
exclude group: 'com.facebook.fbjni', module: 'fbjni'
}
debugImplementation(libs.flipper.flipperNetworkPlugin) {
exclude group: 'com.facebook.fbjni', module: 'fbjni'
}
debugImplementation 'com.facebook.soloader:soloader:0.10.4'
debugImplementation "com.kgurgul.flipper:flipper-realm-android:2.2.0"
// Activate when you want to check for leaks, from time to time.
debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.9.1'
androidTestImplementation libs.androidx.testCore androidTestImplementation libs.androidx.testCore
androidTestImplementation libs.androidx.testRunner androidTestImplementation libs.androidx.testRunner
androidTestImplementation libs.androidx.testRules androidTestImplementation libs.androidx.testRules

View File

@ -1,23 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="im.vector.app">
<application>
<activity android:name=".features.debug.TestLinkifyActivity" />
<activity android:name=".features.debug.DebugPermissionActivity" />
<activity android:name=".features.debug.analytics.DebugAnalyticsActivity" />
<activity android:name=".features.debug.settings.DebugPrivateSettingsActivity" />
<activity android:name=".features.debug.sas.DebugSasEmojiActivity" />
<activity android:name=".features.debug.features.DebugFeaturesSettingsActivity" />
<activity android:name=".features.debug.DebugMenuActivity" />
<activity android:name=".features.debug.leak.DebugMemoryLeaksActivity" />
<activity
android:name="com.facebook.flipper.android.diagnostics.FlipperDiagnosticActivity"
android:exported="true" />
<!-- Used for UI tests to display the BiometricPrompt. It's normal that it appears as an error. -->
<activity android:exported="false" android:name=".features.pin.lockscreen.tests.LockScreenTestActivity" />
</application>
</manifest>

View File

@ -20,8 +20,11 @@ import android.os.Bundle
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.lifecycleScope
import dagger.hilt.android.AndroidEntryPoint import dagger.hilt.android.AndroidEntryPoint
import im.vector.app.R import im.vector.app.R
import im.vector.app.core.error.ErrorFormatter
import im.vector.app.core.extensions.associateContentStateWith import im.vector.app.core.extensions.associateContentStateWith
import im.vector.app.core.extensions.clearErrorOnChange import im.vector.app.core.extensions.clearErrorOnChange
import im.vector.app.core.extensions.content import im.vector.app.core.extensions.content
@ -30,6 +33,7 @@ import im.vector.app.core.extensions.realignPercentagesToParent
import im.vector.app.core.extensions.setOnImeDoneListener import im.vector.app.core.extensions.setOnImeDoneListener
import im.vector.app.core.extensions.showKeyboard import im.vector.app.core.extensions.showKeyboard
import im.vector.app.core.extensions.toReducedUrl import im.vector.app.core.extensions.toReducedUrl
import im.vector.app.core.resources.StringProvider
import im.vector.app.core.utils.ensureProtocol import im.vector.app.core.utils.ensureProtocol
import im.vector.app.core.utils.ensureTrailingSlash import im.vector.app.core.utils.ensureTrailingSlash
import im.vector.app.core.utils.openUrlInExternalBrowser import im.vector.app.core.utils.openUrlInExternalBrowser
@ -37,49 +41,82 @@ import im.vector.app.databinding.FragmentFtueServerSelectionCombinedBinding
import im.vector.app.features.onboarding.OnboardingAction import im.vector.app.features.onboarding.OnboardingAction
import im.vector.app.features.onboarding.OnboardingFlow import im.vector.app.features.onboarding.OnboardingFlow
import im.vector.app.features.onboarding.OnboardingViewEvents import im.vector.app.features.onboarding.OnboardingViewEvents
import im.vector.app.features.onboarding.OnboardingViewModel
import im.vector.app.features.onboarding.OnboardingViewState import im.vector.app.features.onboarding.OnboardingViewState
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import org.matrix.android.sdk.api.failure.isHomeserverUnavailable import org.matrix.android.sdk.api.failure.isHomeserverUnavailable
import reactivecircus.flowbinding.android.view.clicks
import javax.inject.Inject
@AndroidEntryPoint @AndroidEntryPoint
class FtueAuthCombinedServerSelectionFragment : class FtueAuthCombinedServerSelectionFragment : AbstractFtueAuthFragment<FragmentFtueServerSelectionCombinedBinding>() {
AbstractFtueAuthFragment<FragmentFtueServerSelectionCombinedBinding>() {
@Inject lateinit var stringProvider: StringProvider
private lateinit var controller: Controller
override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentFtueServerSelectionCombinedBinding { override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentFtueServerSelectionCombinedBinding {
return FragmentFtueServerSelectionCombinedBinding.inflate(inflater, container, false) return FragmentFtueServerSelectionCombinedBinding.inflate(inflater, container, false).also {
controller = Controller(viewLifecycleOwner, it, stringProvider, errorFormatter)
}
} }
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)
setupViews() controller.listener = object : Controller.Listener {
} override fun onNavigationClicked() {
private fun setupViews() {
views.chooseServerRoot.realignPercentagesToParent()
views.chooseServerToolbar.setNavigationOnClickListener {
viewModel.handle(OnboardingAction.PostViewEvent(OnboardingViewEvents.OnBack)) viewModel.handle(OnboardingAction.PostViewEvent(OnboardingViewEvents.OnBack))
} }
views.chooseServerInput.associateContentStateWith(button = views.chooseServerSubmit, enabledPredicate = { canSubmit(it) })
views.chooseServerInput.setOnImeDoneListener {
if (canSubmit(views.chooseServerInput.content())) {
updateServerUrl()
}
}
views.chooseServerGetInTouch.debouncedClicks { openUrlInExternalBrowser(requireContext(), getString(R.string.ftue_ems_url)) }
views.chooseServerSubmit.debouncedClicks { updateServerUrl() }
views.chooseServerInput.clearErrorOnChange(viewLifecycleOwner)
}
private fun canSubmit(url: String) = url.isNotEmpty() override fun updateServerUrl() {
private fun updateServerUrl() {
viewModel.handle(OnboardingAction.HomeServerChange.EditHomeServer(views.chooseServerInput.content().ensureProtocol().ensureTrailingSlash())) viewModel.handle(OnboardingAction.HomeServerChange.EditHomeServer(views.chooseServerInput.content().ensureProtocol().ensureTrailingSlash()))
} }
override fun getInTouchClicked() {
openUrlInExternalBrowser(requireContext(), getString(R.string.ftue_ems_url))
}
}
}
override fun resetViewModel() { override fun resetViewModel() {
// do nothing // do nothing
} }
override fun updateWithState(state: OnboardingViewState) { override fun updateWithState(state: OnboardingViewState) {
controller.setData(state)
}
override fun onError(throwable: Throwable) {
controller.setError(throwable)
}
class Controller(
lifecycleOwner: LifecycleOwner,
private val views: FragmentFtueServerSelectionCombinedBinding,
private val stringProvider: StringProvider,
private val errorFormatter: ErrorFormatter,
private val viewModel: OnboardingViewModel,
) {
var listener: Listener? = null
init {
views.chooseServerRoot.realignPercentagesToParent()
views.chooseServerToolbar.setNavigationOnClickListener { listener?.onNavigationClicked() }
views.chooseServerInput.associateContentStateWith(button = views.chooseServerSubmit, enabledPredicate = { canSubmit(it) })
views.chooseServerInput.setOnImeDoneListener {
if (canSubmit(views.chooseServerInput.content())) {
listener?.updateServerUrl()
}
}
views.chooseServerGetInTouch.debouncedClicks(lifecycleOwner) { listener?.getInTouchClicked() }
views.chooseServerSubmit.debouncedClicks(lifecycleOwner) { listener?.updateServerUrl() }
views.chooseServerInput.clearErrorOnChange(lifecycleOwner)
}
private fun canSubmit(url: String) = url.isNotEmpty()
fun setData(state: OnboardingViewState) {
views.chooseServerHeaderSubtitle.setText( views.chooseServerHeaderSubtitle.setText(
when (state.onboardingFlow) { when (state.onboardingFlow) {
OnboardingFlow.SignIn -> R.string.ftue_auth_choose_server_sign_in_subtitle OnboardingFlow.SignIn -> R.string.ftue_auth_choose_server_sign_in_subtitle
@ -97,12 +134,26 @@ class FtueAuthCombinedServerSelectionFragment :
views.chooseServerInput.editText().showKeyboard(true) views.chooseServerInput.editText().showKeyboard(true)
} }
override fun onError(throwable: Throwable) { fun setError(throwable: Throwable) {
views.chooseServerInput.error = when { views.chooseServerInput.error = when {
throwable.isHomeserverUnavailable() -> getString(R.string.login_error_homeserver_not_found) throwable.isHomeserverUnavailable() -> stringProvider.getString(R.string.login_error_homeserver_not_found)
else -> errorFormatter.toHumanReadable(throwable) else -> errorFormatter.toHumanReadable(throwable)
} }
println(views.chooseServerInput.error)
} }
private fun String.toReducedUrlKeepingSchemaIfInsecure() = toReducedUrl(keepSchema = this.startsWith("http://")) private fun String.toReducedUrlKeepingSchemaIfInsecure() = toReducedUrl(keepSchema = this.startsWith("http://"))
interface Listener {
fun onNavigationClicked()
fun updateServerUrl()
fun getInTouchClicked()
}
}
}
private fun View.debouncedClicks(lifecycleOwner: LifecycleOwner, onClicked: () -> Unit) {
clicks()
.onEach { onClicked() }
.launchIn(lifecycleOwner.lifecycleScope)
} }

View File

@ -120,7 +120,7 @@
app:layout_constraintHeight_percent="0.03" app:layout_constraintHeight_percent="0.03"
app:layout_constraintTop_toBottomOf="@id/chooseServerInput" /> app:layout_constraintTop_toBottomOf="@id/chooseServerInput" />
<Button <com.google.android.material.button.MaterialButton
android:id="@+id/chooseServerSubmit" android:id="@+id/chooseServerSubmit"
style="@style/Widget.Vector.Button.Login" style="@style/Widget.Vector.Button.Login"
android:layout_width="0dp" android:layout_width="0dp"

View File

@ -0,0 +1,181 @@
/*
* Copyright (c) 2022 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package im.vector.app
import android.os.Build
import android.view.View
import android.widget.FrameLayout
import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.LifecycleRegistry
import androidx.recyclerview.widget.RecyclerView
import app.cash.paparazzi.DeviceConfig.Companion.PIXEL_3
import app.cash.paparazzi.Paparazzi
import com.airbnb.epoxy.EpoxyController
import com.airbnb.mvrx.Success
import im.vector.app.core.error.DefaultErrorFormatter
import im.vector.app.core.extensions.configureWith
import im.vector.app.core.resources.StringProvider
import im.vector.app.core.utils.DimensionConverter
import im.vector.app.databinding.FragmentFtueServerSelectionCombinedBinding
import im.vector.app.features.home.AvatarRenderer
import im.vector.app.features.home.room.detail.timeline.format.RoomHistoryVisibilityFormatter
import im.vector.app.features.onboarding.OnboardingFlow
import im.vector.app.features.onboarding.OnboardingViewState
import im.vector.app.features.onboarding.SelectedHomeserverState
import im.vector.app.features.onboarding.ftueauth.FtueAuthCombinedServerSelectionFragment
import im.vector.app.features.roomprofile.settings.RoomSettingsController
import im.vector.app.features.roomprofile.settings.RoomSettingsViewState
import im.vector.app.test.fakes.FakeErrorFormatter
import im.vector.app.test.fakes.FakeVectorPreferences
import io.mockk.coJustRun
import io.mockk.every
import io.mockk.mockk
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.matrix.android.sdk.api.session.room.model.RoomSummary
import org.matrix.android.sdk.api.util.MatrixItem
import java.lang.reflect.Field
import java.lang.reflect.Modifier
class PaparazziScreenshotTest {
@get:Rule
val paparazzi = Paparazzi(
deviceConfig = PIXEL_3,
theme = "Theme.Vector.Light",
)
@Before
fun setUp() {
setFinalStaticValue(Build::class.java.getDeclaredField("MANUFACTURER"), "GOOGLE")
}
@Test
fun `empty server selection`() {
val (view, binding) = paparazzi.inflate(R.layout.fragment_ftue_server_selection_combined, FragmentFtueServerSelectionCombinedBinding::bind)
val stringProvider = StringProvider(paparazzi.resources)
val lifecycleOwner = fakeLifecycleOwner()
FtueAuthCombinedServerSelectionFragment.Controller(lifecycleOwner, binding, stringProvider, FakeErrorFormatter())
.setData(
OnboardingViewState(
onboardingFlow = OnboardingFlow.SignIn,
)
)
paparazzi.snapshot(view)
}
@Test
fun `server selection with content`() {
val (view, binding) = paparazzi.inflate(R.layout.fragment_ftue_server_selection_combined, FragmentFtueServerSelectionCombinedBinding::bind)
val stringProvider = StringProvider(paparazzi.resources)
val lifecycleOwner = fakeLifecycleOwner()
FtueAuthCombinedServerSelectionFragment.Controller(lifecycleOwner, binding, stringProvider, DefaultErrorFormatter(stringProvider))
.setData(
OnboardingViewState(
onboardingFlow = OnboardingFlow.SignIn,
selectedHomeserver = SelectedHomeserverState(userFacingUrl = "matrix.org")
)
)
paparazzi.snapshot(view)
}
@Test
fun `room settings`() {
// Material components aren't fully supported yet https://github.com/cashapp/paparazzi/issues/223
val strings = StringProvider(paparazzi.resources)
val fakeAvatarRender = FakeAvatarRender()
val fakeDimensionConverter = FakeDimensionConverter()
val fakeVectorPreferences = FakeVectorPreferences().also { it.givenDeveloperMode(false) }
val controller = RoomSettingsController(
strings,
fakeAvatarRender.instance,
fakeDimensionConverter.instance,
RoomHistoryVisibilityFormatter(strings),
fakeVectorPreferences.instance,
)
val view = inflateRecyclerView(controller)
controller.setData(
RoomSettingsViewState(
roomId = "room-id",
roomSummary = Success(
RoomSummary(
roomId = "!room-id",
isEncrypted = true,
encryptionEventTs = null,
typingUsers = emptyList(),
displayName = "A room name",
topic = "A room topic",
)
)
)
)
paparazzi.snapshot(view)
}
private fun inflateRecyclerView(controller: EpoxyController): View {
val view = paparazzi.inflate<FrameLayout>(R.layout.fragment_generic_recycler)
val recyclerView = view.findViewById<RecyclerView>(R.id.genericRecyclerView)
recyclerView.configureWith(controller)
return view
}
}
fun setFinalStaticValue(field: Field, value: Any) {
val modifiersField = Field::class.java.getDeclaredField("modifiers")
modifiersField.isAccessible = true
modifiersField.setInt(field, field.modifiers and Modifier.FINAL.inv())
field.isAccessible = true
field.set(null, value)
}
private fun <B> Paparazzi.inflate(layoutId: Int, binder: (View) -> B): Pair<View, B> {
val view = inflate<View>(layoutId)
val binding = binder(view)
return view to binding
}
private fun fakeLifecycleOwner(): LifecycleOwner {
val lifecycleOwner = mockk<LifecycleOwner>()
every { lifecycleOwner.lifecycle } returns LifecycleRegistry(lifecycleOwner)
return lifecycleOwner
}
class FakeDimensionConverter {
val instance = mockk<DimensionConverter>().also {
every { it.dpToPx(any()) }.answers { arg ->
arg.invocation.args.first() as Int
}
}
}
class FakeAvatarRender {
val instance = mockk<AvatarRenderer>().also {
coJustRun { it.render(any<MatrixItem>(), any()) }
}
}

View File

@ -36,4 +36,8 @@ class FakeVectorPreferences {
fun verifySetSpaceBackstack(value: List<String?>, inverse: Boolean = false) { fun verifySetSpaceBackstack(value: List<String?>, inverse: Boolean = false) {
verify(inverse = inverse) { instance.setSpaceBackstack(value) } verify(inverse = inverse) { instance.setSpaceBackstack(value) }
} }
fun givenDeveloperMode(isDeveloperMode: Boolean) {
every { instance.developerMode() } returns isDeveloperMode
}
} }

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB