Merge branch 'release/0.2.0' into main
2
.github/workflows/build.yml
vendored
|
|
@ -27,7 +27,7 @@ jobs:
|
|||
group: ${{ github.ref == 'refs/heads/develop' && format('build-develop-{0}-{1}', matrix.variant, github.sha) || format('build-{0}-{1}', matrix.variant, github.ref) }}
|
||||
cancel-in-progress: true
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
# Ensure we are building the branch and not the branch after being merged on develop
|
||||
# https://github.com/actions/checkout/issues/881
|
||||
|
|
|
|||
2
.github/workflows/danger.yml
vendored
|
|
@ -7,7 +7,7 @@ jobs:
|
|||
runs-on: ubuntu-latest
|
||||
name: Danger main check
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
- run: |
|
||||
npm install --save-dev @babel/plugin-transform-flow-strip-types
|
||||
- name: Danger
|
||||
|
|
|
|||
2
.github/workflows/gradle-wrapper-update.yml
vendored
|
|
@ -8,7 +8,7 @@ jobs:
|
|||
update-gradle-wrapper:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
- name: Update Gradle Wrapper
|
||||
uses: gradle-update/update-gradle-wrapper-action@v1
|
||||
# Skip in forks
|
||||
|
|
|
|||
|
|
@ -11,5 +11,5 @@ jobs:
|
|||
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: actions/checkout@v4
|
||||
- uses: gradle/wrapper-validation-action@v1
|
||||
|
|
|
|||
2
.github/workflows/maestro.yml
vendored
|
|
@ -24,7 +24,7 @@ jobs:
|
|||
group: ${{ format('maestro-{0}', github.ref) }}
|
||||
cancel-in-progress: true
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
# Ensure we are building the branch and not the branch after being merged on develop
|
||||
# https://github.com/actions/checkout/issues/881
|
||||
|
|
|
|||
2
.github/workflows/nightly.yml
vendored
|
|
@ -16,7 +16,7 @@ jobs:
|
|||
runs-on: ubuntu-latest
|
||||
if: ${{ github.repository == 'vector-im/element-x-android' }}
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
- name: Use JDK 17
|
||||
uses: actions/setup-java@v3
|
||||
with:
|
||||
|
|
|
|||
2
.github/workflows/nightlyReports.yml
vendored
|
|
@ -55,7 +55,7 @@ jobs:
|
|||
name: Dependency analysis
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
- name: Use JDK 17
|
||||
uses: actions/setup-java@v3
|
||||
with:
|
||||
|
|
|
|||
10
.github/workflows/quality.yml
vendored
|
|
@ -17,7 +17,7 @@ jobs:
|
|||
name: Search for forbidden patterns
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
- name: Run code quality check suite
|
||||
run: ./tools/check/check_code_quality.sh
|
||||
|
||||
|
|
@ -29,7 +29,7 @@ jobs:
|
|||
group: ${{ github.ref == 'refs/heads/main' && format('check-main-{0}', github.sha) || github.ref == 'refs/heads/develop' && format('check-develop-{0}', github.sha) || format('check-{0}', github.ref) }}
|
||||
cancel-in-progress: true
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
# Ensure we are building the branch and not the branch after being merged on develop
|
||||
# https://github.com/actions/checkout/issues/881
|
||||
|
|
@ -52,12 +52,6 @@ jobs:
|
|||
name: linting-report
|
||||
path: |
|
||||
*/build/reports/**/*.*
|
||||
- name: 🔊 Publish results to Sonar
|
||||
env:
|
||||
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
|
||||
ORG_GRADLE_PROJECT_SONAR_LOGIN: ${{ secrets.SONAR_TOKEN }}
|
||||
if: ${{ always() && env.SONAR_TOKEN != '' && env.ORG_GRADLE_PROJECT_SONAR_LOGIN != '' }}
|
||||
run: ./gradlew sonar $CI_GRADLE_ARG_PROPERTIES
|
||||
- name: Prepare Danger
|
||||
if: always()
|
||||
run: |
|
||||
|
|
|
|||
2
.github/workflows/release.yml
vendored
|
|
@ -18,7 +18,7 @@ jobs:
|
|||
group: ${{ github.ref == 'refs/head/main' && format('build-release-main-{0}', github.sha) }}
|
||||
cancel-in-progress: true
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
- name: Use JDK 17
|
||||
uses: actions/setup-java@v3
|
||||
with:
|
||||
|
|
|
|||
14
.github/workflows/sonar.yml
vendored
|
|
@ -1,4 +1,4 @@
|
|||
name: Code Quality Checks
|
||||
name: Sonar
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
|
|
@ -10,18 +10,18 @@ on:
|
|||
# Enrich gradle.properties for CI/CD
|
||||
env:
|
||||
GRADLE_OPTS: -Dorg.gradle.jvmargs="-Xmx3072m -Dfile.encoding=UTF-8 -XX:+HeapDumpOnOutOfMemoryError" -XX:MaxMetaspaceSize=512m -Dkotlin.daemon.jvm.options="-Xmx2g" -Dkotlin.incremental=false
|
||||
CI_GRADLE_ARG_PROPERTIES: --stacktrace -PpreDexEnable=false --max-workers 2 --no-daemon --warn
|
||||
CI_GRADLE_ARG_PROPERTIES: --stacktrace -PpreDexEnable=false --max-workers 4 --no-daemon --warn
|
||||
|
||||
jobs:
|
||||
sonar:
|
||||
name: Project Check Suite
|
||||
name: Sonar Quality Checks
|
||||
runs-on: ubuntu-latest
|
||||
# Allow all jobs on main and develop. Just one per PR.
|
||||
concurrency:
|
||||
group: ${{ github.ref == 'refs/heads/main' && format('sonar-main-{0}', github.sha) || github.ref == 'refs/heads/develop' && format('sonar-develop-{0}', github.sha) || format('sonar-{0}', github.ref) }}
|
||||
cancel-in-progress: true
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
# Ensure we are building the branch and not the branch after being merged on develop
|
||||
# https://github.com/actions/checkout/issues/881
|
||||
|
|
@ -41,9 +41,3 @@ jobs:
|
|||
ORG_GRADLE_PROJECT_SONAR_LOGIN: ${{ secrets.SONAR_TOKEN }}
|
||||
if: ${{ always() && env.SONAR_TOKEN != '' && env.ORG_GRADLE_PROJECT_SONAR_LOGIN != '' }}
|
||||
run: ./gradlew sonar $CI_GRADLE_ARG_PROPERTIES
|
||||
- name: Prepare Danger
|
||||
if: always()
|
||||
run: |
|
||||
npm install --save-dev @babel/core
|
||||
npm install --save-dev @babel/plugin-transform-flow-strip-types
|
||||
yarn add danger-plugin-lint-report --dev
|
||||
|
|
|
|||
2
.github/workflows/sync-localazy.yml
vendored
|
|
@ -11,7 +11,7 @@ jobs:
|
|||
# Skip in forks
|
||||
if: github.repository == 'vector-im/element-x-android'
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
- name: Set up Python 3.9
|
||||
uses: actions/setup-python@v4
|
||||
with:
|
||||
|
|
|
|||
10
.github/workflows/tests.yml
vendored
|
|
@ -22,6 +22,16 @@ jobs:
|
|||
group: ${{ github.ref == 'refs/heads/main' && format('unit-tests-main-{0}', github.sha) || github.ref == 'refs/heads/develop' && format('unit-tests-develop-{0}', github.sha) || format('unit-tests-{0}', github.ref) }}
|
||||
cancel-in-progress: true
|
||||
steps:
|
||||
# Increase swapfile size to prevent screenshot tests getting terminated
|
||||
# https://github.com/actions/runner-images/discussions/7188#discussioncomment-6750749
|
||||
- name: 💽 Increase swapfile size
|
||||
run: |
|
||||
sudo swapoff -a
|
||||
sudo fallocate -l 8G /mnt/swapfile
|
||||
sudo chmod 600 /mnt/swapfile
|
||||
sudo mkswap /mnt/swapfile
|
||||
sudo swapon /mnt/swapfile
|
||||
sudo swapon --show
|
||||
- name: ⏬ Checkout with LFS
|
||||
uses: nschloe/action-cached-lfs-checkout@v1.2.2
|
||||
with:
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ appId: ${APP_ID}
|
|||
- tapOn: "gnuradio.org"
|
||||
- extendedWaitUntil:
|
||||
visible: "This server currently doesn’t support sliding sync."
|
||||
timeout: 10_000
|
||||
timeout: 10000
|
||||
- tapOn: "Cancel"
|
||||
- back
|
||||
- back
|
||||
|
|
|
|||
|
|
@ -2,4 +2,4 @@ appId: ${APP_ID}
|
|||
---
|
||||
- extendedWaitUntil:
|
||||
visible: "Help improve Element X dbg"
|
||||
timeout: 10_000
|
||||
timeout: 10000
|
||||
|
|
|
|||
|
|
@ -2,4 +2,4 @@ appId: ${APP_ID}
|
|||
---
|
||||
- extendedWaitUntil:
|
||||
visible: "All Chats"
|
||||
timeout: 10_000
|
||||
timeout: 10000
|
||||
|
|
|
|||
|
|
@ -2,4 +2,4 @@ appId: ${APP_ID}
|
|||
---
|
||||
- extendedWaitUntil:
|
||||
visible: "Be in your element"
|
||||
timeout: 10_000
|
||||
timeout: 10000
|
||||
|
|
|
|||
|
|
@ -2,4 +2,4 @@ appId: ${APP_ID}
|
|||
---
|
||||
- extendedWaitUntil:
|
||||
visible: "Change account provider"
|
||||
timeout: 10_000
|
||||
timeout: 10000
|
||||
|
|
|
|||
|
|
@ -2,4 +2,4 @@ appId: ${APP_ID}
|
|||
---
|
||||
- extendedWaitUntil:
|
||||
visible: ${ROOM_NAME}
|
||||
timeout: 10_000
|
||||
timeout: 10000
|
||||
|
|
|
|||
|
|
@ -3,4 +3,4 @@ appId: ${APP_ID}
|
|||
- extendedWaitUntil:
|
||||
visible:
|
||||
id: "welcome_screen-title"
|
||||
timeout: 10_000
|
||||
timeout: 10000
|
||||
|
|
|
|||
|
|
@ -1,7 +1,8 @@
|
|||
appId: ${APP_ID}
|
||||
---
|
||||
- takeScreenshot: build/maestro/510-Timeline
|
||||
- tapOn: "Message"
|
||||
- tapOn:
|
||||
id: "rich_text_editor"
|
||||
- inputText: "Hello world!"
|
||||
- tapOn: "Send"
|
||||
- hideKeyboard
|
||||
|
|
|
|||
39
CHANGES.md
|
|
@ -1,3 +1,42 @@
|
|||
Changes in Element X v0.2.0 (2023-09-18)
|
||||
========================================
|
||||
|
||||
Features ✨
|
||||
----------
|
||||
- Bump Rust SDK to `v0.1.54`
|
||||
- Add a "Mute" shortcut icon and a "Notifications" section in the room details screen ([#506](https://github.com/vector-im/element-x-android/issues/506))
|
||||
- Add a notification permission screen to the initial flow. ([#897](https://github.com/vector-im/element-x-android/issues/897))
|
||||
- Integrate Element Call into EX by embedding a call in a WebView. ([#1300](https://github.com/vector-im/element-x-android/issues/1300))
|
||||
- Implement Bloom effect modifier. ([#1217](https://github.com/vector-im/element-x-android/issues/1217))
|
||||
- Set color on display name and default avatar in the timeline. ([#1224](https://github.com/vector-im/element-x-android/issues/1224))
|
||||
- Display a thread decorator in timeline so we know when a message is coming from a thread. ([#1236](https://github.com/vector-im/element-x-android/issues/1236))
|
||||
- [Rich text editor] Integrate rich text editor library. Note that markdown is now not supported and further formatting support will be introduced through the rich text editor. ([#1172](https://github.com/vector-im/element-x-android/issues/1172))
|
||||
- [Rich text editor] Add formatting menu (accessible via the '+' button) ([#1261](https://github.com/vector-im/element-x-android/issues/1261))
|
||||
- [Rich text editor] Add feature flag for rich text editor. Markdown support can now be enabled by disabling the rich text editor. ([#1289](https://github.com/vector-im/element-x-android/issues/1289))
|
||||
- [Rich text editor] Update design ([#1332](https://github.com/vector-im/element-x-android/issues/1332))
|
||||
|
||||
Bugfixes 🐛
|
||||
----------
|
||||
- Make links in room topic clickable ([#612](https://github.com/vector-im/element-x-android/issues/612))
|
||||
- Reply action: harmonize conditions in bottom sheet and swipe to reply. ([#1173](https://github.com/vector-im/element-x-android/issues/1173))
|
||||
- Fix system bar color after login on light theme. ([#1222](https://github.com/vector-im/element-x-android/issues/1222))
|
||||
- Fix long click on simple formatted messages ([#1232](https://github.com/vector-im/element-x-android/issues/1232))
|
||||
- Enable polls in release build. ([#1241](https://github.com/vector-im/element-x-android/issues/1241))
|
||||
- Fix top padding in room list when app is opened in offline mode. ([#1297](https://github.com/vector-im/element-x-android/issues/1297))
|
||||
- [Rich text editor] Fix 'text formatting' option only partially visible ([#1335](https://github.com/vector-im/element-x-android/issues/1335))
|
||||
- [Rich text editor] Ensure keyboard opens for reply and text formatting modes ([#1337](https://github.com/vector-im/element-x-android/issues/1337))
|
||||
- [Rich text editor] Fix placeholder spilling onto multiple lines ([#1347](https://github.com/vector-im/element-x-android/issues/1347))
|
||||
|
||||
Other changes
|
||||
-------------
|
||||
- Add a sub-screen "Notifications" in the existing application Settings ([#510](https://github.com/vector-im/element-x-android/issues/510))
|
||||
- Exclude some groups related to analytics to be included. ([#1191](https://github.com/vector-im/element-x-android/issues/1191))
|
||||
- Use the new SyncIndicator API. ([#1244](https://github.com/vector-im/element-x-android/issues/1244))
|
||||
- Improve RoomSummary mapping by using RoomInfo. ([#1251](https://github.com/vector-im/element-x-android/issues/1251))
|
||||
- Ensure Posthog data are sent to "https://posthog.element.io" ([#1269](https://github.com/vector-im/element-x-android/issues/1269))
|
||||
- New app icon, with monochrome support. ([#1363](https://github.com/vector-im/element-x-android/issues/1363))
|
||||
|
||||
|
||||
Changes in Element X v0.1.6 (2023-09-04)
|
||||
========================================
|
||||
|
||||
|
|
|
|||
|
|
@ -198,6 +198,7 @@ dependencies {
|
|||
allLibrariesImpl()
|
||||
allServicesImpl()
|
||||
allFeaturesImpl(rootDir, logger)
|
||||
implementation(projects.features.call)
|
||||
implementation(projects.anvilannotations)
|
||||
implementation(projects.appnav)
|
||||
anvil(projects.anvilcodegen)
|
||||
|
|
|
|||
10
app/src/debug/res/drawable/ic_launcher_background.xml
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="108dp"
|
||||
android:height="108dp"
|
||||
android:viewportWidth="108"
|
||||
android:viewportHeight="108">
|
||||
<path
|
||||
android:fillColor="#F7F07E"
|
||||
android:fillType="evenOdd"
|
||||
android:pathData="m0,0h108v108h-108z" />
|
||||
</vector>
|
||||
|
|
@ -22,8 +22,9 @@
|
|||
|
||||
<application
|
||||
android:name=".ElementXApplication"
|
||||
android:allowBackup="true"
|
||||
android:allowBackup="false"
|
||||
android:dataExtractionRules="@xml/data_extraction_rules"
|
||||
android:enableOnBackInvokedCallback="true"
|
||||
android:fullBackupContent="@xml/backup_rules"
|
||||
android:icon="@mipmap/ic_launcher"
|
||||
android:label="@string/app_name"
|
||||
|
|
|
|||
|
Before Width: | Height: | Size: 47 KiB After Width: | Height: | Size: 260 KiB |
|
|
@ -17,13 +17,20 @@
|
|||
package io.element.android.x.icon
|
||||
|
||||
import androidx.compose.foundation.Image
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.shape.CircleShape
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.ColorFilter
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import io.element.android.x.R
|
||||
|
||||
@Preview
|
||||
|
|
@ -47,3 +54,23 @@ internal fun RoundIconPreview(
|
|||
Image(painter = painterResource(id = R.mipmap.ic_launcher_foreground), contentDescription = null)
|
||||
}
|
||||
}
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
internal fun MonochromeIconPreview(
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
Box(
|
||||
modifier = modifier
|
||||
.size(108.dp)
|
||||
.background(Color(0xFF2F3133))
|
||||
.clip(shape = RoundedCornerShape(32.dp)),
|
||||
contentAlignment = Alignment.Center
|
||||
) {
|
||||
Image(
|
||||
painter = painterResource(id = R.mipmap.ic_launcher_monochrome),
|
||||
colorFilter = ColorFilter.tint(Color(0xFFC3E0F6)),
|
||||
contentDescription = null
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@
|
|||
package io.element.android.x.initializer
|
||||
|
||||
import android.content.Context
|
||||
import android.system.Os
|
||||
import androidx.preference.PreferenceManager
|
||||
import androidx.startup.Initializer
|
||||
import io.element.android.features.preferences.impl.developer.tracing.SharedPrefTracingConfigurationStore
|
||||
|
|
@ -57,6 +58,8 @@ class TracingInitializer : Initializer<Unit> {
|
|||
}
|
||||
bugReporter.cleanLogDirectoryIfNeeded()
|
||||
tracingService.setupTracing(tracingConfiguration)
|
||||
// Also set env variable for rust back trace
|
||||
Os.setenv("RUST_BACKTRACE", "1", true)
|
||||
}
|
||||
|
||||
override fun dependencies(): List<Class<out Initializer<*>>> = mutableListOf()
|
||||
|
|
|
|||
|
|
@ -1,6 +1,22 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
~ 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.
|
||||
~ 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.
|
||||
-->
|
||||
|
||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<background android:drawable="@mipmap/ic_launcher_background"/>
|
||||
<background android:drawable="@drawable/ic_launcher_background"/>
|
||||
<foreground android:drawable="@mipmap/ic_launcher_foreground"/>
|
||||
<!-- Waiting for design monochrome android:drawable="@mipmap/ic_launcher_monochrome" /-->
|
||||
<monochrome android:drawable="@mipmap/ic_launcher_monochrome"/>
|
||||
</adaptive-icon>
|
||||
|
|
|
|||
|
|
@ -1,6 +1,22 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
~ 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.
|
||||
~ 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.
|
||||
-->
|
||||
|
||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<background android:drawable="@mipmap/ic_launcher_background"/>
|
||||
<background android:drawable="@drawable/ic_launcher_background"/>
|
||||
<foreground android:drawable="@mipmap/ic_launcher_foreground"/>
|
||||
<!-- Waiting for design monochrome android:drawable="@mipmap/ic_launcher_monochrome" /-->
|
||||
<monochrome android:drawable="@mipmap/ic_launcher_monochrome"/>
|
||||
</adaptive-icon>
|
||||
|
|
|
|||
|
Before Width: | Height: | Size: 4.2 KiB |
BIN
app/src/main/res/mipmap-hdpi/ic_launcher.webp
Normal file
|
After Width: | Height: | Size: 3.9 KiB |
|
Before Width: | Height: | Size: 12 KiB |
BIN
app/src/main/res/mipmap-hdpi/ic_launcher_background.webp
Normal file
|
After Width: | Height: | Size: 10 KiB |
|
Before Width: | Height: | Size: 3.9 KiB |
BIN
app/src/main/res/mipmap-hdpi/ic_launcher_foreground.webp
Normal file
|
After Width: | Height: | Size: 7.8 KiB |
BIN
app/src/main/res/mipmap-hdpi/ic_launcher_monochrome.webp
Normal file
|
After Width: | Height: | Size: 1.4 KiB |
|
Before Width: | Height: | Size: 5.7 KiB |
BIN
app/src/main/res/mipmap-hdpi/ic_launcher_round.webp
Normal file
|
After Width: | Height: | Size: 5.5 KiB |
|
Before Width: | Height: | Size: 2.8 KiB |
BIN
app/src/main/res/mipmap-mdpi/ic_launcher.webp
Normal file
|
After Width: | Height: | Size: 2.6 KiB |
|
Before Width: | Height: | Size: 7 KiB |
BIN
app/src/main/res/mipmap-mdpi/ic_launcher_background.webp
Normal file
|
After Width: | Height: | Size: 4.7 KiB |
|
Before Width: | Height: | Size: 2.5 KiB |
BIN
app/src/main/res/mipmap-mdpi/ic_launcher_foreground.webp
Normal file
|
After Width: | Height: | Size: 4.6 KiB |
BIN
app/src/main/res/mipmap-mdpi/ic_launcher_monochrome.webp
Normal file
|
After Width: | Height: | Size: 946 B |
|
Before Width: | Height: | Size: 3.5 KiB |
BIN
app/src/main/res/mipmap-mdpi/ic_launcher_round.webp
Normal file
|
After Width: | Height: | Size: 3.2 KiB |
|
Before Width: | Height: | Size: 5.8 KiB |
BIN
app/src/main/res/mipmap-xhdpi/ic_launcher.webp
Normal file
|
After Width: | Height: | Size: 6 KiB |
|
Before Width: | Height: | Size: 17 KiB |
BIN
app/src/main/res/mipmap-xhdpi/ic_launcher_background.webp
Normal file
|
After Width: | Height: | Size: 19 KiB |
|
Before Width: | Height: | Size: 5.4 KiB |
BIN
app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.webp
Normal file
|
After Width: | Height: | Size: 12 KiB |
BIN
app/src/main/res/mipmap-xhdpi/ic_launcher_monochrome.webp
Normal file
|
After Width: | Height: | Size: 1.9 KiB |
|
Before Width: | Height: | Size: 8 KiB |
BIN
app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp
Normal file
|
After Width: | Height: | Size: 8.1 KiB |
|
Before Width: | Height: | Size: 9.4 KiB |
BIN
app/src/main/res/mipmap-xxhdpi/ic_launcher.webp
Normal file
|
After Width: | Height: | Size: 10 KiB |
|
Before Width: | Height: | Size: 28 KiB |
BIN
app/src/main/res/mipmap-xxhdpi/ic_launcher_background.webp
Normal file
|
After Width: | Height: | Size: 46 KiB |
|
Before Width: | Height: | Size: 8.9 KiB |
BIN
app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.webp
Normal file
|
After Width: | Height: | Size: 25 KiB |
BIN
app/src/main/res/mipmap-xxhdpi/ic_launcher_monochrome.webp
Normal file
|
After Width: | Height: | Size: 3.3 KiB |
|
Before Width: | Height: | Size: 13 KiB |
BIN
app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp
Normal file
|
After Width: | Height: | Size: 14 KiB |
|
Before Width: | Height: | Size: 13 KiB |
BIN
app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp
Normal file
|
After Width: | Height: | Size: 16 KiB |
|
Before Width: | Height: | Size: 42 KiB |
BIN
app/src/main/res/mipmap-xxxhdpi/ic_launcher_background.webp
Normal file
|
After Width: | Height: | Size: 84 KiB |
|
Before Width: | Height: | Size: 13 KiB |
BIN
app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.webp
Normal file
|
After Width: | Height: | Size: 43 KiB |
BIN
app/src/main/res/mipmap-xxxhdpi/ic_launcher_monochrome.webp
Normal file
|
After Width: | Height: | Size: 4.6 KiB |
|
Before Width: | Height: | Size: 19 KiB |
BIN
app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp
Normal file
|
After Width: | Height: | Size: 22 KiB |
|
|
@ -15,15 +15,8 @@
|
|||
-->
|
||||
|
||||
<!--
|
||||
Sample backup rules file; uncomment and customize as necessary.
|
||||
See https://developer.android.com/guide/topics/data/autobackup
|
||||
for details.
|
||||
Note: This file is ignored for devices older that API 31
|
||||
See https://developer.android.com/about/versions/12/backup-restore
|
||||
All backup is disabled since it would clash with encryption.
|
||||
-->
|
||||
<full-backup-content>
|
||||
<!--
|
||||
<include domain="sharedpref" path="."/>
|
||||
<exclude domain="sharedpref" path="device.xml"/>
|
||||
-->
|
||||
<exclude domain="root" path="." />
|
||||
</full-backup-content>
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
<?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.
|
||||
|
|
@ -15,21 +15,13 @@
|
|||
-->
|
||||
|
||||
<!--
|
||||
Sample data extraction rules file; uncomment and customize as necessary.
|
||||
See https://developer.android.com/about/versions/12/backup-restore#xml-changes
|
||||
for details.
|
||||
All backup is disabled since it would clash with encryption.
|
||||
-->
|
||||
<data-extraction-rules>
|
||||
<cloud-backup>
|
||||
<!-- TODO: Use <include> and <exclude> to control what is backed up.
|
||||
<include .../>
|
||||
<exclude .../>
|
||||
-->
|
||||
<exclude domain="root" path="." />
|
||||
</cloud-backup>
|
||||
<!--
|
||||
<device-transfer>
|
||||
<include .../>
|
||||
<exclude .../>
|
||||
<exclude domain="root" path="." />
|
||||
</device-transfer>
|
||||
-->
|
||||
</data-extraction-rules>
|
||||
|
|
|
|||
10
app/src/nightly/res/drawable/ic_launcher_background.xml
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="108dp"
|
||||
android:height="108dp"
|
||||
android:viewportWidth="108"
|
||||
android:viewportHeight="108">
|
||||
<path
|
||||
android:fillColor="#07007E"
|
||||
android:fillType="evenOdd"
|
||||
android:pathData="m0,0h108v108h-108z" />
|
||||
</vector>
|
||||
2
app/src/release/res/drawable/ic_launcher_background.xml
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
<bitmap xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:src="@mipmap/ic_launcher_background" />
|
||||
|
|
@ -48,8 +48,6 @@ dependencies {
|
|||
implementation(projects.libraries.designsystem)
|
||||
implementation(projects.libraries.matrixui)
|
||||
implementation(projects.libraries.uiStrings)
|
||||
implementation(projects.libraries.permissions.api)
|
||||
implementation(projects.libraries.permissions.noop)
|
||||
|
||||
implementation(libs.coil)
|
||||
|
||||
|
|
|
|||
|
|
@ -60,7 +60,6 @@ import io.element.android.libraries.di.SessionScope
|
|||
import io.element.android.libraries.matrix.api.MatrixClient
|
||||
import io.element.android.libraries.matrix.api.core.MAIN_SPACE
|
||||
import io.element.android.libraries.matrix.api.core.RoomId
|
||||
import io.element.android.libraries.matrix.api.sync.StartSyncReason
|
||||
import io.element.android.libraries.matrix.api.sync.SyncState
|
||||
import io.element.android.libraries.push.api.notifications.NotificationDrawerManager
|
||||
import io.element.android.services.appnavstate.api.AppNavigationStateService
|
||||
|
|
@ -125,7 +124,7 @@ class LoggedInFlowNode @AssistedInject constructor(
|
|||
onStop = {
|
||||
//Counterpart startSync is done in observeSyncStateAndNetworkStatus method.
|
||||
coroutineScope.launch {
|
||||
syncService.stopSync(StartSyncReason.AppInForeground)
|
||||
syncService.stopSync()
|
||||
}
|
||||
},
|
||||
onDestroy = {
|
||||
|
|
@ -151,7 +150,7 @@ class LoggedInFlowNode @AssistedInject constructor(
|
|||
.collect { (syncState, networkStatus) ->
|
||||
Timber.d("Sync state: $syncState, network status: $networkStatus")
|
||||
if (syncState != SyncState.Running && networkStatus == NetworkStatus.Online) {
|
||||
syncService.startSync(StartSyncReason.AppInForeground)
|
||||
syncService.startSync()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -32,6 +32,7 @@ import dagger.assisted.AssistedInject
|
|||
import io.element.android.anvilannotations.ContributesNode
|
||||
import io.element.android.features.login.api.LoginEntryPoint
|
||||
import io.element.android.features.onboarding.api.OnBoardingEntryPoint
|
||||
import io.element.android.features.preferences.api.ConfigureTracingEntryPoint
|
||||
import io.element.android.libraries.architecture.BackstackNode
|
||||
import io.element.android.libraries.architecture.animation.rememberDefaultTransitionHandler
|
||||
import io.element.android.libraries.di.AppScope
|
||||
|
|
@ -43,6 +44,7 @@ class NotLoggedInFlowNode @AssistedInject constructor(
|
|||
@Assisted buildContext: BuildContext,
|
||||
@Assisted plugins: List<Plugin>,
|
||||
private val onBoardingEntryPoint: OnBoardingEntryPoint,
|
||||
private val configureTracingEntryPoint: ConfigureTracingEntryPoint,
|
||||
private val loginEntryPoint: LoginEntryPoint,
|
||||
private val notLoggedInImageLoaderFactory: NotLoggedInImageLoaderFactory,
|
||||
) : BackstackNode<NotLoggedInFlowNode.NavTarget>(
|
||||
|
|
@ -70,6 +72,9 @@ class NotLoggedInFlowNode @AssistedInject constructor(
|
|||
data class LoginFlow(
|
||||
val isAccountCreation: Boolean,
|
||||
) : NavTarget
|
||||
|
||||
@Parcelize
|
||||
data object ConfigureTracing : NavTarget
|
||||
}
|
||||
|
||||
override fun resolve(navTarget: NavTarget, buildContext: BuildContext): Node {
|
||||
|
|
@ -83,6 +88,10 @@ class NotLoggedInFlowNode @AssistedInject constructor(
|
|||
override fun onSignIn() {
|
||||
backstack.push(NavTarget.LoginFlow(isAccountCreation = false))
|
||||
}
|
||||
|
||||
override fun onOpenDeveloperSettings() {
|
||||
backstack.push(NavTarget.ConfigureTracing)
|
||||
}
|
||||
}
|
||||
onBoardingEntryPoint
|
||||
.nodeBuilder(this, buildContext)
|
||||
|
|
@ -94,6 +103,9 @@ class NotLoggedInFlowNode @AssistedInject constructor(
|
|||
.params(LoginEntryPoint.Params(isAccountCreation = navTarget.isAccountCreation))
|
||||
.build()
|
||||
}
|
||||
NavTarget.ConfigureTracing -> {
|
||||
configureTracingEntryPoint.createNode(this, buildContext)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -16,44 +16,26 @@
|
|||
|
||||
package io.element.android.appnav.loggedin
|
||||
|
||||
import android.Manifest
|
||||
import android.os.Build
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.runtime.derivedStateOf
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.setValue
|
||||
import io.element.android.features.networkmonitor.api.NetworkMonitor
|
||||
import io.element.android.features.networkmonitor.api.NetworkStatus
|
||||
import io.element.android.libraries.architecture.Presenter
|
||||
import io.element.android.libraries.matrix.api.MatrixClient
|
||||
import io.element.android.libraries.matrix.api.roomlist.RoomListService
|
||||
import io.element.android.libraries.permissions.api.PermissionsPresenter
|
||||
import io.element.android.libraries.permissions.noop.NoopPermissionsPresenter
|
||||
import io.element.android.libraries.push.api.PushService
|
||||
import kotlinx.coroutines.delay
|
||||
import javax.inject.Inject
|
||||
|
||||
private const val DELAY_BEFORE_SHOWING_SYNC_SPINNER_IN_MILLIS = 1500L
|
||||
|
||||
class LoggedInPresenter @Inject constructor(
|
||||
private val matrixClient: MatrixClient,
|
||||
private val permissionsPresenterFactory: PermissionsPresenter.Factory,
|
||||
private val networkMonitor: NetworkMonitor,
|
||||
private val pushService: PushService,
|
||||
) : Presenter<LoggedInState> {
|
||||
|
||||
private val postNotificationPermissionsPresenter by lazy {
|
||||
// Ask for POST_NOTIFICATION PERMISSION on Android 13+
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||
permissionsPresenterFactory.create(Manifest.permission.POST_NOTIFICATIONS)
|
||||
} else {
|
||||
NoopPermissionsPresenter()
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
override fun present(): LoggedInState {
|
||||
LaunchedEffect(Unit) {
|
||||
|
|
@ -64,25 +46,15 @@ class LoggedInPresenter @Inject constructor(
|
|||
pushService.registerWith(matrixClient, pushProvider, distributor)
|
||||
}
|
||||
|
||||
val roomListState by matrixClient.roomListService.state.collectAsState()
|
||||
val syncIndicator by matrixClient.roomListService.syncIndicator.collectAsState()
|
||||
val networkStatus by networkMonitor.connectivity.collectAsState()
|
||||
val permissionsState = postNotificationPermissionsPresenter.present()
|
||||
var showSyncSpinner by remember {
|
||||
mutableStateOf(false)
|
||||
}
|
||||
LaunchedEffect(roomListState, networkStatus) {
|
||||
showSyncSpinner = when {
|
||||
networkStatus == NetworkStatus.Offline -> false
|
||||
roomListState == RoomListService.State.Running -> false
|
||||
else -> {
|
||||
delay(DELAY_BEFORE_SHOWING_SYNC_SPINNER_IN_MILLIS)
|
||||
true
|
||||
}
|
||||
val showSyncSpinner by remember {
|
||||
derivedStateOf {
|
||||
networkStatus == NetworkStatus.Online && syncIndicator == RoomListService.SyncIndicator.Show
|
||||
}
|
||||
}
|
||||
return LoggedInState(
|
||||
showSyncSpinner = showSyncSpinner,
|
||||
permissionsState = permissionsState,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,9 +16,6 @@
|
|||
|
||||
package io.element.android.appnav.loggedin
|
||||
|
||||
import io.element.android.libraries.permissions.api.PermissionsState
|
||||
|
||||
data class LoggedInState(
|
||||
val showSyncSpinner: Boolean,
|
||||
val permissionsState: PermissionsState,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -17,7 +17,6 @@
|
|||
package io.element.android.appnav.loggedin
|
||||
|
||||
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
|
||||
import io.element.android.libraries.permissions.api.createDummyPostNotificationPermissionsState
|
||||
|
||||
open class LoggedInStateProvider : PreviewParameterProvider<LoggedInState> {
|
||||
override val values: Sequence<LoggedInState>
|
||||
|
|
@ -32,5 +31,4 @@ fun aLoggedInState(
|
|||
showSyncSpinner: Boolean = true,
|
||||
) = LoggedInState(
|
||||
showSyncSpinner = showSyncSpinner,
|
||||
permissionsState = createDummyPostNotificationPermissionsState(),
|
||||
)
|
||||
|
|
|
|||
|
|
@ -23,21 +23,16 @@ import androidx.compose.foundation.layout.systemBarsPadding
|
|||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.tooling.preview.PreviewParameter
|
||||
import androidx.compose.ui.unit.dp
|
||||
import io.element.android.libraries.androidutils.system.openAppSettingsPage
|
||||
import io.element.android.libraries.designsystem.preview.DayNightPreviews
|
||||
import io.element.android.libraries.designsystem.preview.ElementPreview
|
||||
import io.element.android.libraries.permissions.api.PermissionsView
|
||||
|
||||
@Composable
|
||||
fun LoggedInView(
|
||||
state: LoggedInState,
|
||||
modifier: Modifier = Modifier
|
||||
) {
|
||||
val context = LocalContext.current
|
||||
|
||||
Box(
|
||||
modifier = modifier
|
||||
.fillMaxSize()
|
||||
|
|
@ -49,10 +44,6 @@ fun LoggedInView(
|
|||
.align(Alignment.TopCenter),
|
||||
isVisible = state.showSyncSpinner,
|
||||
)
|
||||
PermissionsView(
|
||||
state = state.permissionsState,
|
||||
openSystemSettings = context::openAppSettingsPage
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -31,10 +31,15 @@ import io.element.android.features.rageshake.test.screenshot.FakeScreenshotHolde
|
|||
import io.element.android.services.apperror.api.AppErrorState
|
||||
import io.element.android.services.apperror.api.AppErrorStateService
|
||||
import io.element.android.services.apperror.impl.DefaultAppErrorStateService
|
||||
import io.element.android.tests.testutils.WarmUpRule
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
|
||||
class RootPresenterTest {
|
||||
@get:Rule
|
||||
val warmUpRule = WarmUpRule()
|
||||
|
||||
@Test
|
||||
fun `present - initial state`() = runTest {
|
||||
val presenter = createPresenter()
|
||||
|
|
|
|||
|
|
@ -26,16 +26,20 @@ import io.element.android.libraries.matrix.api.MatrixClient
|
|||
import io.element.android.libraries.matrix.api.roomlist.RoomListService
|
||||
import io.element.android.libraries.matrix.test.FakeMatrixClient
|
||||
import io.element.android.libraries.matrix.test.roomlist.FakeRoomListService
|
||||
import io.element.android.libraries.permissions.api.PermissionsPresenter
|
||||
import io.element.android.libraries.permissions.noop.NoopPermissionsPresenter
|
||||
import io.element.android.libraries.push.api.PushService
|
||||
import io.element.android.libraries.pushproviders.api.Distributor
|
||||
import io.element.android.libraries.pushproviders.api.PushProvider
|
||||
import io.element.android.tests.testutils.WarmUpRule
|
||||
import io.element.android.tests.testutils.consumeItemsUntilPredicate
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
|
||||
class LoggedInPresenterTest {
|
||||
|
||||
@get:Rule
|
||||
val warmUpRule = WarmUpRule()
|
||||
|
||||
@Test
|
||||
fun `present - initial state`() = runTest {
|
||||
val presenter = createPresenter()
|
||||
|
|
@ -43,7 +47,7 @@ class LoggedInPresenterTest {
|
|||
presenter.present()
|
||||
}.test {
|
||||
val initialState = awaitItem()
|
||||
assertThat(initialState.permissionsState.permission).isEmpty()
|
||||
assertThat(initialState.showSyncSpinner).isFalse()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -68,11 +72,6 @@ class LoggedInPresenterTest {
|
|||
): LoggedInPresenter {
|
||||
return LoggedInPresenter(
|
||||
matrixClient = FakeMatrixClient(roomListService = roomListService),
|
||||
permissionsPresenterFactory = object : PermissionsPresenter.Factory {
|
||||
override fun create(permission: String): PermissionsPresenter {
|
||||
return NoopPermissionsPresenter()
|
||||
}
|
||||
},
|
||||
networkMonitor = FakeNetworkMonitor(networkStatus),
|
||||
pushService = object : PushService {
|
||||
override fun notificationStyleChanged() {
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ import org.jetbrains.kotlin.cli.common.toBooleanLenient
|
|||
buildscript {
|
||||
dependencies {
|
||||
classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:1.9.10")
|
||||
classpath("com.google.gms:google-services:4.3.15")
|
||||
classpath("com.google.gms:google-services:4.4.0")
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -62,7 +62,7 @@ allprojects {
|
|||
config.from(files("$rootDir/tools/detekt/detekt.yml"))
|
||||
}
|
||||
dependencies {
|
||||
detektPlugins("io.nlopez.compose.rules:detekt:0.2.1")
|
||||
detektPlugins("io.nlopez.compose.rules:detekt:0.2.2")
|
||||
}
|
||||
|
||||
// KtLint
|
||||
|
|
@ -143,22 +143,6 @@ sonar {
|
|||
}
|
||||
}
|
||||
|
||||
allprojects {
|
||||
val projectDir = projectDir.toString()
|
||||
sonar {
|
||||
properties {
|
||||
// Note: folders `kotlin` are not supported (yet), I asked on their side: https://community.sonarsource.com/t/82824
|
||||
// As a workaround provide the path in `sonar.sources` property.
|
||||
if (File("$projectDir/src/main/kotlin").exists()) {
|
||||
property("sonar.sources", "src/main/kotlin")
|
||||
}
|
||||
if (File("$projectDir/src/test/kotlin").exists()) {
|
||||
property("sonar.tests", "src/test/kotlin")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
allprojects {
|
||||
tasks.withType<Test> {
|
||||
maxParallelForks = (Runtime.getRuntime().availableProcessors() / 2).coerceAtLeast(1)
|
||||
|
|
@ -261,6 +245,8 @@ koverMerged {
|
|||
includes += "*Presenter"
|
||||
excludes += "*Fake*Presenter"
|
||||
excludes += "io.element.android.appnav.loggedin.LoggedInPresenter$*"
|
||||
// Some options can't be tested at the moment
|
||||
excludes += "io.element.android.features.preferences.impl.developer.DeveloperSettingsPresenter$*"
|
||||
}
|
||||
bound {
|
||||
minValue = 85
|
||||
|
|
|
|||
69
docs/continuous_integration.md
Normal file
|
|
@ -0,0 +1,69 @@
|
|||
# Continuous integration strategy
|
||||
|
||||
<!--- TOC -->
|
||||
|
||||
* [Introduction](#introduction)
|
||||
* [CI tools](#ci-tools)
|
||||
* [Rules](#rules)
|
||||
* [What is the CI checking](#what-is-the-ci-checking)
|
||||
* [What is the CI reporting](#what-is-the-ci-reporting)
|
||||
* [Current choices](#current-choices)
|
||||
* [R8 task](#r8-task)
|
||||
* [Android test (connected test)](#android-test-connected-test)
|
||||
|
||||
<!--- END -->
|
||||
|
||||
## Introduction
|
||||
|
||||
This document gives some information about how we take advantage of the continuous integration (CI).
|
||||
|
||||
## CI tools
|
||||
|
||||
We use GitHub Actions to configure and perform the CI.
|
||||
|
||||
## Rules
|
||||
|
||||
We want:
|
||||
|
||||
1. The CI to detect as soon as possible any issue in the code
|
||||
2. The CI to be fast - it's run on all the Pull Requests, and developers do not like to wait too long
|
||||
3. The CI to be reliable - it should not fail randomly
|
||||
4. The CI to generate artifacts which can be used by the team and the community
|
||||
5. The CI to generate useful logs and reports, not too verbose, not too short
|
||||
6. The developer to be able to run the CI locally - to help with this we have [a script](../tools/check/check_code_quality.sh) the can be run locally and which does more checks that just building and deploying the app.
|
||||
7. The CI to be used as a common environment for the team: generate the screenshots image for the screenshot test, build the release build (unsigned)
|
||||
8. The CI to run repeated tasks, like building the nightly builds, integrating data from external tools (translations, etc.)
|
||||
9. The CI to upgrade our dependencies (Renovate)
|
||||
10. The CI to do some issue triaging
|
||||
|
||||
## What is the CI checking
|
||||
|
||||
The CI checks that:
|
||||
|
||||
1. The code is compiling, without any warnings, for all the app build types and variants and for the minimal app
|
||||
2. The tests are passing
|
||||
3. The code quality is good (detekt, ktlint, lint)
|
||||
4. The code is running and smoke tests are passing (maestro)
|
||||
5. The PullRequest itself is good (with danger)
|
||||
6. Files that must be added with git-lfs are added with git-lfs
|
||||
|
||||
## What is the CI reporting
|
||||
|
||||
The CI reports:
|
||||
|
||||
1. Code coverage reports
|
||||
2. Sonar reports
|
||||
|
||||
## Current choices
|
||||
|
||||
### R8 task
|
||||
|
||||
The CI does not run R8 because it's too slow, and it breaks rule 2.
|
||||
|
||||
The drawback is that the nightly build can fail, as well as the release build.
|
||||
|
||||
Since the nightly build is failing, the team can detect the failure quite fast and react to it.
|
||||
|
||||
### Android test (connected test)
|
||||
|
||||
We limit the number of connected tests (tests under folder `androidTest`), because it often break rule 2 and 3.
|
||||
2
fastlane/metadata/android/en-US/changelogs/40002000.txt
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
Main changes in this version: Element Call, design update, bugfixes
|
||||
Full changelog: https://github.com/vector-im/element-x-android/releases
|
||||
|
|
@ -51,4 +51,5 @@ dependencies {
|
|||
testImplementation(libs.test.mockk)
|
||||
testImplementation(projects.libraries.matrix.test)
|
||||
testImplementation(projects.services.analytics.test)
|
||||
testImplementation(projects.tests.testutils)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,10 +1,10 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||
<string name="screen_analytics_prompt_data_usage">"Wir werden keine personenbezogenen Daten aufzeichnen oder auswerten"</string>
|
||||
<string name="screen_analytics_prompt_data_usage">"Wir zeichnen keine persönlichen Daten auf und erstellen keine Profile."</string>
|
||||
<string name="screen_analytics_prompt_help_us_improve">"Teile anonyme Nutzungsdaten, um uns bei der Identifizierung von Problemen zu helfen."</string>
|
||||
<string name="screen_analytics_prompt_read_terms">"Du kannst alle unsere Nutzerbedingungen %1$s lesen."</string>
|
||||
<string name="screen_analytics_prompt_read_terms">"Du kannst alle unsere Bedingungen lesen %1$s."</string>
|
||||
<string name="screen_analytics_prompt_read_terms_content_link">"hier"</string>
|
||||
<string name="screen_analytics_prompt_settings">"Du kannst dies jederzeit deaktivieren"</string>
|
||||
<string name="screen_analytics_prompt_third_party_sharing">"Wir geben deine Daten nicht an Dritte weiter"</string>
|
||||
<string name="screen_analytics_prompt_title">"Hilf uns, %1$s zu verbessern"</string>
|
||||
<string name="screen_analytics_prompt_settings">"Du kannst diese Funktion jederzeit deaktivieren"</string>
|
||||
<string name="screen_analytics_prompt_third_party_sharing">"Wir geben Ihre Daten nicht an Dritte weiter"</string>
|
||||
<string name="screen_analytics_prompt_title">"Hilf uns %1$s zu verbessern"</string>
|
||||
</resources>
|
||||
|
|
|
|||
|
|
@ -1,10 +1,10 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||
<string name="screen_analytics_prompt_data_usage">"Nous n\'enregistrerons ni ne traiterons aucune donnée personnelle"</string>
|
||||
<string name="screen_analytics_prompt_help_us_improve">"Partagez des données d\'utilisation anonymes pour nous aider à identifier les problèmes."</string>
|
||||
<string name="screen_analytics_prompt_read_terms">"Consultez nos conditions d\'utilisation %1$s."</string>
|
||||
<string name="screen_analytics_prompt_data_usage">"Nous n’enregistrerons ni ne profilerons aucune donnée personnelle"</string>
|
||||
<string name="screen_analytics_prompt_help_us_improve">"Partagez des données d’utilisation anonymes pour nous aider à identifier les problèmes."</string>
|
||||
<string name="screen_analytics_prompt_read_terms">"Vous pouvez lire toutes nos conditions %1$s."</string>
|
||||
<string name="screen_analytics_prompt_read_terms_content_link">"ici"</string>
|
||||
<string name="screen_analytics_prompt_settings">"Vous pouvez désactiver cette fonction à tout moment"</string>
|
||||
<string name="screen_analytics_prompt_settings">"Vous pouvez le désactiver à tout moment"</string>
|
||||
<string name="screen_analytics_prompt_third_party_sharing">"Nous ne partagerons pas vos données avec des tiers"</string>
|
||||
<string name="screen_analytics_prompt_title">"Aidez-nous à améliorer %1$s"</string>
|
||||
<string name="screen_analytics_prompt_title">"Aidez à améliorer %1$s"</string>
|
||||
</resources>
|
||||
|
|
|
|||
|
|
@ -1,5 +1,10 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||
<string name="screen_analytics_prompt_data_usage">"我們不會紀錄或剖繪您的個人資料"</string>
|
||||
<string name="screen_analytics_prompt_help_us_improve">"分享匿名的使用數據以協助我們釐清問題"</string>
|
||||
<string name="screen_analytics_prompt_read_terms">"您可以到 %1$s 閱讀我們的條款。"</string>
|
||||
<string name="screen_analytics_prompt_read_terms_content_link">"這裡"</string>
|
||||
<string name="screen_analytics_prompt_settings">"您可以在任何時候關閉它"</string>
|
||||
<string name="screen_analytics_prompt_third_party_sharing">"我們不會和第三方分享您的資料"</string>
|
||||
<string name="screen_analytics_prompt_title">"讓 %1$s 變得更好"</string>
|
||||
</resources>
|
||||
|
|
|
|||
|
|
@ -23,11 +23,17 @@ import com.google.common.truth.Truth.assertThat
|
|||
import io.element.android.features.analytics.api.AnalyticsOptInEvents
|
||||
import io.element.android.libraries.matrix.test.core.aBuildMeta
|
||||
import io.element.android.services.analytics.test.FakeAnalyticsService
|
||||
import io.element.android.tests.testutils.WarmUpRule
|
||||
import kotlinx.coroutines.flow.first
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
|
||||
class AnalyticsOptInPresenterTest {
|
||||
|
||||
@get:Rule
|
||||
val warmUpRule = WarmUpRule()
|
||||
|
||||
@Test
|
||||
fun `present - enable`() = runTest {
|
||||
val analyticsService = FakeAnalyticsService(isEnabled = false)
|
||||
|
|
|
|||
|
|
@ -23,10 +23,16 @@ import com.google.common.truth.Truth.assertThat
|
|||
import io.element.android.features.analytics.api.AnalyticsOptInEvents
|
||||
import io.element.android.libraries.matrix.test.core.aBuildMeta
|
||||
import io.element.android.services.analytics.test.FakeAnalyticsService
|
||||
import io.element.android.tests.testutils.WarmUpRule
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
|
||||
class AnalyticsPreferencesPresenterTest {
|
||||
|
||||
@get:Rule
|
||||
val warmUpRule = WarmUpRule()
|
||||
|
||||
@Test
|
||||
fun `present - initial state available`() = runTest {
|
||||
val presenter = DefaultAnalyticsPreferencesPresenter(
|
||||
|
|
|
|||
37
features/call/build.gradle.kts
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
/*
|
||||
* 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.
|
||||
* 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.
|
||||
*/
|
||||
|
||||
plugins {
|
||||
id("io.element.android-compose-library")
|
||||
alias(libs.plugins.anvil)
|
||||
alias(libs.plugins.ksp)
|
||||
}
|
||||
|
||||
android {
|
||||
namespace = "io.element.android.features.call"
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation(projects.libraries.architecture)
|
||||
implementation(projects.libraries.designsystem)
|
||||
implementation(projects.libraries.network)
|
||||
implementation(libs.androidx.webkit)
|
||||
ksp(libs.showkase.processor)
|
||||
|
||||
testImplementation(libs.test.junit)
|
||||
testImplementation(libs.test.truth)
|
||||
testImplementation(libs.test.robolectric)
|
||||
}
|
||||
61
features/call/src/main/AndroidManifest.xml
Normal file
|
|
@ -0,0 +1,61 @@
|
|||
<!--
|
||||
~ 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.
|
||||
~ 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.
|
||||
-->
|
||||
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
|
||||
<uses-feature android:name="android.hardware.camera" android:required="false" />
|
||||
<uses-feature android:name="android.hardware.microphone" android:required="false" />
|
||||
|
||||
<uses-permission android:name="android.permission.RECORD_AUDIO" />
|
||||
<uses-permission android:name="android.permission.CAMERA" />
|
||||
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />
|
||||
<uses-permission android:name="android.permission.WAKE_LOCK" />
|
||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
|
||||
|
||||
<application>
|
||||
<activity
|
||||
android:name=".ElementCallActivity"
|
||||
android:label="@string/element_call"
|
||||
android:exported="true"
|
||||
android:taskAffinity="io.element.android.features.call"
|
||||
android:configChanges="screenSize|screenLayout|orientation|keyboardHidden|keyboard|navigation|uiMode"
|
||||
android:launchMode="singleTask">
|
||||
|
||||
<intent-filter android:autoVerify="true">
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<category android:name="android.intent.category.BROWSABLE" />
|
||||
|
||||
<data android:scheme="http" />
|
||||
<data android:scheme="https" />
|
||||
|
||||
<data android:host="call.element.io" />
|
||||
</intent-filter>
|
||||
<!-- Custom scheme to handle urls from other domains in the format: element://call?url=https%3A%2F%2Felement.io -->
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<category android:name="android.intent.category.BROWSABLE" />
|
||||
|
||||
<data android:scheme="element" />
|
||||
<data android:host="call" />
|
||||
</intent-filter>
|
||||
|
||||
</activity>
|
||||
<service android:name=".CallForegroundService" android:enabled="true" android:foregroundServiceType="mediaPlayback" />
|
||||
</application>
|
||||
|
||||
</manifest>
|
||||
|
|
@ -0,0 +1,89 @@
|
|||
/*
|
||||
* 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.
|
||||
* 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 io.element.android.features.call
|
||||
|
||||
import android.app.Service
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.os.Build
|
||||
import android.os.IBinder
|
||||
import androidx.core.app.NotificationChannelCompat
|
||||
import androidx.core.app.NotificationCompat
|
||||
import androidx.core.app.NotificationManagerCompat
|
||||
import androidx.core.app.PendingIntentCompat
|
||||
import androidx.core.graphics.drawable.IconCompat
|
||||
import io.element.android.libraries.designsystem.utils.CommonDrawables
|
||||
|
||||
class CallForegroundService : Service() {
|
||||
|
||||
companion object {
|
||||
fun start(context: Context) {
|
||||
val intent = Intent(context, CallForegroundService::class.java)
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
context.startForegroundService(intent)
|
||||
} else {
|
||||
context.startService(intent)
|
||||
}
|
||||
}
|
||||
|
||||
fun stop(context: Context) {
|
||||
val intent = Intent(context, CallForegroundService::class.java)
|
||||
context.stopService(intent)
|
||||
}
|
||||
}
|
||||
|
||||
private lateinit var notificationManagerCompat: NotificationManagerCompat
|
||||
|
||||
override fun onCreate() {
|
||||
super.onCreate()
|
||||
|
||||
notificationManagerCompat = NotificationManagerCompat.from(this)
|
||||
|
||||
val foregroundServiceChannel = NotificationChannelCompat.Builder(
|
||||
"call_foreground_service_channel",
|
||||
NotificationManagerCompat.IMPORTANCE_LOW,
|
||||
).setName(
|
||||
getString(R.string.call_foreground_service_channel_title_android).ifEmpty { "Ongoing call" }
|
||||
).build()
|
||||
notificationManagerCompat.createNotificationChannel(foregroundServiceChannel)
|
||||
|
||||
val callActivityIntent = Intent(this, ElementCallActivity::class.java)
|
||||
val pendingIntent = PendingIntentCompat.getActivity(this, 0, callActivityIntent, 0, false)
|
||||
val notification = NotificationCompat.Builder(this, foregroundServiceChannel.id)
|
||||
.setSmallIcon(IconCompat.createWithResource(this, CommonDrawables.ic_notification_small))
|
||||
.setContentTitle(getString(R.string.call_foreground_service_title_android))
|
||||
.setContentText(getString(R.string.call_foreground_service_message_android))
|
||||
.setContentIntent(pendingIntent)
|
||||
.build()
|
||||
startForeground(1, notification)
|
||||
}
|
||||
|
||||
@Suppress("DEPRECATION")
|
||||
override fun onDestroy() {
|
||||
super.onDestroy()
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
|
||||
stopForeground(STOP_FOREGROUND_REMOVE)
|
||||
} else {
|
||||
stopForeground(true)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onBind(intent: Intent?): IBinder? {
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,45 @@
|
|||
/*
|
||||
* 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.
|
||||
* 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 io.element.android.features.call
|
||||
|
||||
import android.net.Uri
|
||||
import java.net.URLDecoder
|
||||
|
||||
object CallIntentDataParser {
|
||||
|
||||
private val validHttpSchemes = sequenceOf("http", "https")
|
||||
|
||||
fun parse(data: String?): String? {
|
||||
val parsedUrl = data?.let { Uri.parse(data) } ?: return null
|
||||
val scheme = parsedUrl.scheme
|
||||
return when {
|
||||
scheme in validHttpSchemes && parsedUrl.host == "call.element.io" -> data
|
||||
scheme == "element" && parsedUrl.host == "call" -> {
|
||||
// We use this custom scheme to load arbitrary URLs for other instances of Element Call,
|
||||
// so we can only verify it's an HTTP/HTTPs URL with a non-empty host
|
||||
parsedUrl.getQueryParameter("url")
|
||||
?.let { URLDecoder.decode(it, "utf-8") }
|
||||
?.takeIf {
|
||||
val internalUri = Uri.parse(it)
|
||||
internalUri.scheme in validHttpSchemes && !internalUri.host.isNullOrBlank()
|
||||
}
|
||||
}
|
||||
// This should never be possible, but we still need to take into account the possibility
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
}
|
||||