diff --git a/CHANGES.md b/CHANGES.md index 68d7c48cdd..570df5ec11 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -31,6 +31,9 @@ SDK API changes ⚠️: Build 🧱: - +Test: + - Add `allScreensTest` to cover all screens of the app + Other changes: - Upgrade Realm dependency to 10.0.0 @@ -1044,5 +1047,8 @@ SDK API changes ⚠️: Build 🧱: - +Test: + - + Other changes: - diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/RealmCryptoStore.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/RealmCryptoStore.kt index 72274aa70a..6b83c4f7d1 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/RealmCryptoStore.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/crypto/store/db/RealmCryptoStore.kt @@ -1679,27 +1679,24 @@ internal class RealmCryptoStore @Inject constructor( // Only keep one week history realm.where() .lessThan(IncomingGossipingRequestEntityFields.LOCAL_CREATION_TIMESTAMP, prevWeekTs) - .findAll().let { - Timber.i("## Crypto Clean up ${it.size} IncomingGossipingRequestEntity") - it.deleteAllFromRealm() - } + .findAll() + .also { Timber.i("## Crypto Clean up ${it.size} IncomingGossipingRequestEntity") } + .deleteAllFromRealm() // Clean the cancelled ones? realm.where() .equalTo(OutgoingGossipingRequestEntityFields.REQUEST_STATE_STR, OutgoingGossipingRequestState.CANCELLED.name) .equalTo(OutgoingGossipingRequestEntityFields.TYPE_STR, GossipRequestType.KEY.name) - .findAll().let { - Timber.i("## Crypto Clean up ${it.size} OutgoingGossipingRequestEntity") - it.deleteAllFromRealm() - } + .findAll() + .also { Timber.i("## Crypto Clean up ${it.size} OutgoingGossipingRequestEntity") } + .deleteAllFromRealm() // Only keep one week history realm.where() .lessThan(GossipingEventEntityFields.AGE_LOCAL_TS, prevWeekTs) - .findAll().let { - Timber.i("## Crypto Clean up ${it.size} GossipingEventEntityFields") - it.deleteAllFromRealm() - } + .findAll() + .also { Timber.i("## Crypto Clean up ${it.size} GossipingEventEntityFields") } + .deleteAllFromRealm() // Can we do something for WithHeldSessionEntity? } diff --git a/vector/build.gradle b/vector/build.gradle index ca7cb12e31..037b049a76 100644 --- a/vector/build.gradle +++ b/vector/build.gradle @@ -461,6 +461,10 @@ dependencies { androidTestImplementation "androidx.arch.core:core-testing:$arch_version" // Plant Timber tree for test androidTestImplementation 'net.lachlanmckee:timber-junit-rule:1.0.1' + // "The one who serves a great Espresso" + androidTestImplementation('com.schibsted.spain:barista:3.7.0') { + exclude group: 'org.jetbrains.kotlin' + } } if (getGradle().getStartParameter().getTaskRequests().toString().contains("Gplay")) { diff --git a/vector/src/androidTest/java/im/vector/app/RegistrationTest.kt b/vector/src/androidTest/java/im/vector/app/RegistrationTest.kt index b88356db59..73ca94b148 100644 --- a/vector/src/androidTest/java/im/vector/app/RegistrationTest.kt +++ b/vector/src/androidTest/java/im/vector/app/RegistrationTest.kt @@ -67,7 +67,7 @@ class RegistrationTest { .perform(click()) // Enter local synapse - onView((withId(R.id.loginServerUrlFormHomeServerUrl))) + onView(withId(R.id.loginServerUrlFormHomeServerUrl)) .perform(typeText(homeServerUrl)) // Click on continue @@ -87,7 +87,7 @@ class RegistrationTest { .check(matches(isDisplayed())) // Ensure user id - onView((withId(R.id.loginField))) + onView(withId(R.id.loginField)) .perform(typeText(userId)) // Ensure login button not yet enabled @@ -95,7 +95,7 @@ class RegistrationTest { .check(matches(not(isEnabled()))) // Ensure password - onView((withId(R.id.passwordField))) + onView(withId(R.id.passwordField)) .perform(closeSoftKeyboard(), typeText(password)) // Submit diff --git a/vector/src/androidTest/java/im/vector/app/SecurityBootstrapTest.kt b/vector/src/androidTest/java/im/vector/app/SecurityBootstrapTest.kt index 3ab8fe7dd9..0d0ec3dd2b 100644 --- a/vector/src/androidTest/java/im/vector/app/SecurityBootstrapTest.kt +++ b/vector/src/androidTest/java/im/vector/app/SecurityBootstrapTest.kt @@ -79,7 +79,7 @@ class SecurityBootstrapTest : VerificationTestBase() { fun testBasicBootstrap() { val userId: String = existingSession!!.myUserId - doLogin(homeServerUrl, userId, password) + uiTestBase.login(userId = userId, password = password, homeServerUrl = homeServerUrl) // Thread.sleep(6000) withIdlingResource(activityIdlingResource(HomeActivity::class.java)) { diff --git a/vector/src/androidTest/java/im/vector/app/VerificationTestBase.kt b/vector/src/androidTest/java/im/vector/app/VerificationTestBase.kt index 2a1b6d802f..a4b9983ff4 100644 --- a/vector/src/androidTest/java/im/vector/app/VerificationTestBase.kt +++ b/vector/src/androidTest/java/im/vector/app/VerificationTestBase.kt @@ -18,15 +18,11 @@ package im.vector.app import android.net.Uri import androidx.lifecycle.Observer -import androidx.test.espresso.Espresso -import androidx.test.espresso.action.ViewActions -import androidx.test.espresso.assertion.ViewAssertions -import androidx.test.espresso.matcher.ViewMatchers +import im.vector.app.ui.UiTestBase import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.launch import kotlinx.coroutines.runBlocking -import org.hamcrest.CoreMatchers import org.junit.Assert import org.matrix.android.sdk.api.Matrix import org.matrix.android.sdk.api.MatrixCallback @@ -43,108 +39,12 @@ abstract class VerificationTestBase { val password = "password" val homeServerUrl: String = "http://10.0.2.2:8080" - fun doLogin(homeServerUrl: String, userId: String, password: String) { - Espresso.onView(ViewMatchers.withId(R.id.loginSplashSubmit)) - .check(ViewAssertions.matches(ViewMatchers.isDisplayed())) - .check(ViewAssertions.matches(ViewMatchers.withText(R.string.login_splash_submit))) + protected val uiTestBase = UiTestBase() - Espresso.onView(ViewMatchers.withId(R.id.loginSplashSubmit)) - .perform(ViewActions.click()) - - Espresso.onView(ViewMatchers.withId(R.id.loginServerTitle)) - .check(ViewAssertions.matches(ViewMatchers.isDisplayed())) - .check(ViewAssertions.matches(ViewMatchers.withText(R.string.login_server_title))) - - // Chose custom server - Espresso.onView(ViewMatchers.withId(R.id.loginServerChoiceOther)) - .perform(ViewActions.click()) - - // Enter local synapse - Espresso.onView((ViewMatchers.withId(R.id.loginServerUrlFormHomeServerUrl))) - .perform(ViewActions.typeText(homeServerUrl)) - - Espresso.onView(ViewMatchers.withId(R.id.loginServerUrlFormSubmit)) - .check(ViewAssertions.matches(ViewMatchers.isEnabled())) - .perform(ViewActions.closeSoftKeyboard(), ViewActions.click()) - - // Click on the signin button - Espresso.onView(ViewMatchers.withId(R.id.loginSignupSigninSignIn)) - .check(ViewAssertions.matches(ViewMatchers.isDisplayed())) - .perform(ViewActions.click()) - - // Ensure password flow supported - Espresso.onView(ViewMatchers.withId(R.id.loginField)) - .check(ViewAssertions.matches(ViewMatchers.isDisplayed())) - Espresso.onView(ViewMatchers.withId(R.id.passwordField)) - .check(ViewAssertions.matches(ViewMatchers.isDisplayed())) - - Espresso.onView((ViewMatchers.withId(R.id.loginField))) - .perform(ViewActions.typeText(userId)) - Espresso.onView(ViewMatchers.withId(R.id.loginSubmit)) - .check(ViewAssertions.matches(CoreMatchers.not(ViewMatchers.isEnabled()))) - - Espresso.onView((ViewMatchers.withId(R.id.passwordField))) - .perform(ViewActions.closeSoftKeyboard(), ViewActions.typeText(password)) - - Espresso.onView(ViewMatchers.withId(R.id.loginSubmit)) - .check(ViewAssertions.matches(ViewMatchers.isEnabled())) - .perform(ViewActions.closeSoftKeyboard(), ViewActions.click()) - } - - private fun createAccount(userId: String = "UiAutoTest", password: String = "password", homeServerUrl: String = "http://10.0.2.2:8080") { - Espresso.onView(ViewMatchers.withId(R.id.loginSplashSubmit)) - .check(ViewAssertions.matches(ViewMatchers.isDisplayed())) - .check(ViewAssertions.matches(ViewMatchers.withText(R.string.login_splash_submit))) - - Espresso.onView(ViewMatchers.withId(R.id.loginSplashSubmit)) - .perform(ViewActions.click()) - - Espresso.onView(ViewMatchers.withId(R.id.loginServerTitle)) - .check(ViewAssertions.matches(ViewMatchers.isDisplayed())) - .check(ViewAssertions.matches(ViewMatchers.withText(R.string.login_server_title))) - - // Chose custom server - Espresso.onView(ViewMatchers.withId(R.id.loginServerChoiceOther)) - .perform(ViewActions.click()) - - // Enter local synapse - Espresso.onView((ViewMatchers.withId(R.id.loginServerUrlFormHomeServerUrl))) - .perform(ViewActions.typeText(homeServerUrl)) - - Espresso.onView(ViewMatchers.withId(R.id.loginServerUrlFormSubmit)) - .check(ViewAssertions.matches(ViewMatchers.isEnabled())) - .perform(ViewActions.closeSoftKeyboard(), ViewActions.click()) - - // Click on the signup button - Espresso.onView(ViewMatchers.withId(R.id.loginSignupSigninSubmit)) - .check(ViewAssertions.matches(ViewMatchers.isDisplayed())) - .perform(ViewActions.click()) - - // Ensure password flow supported - Espresso.onView(ViewMatchers.withId(R.id.loginField)) - .check(ViewAssertions.matches(ViewMatchers.isDisplayed())) - Espresso.onView(ViewMatchers.withId(R.id.passwordField)) - .check(ViewAssertions.matches(ViewMatchers.isDisplayed())) - - Espresso.onView((ViewMatchers.withId(R.id.loginField))) - .perform(ViewActions.typeText(userId)) - Espresso.onView(ViewMatchers.withId(R.id.loginSubmit)) - .check(ViewAssertions.matches(CoreMatchers.not(ViewMatchers.isEnabled()))) - - Espresso.onView((ViewMatchers.withId(R.id.passwordField))) - .perform(ViewActions.typeText(password)) - - Espresso.onView(ViewMatchers.withId(R.id.loginSubmit)) - .check(ViewAssertions.matches(ViewMatchers.isEnabled())) - .perform(ViewActions.closeSoftKeyboard(), ViewActions.click()) - - Espresso.onView(ViewMatchers.withId(R.id.homeDrawerFragmentContainer)) - .check(ViewAssertions.matches(ViewMatchers.isDisplayed())) - } - - fun createAccountAndSync(matrix: Matrix, userName: String, - password: String, - withInitialSync: Boolean): Session { + fun createAccountAndSync(matrix: Matrix, + userName: String, + password: String, + withInitialSync: Boolean): Session { val hs = createHomeServerConfig() doSync { @@ -174,7 +74,7 @@ abstract class VerificationTestBase { return session } - fun createHomeServerConfig(): HomeServerConnectionConfig { + private fun createHomeServerConfig(): HomeServerConnectionConfig { return HomeServerConnectionConfig.Builder() .withHomeServerUri(Uri.parse(homeServerUrl)) .build() @@ -200,7 +100,7 @@ abstract class VerificationTestBase { return result!! } - fun syncSession(session: Session) { + private fun syncSession(session: Session) { val lock = CountDownLatch(1) GlobalScope.launch(Dispatchers.Main) { session.open() } diff --git a/vector/src/androidTest/java/im/vector/app/VerifySessionInteractiveTest.kt b/vector/src/androidTest/java/im/vector/app/VerifySessionInteractiveTest.kt index d218b6ef7e..d9005e4a63 100644 --- a/vector/src/androidTest/java/im/vector/app/VerifySessionInteractiveTest.kt +++ b/vector/src/androidTest/java/im/vector/app/VerifySessionInteractiveTest.kt @@ -78,7 +78,7 @@ class VerifySessionInteractiveTest : VerificationTestBase() { fun checkVerifyPopup() { val userId: String = existingSession!!.myUserId - doLogin(homeServerUrl, userId, password) + uiTestBase.login(userId = userId, password = password, homeServerUrl = homeServerUrl) // Thread.sleep(6000) withIdlingResource(activityIdlingResource(HomeActivity::class.java)) { @@ -215,10 +215,10 @@ class VerifySessionInteractiveTest : VerificationTestBase() { } fun signout() { - onView((withId(R.id.groupToolbarAvatarImageView))) + onView(withId(R.id.groupToolbarAvatarImageView)) .perform(click()) - onView((withId(R.id.homeDrawerHeaderSettingsView))) + onView(withId(R.id.homeDrawerHeaderSettingsView)) .perform(click()) onView(withText("General")) diff --git a/vector/src/androidTest/java/im/vector/app/VerifySessionPassphraseTest.kt b/vector/src/androidTest/java/im/vector/app/VerifySessionPassphraseTest.kt index f8c2a89ea8..8a21260ac7 100644 --- a/vector/src/androidTest/java/im/vector/app/VerifySessionPassphraseTest.kt +++ b/vector/src/androidTest/java/im/vector/app/VerifySessionPassphraseTest.kt @@ -88,7 +88,7 @@ class VerifySessionPassphraseTest : VerificationTestBase() { fun checkVerifyWithPassphrase() { val userId: String = existingSession!!.myUserId - doLogin(homeServerUrl, userId, password) + uiTestBase.login(userId = userId, password = password, homeServerUrl = homeServerUrl) // Thread.sleep(6000) withIdlingResource(activityIdlingResource(HomeActivity::class.java)) { @@ -137,10 +137,10 @@ class VerifySessionPassphraseTest : VerificationTestBase() { onView(withId(R.id.ssss__root)).check(matches(isDisplayed())) } - onView((withId(R.id.ssss_passphrase_enter_edittext))) + onView(withId(R.id.ssss_passphrase_enter_edittext)) .perform(typeText(passphrase)) - onView((withId(R.id.ssss_passphrase_submit))) + onView(withId(R.id.ssss_passphrase_submit)) .perform(click()) System.out.println("*** passphrase 1") diff --git a/vector/src/androidTest/java/im/vector/app/espresso/tools/EspressoPreference.kt b/vector/src/androidTest/java/im/vector/app/espresso/tools/EspressoPreference.kt new file mode 100644 index 0000000000..bf60ad681f --- /dev/null +++ b/vector/src/androidTest/java/im/vector/app/espresso/tools/EspressoPreference.kt @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2020 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.espresso.tools + +import android.widget.Switch +import androidx.annotation.StringRes +import androidx.preference.Preference +import androidx.recyclerview.widget.RecyclerView +import androidx.test.espresso.Espresso.onData +import androidx.test.espresso.Espresso.onView +import androidx.test.espresso.action.ViewActions.click +import androidx.test.espresso.contrib.RecyclerViewActions.actionOnItem +import androidx.test.espresso.matcher.PreferenceMatchers.withKey +import androidx.test.espresso.matcher.ViewMatchers.hasDescendant +import androidx.test.espresso.matcher.ViewMatchers.withClassName +import androidx.test.espresso.matcher.ViewMatchers.withId +import androidx.test.espresso.matcher.ViewMatchers.withText +import im.vector.app.R +import org.hamcrest.Matchers.`is` +import org.hamcrest.Matchers.allOf +import org.hamcrest.Matchers.instanceOf + +fun clickOnPreference(@StringRes textResId: Int) { + onView(withId(R.id.recycler_view)) + .perform(actionOnItem( + hasDescendant(withText(textResId)), click())) +} + +fun clickOnSwitchPreference(preferenceKey: String) { + onData(allOf(`is`(instanceOf(Preference::class.java)), withKey(preferenceKey))) + .onChildView(withClassName(`is`(Switch::class.java.name))).perform(click()) +} diff --git a/vector/src/androidTest/java/im/vector/app/espresso/tools/WaitActivity.kt b/vector/src/androidTest/java/im/vector/app/espresso/tools/WaitActivity.kt new file mode 100644 index 0000000000..2cdca62c74 --- /dev/null +++ b/vector/src/androidTest/java/im/vector/app/espresso/tools/WaitActivity.kt @@ -0,0 +1,25 @@ +/* + * Copyright (c) 2020 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.espresso.tools + +import android.app.Activity +import im.vector.app.activityIdlingResource +import im.vector.app.withIdlingResource + +inline fun waitUntilActivityVisible(noinline block: (() -> Unit)) { + withIdlingResource(activityIdlingResource(T::class.java), block) +} diff --git a/vector/src/androidTest/java/im/vector/app/ui/UiAllScreensSanityTest.kt b/vector/src/androidTest/java/im/vector/app/ui/UiAllScreensSanityTest.kt new file mode 100644 index 0000000000..1c05a5d529 --- /dev/null +++ b/vector/src/androidTest/java/im/vector/app/ui/UiAllScreensSanityTest.kt @@ -0,0 +1,460 @@ +/* + * Copyright (c) 2020 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.ui + +import android.view.View +import androidx.recyclerview.widget.RecyclerView +import androidx.test.espresso.Espresso.onView +import androidx.test.espresso.Espresso.pressBack +import androidx.test.espresso.action.ViewActions.closeSoftKeyboard +import androidx.test.espresso.action.ViewActions.longClick +import androidx.test.espresso.contrib.RecyclerViewActions.actionOnItem +import androidx.test.espresso.matcher.ViewMatchers +import androidx.test.espresso.matcher.ViewMatchers.hasDescendant +import androidx.test.espresso.matcher.ViewMatchers.isRoot +import androidx.test.espresso.matcher.ViewMatchers.withId +import androidx.test.espresso.matcher.ViewMatchers.withText +import androidx.test.ext.junit.rules.ActivityScenarioRule +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.LargeTest +import com.schibsted.spain.barista.assertion.BaristaListAssertions.assertListItemCount +import com.schibsted.spain.barista.assertion.BaristaVisibilityAssertions.assertDisplayed +import com.schibsted.spain.barista.interaction.BaristaClickInteractions.clickBack +import com.schibsted.spain.barista.interaction.BaristaClickInteractions.clickOn +import com.schibsted.spain.barista.interaction.BaristaClickInteractions.longClickOn +import com.schibsted.spain.barista.interaction.BaristaDialogInteractions.clickDialogNegativeButton +import com.schibsted.spain.barista.interaction.BaristaDialogInteractions.clickDialogPositiveButton +import com.schibsted.spain.barista.interaction.BaristaEditTextInteractions.writeTo +import com.schibsted.spain.barista.interaction.BaristaListInteractions.clickListItem +import com.schibsted.spain.barista.interaction.BaristaListInteractions.clickListItemChild +import com.schibsted.spain.barista.interaction.BaristaMenuClickInteractions.clickMenu +import com.schibsted.spain.barista.interaction.BaristaMenuClickInteractions.openMenu +import im.vector.app.EspressoHelper +import im.vector.app.R +import im.vector.app.SleepViewAction +import im.vector.app.activityIdlingResource +import im.vector.app.espresso.tools.clickOnPreference +import im.vector.app.espresso.tools.waitUntilActivityVisible +import im.vector.app.features.MainActivity +import im.vector.app.features.createdirect.CreateDirectRoomActivity +import im.vector.app.features.home.HomeActivity +import im.vector.app.features.home.room.detail.RoomDetailActivity +import im.vector.app.features.login.LoginActivity +import im.vector.app.features.roomdirectory.RoomDirectoryActivity +import im.vector.app.initialSyncIdlingResource +import im.vector.app.waitForView +import im.vector.app.withIdlingResource +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith +import java.lang.Thread.sleep +import java.util.UUID + +/** + * This test aim to open every possible screen of the application + */ +@RunWith(AndroidJUnit4::class) +@LargeTest +class UiAllScreensSanityTest { + + @get:Rule + val activityRule = ActivityScenarioRule(MainActivity::class.java) + + private val uiTestBase = UiTestBase() + + // Last passing: 2020-11-09 + @Test + fun allScreensTest() { + // Create an account + val userId = "UiTest_" + UUID.randomUUID().toString() + uiTestBase.createAccount(userId = userId) + + withIdlingResource(activityIdlingResource(HomeActivity::class.java)) { + assertDisplayed(R.id.roomListContainer) + closeSoftKeyboard() + } + + val activity = EspressoHelper.getCurrentActivity()!! + val uiSession = (activity as HomeActivity).activeSessionHolder.getActiveSession() + + withIdlingResource(initialSyncIdlingResource(uiSession)) { + assertDisplayed(R.id.roomListContainer) + } + + assertDisplayed(R.id.bottomNavigationView) + + // Settings + navigateToSettings() + + // Create DM + clickOn(R.id.bottom_action_people) + createDm() + + // Create Room + // First navigate to the other tab + clickOn(R.id.bottom_action_rooms) + createRoom() + + assertDisplayed(R.id.bottomNavigationView) + + // Long click on the room + onView(withId(R.id.roomListView)) + .perform( + actionOnItem( + hasDescendant(withText(R.string.room_displayname_empty_room)), + longClick() + ) + ) + pressBack() + + uiTestBase.signout() + + // We have sent a message in a e2e room, accept to loose it + clickOn(R.id.exitAnywayButton) + // Dark pattern + clickDialogNegativeButton() + + // Login again on the same account + waitUntilActivityVisible { + assertDisplayed(R.id.loginSplashLogo) + } + + uiTestBase.login(userId) + ignoreVerification() + + uiTestBase.signout() + clickDialogPositiveButton() + + // TODO Deactivate account instead of logout? + } + + private fun ignoreVerification() { + Thread.sleep(6000) + val activity = EspressoHelper.getCurrentActivity()!! + + val popup = activity.findViewById(com.tapadoo.alerter.R.id.llAlertBackground) + activity.runOnUiThread { + popup.performClick() + } + + assertDisplayed(R.id.bottomSheetFragmentContainer) + + onView(ViewMatchers.isRoot()).perform(SleepViewAction.sleep(2000)) + + clickOn(R.string.skip) + assertDisplayed(R.string.are_you_sure) + clickOn(R.string.skip) + } + + private fun createRoom() { + clickOn(R.id.createGroupRoomButton) + waitUntilActivityVisible { + assertDisplayed(R.id.publicRoomsList) + } + clickOn(R.string.create_new_room) + + // Create + assertListItemCount(R.id.createRoomForm, 10) + clickListItemChild(R.id.createRoomForm, 9, R.id.form_submit_button) + + waitUntilActivityVisible { + assertDisplayed(R.id.roomDetailContainer) + } + + clickOn(R.id.attachmentButton) + clickBack() + + // Send a message + writeTo(R.id.composerEditText, "Hello world!") + clickOn(R.id.sendButton) + + navigateToRoomSettings() + + // Long click on the message + longClickOnMessageTest() + + // Menu + openMenu() + pressBack() + clickMenu(R.id.voice_call) + pressBack() + clickMenu(R.id.video_call) + pressBack() + + pressBack() + } + + private fun longClickOnMessageTest() { + // Test quick reaction + longClickOnMessage() + // Add quick reaction + clickOn("👍") + + sleep(1000) + + // Open reactions + longClickOn("👍") + pressBack() + + // Test add reaction + longClickOnMessage() + clickOn(R.string.message_add_reaction) + // Filter + // TODO clickMenu(R.id.search) + clickListItem(R.id.emojiRecyclerView, 4) + + // Test Edit mode + longClickOnMessage() + clickOn(R.string.edit) + // TODO Cancel action + writeTo(R.id.composerEditText, "Hello universe!") + clickOn(R.id.sendButton) + // Open edit history + longClickOnMessage("Hello universe! (edited)") + clickOn(R.string.message_view_edit_history) + pressBack() + } + + private fun longClickOnMessage(text: String = "Hello world!") { + onView(withId(R.id.timelineRecyclerView)) + .perform( + actionOnItem( + hasDescendant(withText(text)), + longClick() + ) + ) + } + + private fun navigateToRoomSettings() { + clickOn(R.id.roomToolbarTitleView) + assertDisplayed(R.id.roomProfileAvatarView) + + // Room settings + clickListItem(R.id.matrixProfileRecyclerView, 3) + pressBack() + + // Notifications + clickListItem(R.id.matrixProfileRecyclerView, 5) + pressBack() + + assertDisplayed(R.id.roomProfileAvatarView) + + // People + clickListItem(R.id.matrixProfileRecyclerView, 7) + assertDisplayed(R.id.inviteUsersButton) + navigateToRoomPeople() + // Fab + navigateToInvite() + pressBack() + pressBack() + + assertDisplayed(R.id.roomProfileAvatarView) + + // Uploads + clickListItem(R.id.matrixProfileRecyclerView, 9) + // File tab + clickOn(R.string.uploads_files_title) + pressBack() + + assertDisplayed(R.id.roomProfileAvatarView) + + // Leave + clickListItem(R.id.matrixProfileRecyclerView, 13) + clickDialogNegativeButton() + + // Menu share + // clickMenu(R.id.roomProfileShareAction) + // pressBack() + + pressBack() + } + + private fun navigateToInvite() { + assertDisplayed(R.id.inviteUsersButton) + clickOn(R.id.inviteUsersButton) + closeSoftKeyboard() + pressBack() + } + + private fun navigateToRoomPeople() { + // Open first user + clickListItem(R.id.roomSettingsRecyclerView, 1) + assertDisplayed(R.id.memberProfilePowerLevelView) + + // Verification + clickListItem(R.id.matrixProfileRecyclerView, 1) + clickBack() + + // Role + clickListItem(R.id.matrixProfileRecyclerView, 3) + clickDialogNegativeButton() + + clickBack() + } + + private fun createDm() { + clickOn(R.id.createChatRoomButton) + + withIdlingResource(activityIdlingResource(CreateDirectRoomActivity::class.java)) { + assertDisplayed(R.id.addByMatrixId) + } + + closeSoftKeyboard() + pressBack() + pressBack() + } + + private fun navigateToSettings() { + clickOn(R.id.groupToolbarAvatarImageView) + clickOn(R.id.homeDrawerHeaderSettingsView) + + clickOn(R.string.settings_general_title) + navigateToSettingsGeneral() + pressBack() + + clickOn(R.string.settings_notifications) + navigateToSettingsNotifications() + pressBack() + + clickOn(R.string.settings_preferences) + navigateToSettingsPreferences() + pressBack() + + clickOn(R.string.preference_voice_and_video) + pressBack() + + clickOn(R.string.settings_ignored_users) + pressBack() + + clickOn(R.string.settings_security_and_privacy) + navigateToSettingsSecurity() + pressBack() + + clickOn(R.string.room_settings_labs_pref_title) + pressBack() + + clickOn(R.string.settings_advanced_settings) + navigateToSettingsAdvanced() + pressBack() + + clickOn(R.string.preference_root_help_about) + navigateToSettingsHelp() + pressBack() + + pressBack() + } + + private fun navigateToSettingsHelp() { + /* + clickOn(R.string.settings_app_info_link_title) + Cannot go back... + pressBack() + clickOn(R.string.settings_copyright) + pressBack() + clickOn(R.string.settings_app_term_conditions) + pressBack() + clickOn(R.string.settings_privacy_policy) + pressBack() + */ + clickOn(R.string.settings_third_party_notices) + clickDialogPositiveButton() + } + + private fun navigateToSettingsAdvanced() { + clickOnPreference(R.string.settings_notifications_targets) + pressBack() + + clickOnPreference(R.string.settings_push_rules) + pressBack() + + /* TODO P2 test developer screens + // Enable developer mode + clickOnSwitchPreference("SETTINGS_DEVELOPER_MODE_PREFERENCE_KEY") + + clickOnPreference(R.string.settings_account_data) + clickOn("m.push_rules") + pressBack() + pressBack() + clickOnPreference(R.string.settings_key_requests) + pressBack() + + // Disable developer mode + clickOnSwitchPreference("SETTINGS_DEVELOPER_MODE_PREFERENCE_KEY") + */ + } + + private fun navigateToSettingsSecurity() { + clickOnPreference(R.string.settings_active_sessions_show_all) + pressBack() + + clickOnPreference(R.string.encryption_message_recovery) + // TODO go deeper here + pressBack() + /* Cannot exit + clickOnPreference(R.string.encryption_export_e2e_room_keys) + pressBack() + */ + } + + private fun navigateToSettingsPreferences() { + clickOn(R.string.settings_interface_language) + onView(isRoot()) + .perform(waitForView(withText("Dansk (Danmark)"))) + pressBack() + clickOn(R.string.settings_theme) + clickDialogNegativeButton() + clickOn(R.string.font_size) + clickDialogNegativeButton() + } + + private fun navigateToSettingsNotifications() { + clickOn(R.string.settings_notification_advanced) + pressBack() + /* + clickOn(R.string.settings_noisy_notifications_preferences) + TODO Cannot go back + pressBack() + clickOn(R.string.settings_silent_notifications_preferences) + pressBack() + clickOn(R.string.settings_call_notifications_preferences) + pressBack() + */ + clickOnPreference(R.string.settings_notification_troubleshoot) + pressBack() + } + + private fun navigateToSettingsGeneral() { + clickOn(R.string.settings_profile_picture) + clickDialogPositiveButton() + clickOn(R.string.settings_display_name) + clickDialogNegativeButton() + clickOn(R.string.settings_password) + clickDialogNegativeButton() + clickOn(R.string.settings_emails_and_phone_numbers_title) + pressBack() + clickOn(R.string.settings_discovery_manage) + clickOn(R.string.add_identity_server) + pressBack() + pressBack() + // Identity server + clickOnPreference(R.string.settings_identity_server) + pressBack() + // Deactivate account + clickOnPreference(R.string.settings_deactivate_my_account) + pressBack() + } +} diff --git a/vector/src/androidTest/java/im/vector/app/ui/UiTestBase.kt b/vector/src/androidTest/java/im/vector/app/ui/UiTestBase.kt new file mode 100644 index 0000000000..ed174b50a2 --- /dev/null +++ b/vector/src/androidTest/java/im/vector/app/ui/UiTestBase.kt @@ -0,0 +1,90 @@ +/* + * Copyright (c) 2020 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.ui + +import androidx.test.espresso.Espresso.closeSoftKeyboard +import androidx.test.espresso.Espresso.onView +import androidx.test.espresso.matcher.ViewMatchers.isRoot +import androidx.test.espresso.matcher.ViewMatchers.withId +import com.schibsted.spain.barista.assertion.BaristaEnabledAssertions.assertDisabled +import com.schibsted.spain.barista.assertion.BaristaEnabledAssertions.assertEnabled +import com.schibsted.spain.barista.assertion.BaristaVisibilityAssertions.assertDisplayed +import com.schibsted.spain.barista.interaction.BaristaClickInteractions.clickOn +import com.schibsted.spain.barista.interaction.BaristaEditTextInteractions.writeTo +import im.vector.app.R +import im.vector.app.espresso.tools.waitUntilActivityVisible +import im.vector.app.features.home.HomeActivity +import im.vector.app.waitForView + +class UiTestBase { + fun createAccount(userId: String, password: String = "password", homeServerUrl: String = "http://10.0.2.2:8080") { + initSession(true, userId, password, homeServerUrl) + } + + fun login(userId: String, password: String = "password", homeServerUrl: String = "http://10.0.2.2:8080") { + initSession(false, userId, password, homeServerUrl) + } + + private fun initSession(createAccount: Boolean, + userId: String, + password: String, + homeServerUrl: String) { + assertDisplayed(R.id.loginSplashSubmit, R.string.login_splash_submit) + clickOn(R.id.loginSplashSubmit) + assertDisplayed(R.id.loginServerTitle, R.string.login_server_title) + // Chose custom server + clickOn(R.id.loginServerChoiceOther) + // Enter local synapse + writeTo(R.id.loginServerUrlFormHomeServerUrl, homeServerUrl) + assertEnabled(R.id.loginServerUrlFormSubmit) + closeSoftKeyboard() + clickOn(R.id.loginServerUrlFormSubmit) + onView(isRoot()).perform(waitForView(withId(R.id.loginSignupSigninSubmit))) + + if (createAccount) { + // Click on the signup button + assertDisplayed(R.id.loginSignupSigninSubmit) + clickOn(R.id.loginSignupSigninSubmit) + } else { + // Click on the signin button + assertDisplayed(R.id.loginSignupSigninSignIn) + clickOn(R.id.loginSignupSigninSignIn) + } + + // Ensure password flow supported + assertDisplayed(R.id.loginField) + assertDisplayed(R.id.passwordField) + + writeTo(R.id.loginField, userId) + assertDisabled(R.id.loginSubmit) + writeTo(R.id.passwordField, password) + assertEnabled(R.id.loginSubmit) + + closeSoftKeyboard() + clickOn(R.id.loginSubmit) + + // Wait + waitUntilActivityVisible { + assertDisplayed(R.id.homeDetailFragmentContainer) + } + } + + fun signout() { + clickOn(R.id.groupToolbarAvatarImageView) + clickOn(R.id.homeDrawerHeaderSignoutView) + } +} diff --git a/vector/src/debug/java/im/vector/app/features/debug/sas/DebugSasEmojiActivity.kt b/vector/src/debug/java/im/vector/app/features/debug/sas/DebugSasEmojiActivity.kt index f22784bc12..869058eff6 100644 --- a/vector/src/debug/java/im/vector/app/features/debug/sas/DebugSasEmojiActivity.kt +++ b/vector/src/debug/java/im/vector/app/features/debug/sas/DebugSasEmojiActivity.kt @@ -30,12 +30,12 @@ class DebugSasEmojiActivity : AppCompatActivity() { super.onCreate(savedInstanceState) setContentView(R.layout.fragment_generic_recycler) val controller = SasEmojiController() - recyclerView.configureWith(controller) + genericRecyclerView.configureWith(controller) controller.setData(SasState(getAllVerificationEmojis())) } override fun onDestroy() { - recyclerView.cleanup() + genericRecyclerView.cleanup() super.onDestroy() } } diff --git a/vector/src/main/java/im/vector/app/features/disclaimer/DisclaimerDialog.kt b/vector/src/main/java/im/vector/app/features/disclaimer/DisclaimerDialog.kt index c2cd2e11e3..028d37ff5f 100644 --- a/vector/src/main/java/im/vector/app/features/disclaimer/DisclaimerDialog.kt +++ b/vector/src/main/java/im/vector/app/features/disclaimer/DisclaimerDialog.kt @@ -28,7 +28,7 @@ import im.vector.app.features.settings.VectorSettingsUrls // Increase this value to show again the disclaimer dialog after an upgrade of the application private const val CURRENT_DISCLAIMER_VALUE = 2 -private const val SHARED_PREF_KEY = "LAST_DISCLAIMER_VERSION_VALUE" +const val SHARED_PREF_KEY = "LAST_DISCLAIMER_VERSION_VALUE" fun showDisclaimerDialog(activity: Activity) { val sharedPrefs = DefaultSharedPreferences.getInstance(activity) diff --git a/vector/src/main/java/im/vector/app/features/discovery/DiscoverySettingsFragment.kt b/vector/src/main/java/im/vector/app/features/discovery/DiscoverySettingsFragment.kt index 97d824054d..08bf2f12f0 100644 --- a/vector/src/main/java/im/vector/app/features/discovery/DiscoverySettingsFragment.kt +++ b/vector/src/main/java/im/vector/app/features/discovery/DiscoverySettingsFragment.kt @@ -55,7 +55,7 @@ class DiscoverySettingsFragment @Inject constructor( sharedViewModel = activityViewModelProvider.get(DiscoverySharedViewModel::class.java) controller.listener = this - recyclerView.configureWith(controller) + genericRecyclerView.configureWith(controller) sharedViewModel.navigateEvent.observeEvent(this) { when (it) { @@ -74,7 +74,7 @@ class DiscoverySettingsFragment @Inject constructor( } override fun onDestroyView() { - recyclerView.cleanup() + genericRecyclerView.cleanup() controller.listener = null super.onDestroyView() } diff --git a/vector/src/main/java/im/vector/app/features/home/HomeActivityViewEvents.kt b/vector/src/main/java/im/vector/app/features/home/HomeActivityViewEvents.kt index 2a29e13572..7753a7f58b 100644 --- a/vector/src/main/java/im/vector/app/features/home/HomeActivityViewEvents.kt +++ b/vector/src/main/java/im/vector/app/features/home/HomeActivityViewEvents.kt @@ -22,6 +22,6 @@ import org.matrix.android.sdk.api.util.MatrixItem sealed class HomeActivityViewEvents : VectorViewEvents { data class AskPasswordToInitCrossSigning(val userItem: MatrixItem.UserItem?) : HomeActivityViewEvents() data class OnNewSession(val userItem: MatrixItem.UserItem?, val waitForIncomingRequest: Boolean = true) : HomeActivityViewEvents() - data class OnCrossSignedInvalidated(val userItem: MatrixItem.UserItem?) : HomeActivityViewEvents() + data class OnCrossSignedInvalidated(val userItem: MatrixItem.UserItem) : HomeActivityViewEvents() object PromptToEnableSessionPush : HomeActivityViewEvents() } diff --git a/vector/src/main/java/im/vector/app/features/home/HomeActivityViewModel.kt b/vector/src/main/java/im/vector/app/features/home/HomeActivityViewModel.kt index 48a71db35c..6d0bb7395b 100644 --- a/vector/src/main/java/im/vector/app/features/home/HomeActivityViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/home/HomeActivityViewModel.kt @@ -78,29 +78,30 @@ class HomeActivityViewModel @AssistedInject constructor( } private fun observeCrossSigningReset() { - val safeActiveSession = activeSessionHolder.getSafeActiveSession() - val crossSigningService = safeActiveSession - ?.cryptoService() - ?.crossSigningService() - onceTrusted = crossSigningService - ?.allPrivateKeysKnown() ?: false + val safeActiveSession = activeSessionHolder.getSafeActiveSession() ?: return + + onceTrusted = safeActiveSession + .cryptoService() + .crossSigningService().allPrivateKeysKnown() safeActiveSession - ?.rx() - ?.liveCrossSigningInfo(safeActiveSession.myUserId) - ?.subscribe { + .rx() + .liveCrossSigningInfo(safeActiveSession.myUserId) + .subscribe { val isVerified = it.getOrNull()?.isTrusted() ?: false if (!isVerified && onceTrusted) { // cross signing keys have been reset // Tigger a popup to re-verify - _viewEvents.post( - HomeActivityViewEvents.OnCrossSignedInvalidated( - safeActiveSession.getUser(safeActiveSession.myUserId)?.toMatrixItem() - ) - ) + // Note: user can be null in case of logout + safeActiveSession.getUser(safeActiveSession.myUserId) + ?.toMatrixItem() + ?.let { user -> + _viewEvents.post(HomeActivityViewEvents.OnCrossSignedInvalidated(user)) + } } onceTrusted = isVerified - }?.disposeOnClear() + } + .disposeOnClear() } private fun observeInitialSync() { diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailFragment.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailFragment.kt index 3909a89619..e289234e7a 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailFragment.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailFragment.kt @@ -543,7 +543,7 @@ class RoomDetailFragment @Inject constructor( modelBuildListener = null autoCompleter.clear() debouncer.cancelAll() - recyclerView.cleanup() + timelineRecyclerView.cleanup() super.onDestroyView() } @@ -570,7 +570,7 @@ class RoomDetailFragment @Inject constructor( jumpToBottomViewVisibilityManager = JumpToBottomViewVisibilityManager( jumpToBottomView, debouncer, - recyclerView, + timelineRecyclerView, layoutManager ) } @@ -593,7 +593,7 @@ class RoomDetailFragment @Inject constructor( if (scrollPosition == null) { scrollOnHighlightedEventCallback.scheduleScrollTo(action.eventId) } else { - recyclerView.stopScroll() + timelineRecyclerView.stopScroll() layoutManager.scrollToPosition(scrollPosition) } } @@ -1004,14 +1004,14 @@ class RoomDetailFragment @Inject constructor( timelineEventController.callback = this timelineEventController.timeline = roomDetailViewModel.timeline - recyclerView.trackItemsVisibilityChange() + timelineRecyclerView.trackItemsVisibilityChange() layoutManager = LinearLayoutManager(context, RecyclerView.VERTICAL, true) val stateRestorer = LayoutManagerStateRestorer(layoutManager).register() scrollOnNewMessageCallback = ScrollOnNewMessageCallback(layoutManager, timelineEventController) - scrollOnHighlightedEventCallback = ScrollOnHighlightedEventCallback(recyclerView, layoutManager, timelineEventController) - recyclerView.layoutManager = layoutManager - recyclerView.itemAnimator = null - recyclerView.setHasFixedSize(true) + scrollOnHighlightedEventCallback = ScrollOnHighlightedEventCallback(timelineRecyclerView, layoutManager, timelineEventController) + timelineRecyclerView.layoutManager = layoutManager + timelineRecyclerView.itemAnimator = null + timelineRecyclerView.setHasFixedSize(true) modelBuildListener = OnModelBuildFinishedListener { it.dispatchTo(stateRestorer) it.dispatchTo(scrollOnNewMessageCallback) @@ -1020,7 +1020,7 @@ class RoomDetailFragment @Inject constructor( jumpToBottomViewVisibilityManager.maybeShowJumpToBottomViewVisibilityWithDelay() } timelineEventController.addModelBuildListener(modelBuildListener) - recyclerView.adapter = timelineEventController.adapter + timelineRecyclerView.adapter = timelineEventController.adapter if (vectorPreferences.swipeToReplyIsEnabled()) { val quickReplyHandler = object : RoomMessageTouchHelperCallback.QuickReplayHandler { @@ -1050,9 +1050,9 @@ class RoomDetailFragment @Inject constructor( } val swipeCallback = RoomMessageTouchHelperCallback(requireContext(), R.drawable.ic_reply, quickReplyHandler) val touchHelper = ItemTouchHelper(swipeCallback) - touchHelper.attachToRecyclerView(recyclerView) + touchHelper.attachToRecyclerView(timelineRecyclerView) } - recyclerView.addGlidePreloader( + timelineRecyclerView.addGlidePreloader( epoxyController = timelineEventController, requestManager = GlideApp.with(this), preloader = glidePreloader { requestManager, epoxyModel: MessageImageVideoItem, _ -> diff --git a/vector/src/main/java/im/vector/app/features/popup/PopupAlertManager.kt b/vector/src/main/java/im/vector/app/features/popup/PopupAlertManager.kt index 665eb93428..b2257b250a 100644 --- a/vector/src/main/java/im/vector/app/features/popup/PopupAlertManager.kt +++ b/vector/src/main/java/im/vector/app/features/popup/PopupAlertManager.kt @@ -46,7 +46,7 @@ class PopupAlertManager @Inject constructor(private val avatarRenderer: Lazy? = null private var currentAlerter: VectorAlert? = null - private val alertFiFo = ArrayList() + private val alertFiFo = mutableListOf() fun postVectorAlert(alert: VectorAlert) { synchronized(alertFiFo) { diff --git a/vector/src/main/java/im/vector/app/features/reactions/EmojiSearchResultFragment.kt b/vector/src/main/java/im/vector/app/features/reactions/EmojiSearchResultFragment.kt index 685f0dd64e..28df628cf1 100644 --- a/vector/src/main/java/im/vector/app/features/reactions/EmojiSearchResultFragment.kt +++ b/vector/src/main/java/im/vector/app/features/reactions/EmojiSearchResultFragment.kt @@ -41,12 +41,12 @@ class EmojiSearchResultFragment @Inject constructor( super.onViewCreated(view, savedInstanceState) sharedViewModel = activityViewModelProvider.get(EmojiChooserViewModel::class.java) epoxyController.listener = this - recyclerView.configureWith(epoxyController, showDivider = true) + genericRecyclerView.configureWith(epoxyController, showDivider = true) } override fun onDestroyView() { epoxyController.listener = null - recyclerView.cleanup() + genericRecyclerView.cleanup() super.onDestroyView() } diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/banned/RoomBannedMemberListFragment.kt b/vector/src/main/java/im/vector/app/features/roomprofile/banned/RoomBannedMemberListFragment.kt index 81b977ac97..797e6c8aa3 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/banned/RoomBannedMemberListFragment.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/banned/RoomBannedMemberListFragment.kt @@ -56,7 +56,7 @@ class RoomBannedMemberListFragment @Inject constructor( roomMemberListController.callback = this setupToolbar(roomSettingsToolbar) setupSearchView() - recyclerView.configureWith(roomMemberListController, hasFixedSize = true) + roomSettingsRecyclerView.configureWith(roomMemberListController, hasFixedSize = true) viewModel.observeViewEvents { when (it) { @@ -83,7 +83,7 @@ class RoomBannedMemberListFragment @Inject constructor( } override fun onDestroyView() { - recyclerView.cleanup() + roomSettingsRecyclerView.cleanup() super.onDestroyView() } diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/members/RoomMemberListFragment.kt b/vector/src/main/java/im/vector/app/features/roomprofile/members/RoomMemberListFragment.kt index 1b3e33a161..fb42b8ce27 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/members/RoomMemberListFragment.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/members/RoomMemberListFragment.kt @@ -57,7 +57,7 @@ class RoomMemberListFragment @Inject constructor( setupToolbar(roomSettingsToolbar) setupSearchView() setupInviteUsersButton() - recyclerView.configureWith(roomMemberListController, hasFixedSize = true) + roomSettingsRecyclerView.configureWith(roomMemberListController, hasFixedSize = true) } private fun setupInviteUsersButton() { @@ -65,7 +65,7 @@ class RoomMemberListFragment @Inject constructor( navigator.openInviteUsersToRoom(requireContext(), roomProfileArgs.roomId) } // Hide FAB when list is scrolling - recyclerView.addOnScrollListener( + roomSettingsRecyclerView.addOnScrollListener( object : RecyclerView.OnScrollListener() { override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) { when (newState) { @@ -99,7 +99,7 @@ class RoomMemberListFragment @Inject constructor( } override fun onDestroyView() { - recyclerView.cleanup() + roomSettingsRecyclerView.cleanup() super.onDestroyView() } diff --git a/vector/src/main/java/im/vector/app/features/roomprofile/settings/RoomSettingsFragment.kt b/vector/src/main/java/im/vector/app/features/roomprofile/settings/RoomSettingsFragment.kt index ab9d3c6896..6637b7d943 100644 --- a/vector/src/main/java/im/vector/app/features/roomprofile/settings/RoomSettingsFragment.kt +++ b/vector/src/main/java/im/vector/app/features/roomprofile/settings/RoomSettingsFragment.kt @@ -72,7 +72,7 @@ class RoomSettingsFragment @Inject constructor( super.onViewCreated(view, savedInstanceState) controller.callback = this setupToolbar(roomSettingsToolbar) - recyclerView.configureWith(controller, hasFixedSize = true) + roomSettingsRecyclerView.configureWith(controller, hasFixedSize = true) waiting_view_status_text.setText(R.string.please_wait) waiting_view_status_text.isVisible = true @@ -93,7 +93,8 @@ class RoomSettingsFragment @Inject constructor( } override fun onDestroyView() { - recyclerView.cleanup() + controller.callback = null + roomSettingsRecyclerView.cleanup() super.onDestroyView() } diff --git a/vector/src/main/java/im/vector/app/features/settings/VectorPreferences.kt b/vector/src/main/java/im/vector/app/features/settings/VectorPreferences.kt index 295bb01265..5872c1fa1c 100755 --- a/vector/src/main/java/im/vector/app/features/settings/VectorPreferences.kt +++ b/vector/src/main/java/im/vector/app/features/settings/VectorPreferences.kt @@ -24,6 +24,7 @@ import com.squareup.seismic.ShakeDetector import im.vector.app.BuildConfig import im.vector.app.R import im.vector.app.core.di.DefaultSharedPreferences +import im.vector.app.features.disclaimer.SHARED_PREF_KEY import im.vector.app.features.homeserver.ServerUrlsRepository import im.vector.app.features.themes.ThemeUtils import org.matrix.android.sdk.api.extensions.tryOrNull @@ -248,6 +249,9 @@ class VectorPreferences @Inject constructor(private val context: Context) { // theme keysToKeep.add(ThemeUtils.APPLICATION_THEME_KEY) + // Disclaimer dialog + keysToKeep.add(SHARED_PREF_KEY) + // get all the existing keys val keys = defaultPrefs.all.keys diff --git a/vector/src/main/java/im/vector/app/features/settings/crosssigning/CrossSigningSettingsFragment.kt b/vector/src/main/java/im/vector/app/features/settings/crosssigning/CrossSigningSettingsFragment.kt index ebeac5aca1..f21ec2e8f4 100644 --- a/vector/src/main/java/im/vector/app/features/settings/crosssigning/CrossSigningSettingsFragment.kt +++ b/vector/src/main/java/im/vector/app/features/settings/crosssigning/CrossSigningSettingsFragment.kt @@ -68,12 +68,12 @@ class CrossSigningSettingsFragment @Inject constructor( } private fun setupRecyclerView() { - recyclerView.configureWith(controller, hasFixedSize = false, disableItemAnimation = true) + genericRecyclerView.configureWith(controller, hasFixedSize = false, disableItemAnimation = true) controller.interactionListener = this } override fun onDestroyView() { - recyclerView.cleanup() + genericRecyclerView.cleanup() controller.interactionListener = null super.onDestroyView() } diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/VectorSettingsDevicesFragment.kt b/vector/src/main/java/im/vector/app/features/settings/devices/VectorSettingsDevicesFragment.kt index ae45989a81..a317536d5d 100644 --- a/vector/src/main/java/im/vector/app/features/settings/devices/VectorSettingsDevicesFragment.kt +++ b/vector/src/main/java/im/vector/app/features/settings/devices/VectorSettingsDevicesFragment.kt @@ -61,7 +61,7 @@ class VectorSettingsDevicesFragment @Inject constructor( waiting_view_status_text.setText(R.string.please_wait) waiting_view_status_text.isVisible = true devicesController.callback = this - recyclerView.configureWith(devicesController, showDivider = true) + genericRecyclerView.configureWith(devicesController, showDivider = true) viewModel.observeViewEvents { when (it) { is DevicesViewEvents.Loading -> showLoading(it.message) @@ -97,7 +97,7 @@ class VectorSettingsDevicesFragment @Inject constructor( override fun onDestroyView() { devicesController.callback = null - recyclerView.cleanup() + genericRecyclerView.cleanup() super.onDestroyView() } diff --git a/vector/src/main/java/im/vector/app/features/settings/devtools/AccountDataFragment.kt b/vector/src/main/java/im/vector/app/features/settings/devtools/AccountDataFragment.kt index 07508f41a2..40b910c1ab 100644 --- a/vector/src/main/java/im/vector/app/features/settings/devtools/AccountDataFragment.kt +++ b/vector/src/main/java/im/vector/app/features/settings/devtools/AccountDataFragment.kt @@ -57,13 +57,13 @@ class AccountDataFragment @Inject constructor( override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) - recyclerView.configureWith(epoxyController, showDivider = true) + genericRecyclerView.configureWith(epoxyController, showDivider = true) epoxyController.interactionListener = this } override fun onDestroyView() { super.onDestroyView() - recyclerView.cleanup() + genericRecyclerView.cleanup() epoxyController.interactionListener = null } diff --git a/vector/src/main/java/im/vector/app/features/settings/devtools/GossipingEventsPaperTrailFragment.kt b/vector/src/main/java/im/vector/app/features/settings/devtools/GossipingEventsPaperTrailFragment.kt index 0ceb8e148d..af8881ba92 100644 --- a/vector/src/main/java/im/vector/app/features/settings/devtools/GossipingEventsPaperTrailFragment.kt +++ b/vector/src/main/java/im/vector/app/features/settings/devtools/GossipingEventsPaperTrailFragment.kt @@ -50,13 +50,13 @@ class GossipingEventsPaperTrailFragment @Inject constructor( override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) - recyclerView.configureWith(epoxyController, showDivider = true) + genericRecyclerView.configureWith(epoxyController, showDivider = true) epoxyController.interactionListener = this } override fun onDestroyView() { super.onDestroyView() - recyclerView.cleanup() + genericRecyclerView.cleanup() epoxyController.interactionListener = null } diff --git a/vector/src/main/java/im/vector/app/features/settings/devtools/IncomingKeyRequestListFragment.kt b/vector/src/main/java/im/vector/app/features/settings/devtools/IncomingKeyRequestListFragment.kt index 35f46d9c74..6e205ceceb 100644 --- a/vector/src/main/java/im/vector/app/features/settings/devtools/IncomingKeyRequestListFragment.kt +++ b/vector/src/main/java/im/vector/app/features/settings/devtools/IncomingKeyRequestListFragment.kt @@ -45,11 +45,11 @@ class IncomingKeyRequestListFragment @Inject constructor( override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) - recyclerView.configureWith(epoxyController, showDivider = true) + genericRecyclerView.configureWith(epoxyController, showDivider = true) } override fun onDestroyView() { super.onDestroyView() - recyclerView.cleanup() + genericRecyclerView.cleanup() } } diff --git a/vector/src/main/java/im/vector/app/features/settings/devtools/OutgoingKeyRequestListFragment.kt b/vector/src/main/java/im/vector/app/features/settings/devtools/OutgoingKeyRequestListFragment.kt index a82b5dd6c9..20132d8047 100644 --- a/vector/src/main/java/im/vector/app/features/settings/devtools/OutgoingKeyRequestListFragment.kt +++ b/vector/src/main/java/im/vector/app/features/settings/devtools/OutgoingKeyRequestListFragment.kt @@ -41,13 +41,13 @@ class OutgoingKeyRequestListFragment @Inject constructor( override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) - recyclerView.configureWith(epoxyController, showDivider = true) + genericRecyclerView.configureWith(epoxyController, showDivider = true) // epoxyController.interactionListener = this } override fun onDestroyView() { super.onDestroyView() - recyclerView.cleanup() + genericRecyclerView.cleanup() // epoxyController.interactionListener = null } } diff --git a/vector/src/main/java/im/vector/app/features/settings/ignored/VectorSettingsIgnoredUsersFragment.kt b/vector/src/main/java/im/vector/app/features/settings/ignored/VectorSettingsIgnoredUsersFragment.kt index 2588eef59b..5ad7258cec 100644 --- a/vector/src/main/java/im/vector/app/features/settings/ignored/VectorSettingsIgnoredUsersFragment.kt +++ b/vector/src/main/java/im/vector/app/features/settings/ignored/VectorSettingsIgnoredUsersFragment.kt @@ -49,7 +49,7 @@ class VectorSettingsIgnoredUsersFragment @Inject constructor( waiting_view_status_text.setText(R.string.please_wait) waiting_view_status_text.isVisible = true ignoredUsersController.callback = this - recyclerView.configureWith(ignoredUsersController) + genericRecyclerView.configureWith(ignoredUsersController) viewModel.observeViewEvents { when (it) { is IgnoredUsersViewEvents.Loading -> showLoading(it.message) @@ -60,7 +60,7 @@ class VectorSettingsIgnoredUsersFragment @Inject constructor( override fun onDestroyView() { ignoredUsersController.callback = null - recyclerView.cleanup() + genericRecyclerView.cleanup() super.onDestroyView() } diff --git a/vector/src/main/java/im/vector/app/features/settings/push/PushGatewaysFragment.kt b/vector/src/main/java/im/vector/app/features/settings/push/PushGatewaysFragment.kt index e6e9ce3753..0075d8ef5a 100644 --- a/vector/src/main/java/im/vector/app/features/settings/push/PushGatewaysFragment.kt +++ b/vector/src/main/java/im/vector/app/features/settings/push/PushGatewaysFragment.kt @@ -59,11 +59,11 @@ class PushGatewaysFragment @Inject constructor( override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) - recyclerView.configureWith(epoxyController, showDivider = true) + genericRecyclerView.configureWith(epoxyController, showDivider = true) } override fun onDestroyView() { - recyclerView.cleanup() + genericRecyclerView.cleanup() super.onDestroyView() } diff --git a/vector/src/main/java/im/vector/app/features/settings/push/PushRulesFragment.kt b/vector/src/main/java/im/vector/app/features/settings/push/PushRulesFragment.kt index c361e21254..c5ad04380b 100644 --- a/vector/src/main/java/im/vector/app/features/settings/push/PushRulesFragment.kt +++ b/vector/src/main/java/im/vector/app/features/settings/push/PushRulesFragment.kt @@ -43,11 +43,11 @@ class PushRulesFragment @Inject constructor( override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) - recyclerView.configureWith(epoxyController, showDivider = true) + genericRecyclerView.configureWith(epoxyController, showDivider = true) } override fun onDestroyView() { - recyclerView.cleanup() + genericRecyclerView.cleanup() super.onDestroyView() } diff --git a/vector/src/main/java/im/vector/app/features/settings/threepids/ThreePidsSettingsFragment.kt b/vector/src/main/java/im/vector/app/features/settings/threepids/ThreePidsSettingsFragment.kt index 81033281d8..12ff51dcbd 100644 --- a/vector/src/main/java/im/vector/app/features/settings/threepids/ThreePidsSettingsFragment.kt +++ b/vector/src/main/java/im/vector/app/features/settings/threepids/ThreePidsSettingsFragment.kt @@ -54,7 +54,7 @@ class ThreePidsSettingsFragment @Inject constructor( override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) - recyclerView.configureWith(epoxyController) + genericRecyclerView.configureWith(epoxyController) epoxyController.interactionListener = this viewModel.observeViewEvents { @@ -73,7 +73,7 @@ class ThreePidsSettingsFragment @Inject constructor( override fun onDestroyView() { super.onDestroyView() - recyclerView.cleanup() + genericRecyclerView.cleanup() epoxyController.interactionListener = null } diff --git a/vector/src/main/java/im/vector/app/features/signout/soft/SoftLogoutFragment.kt b/vector/src/main/java/im/vector/app/features/signout/soft/SoftLogoutFragment.kt index 64b71356ec..dbd5028401 100644 --- a/vector/src/main/java/im/vector/app/features/signout/soft/SoftLogoutFragment.kt +++ b/vector/src/main/java/im/vector/app/features/signout/soft/SoftLogoutFragment.kt @@ -70,12 +70,12 @@ class SoftLogoutFragment @Inject constructor( } private fun setupRecyclerView() { - recyclerView.configureWith(softLogoutController) + genericRecyclerView.configureWith(softLogoutController) softLogoutController.listener = this } override fun onDestroyView() { - recyclerView.cleanup() + genericRecyclerView.cleanup() softLogoutController.listener = null super.onDestroyView() } @@ -121,7 +121,7 @@ class SoftLogoutFragment @Inject constructor( } private fun cleanupUi() { - recyclerView.hideKeyboard() + genericRecyclerView.hideKeyboard() } override fun forgetPasswordClicked() { diff --git a/vector/src/main/java/im/vector/app/features/userdirectory/KnownUsersFragment.kt b/vector/src/main/java/im/vector/app/features/userdirectory/KnownUsersFragment.kt index 0ca46cd154..ec684e8eea 100644 --- a/vector/src/main/java/im/vector/app/features/userdirectory/KnownUsersFragment.kt +++ b/vector/src/main/java/im/vector/app/features/userdirectory/KnownUsersFragment.kt @@ -82,7 +82,7 @@ class KnownUsersFragment @Inject constructor( override fun onDestroyView() { knownUsersController.callback = null - recyclerView.cleanup() + knownUsersRecyclerView.cleanup() super.onDestroyView() } @@ -124,7 +124,7 @@ class KnownUsersFragment @Inject constructor( private fun setupRecyclerView() { knownUsersController.callback = this // Don't activate animation as we might have way to much item animation when filtering - recyclerView.configureWith(knownUsersController, disableItemAnimation = true) + knownUsersRecyclerView.configureWith(knownUsersController, disableItemAnimation = true) } private fun setupFilterView() { diff --git a/vector/src/main/java/im/vector/app/features/userdirectory/UserDirectoryFragment.kt b/vector/src/main/java/im/vector/app/features/userdirectory/UserDirectoryFragment.kt index 8787946bf4..70ea9141e7 100644 --- a/vector/src/main/java/im/vector/app/features/userdirectory/UserDirectoryFragment.kt +++ b/vector/src/main/java/im/vector/app/features/userdirectory/UserDirectoryFragment.kt @@ -28,7 +28,6 @@ import im.vector.app.core.extensions.hideKeyboard import im.vector.app.core.extensions.setupAsSearch import im.vector.app.core.extensions.showKeyboard import im.vector.app.core.platform.VectorBaseFragment -import kotlinx.android.synthetic.main.fragment_create_direct_room_directory_users.recyclerView import kotlinx.android.synthetic.main.fragment_user_directory.* import org.matrix.android.sdk.api.session.user.model.User import javax.inject.Inject @@ -51,14 +50,14 @@ class UserDirectoryFragment @Inject constructor( } override fun onDestroyView() { - recyclerView.cleanup() + userDirectoryRecyclerView.cleanup() directRoomController.callback = null super.onDestroyView() } private fun setupRecyclerView() { directRoomController.callback = this - recyclerView.configureWith(directRoomController) + userDirectoryRecyclerView.configureWith(directRoomController) } private fun setupSearchByMatrixIdView() { diff --git a/vector/src/main/res/layout/fragment_create_direct_room.xml b/vector/src/main/res/layout/fragment_create_direct_room.xml deleted file mode 100644 index 8d2bc68fa8..0000000000 --- a/vector/src/main/res/layout/fragment_create_direct_room.xml +++ /dev/null @@ -1,143 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/vector/src/main/res/layout/fragment_create_direct_room_directory_users.xml b/vector/src/main/res/layout/fragment_create_direct_room_directory_users.xml index d1bc9e4645..616c28c31e 100644 --- a/vector/src/main/res/layout/fragment_create_direct_room_directory_users.xml +++ b/vector/src/main/res/layout/fragment_create_direct_room_directory_users.xml @@ -90,7 +90,7 @@ app:layout_constraintTop_toBottomOf="@+id/createDirectRoomSearchByIdContainer" /> + android:overScrollMode="always" + tools:listitem="@layout/item_room" />