Merge branch 'develop' into feature/fga/presenter_tests
This commit is contained in:
4
.github/workflows/build.yml
vendored
4
.github/workflows/build.yml
vendored
@@ -31,3 +31,7 @@ jobs:
|
||||
name: elementx-debug
|
||||
path: |
|
||||
app/build/outputs/apk/debug/app-debug.apk
|
||||
- name: Compile release sources
|
||||
run: ./gradlew compileReleaseSources $CI_GRADLE_ARG_PROPERTIES
|
||||
- name: Compile nighlty sources
|
||||
run: ./gradlew compileNightlySources $CI_GRADLE_ARG_PROPERTIES
|
||||
|
||||
18
.github/workflows/gradle-wrapper-update.yml
vendored
Normal file
18
.github/workflows/gradle-wrapper-update.yml
vendored
Normal file
@@ -0,0 +1,18 @@
|
||||
name: Update Gradle Wrapper
|
||||
|
||||
on:
|
||||
schedule:
|
||||
- cron: "0 0 * * *"
|
||||
|
||||
jobs:
|
||||
update-gradle-wrapper:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Update Gradle Wrapper
|
||||
uses: gradle-update/update-gradle-wrapper-action@v1
|
||||
# Skip in forks
|
||||
if: github.repository == 'vector-im/element-x-android'
|
||||
with:
|
||||
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
target-branch: develop
|
||||
14
.github/workflows/gradle-wrapper-validation.yml
vendored
Normal file
14
.github/workflows/gradle-wrapper-validation.yml
vendored
Normal file
@@ -0,0 +1,14 @@
|
||||
name: "Validate Gradle Wrapper"
|
||||
on:
|
||||
pull_request: { }
|
||||
push:
|
||||
branches: [ main, develop ]
|
||||
|
||||
jobs:
|
||||
validation:
|
||||
name: "Validation"
|
||||
runs-on: ubuntu-latest
|
||||
# No concurrency required, this is a prerequisite to other actions and should run every time.
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: gradle/wrapper-validation-action@v1
|
||||
36
.github/workflows/maestro.yml
vendored
Normal file
36
.github/workflows/maestro.yml
vendored
Normal file
@@ -0,0 +1,36 @@
|
||||
name: Maestro
|
||||
|
||||
on:
|
||||
pull_request: { }
|
||||
push:
|
||||
branches: [ main, develop ]
|
||||
|
||||
# Enrich gradle.properties for CI/CD
|
||||
env:
|
||||
GRADLE_OPTS: -Dorg.gradle.jvmargs="-Xmx3072m -Dfile.encoding=UTF-8 -XX:+HeapDumpOnOutOfMemoryError" -Dkotlin.daemon.jvm.options="-Xmx2560m" -Dkotlin.incremental=false
|
||||
CI_GRADLE_ARG_PROPERTIES: --stacktrace -PpreDexEnable=false --max-workers 2 --no-daemon
|
||||
|
||||
jobs:
|
||||
maestro-cloud:
|
||||
name: Maestro test suite
|
||||
runs-on: ubuntu-latest
|
||||
if: github.ref != 'refs/heads/main'
|
||||
strategy:
|
||||
fail-fast: false
|
||||
# Allow all jobs on develop. Just one per PR.
|
||||
concurrency:
|
||||
group: ${{ github.ref == 'refs/heads/develop' && format('maestro-develop-{0}', github.sha) || format('maestro-debug-{0}', github.ref) }}
|
||||
cancel-in-progress: true
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Assemble debug APK
|
||||
run: ./gradlew assembleDebug $CI_GRADLE_ARG_PROPERTIES
|
||||
- uses: mobile-dev-inc/action-maestro-cloud@v1.1.1
|
||||
with:
|
||||
api-key: ${{ secrets.MAESTRO_CLOUD_API_KEY }}
|
||||
app-file: app/build/outputs/apk/debug/app-debug.apk
|
||||
env: |
|
||||
USERNAME=maestroelement
|
||||
PASSWORD=${{ secrets.MATRIX_MAESTRO_ACCOUNT_PASSWORD }}
|
||||
ROOM_NAME=MyRoom
|
||||
APP_ID=io.element.android.x.debug
|
||||
25
.github/workflows/sync-from-external-sources.yml
vendored
Normal file
25
.github/workflows/sync-from-external-sources.yml
vendored
Normal file
@@ -0,0 +1,25 @@
|
||||
name: Sync Data From External Sources
|
||||
on:
|
||||
schedule:
|
||||
# Every nights at 6
|
||||
- cron: "0 6 * * *"
|
||||
|
||||
jobs:
|
||||
sync-strings:
|
||||
runs-on: ubuntu-latest
|
||||
# Skip in forks
|
||||
if: github.repository == 'vector-im/element-x-android'
|
||||
# No concurrency required, runs every time on a schedule.
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Run local script
|
||||
run: ./tools/strings/importStringsFromElement.sh
|
||||
- name: Create Pull Request
|
||||
uses: peter-evans/create-pull-request@v4
|
||||
with:
|
||||
commit-message: Import strings from Element Android
|
||||
title: Sync strings
|
||||
body: |
|
||||
- Update strings from Element Android
|
||||
branch: sync-strings
|
||||
base: develop
|
||||
2
.gitignore
vendored
2
.gitignore
vendored
@@ -83,3 +83,5 @@ lint/outputs/
|
||||
lint/tmp/
|
||||
# lint/reports/
|
||||
/.idea/deploymentTargetDropDown.xml
|
||||
|
||||
/tmp
|
||||
|
||||
71
.maestro/README.md
Normal file
71
.maestro/README.md
Normal file
@@ -0,0 +1,71 @@
|
||||
# Maestro
|
||||
|
||||
Maestro is a framework that we are using to test navigation across the application.
|
||||
To setup, please refer at [https://maestro.mobile.dev](https://maestro.mobile.dev)
|
||||
|
||||
<!--- TOC -->
|
||||
|
||||
* [Run test](#run-test)
|
||||
* [Output](#output)
|
||||
* [Write test](#write-test)
|
||||
* [CI](#ci)
|
||||
* [iOS](#ios)
|
||||
* [Future](#future)
|
||||
|
||||
<!--- END -->
|
||||
|
||||
## Run test
|
||||
|
||||
From root dir of the project
|
||||
|
||||
*Note: Since ElementX does not allow account creation nor room creation, we have to use an existing account with an existing room to run maestro test suite. So to run locally, please replace `user` and `123` with your test matrix.org account credentials, and `my room` with one of a room this account has join. Note that the test will send messages to this room.*
|
||||
|
||||
```shell
|
||||
maestro test \
|
||||
-e APP_ID=io.element.android.x.debug \
|
||||
-e USERNAME=user \
|
||||
-e PASSWORD=123 \
|
||||
-e ROOM_NAME="my room" \
|
||||
.maestro/allTest.yaml
|
||||
```
|
||||
|
||||
### Output
|
||||
|
||||
Test result will be printed on the console, and screenshots will be generated at `./build/maestro`
|
||||
|
||||
## Write test
|
||||
|
||||
Tests are yaml files. Generally each yaml file should leave the app in the same screen than at the beginning.
|
||||
|
||||
Start the ElementX app and run this command to help writing test.
|
||||
|
||||
```shell
|
||||
maestro studio
|
||||
```
|
||||
|
||||
Note that sometimes, this prevent running the test. So kill the `maestro studio` process to be able to run the test again.
|
||||
|
||||
Also, if updating the application code, do not forget to deploy again the application before running the maestro tests.
|
||||
|
||||
## CI
|
||||
|
||||
The CI is running maestro using the workflow `.github/worflow/maestro.yaml` and [maestro cloud](https://cloud.mobile.dev/). For now we are limited to 100 runs a month.
|
||||
Some GitHub secrets are used to be able to do that: `MAESTRO_CLOUD_API_KEY`, for now api key from `benoitm@element.io` maestro cloud account, and `MATRIX_MAESTRO_ACCOUNT_PASSWORD` which is the password of the account `@maestroelement:matrix.org`. This account contains a room `MyRoom` to be able to run the maestro test suite.
|
||||
|
||||
## iOS
|
||||
|
||||
Need to install `idb-companion` first
|
||||
|
||||
```shell
|
||||
brew install idb-companion
|
||||
```
|
||||
|
||||
Also:
|
||||
https://github.com/mobile-dev-inc/maestro/issues/146
|
||||
https://github.com/mobile-dev-inc/maestro/issues/107
|
||||
So you have to change your input keyboard to QWERTY for it to work properly.
|
||||
|
||||
## Future
|
||||
|
||||
- run on Element X iOS. This is already working but it need some change on the test to make it works. Could pass a PLATFORM parameter to have unique test and use conditional test.
|
||||
- run specific test on both iOS and Android devices to make them communicate together. Could be possible to test room invite and join, verification, call, etc. To be done when Element X will be able to create account and create room. A main script would be able to detect the Android device and the iOS device, and run several maestro tests sequentially, using `--device` parameter to perform a global test.
|
||||
7
.maestro/allTests.yaml
Normal file
7
.maestro/allTests.yaml
Normal file
@@ -0,0 +1,7 @@
|
||||
appId: ${APP_ID}
|
||||
---
|
||||
- runFlow: tests/init.yaml
|
||||
- runFlow: tests/account/login.yaml
|
||||
- runFlow: tests/settings/settings.yaml
|
||||
- runFlow: tests/roomList/roomList.yaml
|
||||
- runFlow: tests/account/logout.yaml
|
||||
5
.maestro/tests/account/changeServer.yaml
Normal file
5
.maestro/tests/account/changeServer.yaml
Normal file
@@ -0,0 +1,5 @@
|
||||
appId: ${APP_ID}
|
||||
---
|
||||
- tapOn: "Change"
|
||||
- takeScreenshot: build/maestro/200-ChangeServer
|
||||
- tapOn: "Continue"
|
||||
21
.maestro/tests/account/login.yaml
Normal file
21
.maestro/tests/account/login.yaml
Normal file
@@ -0,0 +1,21 @@
|
||||
appId: ${APP_ID}
|
||||
---
|
||||
- tapOn: "Get started"
|
||||
- runFlow: ../assertions/assertLoginDisplayed.yaml
|
||||
- takeScreenshot: build/maestro/100-SignIn
|
||||
- runFlow: changeServer.yaml
|
||||
- runFlow: ../assertions/assertLoginDisplayed.yaml
|
||||
- tapOn: "Username or email"
|
||||
# ios
|
||||
# - tapOn:
|
||||
# id: "usernameTextField"
|
||||
# index: 0
|
||||
- inputText: ${USERNAME}
|
||||
- tapOn: "Password"
|
||||
# iOS
|
||||
#- tapOn:
|
||||
# id: "passwordTextField"
|
||||
# index: 0
|
||||
- inputText: ${PASSWORD}
|
||||
- tapOn: "Continue"
|
||||
- runFlow: ../assertions/assertHomeDisplayed.yaml
|
||||
12
.maestro/tests/account/logout.yaml
Normal file
12
.maestro/tests/account/logout.yaml
Normal file
@@ -0,0 +1,12 @@
|
||||
appId: ${APP_ID}
|
||||
---
|
||||
- tapOn: "Settings"
|
||||
- tapOn: "Sign out"
|
||||
- takeScreenshot: build/maestro/900-SignOutDialg
|
||||
# Ensure cancel cancels
|
||||
- tapOn: "Cancel"
|
||||
- tapOn: "Sign out"
|
||||
- tapOn:
|
||||
text: "Sign out"
|
||||
index: 1
|
||||
- runFlow: ../assertions/assertInitDisplayed.yaml
|
||||
5
.maestro/tests/assertions/assertHomeDisplayed.yaml
Normal file
5
.maestro/tests/assertions/assertHomeDisplayed.yaml
Normal file
@@ -0,0 +1,5 @@
|
||||
appId: ${APP_ID}
|
||||
---
|
||||
- extendedWaitUntil:
|
||||
visible: "All Chats"
|
||||
timeout: 10_000
|
||||
5
.maestro/tests/assertions/assertInitDisplayed.yaml
Normal file
5
.maestro/tests/assertions/assertInitDisplayed.yaml
Normal file
@@ -0,0 +1,5 @@
|
||||
appId: ${APP_ID}
|
||||
---
|
||||
- extendedWaitUntil:
|
||||
visible: "Own your conversations."
|
||||
timeout: 10_000
|
||||
5
.maestro/tests/assertions/assertLoginDisplayed.yaml
Normal file
5
.maestro/tests/assertions/assertLoginDisplayed.yaml
Normal file
@@ -0,0 +1,5 @@
|
||||
appId: ${APP_ID}
|
||||
---
|
||||
- extendedWaitUntil:
|
||||
visible: "Welcome back!"
|
||||
timeout: 10_000
|
||||
8
.maestro/tests/init.yaml
Normal file
8
.maestro/tests/init.yaml
Normal file
@@ -0,0 +1,8 @@
|
||||
appId: ${APP_ID}
|
||||
---
|
||||
- clearState
|
||||
- launchApp:
|
||||
clearKeychain: true
|
||||
- tapOn: "Close showkase button"
|
||||
- runFlow: ./assertions/assertInitDisplayed.yaml
|
||||
- takeScreenshot: build/maestro/000-FirstScreen
|
||||
6
.maestro/tests/roomList/roomList.yaml
Normal file
6
.maestro/tests/roomList/roomList.yaml
Normal file
@@ -0,0 +1,6 @@
|
||||
appId: ${APP_ID}
|
||||
---
|
||||
- takeScreenshot: build/maestro/300-RoomList
|
||||
- runFlow: searchRoomList.yaml
|
||||
- runFlow: timeline/timeline.yaml
|
||||
|
||||
15
.maestro/tests/roomList/searchRoomList.yaml
Normal file
15
.maestro/tests/roomList/searchRoomList.yaml
Normal file
@@ -0,0 +1,15 @@
|
||||
appId: ${APP_ID}
|
||||
---
|
||||
- tapOn: "search"
|
||||
- inputText: ${ROOM_NAME.substring(0, 3)}
|
||||
- takeScreenshot: build/maestro/400-SearchRoom
|
||||
- tapOn: ${ROOM_NAME}
|
||||
# Close keyboard
|
||||
- hideKeyboard
|
||||
# Back from timeline
|
||||
- back
|
||||
# Close keyboard
|
||||
- hideKeyboard
|
||||
# Back from search
|
||||
- back
|
||||
- runFlow: ../assertions/assertHomeDisplayed.yaml
|
||||
14
.maestro/tests/roomList/timeline/timeline.yaml
Normal file
14
.maestro/tests/roomList/timeline/timeline.yaml
Normal file
@@ -0,0 +1,14 @@
|
||||
appId: ${APP_ID}
|
||||
---
|
||||
# This is the name of one room
|
||||
# TODO Create a room on a new account
|
||||
- tapOn: ${ROOM_NAME}
|
||||
- takeScreenshot: build/maestro/500-Timeline
|
||||
- tapOn: "Message…"
|
||||
- inputText: "Hello world!"
|
||||
- tapOn: "Toggle full screen mode"
|
||||
- tapOn: "Toggle full screen mode"
|
||||
- tapOn: "Send"
|
||||
- hideKeyboard
|
||||
- back
|
||||
- runFlow: ../../assertions/assertHomeDisplayed.yaml
|
||||
12
.maestro/tests/settings/settings.yaml
Normal file
12
.maestro/tests/settings/settings.yaml
Normal file
@@ -0,0 +1,12 @@
|
||||
appId: ${APP_ID}
|
||||
---
|
||||
- tapOn: "Settings"
|
||||
- assertVisible: "Rage shake to report bug"
|
||||
- takeScreenshot: build/maestro/600-Settings
|
||||
- tapOn:
|
||||
text: "Report bug"
|
||||
index: 1
|
||||
- assertVisible: "Describe your problem here"
|
||||
- back
|
||||
- back
|
||||
- runFlow: ../assertions/assertHomeDisplayed.yaml
|
||||
@@ -20,6 +20,7 @@
|
||||
@Suppress("DSL_SCOPE_VIOLATION")
|
||||
plugins {
|
||||
id("io.element.android-compose-application")
|
||||
alias(libs.plugins.stem)
|
||||
alias(libs.plugins.kotlin.android)
|
||||
alias(libs.plugins.ksp)
|
||||
alias(libs.plugins.anvil)
|
||||
@@ -118,7 +119,7 @@ android {
|
||||
compose = true
|
||||
}
|
||||
composeOptions {
|
||||
kotlinCompilerExtensionVersion = "1.3.2"
|
||||
kotlinCompilerExtensionVersion = "1.4.0"
|
||||
}
|
||||
packagingOptions {
|
||||
resources {
|
||||
@@ -175,8 +176,6 @@ dependencies {
|
||||
|
||||
// https://developer.android.com/studio/write/java8-support#library-desugaring-versions
|
||||
coreLibraryDesugaring("com.android.tools:desugar_jdk_libs:1.2.2")
|
||||
implementation(libs.compose.destinations)
|
||||
ksp(libs.compose.destinations.processor)
|
||||
implementation(libs.appyx.core)
|
||||
|
||||
implementation(libs.androidx.corektx)
|
||||
|
||||
@@ -48,7 +48,7 @@ internal fun ShowkaseButton(
|
||||
.size(16.dp),
|
||||
onClick = onCloseClicked,
|
||||
) {
|
||||
Icon(imageVector = Icons.Filled.Close, contentDescription = "")
|
||||
Icon(imageVector = Icons.Filled.Close, contentDescription = "Close showkase button")
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
~ Copyright (c) 2022 New Vector Ltd
|
||||
~ Copyright (c) 2023 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.
|
||||
@@ -13,7 +14,7 @@
|
||||
~ See the License for the specific language governing permissions and
|
||||
~ limitations under the License.
|
||||
-->
|
||||
|
||||
<resources>
|
||||
|
||||
<resources xmlns:tools="http://schemas.android.com/tools">
|
||||
<!-- The https://github.com/LikeTheSalad/android-stem requires a non empty strings.xml -->
|
||||
<string name="ignored_placeholder" translatable="false" tools:ignore="UnusedResources">ignored</string>
|
||||
</resources>
|
||||
|
||||
@@ -41,6 +41,7 @@ dependencies {
|
||||
implementation(project(":libraries:designsystem"))
|
||||
implementation(project(":libraries:elementresources"))
|
||||
implementation(libs.appyx.core)
|
||||
implementation(project(":libraries:ui-strings"))
|
||||
ksp(libs.showkase.processor)
|
||||
testImplementation(libs.test.junit)
|
||||
androidTestImplementation(libs.test.junitext)
|
||||
|
||||
@@ -20,7 +20,7 @@ import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import io.element.android.x.core.uri.isValidUrl
|
||||
import io.element.android.x.features.login.root.LoginFormState
|
||||
import io.element.android.x.element.resources.R as ElementR
|
||||
import io.element.android.x.ui.strings.R as StringR
|
||||
|
||||
@Composable
|
||||
fun loginError(
|
||||
@@ -30,7 +30,7 @@ fun loginError(
|
||||
return when {
|
||||
data.login.isEmpty() -> "Please enter a login"
|
||||
data.password.isEmpty() -> "Please enter a password"
|
||||
throwable != null -> stringResource(id = ElementR.string.auth_invalid_login_param)
|
||||
throwable != null -> stringResource(id = StringR.string.auth_invalid_login_param)
|
||||
else -> "No error provided"
|
||||
}
|
||||
}
|
||||
@@ -42,7 +42,7 @@ fun changeServerError(
|
||||
): String {
|
||||
return when {
|
||||
data.isEmpty() -> "Please enter a server URL"
|
||||
!data.isValidUrl() -> stringResource(id = ElementR.string.login_error_invalid_home_server)
|
||||
!data.isValidUrl() -> stringResource(id = StringR.string.login_error_invalid_home_server)
|
||||
throwable != null -> "That server doesn’t seem right. Please check the address."
|
||||
else -> "No error provided"
|
||||
}
|
||||
@@ -48,6 +48,7 @@ import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.text.input.ImeAction
|
||||
import androidx.compose.ui.text.input.KeyboardType
|
||||
@@ -61,6 +62,7 @@ import io.element.android.x.core.compose.textFieldState
|
||||
import io.element.android.x.designsystem.ElementXTheme
|
||||
import io.element.android.x.features.login.error.loginError
|
||||
import io.element.android.x.matrix.core.SessionId
|
||||
import io.element.android.x.ui.strings.R as StringR
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
@@ -95,7 +97,7 @@ fun LoginRootScreen(
|
||||
val isError = state.loggedInState is LoggedInState.ErrorLoggingIn
|
||||
// Title
|
||||
Text(
|
||||
text = "Welcome back",
|
||||
text = stringResource(id = StringR.string.ftue_auth_welcome_back_title),
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = 16.dp, vertical = 48.dp),
|
||||
@@ -138,7 +140,7 @@ fun LoginRootScreen(
|
||||
.fillMaxWidth()
|
||||
.padding(top = 60.dp),
|
||||
label = {
|
||||
Text(text = "Email or username")
|
||||
Text(text = stringResource(id = StringR.string.login_signin_username_hint))
|
||||
},
|
||||
onValueChange = {
|
||||
loginFieldState = it
|
||||
@@ -39,6 +39,7 @@ dependencies {
|
||||
implementation(project(":libraries:matrix"))
|
||||
implementation(project(":libraries:designsystem"))
|
||||
implementation(project(":libraries:elementresources"))
|
||||
implementation(project(":libraries:ui-strings"))
|
||||
ksp(libs.showkase.processor)
|
||||
testImplementation(libs.test.junit)
|
||||
androidTestImplementation(libs.test.junitext)
|
||||
|
||||
@@ -30,7 +30,7 @@ import io.element.android.x.designsystem.components.ProgressDialog
|
||||
import io.element.android.x.designsystem.components.dialogs.ConfirmationDialog
|
||||
import io.element.android.x.designsystem.components.preferences.PreferenceCategory
|
||||
import io.element.android.x.designsystem.components.preferences.PreferenceText
|
||||
import io.element.android.x.element.resources.R as ElementR
|
||||
import io.element.android.x.ui.strings.R as StringR
|
||||
|
||||
@Composable
|
||||
fun LogoutPreferenceView(
|
||||
@@ -55,9 +55,9 @@ fun LogoutPreferenceView(
|
||||
// Log out confirmation dialog
|
||||
if (openDialog.value) {
|
||||
ConfirmationDialog(
|
||||
title = stringResource(id = ElementR.string.action_sign_out),
|
||||
content = stringResource(id = ElementR.string.action_sign_out_confirmation_simple),
|
||||
submitText = stringResource(id = ElementR.string.action_sign_out),
|
||||
title = stringResource(id = StringR.string.action_sign_out),
|
||||
content = stringResource(id = StringR.string.action_sign_out_confirmation_simple),
|
||||
submitText = stringResource(id = StringR.string.action_sign_out),
|
||||
onCancelClicked = {
|
||||
openDialog.value = false
|
||||
},
|
||||
@@ -80,9 +80,9 @@ fun LogoutPreferenceView(
|
||||
fun LogoutPreferenceContent(
|
||||
onClick: () -> Unit = {},
|
||||
) {
|
||||
PreferenceCategory(title = stringResource(id = ElementR.string.settings_general_title)) {
|
||||
PreferenceCategory(title = stringResource(id = StringR.string.settings_general_title)) {
|
||||
PreferenceText(
|
||||
title = stringResource(id = ElementR.string.action_sign_out),
|
||||
title = stringResource(id = StringR.string.action_sign_out),
|
||||
icon = Icons.Default.Logout,
|
||||
onClick = onClick
|
||||
)
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user