Compare commits
220 Commits
feature/re
...
feature/cs
Author | SHA1 | Date | |
---|---|---|---|
d1e9844469 | |||
2581bf69e0 | |||
2da4823e33 | |||
70c4b7528d | |||
5dfc0b3c0e | |||
44d1d063e9 | |||
71e50b1bb9 | |||
64c307077f | |||
71e364b42f | |||
054d339b48 | |||
e3b9031e71 | |||
3fa9d7a1d4 | |||
6eafa3c43d | |||
207579c59f | |||
2780ca30a8 | |||
41c54029b5 | |||
f9142fedfd | |||
ef26519993 | |||
e27367e3f2 | |||
dc9db9a438 | |||
6fe455dac0 | |||
a9a1fe2aa0 | |||
d68b447874 | |||
11e3a5def8 | |||
6aae943e77 | |||
8d0322c0c3 | |||
f60a5f568f | |||
00fd866cc8 | |||
8929898397 | |||
73d5110d67 | |||
0c559976d6 | |||
540989f38a | |||
608bbdd4ee | |||
9a5f96f80b | |||
6bf1deb99b | |||
cfca4927e2 | |||
93cb7b8ce6 | |||
dcc430f91b | |||
64216f74ae | |||
8fd15f4082 | |||
38abf31889 | |||
c39cfbe2ae | |||
b00bff0af5 | |||
56a2a3a065 | |||
a64f509872 | |||
359cc67fab | |||
72cd409735 | |||
ceac06caf6 | |||
694df9d845 | |||
360d2a3c2e | |||
9cfc67329a | |||
75c74c25e1 | |||
7dce8c29bc | |||
41337d0ca4 | |||
287feace12 | |||
b3e2eca43d | |||
0818c55b6d | |||
b1b526a516 | |||
b6cbed1c90 | |||
65e2abf402 | |||
bee5da8f64 | |||
ab7b807740 | |||
4c02721ada | |||
5dca31e6f9 | |||
d2e1aff453 | |||
dab80466c5 | |||
2c83ba0824 | |||
657f4d3e9c | |||
9c9c09db2b | |||
c38a601bcc | |||
dcac9aed55 | |||
0225fc7120 | |||
2a2b4e7bd9 | |||
7d872420f0 | |||
0055514f90 | |||
6248cc5552 | |||
347967700b | |||
eaff5ac9f0 | |||
63964ac101 | |||
3b52fa4be8 | |||
81ddb8c5fb | |||
eae8f993e6 | |||
fab1d249f4 | |||
3f1cc466ed | |||
c64d6b6b28 | |||
c9658918ed | |||
6d3028c2d7 | |||
aec7b73345 | |||
56563412aa | |||
a9b8c57464 | |||
314771cce2 | |||
bb65dc5247 | |||
460a72e6b5 | |||
748090d0f9 | |||
968258852f | |||
260cc0dd5f | |||
57cea677e4 | |||
c47eeb9cec | |||
e929019247 | |||
43659dffd3 | |||
82d89825d3 | |||
772670252a | |||
18591d0287 | |||
08dacacdda | |||
3091a337c9 | |||
73580493ea | |||
c188bb290f | |||
d9c8867c0b | |||
87c9f6b2a0 | |||
6830957d31 | |||
96a67a44ac | |||
b9b8527b38 | |||
10520fb1bd | |||
494d893aa3 | |||
9048a1dbbe | |||
04b4f32e16 | |||
d110dac0a6 | |||
be6a4efacb | |||
f75fe1201d | |||
547272b17b | |||
94db36d6c4 | |||
3efcbaaea9 | |||
fde09b4a94 | |||
317503aa2b | |||
be2dad9b17 | |||
2e2d5b9f86 | |||
a7b81a4671 | |||
86a60f7ebd | |||
a6366e47fe | |||
bc467340c9 | |||
4154df7c21 | |||
79e273b1ca | |||
4ab3c39415 | |||
ad243ae41f | |||
2898eae566 | |||
cbd62b9e93 | |||
27374aea3f | |||
a4ef259bd2 | |||
3f10829dcd | |||
289c820b48 | |||
40d4e3fe83 | |||
f4170f55b7 | |||
d8bff8201f | |||
dffcc3f405 | |||
7030258de5 | |||
89a2aad561 | |||
6406c4021a | |||
ddc5a0d30b | |||
d4b1f074e3 | |||
3b68b304f5 | |||
b8cceaa17a | |||
8a7675d2e4 | |||
6cf91e5eca | |||
e938866081 | |||
20108c2603 | |||
02c7deb783 | |||
368cf4b3f0 | |||
c093e07c24 | |||
a97142272c | |||
97fa94592e | |||
0c76178bee | |||
c12bc5e02d | |||
0db1d98f41 | |||
008f6e2bdd | |||
820709d433 | |||
d5838262ea | |||
617f71fd51 | |||
1ac143a065 | |||
eca59114dd | |||
a199eaa171 | |||
26ae034ba9 | |||
99b1c0bed4 | |||
388eae6a1c | |||
ff6d1f611a | |||
157068634a | |||
1d4882e596 | |||
415d9e702b | |||
4c3f7171e7 | |||
a146fc43e4 | |||
ef3fb561e9 | |||
fffdf4b8c1 | |||
3d7562ea8e | |||
fd3fce6deb | |||
753e70775a | |||
2c0bc93f5a | |||
adc51529f2 | |||
63bf4355b9 | |||
41b06bca60 | |||
d6f6764b0c | |||
46226c4efc | |||
f59977f884 | |||
40f1fcab18 | |||
85b119bdcb | |||
6c7bc2b40c | |||
f06211ce4f | |||
d2db5e32fc | |||
4458e28ce2 | |||
14ac3a8ae6 | |||
ca890e1ef4 | |||
9cc2cf8360 | |||
28c837a47f | |||
42cf45c8f3 | |||
8fdce937bd | |||
f36bec7176 | |||
9477316b61 | |||
36d1d52880 | |||
abb44839af | |||
e91276bb76 | |||
b5f40f9732 | |||
24ffd96b6e | |||
c977c651a2 | |||
5c78991ae1 | |||
c467f179e1 | |||
264265a1f7 | |||
a12a9da627 | |||
c42294a21e | |||
03437885ef | |||
5e81fc8dc2 | |||
7a5ff282b6 | |||
e4069ab51b |
41
.buildkite/pipeline.yml
Normal file
@ -0,0 +1,41 @@
|
||||
# Use Docker file from https://hub.docker.com/r/runmymind/docker-android-sdk
|
||||
# Last docker plugin version can be found here:
|
||||
# https://github.com/buildkite-plugins/docker-buildkite-plugin/releases
|
||||
|
||||
# Build debug version of the RiotX application, from the develop branch and the features branches
|
||||
|
||||
steps:
|
||||
- label: "Assemble Debug version"
|
||||
agents:
|
||||
# We use a medium sized instance instead of the normal small ones because
|
||||
# gradle build is long
|
||||
queue: "medium"
|
||||
commands:
|
||||
- "./gradlew clean lintGplayRelease assembleGplayDebug --stacktrace"
|
||||
- "./gradlew lintFdroidRelease assembleFdroidDebug --stacktrace"
|
||||
artifact_paths:
|
||||
- "vector/build/outputs/apk/gplay/debug/*.apk"
|
||||
- "vector/build/outputs/apk/fdroid/debug/*.apk"
|
||||
branches: "develop feature/*"
|
||||
plugins:
|
||||
- docker#v3.1.0:
|
||||
image: "runmymind/docker-android-sdk"
|
||||
|
||||
- label: "Build Google Play unsigned APK"
|
||||
agents:
|
||||
# We use a medium sized instance instead of the normal small ones because
|
||||
# gradle build is long
|
||||
queue: "medium"
|
||||
commands:
|
||||
- "./gradlew clean assembleGplayRelease --stacktrace"
|
||||
artifact_paths:
|
||||
- "vector/build/outputs/apk/gplay/release/*.apk"
|
||||
branches: "master"
|
||||
plugins:
|
||||
- docker#v3.1.0:
|
||||
image: "runmymind/docker-android-sdk"
|
||||
|
||||
# Code quality
|
||||
|
||||
- label: "Code quality"
|
||||
command: "./tools/check/check_code_quality.sh"
|
10
.github/PULL_REQUEST_TEMPLATE.md
vendored
Normal file
@ -0,0 +1,10 @@
|
||||
### Pull Request Checklist
|
||||
|
||||
<!-- Please read [CONTRIBUTING.md](https://github.com/vector-im/riotX-android/blob/develop/CONTRIBUTING.md) before submitting your pull request -->
|
||||
|
||||
- [ ] Changes has been tested on an Android device or Android emulator with API 16
|
||||
- [ ] UI change has been tested on both light and dark themes
|
||||
- [ ] Pull request is based on the develop branch
|
||||
- [ ] Pull request updates [CHANGES.md](https://github.com/vector-im/riotX-android/blob/develop/CHANGES.md)
|
||||
- [ ] Pull request includes screenshots or videos if containing UI changes
|
||||
- [ ] Pull request includes a [sign off](https://github.com/matrix-org/synapse/blob/master/CONTRIBUTING.rst#sign-off)
|
2
.gitignore
vendored
@ -10,3 +10,5 @@
|
||||
/build
|
||||
/captures
|
||||
.externalNativeBuild
|
||||
|
||||
/tmp
|
||||
|
29
.idea/codeStyles/Project.xml
generated
@ -1,29 +0,0 @@
|
||||
<component name="ProjectCodeStyleConfiguration">
|
||||
<code_scheme name="Project" version="173">
|
||||
<Objective-C-extensions>
|
||||
<file>
|
||||
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Import" />
|
||||
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Macro" />
|
||||
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Typedef" />
|
||||
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Enum" />
|
||||
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Constant" />
|
||||
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Global" />
|
||||
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Struct" />
|
||||
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="FunctionPredecl" />
|
||||
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Function" />
|
||||
</file>
|
||||
<class>
|
||||
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Property" />
|
||||
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Synthesize" />
|
||||
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="InitMethod" />
|
||||
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="StaticMethod" />
|
||||
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="InstanceMethod" />
|
||||
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="DeallocMethod" />
|
||||
</class>
|
||||
<extensions>
|
||||
<pair source="cpp" header="h" fileNamingConvention="NONE" />
|
||||
<pair source="c" header="h" fileNamingConvention="NONE" />
|
||||
</extensions>
|
||||
</Objective-C-extensions>
|
||||
</code_scheme>
|
||||
</component>
|
5
.idea/codeStyles/codeStyleConfig.xml
generated
@ -1,5 +0,0 @@
|
||||
<component name="ProjectCodeStyleConfiguration">
|
||||
<state>
|
||||
<option name="PREFERRED_PROJECT_CODE_STYLE" value="Default" />
|
||||
</state>
|
||||
</component>
|
13
.idea/dictionaries/ganfra.xml
generated
@ -1,13 +0,0 @@
|
||||
<component name="ProjectDictionaryState">
|
||||
<dictionary name="ganfra">
|
||||
<words>
|
||||
<w>connectable</w>
|
||||
<w>coroutine</w>
|
||||
<w>merlins</w>
|
||||
<w>moshi</w>
|
||||
<w>persistor</w>
|
||||
<w>synchronizer</w>
|
||||
<w>untimelined</w>
|
||||
</words>
|
||||
</dictionary>
|
||||
</component>
|
20
.idea/gradle.xml
generated
@ -1,20 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="GradleSettings">
|
||||
<option name="linkedExternalProjectsSettings">
|
||||
<GradleProjectSettings>
|
||||
<option name="distributionType" value="DEFAULT_WRAPPED" />
|
||||
<option name="externalProjectPath" value="$PROJECT_DIR$" />
|
||||
<option name="modules">
|
||||
<set>
|
||||
<option value="$PROJECT_DIR$" />
|
||||
<option value="$PROJECT_DIR$/app" />
|
||||
<option value="$PROJECT_DIR$/matrix-sdk-android" />
|
||||
<option value="$PROJECT_DIR$/matrix-sdk-android-rx" />
|
||||
</set>
|
||||
</option>
|
||||
<option name="resolveModulePerSourceSet" value="false" />
|
||||
</GradleProjectSettings>
|
||||
</option>
|
||||
</component>
|
||||
</project>
|
6
.idea/inspectionProfiles/Project_Default.xml
generated
@ -1,6 +0,0 @@
|
||||
<component name="InspectionProjectProfileManager">
|
||||
<profile version="1.0">
|
||||
<option name="myName" value="Project Default" />
|
||||
<inspection_tool class="DifferentStdlibGradleVersion" enabled="false" level="WARNING" enabled_by_default="false" />
|
||||
</profile>
|
||||
</component>
|
34
.idea/misc.xml
generated
@ -1,34 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="NullableNotNullManager">
|
||||
<option name="myDefaultNullable" value="android.support.annotation.Nullable" />
|
||||
<option name="myDefaultNotNull" value="android.support.annotation.NonNull" />
|
||||
<option name="myNullables">
|
||||
<value>
|
||||
<list size="5">
|
||||
<item index="0" class="java.lang.String" itemvalue="org.jetbrains.annotations.Nullable" />
|
||||
<item index="1" class="java.lang.String" itemvalue="javax.annotation.Nullable" />
|
||||
<item index="2" class="java.lang.String" itemvalue="javax.annotation.CheckForNull" />
|
||||
<item index="3" class="java.lang.String" itemvalue="edu.umd.cs.findbugs.annotations.Nullable" />
|
||||
<item index="4" class="java.lang.String" itemvalue="android.support.annotation.Nullable" />
|
||||
</list>
|
||||
</value>
|
||||
</option>
|
||||
<option name="myNotNulls">
|
||||
<value>
|
||||
<list size="4">
|
||||
<item index="0" class="java.lang.String" itemvalue="org.jetbrains.annotations.NotNull" />
|
||||
<item index="1" class="java.lang.String" itemvalue="javax.annotation.Nonnull" />
|
||||
<item index="2" class="java.lang.String" itemvalue="edu.umd.cs.findbugs.annotations.NonNull" />
|
||||
<item index="3" class="java.lang.String" itemvalue="android.support.annotation.NonNull" />
|
||||
</list>
|
||||
</value>
|
||||
</option>
|
||||
</component>
|
||||
<component name="ProjectRootManager" version="2" languageLevel="JDK_1_8" project-jdk-name="1.8" project-jdk-type="JavaSDK">
|
||||
<output url="file://$PROJECT_DIR$/build/classes" />
|
||||
</component>
|
||||
<component name="ProjectType">
|
||||
<option name="id" value="Android" />
|
||||
</component>
|
||||
</project>
|
12
.idea/runConfigurations.xml
generated
@ -1,12 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="RunConfigurationProducerService">
|
||||
<option name="ignoredProducers">
|
||||
<set>
|
||||
<option value="org.jetbrains.plugins.gradle.execution.test.runner.AllInPackageGradleConfigurationProducer" />
|
||||
<option value="org.jetbrains.plugins.gradle.execution.test.runner.TestClassGradleConfigurationProducer" />
|
||||
<option value="org.jetbrains.plugins.gradle.execution.test.runner.TestMethodGradleConfigurationProducer" />
|
||||
</set>
|
||||
</option>
|
||||
</component>
|
||||
</project>
|
60
.travis.yml
Normal file
@ -0,0 +1,60 @@
|
||||
# FTR: Configuration on https://travis-ci.org/vector-im/riotX-android/settings
|
||||
#
|
||||
# - Build only if .travis.yml is present -> On
|
||||
# - Limit concurrent jobs -> Off
|
||||
# - Build pushed branches -> On (build the branch)
|
||||
# - Build pushed pull request -> On (build the PR after auto-merge)
|
||||
#
|
||||
# - Auto cancel branch builds -> On
|
||||
# - Auto cancel pull request builds -> On
|
||||
|
||||
language: android
|
||||
jdk: oraclejdk8
|
||||
sudo: false
|
||||
|
||||
notifications:
|
||||
email: false
|
||||
|
||||
android:
|
||||
components:
|
||||
# Uncomment the lines below if you want to
|
||||
# use the latest revision of Android SDK Tools
|
||||
- tools
|
||||
- platform-tools
|
||||
|
||||
# The BuildTools version used by your project
|
||||
- build-tools-28.0.3
|
||||
|
||||
# The SDK version used to compile your project
|
||||
- android-28
|
||||
|
||||
before_cache:
|
||||
- rm -f $HOME/.gradle/caches/modules-2/modules-2.lock
|
||||
- rm -fr $HOME/.gradle/caches/*/plugin-resolution/
|
||||
|
||||
cache:
|
||||
directories:
|
||||
- $HOME/.gradle/caches/
|
||||
- $HOME/.gradle/wrapper/
|
||||
- $HOME/.android/build-cache
|
||||
|
||||
# Build with the development SDK
|
||||
before_script:
|
||||
# Not necessary for the moment
|
||||
# - /bin/sh ./set_debug_env.sh
|
||||
|
||||
# Just build the project for now
|
||||
script:
|
||||
# Build app (assembleGplayRelease assembleFdroidRelease)
|
||||
# Build Android test (assembleAndroidTest) (disabled for now)
|
||||
# Code quality (lintGplayRelease lintFdroidRelease)
|
||||
# Split into two steps because if a task contain Fdroid, PlayService will be disabled
|
||||
- ./gradlew clean assembleGplayRelease lintGplayRelease --stacktrace
|
||||
- ./gradlew clean assembleFdroidRelease lintFdroidRelease --stacktrace
|
||||
# Run unitary test (Disable for now, see https://travis-ci.org/vector-im/riot-android/builds/502504370)
|
||||
# - ./gradlew testGplayReleaseUnitTest --stacktrace
|
||||
# Other code quality check
|
||||
- ./tools/check/check_code_quality.sh
|
||||
- ./tools/travis/check_pr.sh
|
||||
# Check that indonesians file are identical. Due to Android issue, the resource folder must be value-in/, and Weblate export data into value-id/.
|
||||
- diff ./vector/src/main/res/values-id/strings.xml ./vector/src/main/res/values-in/strings.xml
|
0
AUTHORS.md
Normal file
49
CHANGES.md
Normal file
@ -0,0 +1,49 @@
|
||||
Changes in RiotX 0.XX (2019-XX-XX)
|
||||
===================================================
|
||||
|
||||
Features:
|
||||
- Contextual action menu for messages in room
|
||||
|
||||
Improvements:
|
||||
-
|
||||
|
||||
Other changes:
|
||||
-
|
||||
|
||||
Bugfix:
|
||||
-
|
||||
|
||||
Translations:
|
||||
-
|
||||
|
||||
Build:
|
||||
-
|
||||
|
||||
|
||||
|
||||
=======================================================
|
||||
+ TEMPLATE WHEN PREPARING A NEW RELEASE +
|
||||
=======================================================
|
||||
|
||||
|
||||
Changes in RiotX 0.XX (2019-XX-XX)
|
||||
===================================================
|
||||
|
||||
Features:
|
||||
-
|
||||
|
||||
Improvements:
|
||||
-
|
||||
|
||||
Other changes:
|
||||
-
|
||||
|
||||
Bugfix:
|
||||
-
|
||||
|
||||
Translations:
|
||||
-
|
||||
|
||||
Build:
|
||||
-
|
||||
|
76
CONTRIBUTING.md
Normal file
@ -0,0 +1,76 @@
|
||||
# Contributing code to Matrix
|
||||
|
||||
Please read https://github.com/matrix-org/synapse/blob/master/CONTRIBUTING.rst
|
||||
|
||||
Android support can be found in this [](https://matrix.to/#/#riot-android:matrix.org) room.
|
||||
|
||||
Dedicated room for RiotX: [](https://matrix.to/#/#riotx:matrix.org)
|
||||
|
||||
# Specific rules for Matrix Android projects
|
||||
|
||||
## Android Studio settings
|
||||
|
||||
Please set the "hard wrap" setting of Android Studio to 160 chars, this is the setting we use internally to format the source code (Menu `Settings/Editor/Code Style` then `Hard wrap at`).
|
||||
|
||||
## Compilation
|
||||
|
||||
For now, the Matrix SDK and the RiotX application are in the same project. So there is no specific thing to do, this project should compile without any special action.
|
||||
|
||||
## I want to help translating RiotX
|
||||
|
||||
If you want to fix an issue with an English string, please submit a PR.
|
||||
If you want to fix an issue in other languages, or add a missing translation, or even add a new language, please use [Weblate](https://translate.riot.im/projects/riot-android/).
|
||||
|
||||
For the moment, Strings from Riot will be used, there is no dedicated project in Weblate for RiotX.
|
||||
|
||||
## I want to submit a PR to fix an issue
|
||||
|
||||
Please check if a corresponding issue exists. If yes, please let us know in a comment that you're working on it.
|
||||
If an issue does not exist yet, it may be relevant to open a new issue and let us know that you're implementing it.
|
||||
|
||||
### Kotlin
|
||||
|
||||
This project is full Kotlin. Please do not write Java classes.
|
||||
|
||||
### CHANGES.md
|
||||
|
||||
Please add a line to the top of the file `CHANGES.md` describing your change.
|
||||
|
||||
### Code quality
|
||||
|
||||
Make sure the following commands execute without any error:
|
||||
|
||||
> ./tools/check/check_code_quality.sh
|
||||
|
||||
> ./gradlew lintGplayRelease
|
||||
|
||||
### Unit tests
|
||||
|
||||
Make sure the following commands execute without any error:
|
||||
|
||||
> ./gradlew testGplayReleaseUnitTest
|
||||
|
||||
### Tests
|
||||
|
||||
RiotX is currently supported on Android Jelly Bean (API 16+): please test your change on an Android device (or Android emulator) running with API 16. Many issues can happen (including crashes) on older devices.
|
||||
Also, if possible, please test your change on a real device. Testing on Android emulator may not be sufficient.
|
||||
|
||||
### Internationalisation
|
||||
|
||||
When adding new string resources, please only add new entries in file `value/strings.xml`. Translations will be added later by the community of translators with a specific tool named [Weblate](https://translate.riot.im/projects/riot-android/).
|
||||
Do not hesitate to use plurals when appropriate.
|
||||
|
||||
### Layout
|
||||
|
||||
When adding or editing layouts, make sure the layout will render correctly if device uses a RTL (Right To Left) language.
|
||||
You can check this in the layout editor preview by selecting any RTL language (ex: Arabic).
|
||||
|
||||
Also please check that the colors are ok for all the current themes of RiotX. Please use `?attr` instead of `@color` to reference colors in the layout. You can check this in the layout editor preview by selecting all the main themes (`AppTheme.Status`, `AppTheme.Dark`, etc.).
|
||||
|
||||
### Authors
|
||||
|
||||
Feel free to add an entry in file AUTHORS.md
|
||||
|
||||
## Thanks
|
||||
|
||||
Thanks for contributing to Matrix projects!
|
176
LICENSE
Normal file
@ -0,0 +1,176 @@
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
21
README.md
@ -1 +1,20 @@
|
||||
# riot-android-redesign-PoC
|
||||
[](https://buildkite.com/matrix-dot-org/riotx-android)
|
||||
[](https://translate.riot.im/engage/riot-android/?utm_source=widget)
|
||||
[](https://matrix.to/#/#riotx:matrix.org)
|
||||
[](https://sonarcloud.io/dashboard?id=vector.android.riotx)
|
||||
[](https://sonarcloud.io/dashboard?id=vector.android.riotx)
|
||||
[](https://sonarcloud.io/dashboard?id=vector.android.riotx)
|
||||
|
||||
# RiotX Android
|
||||
|
||||
RiotX is an Android Matrix Client currently in development. The application is not yet available on the PlayStore.
|
||||
|
||||
It's based on a new Matrix SDK, written in Kotlin.
|
||||
|
||||
Download nightly build here: [](https://buildkite.com/matrix-dot-org/riotx-android/builds?branch=develop)
|
||||
|
||||
Matrix Room: [](https://matrix.to/#/#riotx:matrix.org)
|
||||
|
||||
## Contributing
|
||||
|
||||
Please refer to [CONTRIBUTING.md](https://github.com/vector-im/riotX-android/blob/develop/CONTRIBUTING.md) if you want to contribute the Matrix on Android projects!
|
||||
|
105
app/build.gradle
@ -1,105 +0,0 @@
|
||||
apply plugin: 'com.android.application'
|
||||
apply plugin: 'kotlin-android'
|
||||
apply plugin: 'kotlin-android-extensions'
|
||||
apply plugin: 'kotlin-kapt'
|
||||
|
||||
kapt {
|
||||
correctErrorTypes = true
|
||||
}
|
||||
|
||||
androidExtensions {
|
||||
experimental = true
|
||||
}
|
||||
|
||||
def versionMajor = 0
|
||||
def versionMinor = 1
|
||||
def versionPatch = 0
|
||||
|
||||
def generateVersionCodeFromTimestamp() {
|
||||
// It's unix timestamp divided by 10: It's incremented by one every 10 seconds.
|
||||
return (System.currentTimeMillis() / 1_000 / 10).toInteger()
|
||||
}
|
||||
|
||||
def generateVersionCodeFromVersionName() {
|
||||
return versionMajor * 10000 + versionMinor * 100 + versionPatch
|
||||
}
|
||||
|
||||
android {
|
||||
compileSdkVersion 28
|
||||
defaultConfig {
|
||||
applicationId "im.vector.riotredesign"
|
||||
minSdkVersion 16
|
||||
targetSdkVersion 28
|
||||
multiDexEnabled true
|
||||
versionCode generateVersionCodeFromTimestamp()
|
||||
versionName "${versionMajor}.${versionMinor}.${versionPatch}"
|
||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||
}
|
||||
buildTypes {
|
||||
release {
|
||||
minifyEnabled false
|
||||
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
|
||||
}
|
||||
}
|
||||
|
||||
compileOptions {
|
||||
sourceCompatibility JavaVersion.VERSION_1_8
|
||||
targetCompatibility JavaVersion.VERSION_1_8
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
dependencies {
|
||||
|
||||
def epoxy_version = "3.0.0"
|
||||
def arrow_version = "0.8.2"
|
||||
def glide_version = "4.8.0"
|
||||
|
||||
implementation project(":matrix-sdk-android")
|
||||
implementation project(":matrix-sdk-android-rx")
|
||||
implementation 'com.android.support:multidex:1.0.3'
|
||||
|
||||
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
|
||||
|
||||
implementation 'androidx.appcompat:appcompat:1.1.0-alpha01'
|
||||
implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
|
||||
implementation 'androidx.core:core-ktx:1.0.1'
|
||||
|
||||
// Paging
|
||||
implementation 'androidx.paging:paging-runtime:2.0.0'
|
||||
|
||||
implementation 'com.jakewharton.threetenabp:threetenabp:1.1.1'
|
||||
implementation 'com.jakewharton.timber:timber:4.7.1'
|
||||
|
||||
// rx
|
||||
implementation 'io.reactivex.rxjava2:rxkotlin:2.3.0'
|
||||
implementation 'io.reactivex.rxjava2:rxandroid:2.1.0'
|
||||
implementation 'com.jakewharton.rxrelay2:rxrelay:2.1.0'
|
||||
implementation 'com.jakewharton.rxbinding2:rxbinding:2.2.0'
|
||||
|
||||
implementation("com.airbnb.android:epoxy:$epoxy_version")
|
||||
kapt "com.airbnb.android:epoxy-processor:$epoxy_version"
|
||||
implementation 'com.airbnb.android:mvrx:0.7.0'
|
||||
|
||||
// FP
|
||||
implementation "io.arrow-kt:arrow-core:$arrow_version"
|
||||
|
||||
// UI
|
||||
implementation "com.github.bumptech.glide:glide:$glide_version"
|
||||
implementation "com.github.bumptech.glide:okhttp3-integration:$glide_version"
|
||||
kapt "com.github.bumptech.glide:compiler:$glide_version"
|
||||
|
||||
implementation 'com.amulyakhare:com.amulyakhare.textdrawable:1.0.1'
|
||||
implementation 'com.google.android.material:material:1.1.0-alpha02'
|
||||
|
||||
// DI
|
||||
implementation "org.koin:koin-android:$koin_version"
|
||||
implementation "org.koin:koin-android-scope:$koin_version"
|
||||
|
||||
// TESTS
|
||||
testImplementation 'junit:junit:4.12'
|
||||
androidTestImplementation 'androidx.test:runner:1.1.1'
|
||||
androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.1'
|
||||
}
|
||||
|
||||
|
@ -1,31 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="im.vector.riotredesign">
|
||||
|
||||
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
|
||||
<application
|
||||
android:name=".Riot"
|
||||
android:allowBackup="true"
|
||||
android:icon="@mipmap/ic_launcher"
|
||||
android:label="@string/app_name"
|
||||
android:roundIcon="@mipmap/ic_launcher_round"
|
||||
android:supportsRtl="true"
|
||||
android:theme="@style/Theme.Riot">
|
||||
|
||||
<activity
|
||||
android:name=".features.MainActivity"
|
||||
android:theme="@style/Theme.Riot.Splash">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
|
||||
<activity android:name=".features.home.HomeActivity" />
|
||||
<activity android:name=".features.login.LoginActivity" />
|
||||
</application>
|
||||
|
||||
</manifest>
|
@ -1,46 +0,0 @@
|
||||
/*
|
||||
* Copyright 2019 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.riotredesign
|
||||
|
||||
import android.app.Application
|
||||
import android.content.Context
|
||||
import androidx.multidex.MultiDex
|
||||
import com.jakewharton.threetenabp.AndroidThreeTen
|
||||
import im.vector.matrix.android.BuildConfig
|
||||
import im.vector.riotredesign.core.di.AppModule
|
||||
import org.koin.log.EmptyLogger
|
||||
import org.koin.standalone.StandAloneContext.startKoin
|
||||
import timber.log.Timber
|
||||
|
||||
|
||||
class Riot : Application() {
|
||||
|
||||
override fun onCreate() {
|
||||
super.onCreate()
|
||||
if (BuildConfig.DEBUG) {
|
||||
Timber.plant(Timber.DebugTree())
|
||||
}
|
||||
AndroidThreeTen.init(this)
|
||||
startKoin(listOf(AppModule(this).definition), logger = EmptyLogger())
|
||||
}
|
||||
|
||||
override fun attachBaseContext(base: Context) {
|
||||
super.attachBaseContext(base)
|
||||
MultiDex.install(this)
|
||||
}
|
||||
|
||||
}
|
@ -1,74 +0,0 @@
|
||||
/*
|
||||
* Copyright 2019 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.riotredesign.core.epoxy
|
||||
|
||||
import android.view.View
|
||||
import androidx.annotation.IdRes
|
||||
import androidx.annotation.LayoutRes
|
||||
import com.airbnb.epoxy.EpoxyModel
|
||||
import com.airbnb.epoxy.OnModelVisibilityStateChangedListener
|
||||
import kotlin.properties.ReadOnlyProperty
|
||||
import kotlin.reflect.KProperty
|
||||
|
||||
abstract class KotlinModel(
|
||||
@LayoutRes private val layoutRes: Int
|
||||
) : EpoxyModel<View>() {
|
||||
|
||||
private var view: View? = null
|
||||
private var onBindCallback: (() -> Unit)? = null
|
||||
private var onModelVisibilityStateChangedListener: OnModelVisibilityStateChangedListener<KotlinModel, View>? = null
|
||||
|
||||
abstract fun bind()
|
||||
|
||||
override fun bind(view: View) {
|
||||
this.view = view
|
||||
onBindCallback?.invoke()
|
||||
bind()
|
||||
}
|
||||
|
||||
override fun unbind(view: View) {
|
||||
this.view = null
|
||||
}
|
||||
|
||||
fun onBind(lambda: (() -> Unit)?): KotlinModel {
|
||||
onBindCallback = lambda
|
||||
return this
|
||||
}
|
||||
|
||||
override fun onVisibilityStateChanged(visibilityState: Int, view: View) {
|
||||
onModelVisibilityStateChangedListener?.onVisibilityStateChanged(this, view, visibilityState)
|
||||
super.onVisibilityStateChanged(visibilityState, view)
|
||||
}
|
||||
|
||||
fun setOnVisibilityStateChanged(listener: OnModelVisibilityStateChangedListener<KotlinModel, View>): KotlinModel {
|
||||
this.onModelVisibilityStateChangedListener = listener
|
||||
return this
|
||||
}
|
||||
|
||||
override fun getDefaultLayout() = layoutRes
|
||||
|
||||
protected fun <V : View> bind(@IdRes id: Int) = object : ReadOnlyProperty<KotlinModel, V> {
|
||||
override fun getValue(thisRef: KotlinModel, property: KProperty<*>): V {
|
||||
// This is not efficient because it looks up the view by id every time (it loses
|
||||
// the pattern of a "holder" to cache that look up). But it is simple to use and could
|
||||
// be optimized with a map
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
return view?.findViewById(id) as V?
|
||||
?: throw IllegalStateException("View ID $id for '${property.name}' not found.")
|
||||
}
|
||||
}
|
||||
}
|
@ -1,41 +0,0 @@
|
||||
/*
|
||||
* Copyright 2019 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.riotredesign.features
|
||||
|
||||
import android.os.Bundle
|
||||
import im.vector.matrix.android.api.Matrix
|
||||
import im.vector.riotredesign.core.platform.RiotActivity
|
||||
import im.vector.riotredesign.features.home.HomeActivity
|
||||
import im.vector.riotredesign.features.login.LoginActivity
|
||||
|
||||
|
||||
class MainActivity : RiotActivity() {
|
||||
|
||||
private val authenticator = Matrix.getInstance().authenticator()
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
val intent = if (authenticator.hasActiveSessions()) {
|
||||
HomeActivity.newIntent(this)
|
||||
} else {
|
||||
LoginActivity.newIntent(this)
|
||||
}
|
||||
startActivity(intent)
|
||||
finish()
|
||||
}
|
||||
|
||||
}
|
@ -1,57 +0,0 @@
|
||||
/*
|
||||
* Copyright 2019 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.riotredesign.features.home
|
||||
|
||||
import android.widget.ImageView
|
||||
import androidx.core.content.ContextCompat
|
||||
import com.amulyakhare.textdrawable.TextDrawable
|
||||
import com.bumptech.glide.request.RequestOptions
|
||||
import im.vector.matrix.android.api.Matrix
|
||||
import im.vector.matrix.android.api.session.room.model.RoomMember
|
||||
import im.vector.matrix.android.api.session.room.model.RoomSummary
|
||||
import im.vector.riotredesign.R
|
||||
import im.vector.riotredesign.core.extensions.firstCharAsString
|
||||
import im.vector.riotredesign.core.glide.GlideApp
|
||||
|
||||
object AvatarRenderer {
|
||||
|
||||
fun render(roomMember: RoomMember, imageView: ImageView) {
|
||||
render(roomMember.avatarUrl, roomMember.displayName, imageView)
|
||||
}
|
||||
|
||||
fun render(roomSummary: RoomSummary, imageView: ImageView) {
|
||||
render(roomSummary.avatarUrl, roomSummary.displayName, imageView)
|
||||
}
|
||||
|
||||
fun render(avatarUrl: String?, name: String?, imageView: ImageView) {
|
||||
if (name.isNullOrEmpty()) {
|
||||
return
|
||||
}
|
||||
val resolvedUrl = Matrix.getInstance().currentSession.contentUrlResolver().resolveFullSize(avatarUrl)
|
||||
val avatarColor = ContextCompat.getColor(imageView.context, R.color.pale_teal)
|
||||
val fallbackDrawable = TextDrawable.builder().buildRound(name.firstCharAsString().toUpperCase(), avatarColor)
|
||||
|
||||
GlideApp
|
||||
.with(imageView)
|
||||
.load(resolvedUrl)
|
||||
.placeholder(fallbackDrawable)
|
||||
.apply(RequestOptions.circleCropTransform())
|
||||
.into(imageView)
|
||||
}
|
||||
|
||||
|
||||
}
|
@ -1,100 +0,0 @@
|
||||
/*
|
||||
* Copyright 2019 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.riotredesign.features.home
|
||||
|
||||
import im.vector.riotredesign.features.home.group.SelectedGroupHolder
|
||||
import im.vector.riotredesign.features.home.room.VisibleRoomHolder
|
||||
import im.vector.riotredesign.features.home.room.detail.timeline.DefaultItemFactory
|
||||
import im.vector.riotredesign.features.home.room.detail.timeline.MessageItemFactory
|
||||
import im.vector.riotredesign.features.home.room.detail.timeline.RoomMemberItemFactory
|
||||
import im.vector.riotredesign.features.home.room.detail.timeline.RoomNameItemFactory
|
||||
import im.vector.riotredesign.features.home.room.detail.timeline.RoomTopicItemFactory
|
||||
import im.vector.riotredesign.features.home.room.detail.timeline.TimelineDateFormatter
|
||||
import im.vector.riotredesign.features.home.room.detail.timeline.TimelineEventController
|
||||
import im.vector.riotredesign.features.home.room.detail.timeline.TimelineItemFactory
|
||||
import im.vector.riotredesign.features.home.room.detail.timeline.helper.TimelineMediaSizeProvider
|
||||
import im.vector.riotredesign.features.home.room.list.RoomSummaryComparator
|
||||
import im.vector.riotredesign.features.home.room.list.RoomSummaryController
|
||||
import org.koin.dsl.module.module
|
||||
|
||||
class HomeModule {
|
||||
|
||||
val definition = module(override = true) {
|
||||
|
||||
single {
|
||||
TimelineDateFormatter(get())
|
||||
}
|
||||
|
||||
single {
|
||||
MessageItemFactory(get(), get())
|
||||
}
|
||||
|
||||
single {
|
||||
RoomNameItemFactory(get())
|
||||
}
|
||||
|
||||
single {
|
||||
RoomTopicItemFactory(get())
|
||||
}
|
||||
|
||||
single {
|
||||
RoomMemberItemFactory(get())
|
||||
}
|
||||
|
||||
single {
|
||||
DefaultItemFactory()
|
||||
}
|
||||
|
||||
single {
|
||||
TimelineItemFactory(get(), get(), get(), get(), get())
|
||||
}
|
||||
|
||||
single {
|
||||
HomeNavigator()
|
||||
}
|
||||
|
||||
factory {
|
||||
RoomSummaryController(get())
|
||||
}
|
||||
|
||||
factory { (roomId: String) ->
|
||||
TimelineEventController(roomId, get(), get(), get())
|
||||
}
|
||||
|
||||
single {
|
||||
TimelineMediaSizeProvider()
|
||||
}
|
||||
|
||||
single {
|
||||
SelectedGroupHolder()
|
||||
}
|
||||
|
||||
single {
|
||||
VisibleRoomHolder()
|
||||
}
|
||||
|
||||
single {
|
||||
HomePermalinkHandler(get())
|
||||
}
|
||||
|
||||
single {
|
||||
RoomSummaryComparator()
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
@ -1,41 +0,0 @@
|
||||
/*
|
||||
* Copyright 2019 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.riotredesign.features.home.group
|
||||
|
||||
import android.widget.ImageView
|
||||
import im.vector.riotredesign.R
|
||||
import im.vector.riotredesign.core.epoxy.KotlinModel
|
||||
import im.vector.riotredesign.core.platform.CheckableFrameLayout
|
||||
import im.vector.riotredesign.features.home.AvatarRenderer
|
||||
|
||||
|
||||
data class GroupSummaryItem(
|
||||
val groupName: CharSequence,
|
||||
val avatarUrl: String?,
|
||||
val isSelected: Boolean,
|
||||
val listener: (() -> Unit)? = null
|
||||
) : KotlinModel(R.layout.item_group) {
|
||||
|
||||
private val avatarImageView by bind<ImageView>(R.id.groupAvatarImageView)
|
||||
private val rootView by bind<CheckableFrameLayout>(R.id.itemGroupLayout)
|
||||
|
||||
override fun bind() {
|
||||
rootView.isSelected = isSelected
|
||||
rootView.setOnClickListener { listener?.invoke() }
|
||||
AvatarRenderer.render(avatarUrl, groupName.toString(), avatarImageView)
|
||||
}
|
||||
}
|
@ -1,154 +0,0 @@
|
||||
/*
|
||||
* Copyright 2019 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.riotredesign.features.home.room.detail
|
||||
|
||||
import android.os.Bundle
|
||||
import android.os.Parcelable
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.airbnb.epoxy.EpoxyVisibilityTracker
|
||||
import com.airbnb.mvrx.Success
|
||||
import com.airbnb.mvrx.args
|
||||
import com.airbnb.mvrx.fragmentViewModel
|
||||
import im.vector.matrix.android.api.session.room.timeline.TimelineEvent
|
||||
import im.vector.riotredesign.R
|
||||
import im.vector.riotredesign.core.platform.RiotFragment
|
||||
import im.vector.riotredesign.core.platform.ToolbarConfigurable
|
||||
import im.vector.riotredesign.features.home.AvatarRenderer
|
||||
import im.vector.riotredesign.features.home.HomePermalinkHandler
|
||||
import im.vector.riotredesign.features.home.room.detail.timeline.TimelineEventController
|
||||
import kotlinx.android.parcel.Parcelize
|
||||
import kotlinx.android.synthetic.main.fragment_room_detail.*
|
||||
import org.koin.android.ext.android.inject
|
||||
import org.koin.core.parameter.parametersOf
|
||||
|
||||
@Parcelize
|
||||
data class RoomDetailArgs(
|
||||
val roomId: String,
|
||||
val eventId: String? = null
|
||||
) : Parcelable
|
||||
|
||||
class RoomDetailFragment : RiotFragment(), TimelineEventController.Callback {
|
||||
|
||||
companion object {
|
||||
|
||||
fun newInstance(args: RoomDetailArgs): RoomDetailFragment {
|
||||
return RoomDetailFragment().apply {
|
||||
setArguments(args)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private val roomDetailViewModel: RoomDetailViewModel by fragmentViewModel()
|
||||
private val roomDetailArgs: RoomDetailArgs by args()
|
||||
|
||||
private val timelineEventController by inject<TimelineEventController> { parametersOf(roomDetailArgs.roomId) }
|
||||
private val homePermalinkHandler by inject<HomePermalinkHandler>()
|
||||
private lateinit var scrollOnNewMessageCallback: ScrollOnNewMessageCallback
|
||||
|
||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
|
||||
return inflater.inflate(R.layout.fragment_room_detail, container, false)
|
||||
}
|
||||
|
||||
override fun onActivityCreated(savedInstanceState: Bundle?) {
|
||||
super.onActivityCreated(savedInstanceState)
|
||||
setupRecyclerView()
|
||||
setupToolbar()
|
||||
setupSendButton()
|
||||
roomDetailViewModel.subscribe { renderState(it) }
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
roomDetailViewModel.process(RoomDetailActions.IsDisplayed)
|
||||
}
|
||||
|
||||
private fun setupToolbar() {
|
||||
val parentActivity = riotActivity
|
||||
if (parentActivity is ToolbarConfigurable) {
|
||||
parentActivity.configure(toolbar)
|
||||
}
|
||||
}
|
||||
|
||||
private fun setupRecyclerView() {
|
||||
val epoxyVisibilityTracker = EpoxyVisibilityTracker()
|
||||
epoxyVisibilityTracker.attach(recyclerView)
|
||||
val layoutManager = LinearLayoutManager(context, RecyclerView.VERTICAL, true)
|
||||
scrollOnNewMessageCallback = ScrollOnNewMessageCallback(layoutManager)
|
||||
recyclerView.layoutManager = layoutManager
|
||||
recyclerView.setHasFixedSize(true)
|
||||
timelineEventController.addModelBuildListener { it.dispatchTo(scrollOnNewMessageCallback) }
|
||||
recyclerView.setController(timelineEventController)
|
||||
timelineEventController.callback = this
|
||||
}
|
||||
|
||||
private fun setupSendButton() {
|
||||
sendButton.setOnClickListener {
|
||||
val textMessage = composerEditText.text.toString()
|
||||
if (textMessage.isNotBlank()) {
|
||||
composerEditText.text = null
|
||||
roomDetailViewModel.process(RoomDetailActions.SendMessage(textMessage))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun renderState(state: RoomDetailViewState) {
|
||||
renderRoomSummary(state)
|
||||
renderTimeline(state)
|
||||
}
|
||||
|
||||
private fun renderTimeline(state: RoomDetailViewState) {
|
||||
when (state.asyncTimelineData) {
|
||||
is Success -> {
|
||||
val timelineData = state.asyncTimelineData()
|
||||
val lockAutoScroll = timelineData?.let {
|
||||
it.events == timelineEventController.currentList && it.isLoadingForward
|
||||
} ?: true
|
||||
|
||||
scrollOnNewMessageCallback.isLocked.set(lockAutoScroll)
|
||||
timelineEventController.update(timelineData)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun renderRoomSummary(state: RoomDetailViewState) {
|
||||
state.asyncRoomSummary()?.let {
|
||||
toolbarTitleView.text = it.displayName
|
||||
AvatarRenderer.render(it, toolbarAvatarImageView)
|
||||
if (it.topic.isNotEmpty()) {
|
||||
toolbarSubtitleView.visibility = View.VISIBLE
|
||||
toolbarSubtitleView.text = it.topic
|
||||
} else {
|
||||
toolbarSubtitleView.visibility = View.GONE
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TimelineEventController.Callback ************************************************************
|
||||
|
||||
override fun onUrlClicked(url: String) {
|
||||
homePermalinkHandler.launch(url)
|
||||
}
|
||||
|
||||
override fun onEventVisible(event: TimelineEvent, index: Int) {
|
||||
roomDetailViewModel.process(RoomDetailActions.EventDisplayed(event, index))
|
||||
}
|
||||
|
||||
}
|
@ -1,141 +0,0 @@
|
||||
/*
|
||||
* Copyright 2019 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.riotredesign.features.home.room.detail
|
||||
|
||||
import com.airbnb.mvrx.MvRxViewModelFactory
|
||||
import com.airbnb.mvrx.ViewModelContext
|
||||
import com.jakewharton.rxrelay2.BehaviorRelay
|
||||
import im.vector.matrix.android.api.Matrix
|
||||
import im.vector.matrix.android.api.MatrixCallback
|
||||
import im.vector.matrix.android.api.session.Session
|
||||
import im.vector.matrix.android.api.session.events.model.Event
|
||||
import im.vector.matrix.android.api.session.room.Room
|
||||
import im.vector.matrix.rx.rx
|
||||
import im.vector.riotredesign.core.extensions.lastMinBy
|
||||
import im.vector.riotredesign.core.platform.RiotViewModel
|
||||
import im.vector.riotredesign.features.home.room.VisibleRoomHolder
|
||||
import io.reactivex.rxkotlin.subscribeBy
|
||||
import org.koin.android.ext.android.get
|
||||
import timber.log.Timber
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
class RoomDetailViewModel(initialState: RoomDetailViewState,
|
||||
private val session: Session,
|
||||
private val visibleRoomHolder: VisibleRoomHolder
|
||||
) : RiotViewModel<RoomDetailViewState>(initialState), Room.Listener {
|
||||
|
||||
private val room = session.getRoom(initialState.roomId)!!
|
||||
private val roomId = initialState.roomId
|
||||
private val eventId = initialState.eventId
|
||||
|
||||
private val displayedEventsObservable = BehaviorRelay.create<RoomDetailActions.EventDisplayed>()
|
||||
|
||||
companion object : MvRxViewModelFactory<RoomDetailViewModel, RoomDetailViewState> {
|
||||
|
||||
@JvmStatic
|
||||
override fun create(viewModelContext: ViewModelContext, state: RoomDetailViewState): RoomDetailViewModel? {
|
||||
val currentSession = Matrix.getInstance().currentSession
|
||||
val visibleRoomHolder = viewModelContext.activity.get<VisibleRoomHolder>()
|
||||
return RoomDetailViewModel(state, currentSession, visibleRoomHolder)
|
||||
}
|
||||
}
|
||||
|
||||
init {
|
||||
observeRoomSummary()
|
||||
observeTimeline()
|
||||
observeDisplayedEvents()
|
||||
room.loadRoomMembersIfNeeded()
|
||||
room.addListener(this)
|
||||
}
|
||||
|
||||
fun process(action: RoomDetailActions) {
|
||||
when (action) {
|
||||
is RoomDetailActions.SendMessage -> handleSendMessage(action)
|
||||
is RoomDetailActions.IsDisplayed -> handleIsDisplayed()
|
||||
is RoomDetailActions.EventDisplayed -> handleEventDisplayed(action)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCleared() {
|
||||
room.removeListener(this)
|
||||
super.onCleared()
|
||||
}
|
||||
|
||||
// Room.Listener *******************************************************************************
|
||||
|
||||
override fun onReadReceiptsUpdated() {
|
||||
Timber.v("On read receipts updated")
|
||||
}
|
||||
|
||||
// PRIVATE METHODS *****************************************************************************
|
||||
|
||||
private fun handleSendMessage(action: RoomDetailActions.SendMessage) {
|
||||
room.sendTextMessage(action.text, callback = object : MatrixCallback<Event> {})
|
||||
}
|
||||
|
||||
private fun handleEventDisplayed(action: RoomDetailActions.EventDisplayed) {
|
||||
displayedEventsObservable.accept(action)
|
||||
|
||||
}
|
||||
|
||||
private fun handleIsDisplayed() {
|
||||
visibleRoomHolder.setVisibleRoom(roomId)
|
||||
}
|
||||
|
||||
private fun observeDisplayedEvents() {
|
||||
// We are buffering scroll events for one second
|
||||
// and keep the most recent one to set the read receipt on.
|
||||
displayedEventsObservable.hide()
|
||||
.buffer(1, TimeUnit.SECONDS)
|
||||
.filter { it.isNotEmpty() }
|
||||
.subscribeBy(onNext = { actions ->
|
||||
val eventIds = actions.mapNotNull { it.event.root.eventId }
|
||||
withState { state ->
|
||||
val newMapOfReadReceipts = HashMap(state.readReceiptsForEventId)
|
||||
eventIds.forEach {
|
||||
if (newMapOfReadReceipts.containsKey(it).not()) {
|
||||
val readReceipts = room.readReceipts(it)
|
||||
newMapOfReadReceipts[it] = readReceipts
|
||||
}
|
||||
}
|
||||
setState { copy(readReceiptsForEventId = newMapOfReadReceipts) }
|
||||
}
|
||||
|
||||
val mostRecentEvent = actions.lastMinBy { it.index }
|
||||
mostRecentEvent?.event?.root?.eventId?.let { eventId ->
|
||||
room.setReadReceipt(eventId, callback = object : MatrixCallback<Void> {})
|
||||
}
|
||||
})
|
||||
.disposeOnClear()
|
||||
}
|
||||
|
||||
private fun observeRoomSummary() {
|
||||
room.rx().liveRoomSummary()
|
||||
.execute { async ->
|
||||
copy(asyncRoomSummary = async)
|
||||
}
|
||||
}
|
||||
|
||||
private fun observeTimeline() {
|
||||
room.rx().timeline(eventId)
|
||||
.execute { timelineData ->
|
||||
copy(asyncTimelineData = timelineData)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
@ -1,49 +0,0 @@
|
||||
/*
|
||||
* Copyright 2019 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.riotredesign.features.home.room.detail.timeline
|
||||
|
||||
import android.view.View
|
||||
import android.widget.ImageView
|
||||
import android.widget.TextView
|
||||
import androidx.annotation.LayoutRes
|
||||
import im.vector.riotredesign.core.epoxy.KotlinModel
|
||||
import im.vector.riotredesign.features.home.AvatarRenderer
|
||||
|
||||
abstract class AbsMessageItem(private val informationData: MessageInformationData,
|
||||
@LayoutRes layoutRes: Int
|
||||
) : KotlinModel(layoutRes) {
|
||||
|
||||
protected abstract val avatarImageView: ImageView
|
||||
protected abstract val memberNameView: TextView
|
||||
protected abstract val timeView: TextView
|
||||
|
||||
override fun bind() {
|
||||
if (informationData.showInformation) {
|
||||
avatarImageView.visibility = View.VISIBLE
|
||||
memberNameView.visibility = View.VISIBLE
|
||||
timeView.visibility = View.VISIBLE
|
||||
timeView.text = informationData.time
|
||||
memberNameView.text = informationData.memberName
|
||||
AvatarRenderer.render(informationData.avatarUrl, informationData.memberName?.toString(), avatarImageView)
|
||||
} else {
|
||||
avatarImageView.visibility = View.GONE
|
||||
memberNameView.visibility = View.GONE
|
||||
timeView.visibility = View.GONE
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -1,40 +0,0 @@
|
||||
/*
|
||||
* Copyright 2019 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.riotredesign.features.home.room.detail.timeline
|
||||
|
||||
import android.widget.ImageView
|
||||
import android.widget.TextView
|
||||
import im.vector.riotredesign.R
|
||||
import im.vector.riotredesign.features.media.MediaContentRenderer
|
||||
|
||||
class MessageImageItem(
|
||||
private val mediaData: MediaContentRenderer.Data,
|
||||
informationData: MessageInformationData
|
||||
) : AbsMessageItem(informationData, R.layout.item_timeline_event_image_message) {
|
||||
|
||||
override val avatarImageView by bind<ImageView>(R.id.messageAvatarImageView)
|
||||
override val memberNameView by bind<TextView>(R.id.messageMemberNameView)
|
||||
override val timeView by bind<TextView>(R.id.messageTimeView)
|
||||
private val imageView by bind<ImageView>(R.id.messageImageView)
|
||||
|
||||
override fun bind() {
|
||||
super.bind()
|
||||
MediaContentRenderer.render(mediaData, MediaContentRenderer.Mode.THUMBNAIL, imageView)
|
||||
}
|
||||
|
||||
|
||||
}
|
@ -1,110 +0,0 @@
|
||||
/*
|
||||
* Copyright 2019 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.riotredesign.features.home.room.detail.timeline
|
||||
|
||||
import android.text.SpannableStringBuilder
|
||||
import android.text.util.Linkify
|
||||
import im.vector.matrix.android.api.permalinks.MatrixLinkify
|
||||
import im.vector.matrix.android.api.permalinks.MatrixPermalinkSpan
|
||||
import im.vector.matrix.android.api.session.events.model.EventType
|
||||
import im.vector.matrix.android.api.session.events.model.toModel
|
||||
import im.vector.matrix.android.api.session.room.model.message.MessageContent
|
||||
import im.vector.matrix.android.api.session.room.model.message.MessageImageContent
|
||||
import im.vector.matrix.android.api.session.room.model.message.MessageTextContent
|
||||
import im.vector.matrix.android.api.session.room.timeline.TimelineEvent
|
||||
import im.vector.riotredesign.core.extensions.localDateTime
|
||||
import im.vector.riotredesign.features.home.room.detail.timeline.helper.TimelineMediaSizeProvider
|
||||
import im.vector.riotredesign.features.media.MediaContentRenderer
|
||||
|
||||
class MessageItemFactory(private val timelineMediaSizeProvider: TimelineMediaSizeProvider,
|
||||
private val timelineDateFormatter: TimelineDateFormatter) {
|
||||
|
||||
private val messagesDisplayedWithInformation = HashSet<String?>()
|
||||
|
||||
fun create(event: TimelineEvent,
|
||||
nextEvent: TimelineEvent?,
|
||||
callback: TimelineEventController.Callback?
|
||||
): AbsMessageItem? {
|
||||
|
||||
val roomMember = event.roomMember
|
||||
val nextRoomMember = nextEvent?.roomMember
|
||||
|
||||
val date = event.root.localDateTime()
|
||||
val nextDate = nextEvent?.root?.localDateTime()
|
||||
val addDaySeparator = date.toLocalDate() != nextDate?.toLocalDate()
|
||||
val isNextMessageReceivedMoreThanOneHourAgo = nextDate?.isBefore(date.minusMinutes(60))
|
||||
?: false
|
||||
|
||||
if (addDaySeparator
|
||||
|| nextRoomMember != roomMember
|
||||
|| nextEvent?.root?.type != EventType.MESSAGE
|
||||
|| isNextMessageReceivedMoreThanOneHourAgo) {
|
||||
messagesDisplayedWithInformation.add(event.root.eventId)
|
||||
}
|
||||
|
||||
val messageContent: MessageContent = event.root.content.toModel() ?: return null
|
||||
val showInformation = messagesDisplayedWithInformation.contains(event.root.eventId)
|
||||
val time = timelineDateFormatter.formatMessageHour(date)
|
||||
val avatarUrl = roomMember?.avatarUrl
|
||||
val memberName = roomMember?.displayName ?: event.root.sender
|
||||
val informationData = MessageInformationData(time, avatarUrl, memberName, showInformation)
|
||||
|
||||
return when (messageContent) {
|
||||
is MessageTextContent -> buildTextMessageItem(messageContent, informationData, callback)
|
||||
is MessageImageContent -> buildImageMessageItem(messageContent, informationData)
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
|
||||
private fun buildImageMessageItem(messageContent: MessageImageContent,
|
||||
informationData: MessageInformationData): MessageImageItem? {
|
||||
|
||||
val (maxWidth, maxHeight) = timelineMediaSizeProvider.getMaxSize()
|
||||
val data = MediaContentRenderer.Data(
|
||||
url = messageContent.url,
|
||||
height = messageContent.info?.height,
|
||||
maxHeight = maxHeight,
|
||||
width = messageContent.info?.width,
|
||||
maxWidth = maxWidth,
|
||||
rotation = messageContent.info?.rotation,
|
||||
orientation = messageContent.info?.orientation
|
||||
)
|
||||
return MessageImageItem(data, informationData)
|
||||
}
|
||||
|
||||
private fun buildTextMessageItem(messageContent: MessageTextContent,
|
||||
informationData: MessageInformationData,
|
||||
callback: TimelineEventController.Callback?): MessageTextItem? {
|
||||
|
||||
val message = messageContent.body.let {
|
||||
val spannable = SpannableStringBuilder(it)
|
||||
MatrixLinkify.addLinks(spannable, object : MatrixPermalinkSpan.Callback {
|
||||
override fun onUrlClicked(url: String) {
|
||||
callback?.onUrlClicked(url)
|
||||
}
|
||||
})
|
||||
Linkify.addLinks(spannable, Linkify.ALL)
|
||||
spannable
|
||||
}
|
||||
return MessageTextItem(
|
||||
message = message,
|
||||
informationData = informationData
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
}
|
@ -1,39 +0,0 @@
|
||||
/*
|
||||
* Copyright 2019 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.riotredesign.features.home.room.detail.timeline
|
||||
|
||||
import android.widget.ImageView
|
||||
import android.widget.TextView
|
||||
import im.vector.matrix.android.api.permalinks.MatrixLinkify
|
||||
import im.vector.riotredesign.R
|
||||
|
||||
class MessageTextItem(
|
||||
val message: CharSequence? = null,
|
||||
informationData: MessageInformationData
|
||||
) : AbsMessageItem(informationData, R.layout.item_timeline_event_text_message) {
|
||||
|
||||
override val avatarImageView by bind<ImageView>(R.id.messageAvatarImageView)
|
||||
override val memberNameView by bind<TextView>(R.id.messageMemberNameView)
|
||||
override val timeView by bind<TextView>(R.id.messageTimeView)
|
||||
private val messageView by bind<TextView>(R.id.messageTextView)
|
||||
|
||||
override fun bind() {
|
||||
super.bind()
|
||||
messageView.text = message
|
||||
MatrixLinkify.addLinkMovementMethod(messageView)
|
||||
}
|
||||
}
|
@ -1,39 +0,0 @@
|
||||
/*
|
||||
*
|
||||
* * Copyright 2019 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.riotredesign.features.home.room.detail.timeline
|
||||
|
||||
import android.widget.ImageView
|
||||
import android.widget.TextView
|
||||
import im.vector.riotredesign.R
|
||||
import im.vector.riotredesign.core.epoxy.KotlinModel
|
||||
|
||||
class ReadReceiptsItem() : KotlinModel(R.layout.item_timeline_read_receipts) {
|
||||
|
||||
private val moreText by bind<TextView>(R.id.message_more_than_expected)
|
||||
private val avatarReceipt1 by bind<ImageView>(R.id.message_avatar_receipt_1)
|
||||
private val avatarReceipt2 by bind<ImageView>(R.id.message_avatar_receipt_1)
|
||||
private val avatarReceipt3 by bind<ImageView>(R.id.message_avatar_receipt_1)
|
||||
private val avatarReceipt4 by bind<ImageView>(R.id.message_avatar_receipt_1)
|
||||
private val avatarReceipt5 by bind<ImageView>(R.id.message_avatar_receipt_1)
|
||||
private val avatarReceipts = listOf(avatarReceipt1, avatarReceipt2, avatarReceipt3, avatarReceipt4, avatarReceipt5)
|
||||
|
||||
override fun bind() {
|
||||
|
||||
}
|
||||
}
|
@ -1,114 +0,0 @@
|
||||
/*
|
||||
* Copyright 2019 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.riotredesign.features.home.room.detail.timeline
|
||||
|
||||
import android.view.View
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.airbnb.epoxy.EpoxyAsyncUtil
|
||||
import com.airbnb.epoxy.EpoxyModel
|
||||
import com.airbnb.epoxy.OnModelVisibilityStateChangedListener
|
||||
import com.airbnb.epoxy.VisibilityState
|
||||
import im.vector.matrix.android.api.session.events.model.EventType
|
||||
import im.vector.matrix.android.api.session.room.timeline.TimelineData
|
||||
import im.vector.matrix.android.api.session.room.timeline.TimelineEvent
|
||||
import im.vector.riotredesign.core.epoxy.KotlinModel
|
||||
import im.vector.riotredesign.core.extensions.localDateTime
|
||||
import im.vector.riotredesign.features.home.LoadingItemModel_
|
||||
import im.vector.riotredesign.features.home.room.detail.timeline.helper.TimelineMediaSizeProvider
|
||||
import im.vector.riotredesign.features.home.room.detail.timeline.paging.PagedListEpoxyController
|
||||
|
||||
class TimelineEventController(private val roomId: String,
|
||||
private val dateFormatter: TimelineDateFormatter,
|
||||
private val timelineItemFactory: TimelineItemFactory,
|
||||
private val timelineMediaSizeProvider: TimelineMediaSizeProvider
|
||||
) : PagedListEpoxyController<TimelineEvent>(
|
||||
EpoxyAsyncUtil.getAsyncBackgroundHandler(),
|
||||
EpoxyAsyncUtil.getAsyncBackgroundHandler()
|
||||
) {
|
||||
init {
|
||||
setFilterDuplicates(true)
|
||||
}
|
||||
|
||||
private var isLoadingForward: Boolean = false
|
||||
private var isLoadingBackward: Boolean = false
|
||||
private var hasReachedEnd: Boolean = false
|
||||
|
||||
var callback: Callback? = null
|
||||
|
||||
fun update(timelineData: TimelineData?) {
|
||||
timelineData?.let {
|
||||
isLoadingForward = it.isLoadingForward
|
||||
isLoadingBackward = it.isLoadingBackward
|
||||
hasReachedEnd = it.events.lastOrNull()?.root?.type == EventType.STATE_ROOM_CREATE
|
||||
submitList(it.events)
|
||||
requestModelBuild()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onAttachedToRecyclerView(recyclerView: RecyclerView) {
|
||||
super.onAttachedToRecyclerView(recyclerView)
|
||||
timelineMediaSizeProvider.recyclerView = recyclerView
|
||||
}
|
||||
|
||||
override fun buildItemModels(currentPosition: Int, items: List<TimelineEvent?>): List<EpoxyModel<*>> {
|
||||
if (items.isNullOrEmpty()) {
|
||||
return emptyList()
|
||||
}
|
||||
val epoxyModels = ArrayList<EpoxyModel<*>>()
|
||||
val event = items[currentPosition] ?: return emptyList()
|
||||
val nextEvent = if (currentPosition + 1 < items.size) items[currentPosition + 1] else null
|
||||
|
||||
val date = event.root.localDateTime()
|
||||
val nextDate = nextEvent?.root?.localDateTime()
|
||||
val addDaySeparator = date.toLocalDate() != nextDate?.toLocalDate()
|
||||
|
||||
timelineItemFactory.create(event, nextEvent, callback)?.also {
|
||||
it.id(event.localId)
|
||||
it.setOnVisibilityStateChanged(OnModelVisibilityStateChangedListener<KotlinModel, View> { model, view, visibilityState ->
|
||||
if (visibilityState == VisibilityState.VISIBLE) {
|
||||
callback?.onEventVisible(event, currentPosition)
|
||||
}
|
||||
})
|
||||
epoxyModels.add(it)
|
||||
}
|
||||
if (addDaySeparator) {
|
||||
val formattedDay = dateFormatter.formatMessageDay(date)
|
||||
val daySeparatorItem = DaySeparatorItem(formattedDay).id(roomId + formattedDay)
|
||||
epoxyModels.add(daySeparatorItem)
|
||||
}
|
||||
return epoxyModels
|
||||
}
|
||||
|
||||
override fun addModels(models: List<EpoxyModel<*>>) {
|
||||
LoadingItemModel_()
|
||||
.id(roomId + "forward_loading_item")
|
||||
.addIf(isLoadingForward, this)
|
||||
|
||||
super.add(models)
|
||||
|
||||
LoadingItemModel_()
|
||||
.id(roomId + "backward_loading_item")
|
||||
.addIf(!hasReachedEnd, this)
|
||||
}
|
||||
|
||||
|
||||
interface Callback {
|
||||
fun onEventVisible(event: TimelineEvent, index: Int)
|
||||
fun onUrlClicked(url: String)
|
||||
}
|
||||
|
||||
}
|
@ -1,42 +0,0 @@
|
||||
/*
|
||||
* Copyright 2019 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.riotredesign.features.home.room.detail.timeline
|
||||
|
||||
import im.vector.matrix.android.api.session.events.model.EventType
|
||||
import im.vector.matrix.android.api.session.room.timeline.TimelineEvent
|
||||
import im.vector.riotredesign.core.epoxy.KotlinModel
|
||||
|
||||
class TimelineItemFactory(private val messageItemFactory: MessageItemFactory,
|
||||
private val roomNameItemFactory: RoomNameItemFactory,
|
||||
private val roomTopicItemFactory: RoomTopicItemFactory,
|
||||
private val roomMemberItemFactory: RoomMemberItemFactory,
|
||||
private val defaultItemFactory: DefaultItemFactory) {
|
||||
|
||||
fun create(event: TimelineEvent,
|
||||
nextEvent: TimelineEvent?,
|
||||
callback: TimelineEventController.Callback?): KotlinModel? {
|
||||
|
||||
return when (event.root.type) {
|
||||
EventType.MESSAGE -> messageItemFactory.create(event, nextEvent, callback)
|
||||
EventType.STATE_ROOM_NAME -> roomNameItemFactory.create(event)
|
||||
EventType.STATE_ROOM_TOPIC -> roomTopicItemFactory.create(event)
|
||||
EventType.STATE_ROOM_MEMBER -> roomMemberItemFactory.create(event)
|
||||
else -> defaultItemFactory.create(event)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -1,129 +0,0 @@
|
||||
/*
|
||||
* Copyright 2019 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.riotredesign.features.home.room.detail.timeline.paging
|
||||
|
||||
import androidx.paging.PagedList
|
||||
import android.os.Handler
|
||||
import androidx.recyclerview.widget.DiffUtil
|
||||
import com.airbnb.epoxy.EpoxyController
|
||||
import com.airbnb.epoxy.EpoxyModel
|
||||
import com.airbnb.epoxy.EpoxyViewHolder
|
||||
|
||||
/**
|
||||
* An [EpoxyController] that can work with a [PagedList].
|
||||
*
|
||||
* Internally, it caches the model for each item in the [PagedList]. You should override
|
||||
* [buildItemModel] method to build the model for the given item. Since [PagedList] might include
|
||||
* `null` items if placeholders are enabled, this method needs to handle `null` values in the list.
|
||||
*
|
||||
* By default, the model for each item is added to the model list. To change this behavior (to
|
||||
* filter items or inject extra items), you can override [addModels] function and manually add built
|
||||
* models.
|
||||
*
|
||||
* @param T The type of the items in the [PagedList].
|
||||
*/
|
||||
abstract class PagedListEpoxyController<T>(
|
||||
/**
|
||||
* The handler to use for building models. By default this uses the main thread, but you can use
|
||||
* [EpoxyAsyncUtil.getAsyncBackgroundHandler] to do model building in the background.
|
||||
*
|
||||
* The notify thread of your PagedList (from setNotifyExecutor in the PagedList Builder) must be
|
||||
* the same as this thread. Otherwise Epoxy will crash.
|
||||
*/
|
||||
modelBuildingHandler: Handler = EpoxyController.defaultModelBuildingHandler,
|
||||
/**
|
||||
* The handler to use when calculating the diff between built model lists.
|
||||
* By default this uses the main thread, but you can use
|
||||
* [EpoxyAsyncUtil.getAsyncBackgroundHandler] to do diffing in the background.
|
||||
*/
|
||||
diffingHandler: Handler = EpoxyController.defaultDiffingHandler,
|
||||
/**
|
||||
* [PagedListEpoxyController] uses an [DiffUtil.ItemCallback] to detect changes between
|
||||
* [PagedList]s. By default, it relies on simple object equality but you can provide a custom
|
||||
* one if you don't use all fields in the object in your models.
|
||||
*/
|
||||
itemDiffCallback: DiffUtil.ItemCallback<T> = DEFAULT_ITEM_DIFF_CALLBACK as DiffUtil.ItemCallback<T>
|
||||
) : EpoxyController(modelBuildingHandler, diffingHandler) {
|
||||
// this is where we keep the already built models
|
||||
protected val modelCache = PagedListModelCache(
|
||||
modelBuilder = { pos, item ->
|
||||
buildItemModels(pos, item)
|
||||
},
|
||||
rebuildCallback = {
|
||||
requestModelBuild()
|
||||
},
|
||||
itemDiffCallback = itemDiffCallback,
|
||||
modelBuildingHandler = modelBuildingHandler
|
||||
)
|
||||
|
||||
var currentList: PagedList<T>? = null
|
||||
private set
|
||||
|
||||
final override fun buildModels() {
|
||||
addModels(modelCache.getModels())
|
||||
}
|
||||
|
||||
override fun onModelBound(
|
||||
holder: EpoxyViewHolder,
|
||||
boundModel: EpoxyModel<*>,
|
||||
position: Int,
|
||||
previouslyBoundModel: EpoxyModel<*>?
|
||||
) {
|
||||
modelCache.loadAround(boundModel)
|
||||
}
|
||||
|
||||
/**
|
||||
* This function adds all built models to the adapter. You can override this method to add extra
|
||||
* items into the model list or remove some.
|
||||
*/
|
||||
open fun addModels(models: List<EpoxyModel<*>>) {
|
||||
super.add(models)
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds the model for a given item. This must return a single model for each item. If you want
|
||||
* to inject headers etc, you can override [addModels] function.
|
||||
*
|
||||
* If the `item` is `null`, you should provide the placeholder. If your [PagedList] is configured
|
||||
* without placeholders, you don't need to handle the `null` case.
|
||||
*/
|
||||
abstract fun buildItemModels(currentPosition: Int, items: List<T?>): List<EpoxyModel<*>>
|
||||
|
||||
/**
|
||||
* Submit a new paged list.
|
||||
*
|
||||
* A diff will be calculated between this list and the previous list so you may still get calls
|
||||
* to [buildItemModel] with items from the previous list.
|
||||
*/
|
||||
fun submitList(newList: PagedList<T>?) {
|
||||
currentList = newList
|
||||
modelCache.submitList(newList)
|
||||
}
|
||||
|
||||
companion object {
|
||||
/**
|
||||
* [PagedListEpoxyController] calculates a diff on top of the PagedList to check which
|
||||
* models are invalidated.
|
||||
* This is the default [DiffUtil.ItemCallback] which uses object equality.
|
||||
*/
|
||||
val DEFAULT_ITEM_DIFF_CALLBACK = object : DiffUtil.ItemCallback<Any>() {
|
||||
override fun areItemsTheSame(oldItem: Any, newItem: Any) = oldItem == newItem
|
||||
|
||||
override fun areContentsTheSame(oldItem: Any, newItem: Any) = oldItem == newItem
|
||||
}
|
||||
}
|
||||
}
|
@ -1,149 +0,0 @@
|
||||
/*
|
||||
* Copyright 2019 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.riotredesign.features.home.room.detail.timeline.paging
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.os.Handler
|
||||
import androidx.paging.AsyncPagedListDiffer
|
||||
import androidx.paging.PagedList
|
||||
import androidx.recyclerview.widget.AsyncDifferConfig
|
||||
import androidx.recyclerview.widget.DiffUtil
|
||||
import androidx.recyclerview.widget.ListUpdateCallback
|
||||
import com.airbnb.epoxy.EpoxyModel
|
||||
import java.util.concurrent.Executor
|
||||
import java.util.concurrent.atomic.AtomicBoolean
|
||||
|
||||
/**
|
||||
* A PagedList stream wrapper that caches models built for each item. It tracks changes in paged lists and caches
|
||||
* models for each item when they are invalidated to avoid rebuilding models for the whole list when PagedList is
|
||||
* updated.
|
||||
*/
|
||||
class PagedListModelCache<T>(
|
||||
private val modelBuilder: (itemIndex: Int, items: List<T>) -> List<EpoxyModel<*>>,
|
||||
private val rebuildCallback: () -> Unit,
|
||||
private val itemDiffCallback: DiffUtil.ItemCallback<T>,
|
||||
private val diffExecutor: Executor? = null,
|
||||
private val modelBuildingHandler: Handler
|
||||
) {
|
||||
|
||||
|
||||
// Int is the index of the pagedList item
|
||||
// We have to be able to find the pagedlist position coming from an epoxy model to trigger
|
||||
// LoadAround with accuracy
|
||||
private val modelCache = linkedMapOf<EpoxyModel<*>, Int>()
|
||||
private var isCacheStale = AtomicBoolean(true)
|
||||
|
||||
/**
|
||||
* Tracks the last accessed position so that we can report it back to the paged list when models are built.
|
||||
*/
|
||||
private var lastPosition: Int? = null
|
||||
|
||||
/**
|
||||
* Observer for the PagedList changes that invalidates the model cache when data is updated.
|
||||
*/
|
||||
private val updateCallback = object : ListUpdateCallback {
|
||||
override fun onChanged(position: Int, count: Int, payload: Any?) {
|
||||
invalidate()
|
||||
rebuildCallback()
|
||||
}
|
||||
|
||||
override fun onMoved(fromPosition: Int, toPosition: Int) {
|
||||
invalidate()
|
||||
rebuildCallback()
|
||||
}
|
||||
|
||||
override fun onInserted(position: Int, count: Int) {
|
||||
invalidate()
|
||||
rebuildCallback()
|
||||
}
|
||||
|
||||
override fun onRemoved(position: Int, count: Int) {
|
||||
invalidate()
|
||||
rebuildCallback()
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressLint("RestrictedApi")
|
||||
private val asyncDiffer = AsyncPagedListDiffer<T>(
|
||||
updateCallback,
|
||||
AsyncDifferConfig.Builder<T>(
|
||||
itemDiffCallback
|
||||
).also { builder ->
|
||||
if (diffExecutor != null) {
|
||||
builder.setBackgroundThreadExecutor(diffExecutor)
|
||||
}
|
||||
// we have to reply on this private API, otherwise, paged list might be changed when models are being built,
|
||||
// potentially creating concurrent modification problems.
|
||||
builder.setMainThreadExecutor { runnable: Runnable ->
|
||||
modelBuildingHandler.post(runnable)
|
||||
}
|
||||
}.build()
|
||||
)
|
||||
|
||||
fun submitList(pagedList: PagedList<T>?) {
|
||||
asyncDiffer.submitList(pagedList)
|
||||
}
|
||||
|
||||
fun getModels(): List<EpoxyModel<*>> {
|
||||
if (isCacheStale.compareAndSet(true, false)) {
|
||||
asyncDiffer.currentList?.forEachIndexed { position, _ ->
|
||||
buildModel(position)
|
||||
}
|
||||
}
|
||||
lastPosition?.let {
|
||||
triggerLoadAround(it)
|
||||
}
|
||||
return modelCache.keys.toList()
|
||||
}
|
||||
|
||||
fun loadAround(model: EpoxyModel<*>) {
|
||||
modelCache[model]?.let { itemPosition ->
|
||||
triggerLoadAround(itemPosition)
|
||||
lastPosition = itemPosition
|
||||
}
|
||||
}
|
||||
|
||||
// PRIVATE METHODS *****************************************************************************
|
||||
|
||||
private fun invalidate() {
|
||||
modelCache.clear()
|
||||
isCacheStale.set(true)
|
||||
}
|
||||
|
||||
private fun cacheModelsAtPosition(itemPosition: Int, epoxyModels: Set<EpoxyModel<*>>) {
|
||||
epoxyModels.forEach {
|
||||
modelCache[it] = itemPosition
|
||||
}
|
||||
}
|
||||
|
||||
private fun buildModel(pos: Int) {
|
||||
if (pos >= asyncDiffer.currentList?.size ?: 0) {
|
||||
return
|
||||
}
|
||||
modelBuilder(pos, asyncDiffer.currentList as List<T>).also {
|
||||
cacheModelsAtPosition(pos, it.toSet())
|
||||
}
|
||||
}
|
||||
|
||||
private fun triggerLoadAround(position: Int) {
|
||||
asyncDiffer.currentList?.let {
|
||||
if (it.size > 0) {
|
||||
it.loadAround(Math.min(position, it.size - 1))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,52 +0,0 @@
|
||||
/*
|
||||
* Copyright 2019 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.riotredesign.features.home.room.list
|
||||
|
||||
import android.view.ViewGroup
|
||||
import android.widget.TextView
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.core.graphics.drawable.DrawableCompat
|
||||
import im.vector.riotredesign.R
|
||||
import im.vector.riotredesign.core.epoxy.KotlinModel
|
||||
|
||||
data class RoomCategoryItem(
|
||||
val title: CharSequence,
|
||||
val isExpanded: Boolean,
|
||||
val unreadCount: Int,
|
||||
val showHighlighted: Boolean,
|
||||
val listener: (() -> Unit)? = null
|
||||
) : KotlinModel(R.layout.item_room_category) {
|
||||
|
||||
private val unreadCounterBadgeView by bind<UnreadCounterBadgeView>(R.id.roomCategoryUnreadCounterBadgeView)
|
||||
private val titleView by bind<TextView>(R.id.roomCategoryTitleView)
|
||||
private val rootView by bind<ViewGroup>(R.id.roomCategoryRootView)
|
||||
|
||||
private val tintColor by lazy {
|
||||
ContextCompat.getColor(rootView.context, R.color.bluey_grey_two)
|
||||
}
|
||||
|
||||
override fun bind() {
|
||||
val expandedArrowDrawableRes = if (isExpanded) R.drawable.ic_expand_more_white else R.drawable.ic_expand_less_white
|
||||
val expandedArrowDrawable = ContextCompat.getDrawable(rootView.context, expandedArrowDrawableRes)?.also {
|
||||
DrawableCompat.setTint(it, tintColor)
|
||||
}
|
||||
unreadCounterBadgeView.render(unreadCount, showHighlighted)
|
||||
titleView.setCompoundDrawablesWithIntrinsicBounds(expandedArrowDrawable, null, null, null)
|
||||
titleView.text = title
|
||||
rootView.setOnClickListener { listener?.invoke() }
|
||||
}
|
||||
}
|
@ -1,39 +0,0 @@
|
||||
/*
|
||||
* Copyright 2019 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.riotredesign.features.home.room.list
|
||||
|
||||
import com.airbnb.mvrx.Async
|
||||
import com.airbnb.mvrx.MvRxState
|
||||
import com.airbnb.mvrx.Uninitialized
|
||||
import im.vector.matrix.android.api.session.room.model.RoomSummary
|
||||
|
||||
data class RoomListViewState(
|
||||
val asyncRooms: Async<RoomSummaries> = Uninitialized,
|
||||
val selectedRoomId: String? = null
|
||||
) : MvRxState
|
||||
|
||||
data class RoomSummaries(
|
||||
val favourites: List<RoomSummary>,
|
||||
val directRooms: List<RoomSummary>,
|
||||
val groupRooms: List<RoomSummary>,
|
||||
val lowPriorities: List<RoomSummary>,
|
||||
val serverNotices: List<RoomSummary>
|
||||
)
|
||||
|
||||
fun RoomSummaries?.isNullOrEmpty(): Boolean {
|
||||
return this == null || (directRooms.isEmpty() && groupRooms.isEmpty() && favourites.isEmpty() && lowPriorities.isEmpty() && serverNotices.isEmpty())
|
||||
}
|
@ -1,124 +0,0 @@
|
||||
/*
|
||||
* Copyright 2019 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.riotredesign.features.home.room.list
|
||||
|
||||
import androidx.annotation.StringRes
|
||||
import com.airbnb.epoxy.TypedEpoxyController
|
||||
import im.vector.matrix.android.api.session.room.model.RoomSummary
|
||||
import im.vector.riotredesign.R
|
||||
import im.vector.riotredesign.core.resources.StringProvider
|
||||
|
||||
class RoomSummaryController(private val stringProvider: StringProvider
|
||||
) : TypedEpoxyController<RoomListViewState>() {
|
||||
|
||||
private var isFavoriteRoomsExpanded = true
|
||||
private var isDirectRoomsExpanded = false
|
||||
private var isGroupRoomsExpanded = false
|
||||
private var isLowPriorityRoomsExpanded = false
|
||||
private var isServerNoticeRoomsExpanded = false
|
||||
|
||||
var callback: Callback? = null
|
||||
|
||||
override fun buildModels(viewState: RoomListViewState) {
|
||||
val roomSummaries = viewState.asyncRooms()
|
||||
val favourites = roomSummaries?.favourites ?: emptyList()
|
||||
buildRoomCategory(viewState, favourites, R.string.room_list_favourites, isFavoriteRoomsExpanded) {
|
||||
isFavoriteRoomsExpanded = !isFavoriteRoomsExpanded
|
||||
}
|
||||
if (isFavoriteRoomsExpanded) {
|
||||
buildRoomModels(favourites, viewState.selectedRoomId)
|
||||
}
|
||||
|
||||
val directRooms = roomSummaries?.directRooms ?: emptyList()
|
||||
buildRoomCategory(viewState, directRooms, R.string.room_list_direct, isDirectRoomsExpanded) {
|
||||
isDirectRoomsExpanded = !isDirectRoomsExpanded
|
||||
}
|
||||
if (isDirectRoomsExpanded) {
|
||||
buildRoomModels(directRooms, viewState.selectedRoomId)
|
||||
}
|
||||
|
||||
val groupRooms = roomSummaries?.groupRooms ?: emptyList()
|
||||
buildRoomCategory(viewState, groupRooms, R.string.room_list_group, isGroupRoomsExpanded) {
|
||||
isGroupRoomsExpanded = !isGroupRoomsExpanded
|
||||
}
|
||||
if (isGroupRoomsExpanded) {
|
||||
buildRoomModels(groupRooms, viewState.selectedRoomId)
|
||||
}
|
||||
|
||||
val lowPriorities = roomSummaries?.lowPriorities ?: emptyList()
|
||||
buildRoomCategory(viewState, lowPriorities, R.string.room_list_low_priority, isLowPriorityRoomsExpanded) {
|
||||
isLowPriorityRoomsExpanded = !isLowPriorityRoomsExpanded
|
||||
}
|
||||
if (isLowPriorityRoomsExpanded) {
|
||||
buildRoomModels(lowPriorities, viewState.selectedRoomId)
|
||||
}
|
||||
|
||||
val serverNotices = roomSummaries?.serverNotices ?: emptyList()
|
||||
buildRoomCategory(viewState, serverNotices, R.string.room_list_system_alert, isServerNoticeRoomsExpanded) {
|
||||
isServerNoticeRoomsExpanded = !isServerNoticeRoomsExpanded
|
||||
}
|
||||
if (isServerNoticeRoomsExpanded) {
|
||||
buildRoomModels(serverNotices, viewState.selectedRoomId)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private fun buildRoomCategory(viewState: RoomListViewState, summaries: List<RoomSummary>, @StringRes titleRes: Int, isExpanded: Boolean, mutateExpandedState: () -> Unit) {
|
||||
//TODO should add some business logic later
|
||||
val unreadCount = if (summaries.isEmpty()) {
|
||||
0
|
||||
} else {
|
||||
summaries.map { it.notificationCount }.reduce { acc, i -> acc + i }
|
||||
}
|
||||
val showHighlighted = summaries.any { it.highlightCount > 0 }
|
||||
RoomCategoryItem(
|
||||
title = stringProvider.getString(titleRes).toUpperCase(),
|
||||
isExpanded = isExpanded,
|
||||
unreadCount = unreadCount,
|
||||
showHighlighted = showHighlighted,
|
||||
listener = {
|
||||
mutateExpandedState()
|
||||
setData(viewState)
|
||||
}
|
||||
)
|
||||
.id(titleRes)
|
||||
.addTo(this)
|
||||
}
|
||||
|
||||
private fun buildRoomModels(summaries: List<RoomSummary>, selectedRoomId: String?) {
|
||||
summaries.forEach { roomSummary ->
|
||||
val unreadCount = roomSummary.notificationCount
|
||||
val showHighlighted = roomSummary.highlightCount > 0
|
||||
val isSelected = roomSummary.roomId == selectedRoomId
|
||||
RoomSummaryItem(
|
||||
roomName = roomSummary.displayName,
|
||||
avatarUrl = roomSummary.avatarUrl,
|
||||
isSelected = isSelected,
|
||||
showHighlighted = showHighlighted,
|
||||
unreadCount = unreadCount,
|
||||
listener = { callback?.onRoomSelected(roomSummary) }
|
||||
)
|
||||
.id(roomSummary.roomId)
|
||||
.addTo(this)
|
||||
}
|
||||
}
|
||||
|
||||
interface Callback {
|
||||
fun onRoomSelected(room: RoomSummary)
|
||||
}
|
||||
|
||||
}
|
@ -1,48 +0,0 @@
|
||||
/*
|
||||
* Copyright 2019 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.riotredesign.features.home.room.list
|
||||
|
||||
import android.widget.ImageView
|
||||
import android.widget.TextView
|
||||
import im.vector.riotredesign.R
|
||||
import im.vector.riotredesign.core.epoxy.KotlinModel
|
||||
import im.vector.riotredesign.core.platform.CheckableFrameLayout
|
||||
import im.vector.riotredesign.features.home.AvatarRenderer
|
||||
|
||||
|
||||
data class RoomSummaryItem(
|
||||
val roomName: CharSequence,
|
||||
val avatarUrl: String?,
|
||||
val isSelected: Boolean,
|
||||
val unreadCount: Int,
|
||||
val showHighlighted: Boolean,
|
||||
val listener: (() -> Unit)? = null
|
||||
) : KotlinModel(R.layout.item_room) {
|
||||
|
||||
private val unreadCounterBadgeView by bind<UnreadCounterBadgeView>(R.id.roomUnreadCounterBadgeView)
|
||||
private val titleView by bind<TextView>(R.id.roomNameView)
|
||||
private val avatarImageView by bind<ImageView>(R.id.roomAvatarImageView)
|
||||
private val rootView by bind<CheckableFrameLayout>(R.id.itemRoomLayout)
|
||||
|
||||
override fun bind() {
|
||||
unreadCounterBadgeView.render(unreadCount, showHighlighted)
|
||||
rootView.isChecked = isSelected
|
||||
rootView.setOnClickListener { listener?.invoke() }
|
||||
titleView.text = roomName
|
||||
AvatarRenderer.render(avatarUrl, roomName.toString(), avatarImageView)
|
||||
}
|
||||
}
|
Before Width: | Height: | Size: 30 KiB |
@ -1,64 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingLeft="16dp"
|
||||
android:paddingRight="16dp">
|
||||
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/messageAvatarImageView"
|
||||
android:layout_width="40dp"
|
||||
android:layout_height="40dp"
|
||||
android:layout_marginTop="8dp"
|
||||
android:layout_marginBottom="8dp"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
tools:src="@tools:sample/avatars" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/messageMemberNameView"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="64dp"
|
||||
android:layout_marginTop="8dp"
|
||||
android:layout_marginEnd="8dp"
|
||||
android:ellipsize="end"
|
||||
android:maxLines="1"
|
||||
android:textSize="15sp"
|
||||
app:layout_constraintBottom_toTopOf="@+id/toolbarSubtitleView"
|
||||
app:layout_constraintEnd_toStartOf="@+id/messageTimeView"
|
||||
app:layout_constraintHorizontal_bias="0.0"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
tools:text="@tools:sample/full_names" />
|
||||
|
||||
|
||||
<TextView
|
||||
android:id="@+id/messageTimeView"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="8dp"
|
||||
android:textColor="@color/brown_grey"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintHorizontal_bias="0.0"
|
||||
app:layout_constraintTop_toTopOf="@id/messageMemberNameView"
|
||||
tools:text="@tools:sample/date/hhmm" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/messageImageView"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
android:layout_marginStart="64dp"
|
||||
android:layout_marginTop="8dp"
|
||||
android:layout_marginEnd="32dp"
|
||||
android:layout_marginBottom="8dp"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintHorizontal_bias="0"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/messageMemberNameView" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
@ -1,63 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingLeft="16dp"
|
||||
android:paddingRight="16dp">
|
||||
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/messageAvatarImageView"
|
||||
android:layout_width="40dp"
|
||||
android:layout_height="40dp"
|
||||
android:layout_marginTop="8dp"
|
||||
android:layout_marginBottom="8dp"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
tools:src="@tools:sample/avatars" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/messageMemberNameView"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="64dp"
|
||||
android:layout_marginTop="8dp"
|
||||
android:layout_marginEnd="8dp"
|
||||
android:ellipsize="end"
|
||||
android:maxLines="1"
|
||||
android:textSize="15sp"
|
||||
app:layout_constraintBottom_toTopOf="@+id/toolbarSubtitleView"
|
||||
app:layout_constraintEnd_toStartOf="@+id/messageTimeView"
|
||||
app:layout_constraintHorizontal_bias="0.0"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
tools:text="@tools:sample/full_names" />
|
||||
|
||||
|
||||
<TextView
|
||||
android:id="@+id/messageTimeView"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="8dp"
|
||||
android:textColor="@color/brown_grey"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintHorizontal_bias="0.0"
|
||||
app:layout_constraintTop_toTopOf="@id/messageMemberNameView"
|
||||
tools:text="@tools:sample/date/hhmm" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/messageTextView"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="64dp"
|
||||
android:layout_marginBottom="8dp"
|
||||
android:textColor="@color/dark_grey"
|
||||
android:textSize="14sp"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/messageMemberNameView"
|
||||
tools:text="Alright finished work, heading there in about 20 mins…
Ping me when you’re outside" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
@ -1,74 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
|
||||
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/read_receipt_avatars_list"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_alignParentEnd="true"
|
||||
android:layout_alignParentRight="true"
|
||||
android:layout_marginEnd="12dp"
|
||||
android:layout_marginRight="12dp"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/message_more_than_expected"
|
||||
android:layout_width="20dp"
|
||||
android:layout_height="match_parent"
|
||||
android:gravity="center"
|
||||
android:textSize="9sp"
|
||||
tools:text="999+" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/message_avatar_receipt_5"
|
||||
android:layout_width="12dp"
|
||||
android:layout_height="12dp"
|
||||
android:layout_marginEnd="6dp"
|
||||
android:layout_marginRight="6dp"
|
||||
android:adjustViewBounds="true"
|
||||
android:scaleType="centerCrop" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/message_avatar_receipt_4"
|
||||
android:layout_width="12dp"
|
||||
android:layout_height="12dp"
|
||||
android:layout_marginEnd="6dp"
|
||||
android:layout_marginRight="6dp"
|
||||
android:adjustViewBounds="true"
|
||||
android:scaleType="centerCrop" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/message_avatar_receipt_3"
|
||||
android:layout_width="12dp"
|
||||
android:layout_height="12dp"
|
||||
android:layout_marginEnd="6dp"
|
||||
android:layout_marginRight="6dp"
|
||||
android:adjustViewBounds="true"
|
||||
android:scaleType="centerCrop" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/message_avatar_receipt_2"
|
||||
android:layout_width="12dp"
|
||||
android:layout_height="12dp"
|
||||
android:layout_marginEnd="6dp"
|
||||
android:layout_marginRight="6dp"
|
||||
android:adjustViewBounds="true"
|
||||
android:scaleType="centerCrop" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/message_avatar_receipt_1"
|
||||
android:layout_width="19dp"
|
||||
android:layout_height="12dp"
|
||||
android:layout_marginEnd="6dp"
|
||||
android:layout_marginRight="6dp"
|
||||
android:adjustViewBounds="true"
|
||||
android:scaleType="centerCrop" />
|
||||
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</RelativeLayout>
|
@ -1,5 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<background android:drawable="@drawable/ic_launcher_background" />
|
||||
<foreground android:drawable="@drawable/ic_launcher_foreground" />
|
||||
</adaptive-icon>
|
@ -1,5 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<background android:drawable="@drawable/ic_launcher_background" />
|
||||
<foreground android:drawable="@drawable/ic_launcher_foreground" />
|
||||
</adaptive-icon>
|
Before Width: | Height: | Size: 3.0 KiB |
Before Width: | Height: | Size: 4.9 KiB |
Before Width: | Height: | Size: 2.0 KiB |
Before Width: | Height: | Size: 2.8 KiB |
Before Width: | Height: | Size: 4.5 KiB |
Before Width: | Height: | Size: 6.9 KiB |
Before Width: | Height: | Size: 6.3 KiB |
Before Width: | Height: | Size: 10 KiB |
Before Width: | Height: | Size: 9.0 KiB |
Before Width: | Height: | Size: 15 KiB |
@ -1,9 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
|
||||
<style name="Base.V21.Theme.Riot" parent="Base.V1.Theme.Riot"></style>
|
||||
|
||||
<style name="Base.Theme.Riot" parent="Base.V21.Theme.Riot" />
|
||||
|
||||
|
||||
</resources>
|
@ -1,15 +0,0 @@
|
||||
<resources>
|
||||
<string name="app_name">"Riot X"</string>
|
||||
|
||||
<string name="global_retry">"Retry"</string>
|
||||
<string name="error_no_network">"No network connection"</string>
|
||||
<string name="error_common">"An error occurred"</string>
|
||||
|
||||
<string name="room_list_empty">"Join a room to start using the app."</string>
|
||||
<string name="room_list_favourites">"Favourites"</string>
|
||||
<string name="room_list_direct">"People"</string>
|
||||
<string name="room_list_group">"Rooms"</string>
|
||||
<string name="room_list_low_priority">"Low priority"</string>
|
||||
<string name="room_list_system_alert">"System Alerts"</string>
|
||||
|
||||
</resources>
|
@ -1,4 +0,0 @@
|
||||
<resources>
|
||||
|
||||
|
||||
</resources>
|
@ -1,11 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
|
||||
<style name="Widget.Button" parent="Widget.AppCompat.Button">
|
||||
<item name="android:minHeight">48dp</item>
|
||||
<item name="android:background">?attr/colorAccent</item>
|
||||
<item name="android:textColor">@android:color/white</item>
|
||||
</style>
|
||||
|
||||
|
||||
</resources>
|
@ -1,13 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
|
||||
<style name="Theme.Riot" parent="Base.Theme.Riot">
|
||||
|
||||
</style>
|
||||
|
||||
<style name="Theme.Riot.Splash">
|
||||
<item name="android:windowBackground">@drawable/bg_splash</item>
|
||||
</style>
|
||||
|
||||
|
||||
</resources>
|
@ -1,15 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
|
||||
<style name="Base.V1.Theme.Riot" parent="Theme.AppCompat.Light.NoActionBar">
|
||||
<item name="colorPrimary">@color/dark</item>
|
||||
<item name="colorPrimaryDark">@color/dark</item>
|
||||
<item name="colorAccent">@color/pale_teal</item>
|
||||
<item name="buttonStyle">@style/Widget.Button</item>
|
||||
</style>
|
||||
|
||||
<style name="Base.Theme.Riot" parent="Base.V1.Theme.Riot" />
|
||||
|
||||
|
||||
|
||||
</resources>
|
@ -1,2 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources></resources>
|
@ -1,2 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources></resources>
|
41
build.gradle
@ -1,16 +1,21 @@
|
||||
// Top-level build file where you can add configuration options common to all sub-projects/modules.
|
||||
|
||||
buildscript {
|
||||
ext.kotlin_version = '1.3.11'
|
||||
ext.kotlin_version = '1.3.21'
|
||||
ext.koin_version = '1.0.2'
|
||||
repositories {
|
||||
google()
|
||||
jcenter()
|
||||
maven {
|
||||
url "https://plugins.gradle.org/m2/"
|
||||
}
|
||||
}
|
||||
dependencies {
|
||||
classpath 'com.android.tools.build:gradle:3.3.0'
|
||||
classpath 'com.android.tools.build:gradle:3.3.2'
|
||||
classpath 'com.google.gms:google-services:4.2.0'
|
||||
classpath "com.airbnb.okreplay:gradle-plugin:1.4.0"
|
||||
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
|
||||
classpath 'org.sonarsource.scanner.gradle:sonarqube-gradle-plugin:2.6.2'
|
||||
|
||||
// NOTE: Do not place your application dependencies here; they belong
|
||||
// in the individual module build.gradle files
|
||||
@ -19,12 +24,42 @@ buildscript {
|
||||
|
||||
allprojects {
|
||||
repositories {
|
||||
maven { url "http://dl.bintray.com/piasy/maven" }
|
||||
maven { url 'https://jitpack.io' }
|
||||
maven { url 'https://oss.sonatype.org/content/repositories/snapshots/' }
|
||||
google()
|
||||
jcenter()
|
||||
maven { url 'https://jitpack.io' }
|
||||
}
|
||||
}
|
||||
|
||||
task clean(type: Delete) {
|
||||
delete rootProject.buildDir
|
||||
}
|
||||
|
||||
apply plugin: 'org.sonarqube'
|
||||
|
||||
sonarqube {
|
||||
properties {
|
||||
property "sonar.projectName", "RiotX-Android"
|
||||
property "sonar.projectKey", "vector.android.riotx"
|
||||
property "sonar.host.url", "https://sonarcloud.io"
|
||||
property "sonar.projectVersion", project(":vector").android.defaultConfig.versionName
|
||||
property "sonar.sourceEncoding", "UTF-8"
|
||||
property "sonar.links.homepage", "https://github.com/vector-im/riotX-android/"
|
||||
property "sonar.links.ci", "https://buildkite.com/matrix-dot-org/riotx-android"
|
||||
property "sonar.links.scm", "https://github.com/vector-im/riotX-android/"
|
||||
property "sonar.links.issue", "https://github.com/vector-im/riotX-android/issues"
|
||||
property "sonar.organization", "new_vector_ltd_organization"
|
||||
property "sonar.login", project.hasProperty("SONAR_LOGIN") ? SONAR_LOGIN : "invalid"
|
||||
}
|
||||
}
|
||||
|
||||
project(":vector") {
|
||||
sonarqube {
|
||||
properties {
|
||||
property "sonar.sources", project(":vector").android.sourceSets.main.java.srcDirs
|
||||
// exclude source code from analyses separated by a colon (:)
|
||||
// property "sonar.exclusions", "**/*.*"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
BIN
gradle/wrapper/gradle-wrapper.jar
vendored
4
gradle/wrapper/gradle-wrapper.properties
vendored
@ -1,6 +1,6 @@
|
||||
#Wed Jan 16 18:28:13 CET 2019
|
||||
#Tue Mar 19 09:53:05 CET 2019
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-4.10.1-all.zip
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-5.2.1-all.zip
|
||||
|
2
gradlew
vendored
@ -28,7 +28,7 @@ APP_NAME="Gradle"
|
||||
APP_BASE_NAME=`basename "$0"`
|
||||
|
||||
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
DEFAULT_JVM_OPTS=""
|
||||
DEFAULT_JVM_OPTS='"-Xmx64m"'
|
||||
|
||||
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
||||
MAX_FD="maximum"
|
||||
|
2
gradlew.bat
vendored
@ -14,7 +14,7 @@ set APP_BASE_NAME=%~n0
|
||||
set APP_HOME=%DIRNAME%
|
||||
|
||||
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
set DEFAULT_JVM_OPTS=
|
||||
set DEFAULT_JVM_OPTS="-Xmx64m"
|
||||
|
||||
@rem Find java.exe
|
||||
if defined JAVA_HOME goto findJavaFromJavaHome
|
||||
|
@ -1,4 +1,3 @@
|
||||
|
||||
/*
|
||||
* Copyright 2019 New Vector Ltd
|
||||
*
|
||||
@ -18,9 +17,7 @@
|
||||
package im.vector.matrix.rx
|
||||
|
||||
import im.vector.matrix.android.api.session.room.Room
|
||||
import im.vector.matrix.android.api.session.room.model.ReadReceipt
|
||||
import im.vector.matrix.android.api.session.room.model.RoomSummary
|
||||
import im.vector.matrix.android.api.session.room.timeline.TimelineData
|
||||
import io.reactivex.Observable
|
||||
|
||||
class RxRoom(private val room: Room) {
|
||||
@ -29,12 +26,8 @@ class RxRoom(private val room: Room) {
|
||||
return room.roomSummary.asObservable()
|
||||
}
|
||||
|
||||
fun timeline(eventId: String? = null): Observable<TimelineData> {
|
||||
return room.timeline(eventId).asObservable()
|
||||
}
|
||||
|
||||
fun liveReadReceipts(): Observable<List<ReadReceipt>> {
|
||||
return room.readReceipts().asObservable()
|
||||
fun liveRoomMemberIds(): Observable<List<String>> {
|
||||
return room.getRoomMemberIdsLive().asObservable()
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,6 +1,7 @@
|
||||
apply plugin: 'com.android.library'
|
||||
apply plugin: 'kotlin-android'
|
||||
apply plugin: 'kotlin-kapt'
|
||||
apply plugin: 'kotlin-android-extensions'
|
||||
apply plugin: 'realm-android'
|
||||
apply plugin: 'okreplay'
|
||||
|
||||
@ -19,6 +20,10 @@ repositories {
|
||||
jcenter()
|
||||
}
|
||||
|
||||
androidExtensions {
|
||||
experimental = true
|
||||
}
|
||||
|
||||
android {
|
||||
compileSdkVersion 28
|
||||
testOptions.unitTests.includeAndroidResources = true
|
||||
@ -28,12 +33,28 @@ android {
|
||||
targetSdkVersion 28
|
||||
versionCode 1
|
||||
versionName "1.0"
|
||||
multiDexEnabled true
|
||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||
|
||||
resValue "string", "git_sdk_revision", "\"${gitRevision()}\""
|
||||
resValue "string", "git_sdk_revision_unix_date", "\"${gitRevisionUnixDate()}\""
|
||||
resValue "string", "git_sdk_revision_date", "\"${gitRevisionDate()}\""
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
|
||||
debug {
|
||||
// Set to true to log privacy or sensible data, such as token
|
||||
buildConfigField "boolean", "LOG_PRIVATE_DATA", "false"
|
||||
|
||||
// Set to BODY instead of NONE to enable logging
|
||||
buildConfigField "okhttp3.logging.HttpLoggingInterceptor.Level", "OKHTTP_LOGGING_LEVEL", "okhttp3.logging.HttpLoggingInterceptor.Level.NONE"
|
||||
}
|
||||
|
||||
release {
|
||||
buildConfigField "boolean", "LOG_PRIVATE_DATA", "false"
|
||||
buildConfigField "okhttp3.logging.HttpLoggingInterceptor.Level", "OKHTTP_LOGGING_LEVEL", "okhttp3.logging.HttpLoggingInterceptor.Level.NONE"
|
||||
|
||||
minifyEnabled false
|
||||
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
|
||||
}
|
||||
@ -42,12 +63,31 @@ android {
|
||||
adbOptions {
|
||||
installOptions "-g"
|
||||
}
|
||||
|
||||
lintOptions {
|
||||
lintConfig file("lint.xml")
|
||||
}
|
||||
}
|
||||
|
||||
static def gitRevision() {
|
||||
def cmd = "git rev-parse --short HEAD"
|
||||
return cmd.execute().text.trim()
|
||||
}
|
||||
|
||||
static def gitRevisionUnixDate() {
|
||||
def cmd = "git show -s --format=%ct HEAD^{commit}"
|
||||
return cmd.execute().text.trim()
|
||||
}
|
||||
|
||||
static def gitRevisionDate() {
|
||||
def cmd = "git show -s --format=%ci HEAD^{commit}"
|
||||
return cmd.execute().text.trim()
|
||||
}
|
||||
|
||||
dependencies {
|
||||
|
||||
def arrow_version = "0.8.0"
|
||||
def support_version = '1.1.0-alpha01'
|
||||
def support_version = '1.1.0-alpha03'
|
||||
def moshi_version = '1.8.0'
|
||||
def lifecycle_version = '2.0.0'
|
||||
def coroutines_version = "1.0.1"
|
||||
@ -66,7 +106,7 @@ dependencies {
|
||||
// Network
|
||||
implementation 'com.squareup.retrofit2:retrofit:2.4.0'
|
||||
implementation 'com.squareup.retrofit2:converter-moshi:2.4.0'
|
||||
implementation 'com.squareup.okhttp3:okhttp:3.10.0'
|
||||
implementation 'com.squareup.okhttp3:okhttp:3.11.0'
|
||||
implementation 'com.squareup.okhttp3:logging-interceptor:3.10.0'
|
||||
implementation 'com.novoda:merlin:1.1.6'
|
||||
implementation "com.squareup.moshi:moshi-adapters:$moshi_version"
|
||||
@ -76,11 +116,8 @@ dependencies {
|
||||
implementation 'com.github.Zhuinden:realm-monarchy:0.5.1'
|
||||
kapt 'dk.ilios:realmfieldnameshelper:1.1.1'
|
||||
|
||||
// Paging
|
||||
implementation 'androidx.paging:paging-runtime:2.0.0'
|
||||
|
||||
// Work
|
||||
implementation "android.arch.work:work-runtime-ktx:1.0.0-beta02"
|
||||
implementation "android.arch.work:work-runtime-ktx:1.0.0"
|
||||
|
||||
// FP
|
||||
implementation "io.arrow-kt:arrow-core:$arrow_version"
|
||||
@ -95,6 +132,7 @@ dependencies {
|
||||
|
||||
// Logging
|
||||
implementation 'com.jakewharton.timber:timber:4.7.1'
|
||||
implementation 'com.facebook.stetho:stetho-okhttp3:1.5.0'
|
||||
|
||||
debugImplementation 'com.airbnb.okreplay:okreplay:1.4.0'
|
||||
releaseImplementation 'com.airbnb.okreplay:noop:1.4.0'
|
||||
@ -103,20 +141,20 @@ dependencies {
|
||||
testImplementation 'junit:junit:4.12'
|
||||
testImplementation 'org.robolectric:robolectric:4.0.2'
|
||||
testImplementation "org.koin:koin-test:$koin_version"
|
||||
testImplementation 'org.robolectric:shadows-support-v4:3.0'
|
||||
//testImplementation 'org.robolectric:shadows-support-v4:3.0'
|
||||
testImplementation "io.mockk:mockk:1.8.13.kotlin13"
|
||||
testImplementation 'org.amshove.kluent:kluent-android:1.44'
|
||||
testImplementation "androidx.arch.core:core-testing:$lifecycle_version"
|
||||
testImplementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutines_version"
|
||||
|
||||
androidTestImplementation "org.koin:koin-test:$koin_version"
|
||||
androidTestImplementation 'androidx.test:core:1.1.0'
|
||||
androidTestImplementation 'androidx.test:runner:1.1.1'
|
||||
androidTestImplementation 'androidx.test:rules:1.1.1'
|
||||
androidTestImplementation 'androidx.test.ext:junit:1.1.0'
|
||||
androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.1'
|
||||
androidTestImplementation 'org.amshove.kluent:kluent-android:1.44'
|
||||
androidTestImplementation "io.mockk:mockk-android:1.8.13.kotlin13"
|
||||
androidTestImplementation "androidx.arch.core:core-testing:$lifecycle_version"
|
||||
androidTestImplementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutines_version"
|
||||
|
||||
|
||||
}
|
||||
|
33
matrix-sdk-android/lint.xml
Normal file
@ -0,0 +1,33 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<lint>
|
||||
<!-- Modify some severity -->
|
||||
|
||||
<!-- Resource -->
|
||||
<issue id="MissingTranslation" severity="warning" />
|
||||
<issue id="TypographyEllipsis" severity="error" />
|
||||
<issue id="ImpliedQuantity" severity="warning" />
|
||||
|
||||
<!-- UX -->
|
||||
<issue id="ButtonOrder" severity="error" />
|
||||
|
||||
<!-- Layout -->
|
||||
<issue id="UnknownIdInLayout" severity="error" />
|
||||
<issue id="StringFormatCount" severity="error" />
|
||||
<issue id="HardcodedText" severity="error" />
|
||||
<issue id="SpUsage" severity="error" />
|
||||
<issue id="ObsoleteLayoutParam" severity="error" />
|
||||
<issue id="InefficientWeight" severity="error" />
|
||||
<issue id="DisableBaselineAlignment" severity="error" />
|
||||
<issue id="ScrollViewSize" severity="error" />
|
||||
|
||||
<!-- RTL -->
|
||||
<issue id="RtlEnabled" severity="error" />
|
||||
<issue id="RtlHardcoded" severity="error" />
|
||||
<issue id="RtlSymmetry" severity="error" />
|
||||
|
||||
<!-- Code -->
|
||||
<issue id="SetTextI18n" severity="error" />
|
||||
<issue id="ViewConstructor" severity="error" />
|
||||
<issue id="UseValueOf" severity="error" />
|
||||
|
||||
</lint>
|
@ -16,10 +16,9 @@
|
||||
|
||||
package im.vector.matrix.android.session.room.timeline
|
||||
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
import com.zhuinden.monarchy.Monarchy
|
||||
import im.vector.matrix.android.InstrumentedTest
|
||||
import im.vector.matrix.android.api.session.events.model.Event
|
||||
import im.vector.matrix.android.api.session.events.model.EventType
|
||||
import im.vector.matrix.android.internal.database.helper.add
|
||||
import im.vector.matrix.android.internal.database.helper.addAll
|
||||
import im.vector.matrix.android.internal.database.helper.isUnlinked
|
||||
@ -27,6 +26,9 @@ import im.vector.matrix.android.internal.database.helper.lastStateIndex
|
||||
import im.vector.matrix.android.internal.database.helper.merge
|
||||
import im.vector.matrix.android.internal.database.model.ChunkEntity
|
||||
import im.vector.matrix.android.internal.session.room.timeline.PaginationDirection
|
||||
import im.vector.matrix.android.session.room.timeline.RoomDataHelper.createFakeListOfEvents
|
||||
import im.vector.matrix.android.session.room.timeline.RoomDataHelper.createFakeMessageEvent
|
||||
import im.vector.matrix.android.session.room.timeline.RoomDataHelper.createFakeRoomMemberEvent
|
||||
import io.realm.Realm
|
||||
import io.realm.RealmConfiguration
|
||||
import io.realm.kotlin.createObject
|
||||
@ -35,9 +37,10 @@ import org.amshove.kluent.shouldBeTrue
|
||||
import org.amshove.kluent.shouldEqual
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
import kotlin.random.Random
|
||||
import org.junit.runner.RunWith
|
||||
|
||||
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
internal class ChunkEntityTest : InstrumentedTest {
|
||||
|
||||
private lateinit var monarchy: Monarchy
|
||||
@ -54,7 +57,7 @@ internal class ChunkEntityTest : InstrumentedTest {
|
||||
fun add_shouldAdd_whenNotAlreadyIncluded() {
|
||||
monarchy.runTransactionSync { realm ->
|
||||
val chunk: ChunkEntity = realm.createObject()
|
||||
val fakeEvent = createFakeEvent(false)
|
||||
val fakeEvent = createFakeMessageEvent()
|
||||
chunk.add("roomId", fakeEvent, PaginationDirection.FORWARDS)
|
||||
chunk.events.size shouldEqual 1
|
||||
}
|
||||
@ -64,7 +67,7 @@ internal class ChunkEntityTest : InstrumentedTest {
|
||||
fun add_shouldNotAdd_whenAlreadyIncluded() {
|
||||
monarchy.runTransactionSync { realm ->
|
||||
val chunk: ChunkEntity = realm.createObject()
|
||||
val fakeEvent = createFakeEvent(false)
|
||||
val fakeEvent = createFakeMessageEvent()
|
||||
chunk.add("roomId", fakeEvent, PaginationDirection.FORWARDS)
|
||||
chunk.add("roomId", fakeEvent, PaginationDirection.FORWARDS)
|
||||
chunk.events.size shouldEqual 1
|
||||
@ -75,7 +78,7 @@ internal class ChunkEntityTest : InstrumentedTest {
|
||||
fun add_shouldStateIndexIncremented_whenStateEventIsAddedForward() {
|
||||
monarchy.runTransactionSync { realm ->
|
||||
val chunk: ChunkEntity = realm.createObject()
|
||||
val fakeEvent = createFakeEvent(true)
|
||||
val fakeEvent = createFakeRoomMemberEvent()
|
||||
chunk.add("roomId", fakeEvent, PaginationDirection.FORWARDS)
|
||||
chunk.lastStateIndex(PaginationDirection.FORWARDS) shouldEqual 1
|
||||
}
|
||||
@ -85,7 +88,7 @@ internal class ChunkEntityTest : InstrumentedTest {
|
||||
fun add_shouldStateIndexNotIncremented_whenNoStateEventIsAdded() {
|
||||
monarchy.runTransactionSync { realm ->
|
||||
val chunk: ChunkEntity = realm.createObject()
|
||||
val fakeEvent = createFakeEvent(false)
|
||||
val fakeEvent = createFakeMessageEvent()
|
||||
chunk.add("roomId", fakeEvent, PaginationDirection.FORWARDS)
|
||||
chunk.lastStateIndex(PaginationDirection.FORWARDS) shouldEqual 0
|
||||
}
|
||||
@ -134,13 +137,13 @@ internal class ChunkEntityTest : InstrumentedTest {
|
||||
val chunk2: ChunkEntity = realm.createObject()
|
||||
val eventsForChunk1 = createFakeListOfEvents(30)
|
||||
val eventsForChunk2 = eventsForChunk1 + createFakeListOfEvents(10)
|
||||
chunk1.isLast = true
|
||||
chunk2.isLast = false
|
||||
chunk1.isLastForward = true
|
||||
chunk2.isLastForward = false
|
||||
chunk1.addAll("roomId", eventsForChunk1, PaginationDirection.FORWARDS)
|
||||
chunk2.addAll("roomId", eventsForChunk2, PaginationDirection.BACKWARDS)
|
||||
chunk1.merge("roomId", chunk2, PaginationDirection.BACKWARDS)
|
||||
chunk1.events.size shouldEqual 40
|
||||
chunk1.isLast.shouldBeTrue()
|
||||
chunk1.isLastForward.shouldBeTrue()
|
||||
}
|
||||
}
|
||||
|
||||
@ -196,15 +199,4 @@ internal class ChunkEntityTest : InstrumentedTest {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private fun createFakeListOfEvents(size: Int = 10): List<Event> {
|
||||
return (0 until size).map { createFakeEvent(Random.nextBoolean()) }
|
||||
}
|
||||
|
||||
private fun createFakeEvent(asStateEvent: Boolean = false): Event {
|
||||
val eventId = Random.nextLong(System.currentTimeMillis()).toString()
|
||||
val type = if (asStateEvent) EventType.STATE_ROOM_NAME else EventType.MESSAGE
|
||||
return Event(type, eventId)
|
||||
}
|
||||
|
||||
}
|
@ -25,7 +25,7 @@ import kotlin.random.Random
|
||||
|
||||
internal class FakeGetContextOfEventTask(private val tokenChunkEventPersistor: TokenChunkEventPersistor) : GetContextOfEventTask {
|
||||
|
||||
override fun execute(params: GetContextOfEventTask.Params): Try<TokenChunkEvent> {
|
||||
override fun execute(params: GetContextOfEventTask.Params): Try<TokenChunkEventPersistor.Result> {
|
||||
val fakeEvents = RoomDataHelper.createFakeListOfEvents(30)
|
||||
val tokenChunkEvent = FakeTokenChunkEvent(
|
||||
Random.nextLong(System.currentTimeMillis()).toString(),
|
||||
@ -33,7 +33,6 @@ internal class FakeGetContextOfEventTask(private val tokenChunkEventPersistor: T
|
||||
fakeEvents
|
||||
)
|
||||
return tokenChunkEventPersistor.insertInDb(tokenChunkEvent, params.roomId, PaginationDirection.BACKWARDS)
|
||||
.map { tokenChunkEvent }
|
||||
}
|
||||
|
||||
|
||||
|
@ -18,17 +18,15 @@ package im.vector.matrix.android.session.room.timeline
|
||||
|
||||
import arrow.core.Try
|
||||
import im.vector.matrix.android.internal.session.room.timeline.PaginationTask
|
||||
import im.vector.matrix.android.internal.session.room.timeline.TokenChunkEvent
|
||||
import im.vector.matrix.android.internal.session.room.timeline.TokenChunkEventPersistor
|
||||
import kotlin.random.Random
|
||||
|
||||
internal class FakePaginationTask(private val tokenChunkEventPersistor: TokenChunkEventPersistor) : PaginationTask {
|
||||
|
||||
override fun execute(params: PaginationTask.Params): Try<TokenChunkEvent> {
|
||||
override fun execute(params: PaginationTask.Params): Try<TokenChunkEventPersistor.Result> {
|
||||
val fakeEvents = RoomDataHelper.createFakeListOfEvents(30)
|
||||
val tokenChunkEvent = FakeTokenChunkEvent(params.from, Random.nextLong(System.currentTimeMillis()).toString(), fakeEvents)
|
||||
return tokenChunkEventPersistor.insertInDb(tokenChunkEvent, params.roomId, params.direction)
|
||||
.map { tokenChunkEvent }
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -17,9 +17,14 @@
|
||||
package im.vector.matrix.android.session.room.timeline
|
||||
|
||||
import com.zhuinden.monarchy.Monarchy
|
||||
import im.vector.matrix.android.api.session.events.model.Content
|
||||
import im.vector.matrix.android.api.session.events.model.Event
|
||||
import im.vector.matrix.android.api.session.events.model.EventType
|
||||
import im.vector.matrix.android.api.session.room.model.MyMembership
|
||||
import im.vector.matrix.android.api.session.events.model.toContent
|
||||
import im.vector.matrix.android.api.session.room.model.Membership
|
||||
import im.vector.matrix.android.api.session.room.model.RoomMember
|
||||
import im.vector.matrix.android.api.session.room.model.message.MessageTextContent
|
||||
import im.vector.matrix.android.api.session.room.model.message.MessageType
|
||||
import im.vector.matrix.android.internal.database.helper.addAll
|
||||
import im.vector.matrix.android.internal.database.helper.addOrUpdate
|
||||
import im.vector.matrix.android.internal.database.model.ChunkEntity
|
||||
@ -30,27 +35,56 @@ import kotlin.random.Random
|
||||
|
||||
object RoomDataHelper {
|
||||
|
||||
private const val FAKE_TEST_SENDER = "@sender:test.org"
|
||||
private val EVENT_FACTORIES = hashMapOf(
|
||||
0 to { createFakeMessageEvent() },
|
||||
1 to { createFakeRoomMemberEvent() }
|
||||
)
|
||||
|
||||
fun createFakeListOfEvents(size: Int = 10): List<Event> {
|
||||
return (0 until size).map { createFakeEvent(Random.nextBoolean()) }
|
||||
return (0 until size).mapNotNull {
|
||||
val nextInt = Random.nextInt(EVENT_FACTORIES.size)
|
||||
EVENT_FACTORIES[nextInt]?.invoke()
|
||||
}
|
||||
}
|
||||
|
||||
fun createFakeEvent(asStateEvent: Boolean = false): Event {
|
||||
val eventId = Random.nextLong(System.currentTimeMillis()).toString()
|
||||
val type = if (asStateEvent) EventType.STATE_ROOM_NAME else EventType.MESSAGE
|
||||
return Event(type, eventId)
|
||||
fun createFakeEvent(type: String,
|
||||
content: Content? = null,
|
||||
prevContent: Content? = null,
|
||||
sender: String = FAKE_TEST_SENDER,
|
||||
stateKey: String = FAKE_TEST_SENDER
|
||||
): Event {
|
||||
return Event(
|
||||
type = type,
|
||||
eventId = Random.nextLong().toString(),
|
||||
content = content,
|
||||
prevContent = prevContent,
|
||||
sender = sender,
|
||||
stateKey = stateKey
|
||||
)
|
||||
}
|
||||
|
||||
fun createFakeMessageEvent(): Event {
|
||||
val message = MessageTextContent(MessageType.MSGTYPE_TEXT, "Fake message #${Random.nextLong()}").toContent()
|
||||
return createFakeEvent(EventType.MESSAGE, message)
|
||||
}
|
||||
|
||||
fun createFakeRoomMemberEvent(): Event {
|
||||
val roomMember = RoomMember(Membership.JOIN, "Fake name #${Random.nextLong()}").toContent()
|
||||
return createFakeEvent(EventType.STATE_ROOM_MEMBER, roomMember)
|
||||
}
|
||||
|
||||
fun fakeInitialSync(monarchy: Monarchy, roomId: String) {
|
||||
monarchy.runTransactionSync { realm ->
|
||||
val roomEntity = realm.createObject<RoomEntity>(roomId)
|
||||
roomEntity.membership = MyMembership.JOINED
|
||||
val eventList = createFakeListOfEvents(30)
|
||||
roomEntity.membership = Membership.JOIN
|
||||
val eventList = createFakeListOfEvents(10)
|
||||
val chunkEntity = realm.createObject<ChunkEntity>().apply {
|
||||
nextToken = null
|
||||
prevToken = Random.nextLong(System.currentTimeMillis()).toString()
|
||||
isLast = true
|
||||
isLastForward = true
|
||||
}
|
||||
chunkEntity.addAll("roomId", eventList, PaginationDirection.FORWARDS)
|
||||
chunkEntity.addAll(roomId, eventList, PaginationDirection.FORWARDS)
|
||||
roomEntity.addOrUpdate(chunkEntity)
|
||||
}
|
||||
}
|
||||
|
@ -1,77 +0,0 @@
|
||||
/*
|
||||
* Copyright 2019 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.matrix.android.session.room.timeline
|
||||
|
||||
import androidx.arch.core.executor.testing.InstantTaskExecutorRule
|
||||
import androidx.test.annotation.UiThreadTest
|
||||
import com.zhuinden.monarchy.Monarchy
|
||||
import im.vector.matrix.android.InstrumentedTest
|
||||
import im.vector.matrix.android.LiveDataTestObserver
|
||||
import im.vector.matrix.android.MainThreadExecutor
|
||||
import im.vector.matrix.android.internal.session.room.members.RoomMemberExtractor
|
||||
import im.vector.matrix.android.internal.session.room.timeline.DefaultTimelineService
|
||||
import im.vector.matrix.android.internal.session.room.timeline.TimelineBoundaryCallback
|
||||
import im.vector.matrix.android.internal.session.room.timeline.TokenChunkEventPersistor
|
||||
import im.vector.matrix.android.internal.task.TaskExecutor
|
||||
import im.vector.matrix.android.internal.util.PagingRequestHelper
|
||||
import im.vector.matrix.android.testCoroutineDispatchers
|
||||
import io.realm.Realm
|
||||
import io.realm.RealmConfiguration
|
||||
import org.amshove.kluent.shouldEqual
|
||||
import org.junit.Before
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
|
||||
internal class TimelineHolderTest : InstrumentedTest {
|
||||
|
||||
@get:Rule val testRule = InstantTaskExecutorRule()
|
||||
private lateinit var monarchy: Monarchy
|
||||
|
||||
@Before
|
||||
fun setup() {
|
||||
Realm.init(context())
|
||||
val testConfiguration = RealmConfiguration.Builder().name("test-realm").build()
|
||||
Realm.deleteRealm(testConfiguration)
|
||||
monarchy = Monarchy.Builder().setRealmConfiguration(testConfiguration).build()
|
||||
}
|
||||
|
||||
@Test
|
||||
@UiThreadTest
|
||||
fun backPaginate_shouldLoadMoreEvents_whenLoadAroundIsCalled() {
|
||||
val roomId = "roomId"
|
||||
val taskExecutor = TaskExecutor(testCoroutineDispatchers)
|
||||
val tokenChunkEventPersistor = TokenChunkEventPersistor(monarchy)
|
||||
val paginationTask = FakePaginationTask(tokenChunkEventPersistor)
|
||||
val getContextOfEventTask = FakeGetContextOfEventTask(tokenChunkEventPersistor)
|
||||
val boundaryCallback = TimelineBoundaryCallback(roomId, taskExecutor, paginationTask, monarchy, PagingRequestHelper(MainThreadExecutor()))
|
||||
|
||||
RoomDataHelper.fakeInitialSync(monarchy, roomId)
|
||||
val timelineHolder = DefaultTimelineService(roomId, monarchy, taskExecutor, boundaryCallback, getContextOfEventTask, RoomMemberExtractor(monarchy, roomId))
|
||||
val timelineObserver = LiveDataTestObserver.test(timelineHolder.timeline())
|
||||
timelineObserver.awaitNextValue().assertHasValue()
|
||||
var timelineData = timelineObserver.value()
|
||||
timelineData.events.size shouldEqual 30
|
||||
(0 until timelineData.events.size).map {
|
||||
timelineData.events.loadAround(it)
|
||||
}
|
||||
timelineObserver.awaitNextValue().assertHasValue()
|
||||
timelineData = timelineObserver.value()
|
||||
timelineData.events.size shouldEqual 60
|
||||
}
|
||||
|
||||
|
||||
}
|
@ -0,0 +1,103 @@
|
||||
/*
|
||||
* Copyright 2019 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.matrix.android.session.room.timeline
|
||||
|
||||
import com.zhuinden.monarchy.Monarchy
|
||||
import im.vector.matrix.android.InstrumentedTest
|
||||
import im.vector.matrix.android.api.auth.data.Credentials
|
||||
import im.vector.matrix.android.api.session.room.timeline.Timeline
|
||||
import im.vector.matrix.android.api.session.room.timeline.TimelineEvent
|
||||
import im.vector.matrix.android.internal.session.room.EventRelationExtractor
|
||||
import im.vector.matrix.android.internal.session.room.EventRelationsAggregationUpdater
|
||||
import im.vector.matrix.android.internal.session.room.members.SenderRoomMemberExtractor
|
||||
import im.vector.matrix.android.internal.session.room.timeline.DefaultTimeline
|
||||
import im.vector.matrix.android.internal.session.room.timeline.TimelineEventFactory
|
||||
import im.vector.matrix.android.internal.session.room.timeline.TokenChunkEventPersistor
|
||||
import im.vector.matrix.android.internal.task.TaskExecutor
|
||||
import im.vector.matrix.android.testCoroutineDispatchers
|
||||
import io.realm.Realm
|
||||
import io.realm.RealmConfiguration
|
||||
import org.amshove.kluent.shouldEqual
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
import timber.log.Timber
|
||||
import java.util.concurrent.CountDownLatch
|
||||
|
||||
internal class TimelineTest : InstrumentedTest {
|
||||
|
||||
companion object {
|
||||
private const val ROOM_ID = "roomId"
|
||||
}
|
||||
|
||||
private lateinit var monarchy: Monarchy
|
||||
|
||||
@Before
|
||||
fun setup() {
|
||||
Timber.plant(Timber.DebugTree())
|
||||
Realm.init(context())
|
||||
val testConfiguration = RealmConfiguration.Builder().name("test-realm").build()
|
||||
Realm.deleteRealm(testConfiguration)
|
||||
monarchy = Monarchy.Builder().setRealmConfiguration(testConfiguration).build()
|
||||
RoomDataHelper.fakeInitialSync(monarchy, ROOM_ID)
|
||||
}
|
||||
|
||||
private fun createTimeline(initialEventId: String? = null): Timeline {
|
||||
val taskExecutor = TaskExecutor(testCoroutineDispatchers)
|
||||
val erau = EventRelationsAggregationUpdater(Credentials("", "", "", null, null))
|
||||
val tokenChunkEventPersistor = TokenChunkEventPersistor(monarchy, erau)
|
||||
val paginationTask = FakePaginationTask(tokenChunkEventPersistor)
|
||||
val getContextOfEventTask = FakeGetContextOfEventTask(tokenChunkEventPersistor)
|
||||
val roomMemberExtractor = SenderRoomMemberExtractor(ROOM_ID)
|
||||
val timelineEventFactory = TimelineEventFactory(roomMemberExtractor, EventRelationExtractor())
|
||||
return DefaultTimeline(
|
||||
ROOM_ID,
|
||||
initialEventId,
|
||||
monarchy.realmConfiguration,
|
||||
taskExecutor,
|
||||
getContextOfEventTask,
|
||||
timelineEventFactory,
|
||||
paginationTask,
|
||||
null)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun backPaginate_shouldLoadMoreEvents_whenPaginateIsCalled() {
|
||||
val timeline = createTimeline()
|
||||
timeline.start()
|
||||
val paginationCount = 30
|
||||
var initialLoad = 0
|
||||
val latch = CountDownLatch(2)
|
||||
var timelineEvents: List<TimelineEvent> = emptyList()
|
||||
timeline.listener = object : Timeline.Listener {
|
||||
override fun onUpdated(snapshot: List<TimelineEvent>) {
|
||||
if (snapshot.isNotEmpty()) {
|
||||
if (initialLoad == 0) {
|
||||
initialLoad = snapshot.size
|
||||
}
|
||||
timelineEvents = snapshot
|
||||
latch.countDown()
|
||||
timeline.paginate(Timeline.Direction.BACKWARDS, paginationCount)
|
||||
}
|
||||
}
|
||||
}
|
||||
latch.await()
|
||||
timelineEvents.size shouldEqual initialLoad + paginationCount
|
||||
timeline.dispose()
|
||||
}
|
||||
|
||||
|
||||
}
|
@ -0,0 +1,99 @@
|
||||
/*
|
||||
* Copyright (C) 2016 Jeff Gilfelt.
|
||||
* Copyright 2019 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.matrix.android.internal.network.interceptors
|
||||
|
||||
import okhttp3.Interceptor
|
||||
import okhttp3.Response
|
||||
import okhttp3.logging.HttpLoggingInterceptor
|
||||
import okio.Buffer
|
||||
import java.io.IOException
|
||||
import java.nio.charset.Charset
|
||||
|
||||
/**
|
||||
* An OkHttp interceptor that logs requests as curl shell commands. They can then
|
||||
* be copied, pasted and executed inside a terminal environment. This might be
|
||||
* useful for troubleshooting client/server API interaction during development,
|
||||
* making it easy to isolate and share requests made by the app. <p> Warning: The
|
||||
* logs generated by this interceptor have the potential to leak sensitive
|
||||
* information. It should only be used in a controlled manner or in a
|
||||
* non-production environment.
|
||||
*/
|
||||
internal class CurlLoggingInterceptor(private val logger: HttpLoggingInterceptor.Logger = HttpLoggingInterceptor.Logger.DEFAULT)
|
||||
: Interceptor {
|
||||
|
||||
/**
|
||||
* Set any additional curl command options (see 'curl --help').
|
||||
*/
|
||||
var curlOptions: String? = null
|
||||
|
||||
@Throws(IOException::class)
|
||||
override fun intercept(chain: Interceptor.Chain): Response {
|
||||
val request = chain.request()
|
||||
|
||||
var compressed = false
|
||||
|
||||
var curlCmd = "curl"
|
||||
if (curlOptions != null) {
|
||||
curlCmd += " " + curlOptions!!
|
||||
}
|
||||
curlCmd += " -X " + request.method()
|
||||
|
||||
val requestBody = request.body()
|
||||
if (requestBody != null) {
|
||||
val buffer = Buffer()
|
||||
requestBody.writeTo(buffer)
|
||||
var charset: Charset? = UTF8
|
||||
val contentType = requestBody.contentType()
|
||||
if (contentType != null) {
|
||||
charset = contentType.charset(UTF8)
|
||||
}
|
||||
// try to keep to a single line and use a subshell to preserve any line breaks
|
||||
curlCmd += " --data $'" + buffer.readString(charset!!).replace("\n", "\\n") + "'"
|
||||
}
|
||||
|
||||
val headers = request.headers()
|
||||
var i = 0
|
||||
val count = headers.size()
|
||||
while (i < count) {
|
||||
val name = headers.name(i)
|
||||
val value = headers.value(i)
|
||||
if ("Accept-Encoding".equals(name, ignoreCase = true) && "gzip".equals(value, ignoreCase = true)) {
|
||||
compressed = true
|
||||
}
|
||||
curlCmd += " -H \"$name: $value\""
|
||||
i++
|
||||
}
|
||||
|
||||
curlCmd += ((if (compressed) " --compressed " else " ") + "'" + request.url().toString()
|
||||
// Replace localhost for emulator by localhost for shell
|
||||
.replace("://10.0.2.2:8080/".toRegex(), "://127.0.0.1:8080/")
|
||||
+ "'")
|
||||
|
||||
// Add Json formatting
|
||||
curlCmd += " | python -m json.tool"
|
||||
|
||||
logger.log("--- cURL (" + request.url() + ")")
|
||||
logger.log(curlCmd)
|
||||
|
||||
return chain.proceed(request)
|
||||
}
|
||||
|
||||
companion object {
|
||||
private val UTF8 = Charset.forName("UTF-8")
|
||||
}
|
||||
}
|
@ -0,0 +1,76 @@
|
||||
/*
|
||||
* Copyright 2019 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.matrix.android.internal.network.interceptors
|
||||
|
||||
import androidx.annotation.NonNull
|
||||
import im.vector.matrix.android.BuildConfig
|
||||
import okhttp3.logging.HttpLoggingInterceptor
|
||||
import org.json.JSONArray
|
||||
import org.json.JSONException
|
||||
import org.json.JSONObject
|
||||
import timber.log.Timber
|
||||
|
||||
class FormattedJsonHttpLogger : HttpLoggingInterceptor.Logger {
|
||||
|
||||
companion object {
|
||||
private const val INDENT_SPACE = 2
|
||||
}
|
||||
|
||||
/**
|
||||
* Log the message and try to log it again as a JSON formatted string
|
||||
* Note: it can consume a lot of memory but it is only in DEBUG mode
|
||||
*
|
||||
* @param message
|
||||
*/
|
||||
@Synchronized
|
||||
override fun log(@NonNull message: String) {
|
||||
// In RELEASE there is no log, but for sure, test again BuildConfig.DEBUG
|
||||
if (BuildConfig.DEBUG) {
|
||||
Timber.v(message)
|
||||
|
||||
if (message.startsWith("{")) {
|
||||
// JSON Detected
|
||||
try {
|
||||
val o = JSONObject(message)
|
||||
logJson(o.toString(INDENT_SPACE))
|
||||
} catch (e: JSONException) {
|
||||
// Finally this is not a JSON string...
|
||||
Timber.e(e)
|
||||
}
|
||||
|
||||
} else if (message.startsWith("[")) {
|
||||
// JSON Array detected
|
||||
try {
|
||||
val o = JSONArray(message)
|
||||
logJson(o.toString(INDENT_SPACE))
|
||||
} catch (e: JSONException) {
|
||||
// Finally not JSON...
|
||||
Timber.e(e)
|
||||
}
|
||||
|
||||
}
|
||||
// Else not a json string to log
|
||||
}
|
||||
}
|
||||
|
||||
private fun logJson(formattedJson: String) {
|
||||
val arr = formattedJson.split("\n".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray()
|
||||
for (s in arr) {
|
||||
Timber.v(s)
|
||||
}
|
||||
}
|
||||
}
|
@ -21,11 +21,13 @@ import androidx.lifecycle.ProcessLifecycleOwner
|
||||
import com.zhuinden.monarchy.Monarchy
|
||||
import im.vector.matrix.android.api.auth.Authenticator
|
||||
import im.vector.matrix.android.api.session.Session
|
||||
import im.vector.matrix.android.api.session.sync.FilterService
|
||||
import im.vector.matrix.android.internal.auth.AuthModule
|
||||
import im.vector.matrix.android.internal.di.MatrixKoinComponent
|
||||
import im.vector.matrix.android.internal.di.MatrixKoinHolder
|
||||
import im.vector.matrix.android.internal.di.MatrixModule
|
||||
import im.vector.matrix.android.internal.di.NetworkModule
|
||||
import im.vector.matrix.android.internal.network.UserAgentHolder
|
||||
import im.vector.matrix.android.internal.util.BackgroundDetectionObserver
|
||||
import org.koin.standalone.inject
|
||||
import java.util.concurrent.atomic.AtomicBoolean
|
||||
@ -38,8 +40,9 @@ import java.util.concurrent.atomic.AtomicBoolean
|
||||
class Matrix private constructor(context: Context) : MatrixKoinComponent {
|
||||
|
||||
private val authenticator by inject<Authenticator>()
|
||||
private val userAgentHolder by inject<UserAgentHolder>()
|
||||
private val backgroundDetectionObserver by inject<BackgroundDetectionObserver>()
|
||||
lateinit var currentSession: Session
|
||||
var currentSession: Session? = null
|
||||
|
||||
init {
|
||||
Monarchy.init(context)
|
||||
@ -48,10 +51,11 @@ class Matrix private constructor(context: Context) : MatrixKoinComponent {
|
||||
val authModule = AuthModule().definition
|
||||
MatrixKoinHolder.instance.loadModules(listOf(matrixModule, networkModule, authModule))
|
||||
ProcessLifecycleOwner.get().lifecycle.addObserver(backgroundDetectionObserver)
|
||||
val lastActiveSession = authenticator.getLastActiveSession()
|
||||
if (lastActiveSession != null) {
|
||||
currentSession = lastActiveSession
|
||||
currentSession.open()
|
||||
authenticator.getLastActiveSession()?.also {
|
||||
currentSession = it
|
||||
it.open()
|
||||
it.setFilter(FilterService.FilterPreset.RiotFilter)
|
||||
it.startSync()
|
||||
}
|
||||
}
|
||||
|
||||
@ -59,6 +63,15 @@ class Matrix private constructor(context: Context) : MatrixKoinComponent {
|
||||
return authenticator
|
||||
}
|
||||
|
||||
/**
|
||||
* Set application flavor, to alter user agent.
|
||||
*/
|
||||
fun setApplicationFlavor(flavor: String) {
|
||||
userAgentHolder.setApplicationFlavor(flavor)
|
||||
}
|
||||
|
||||
fun getUserAgent() = userAgentHolder.userAgent
|
||||
|
||||
companion object {
|
||||
private lateinit var instance: Matrix
|
||||
private val isInit = AtomicBoolean(false)
|
||||
|
@ -24,7 +24,7 @@ interface MatrixCallback<in T> {
|
||||
|
||||
/**
|
||||
* On success method, default to no-op
|
||||
* @param data the data successfuly returned from the async function
|
||||
* @param data the data successfully returned from the async function
|
||||
*/
|
||||
fun onSuccess(data: T) {
|
||||
//no-op
|
||||
|
@ -44,6 +44,10 @@ object MatrixPatterns {
|
||||
private val MATRIX_EVENT_IDENTIFIER_REGEX = "\\$[A-Z0-9]+$DOMAIN_REGEX"
|
||||
val PATTERN_CONTAIN_MATRIX_EVENT_IDENTIFIER = Pattern.compile(MATRIX_EVENT_IDENTIFIER_REGEX, Pattern.CASE_INSENSITIVE)
|
||||
|
||||
// regex pattern to find message ids in a string.
|
||||
private val MATRIX_EVENT_IDENTIFIER_V3_REGEX = "\\$[A-Z0-9/+]+"
|
||||
val PATTERN_CONTAIN_MATRIX_EVENT_IDENTIFIER_V3 = Pattern.compile(MATRIX_EVENT_IDENTIFIER_V3_REGEX, Pattern.CASE_INSENSITIVE)
|
||||
|
||||
// regex pattern to find group ids in a string.
|
||||
private val MATRIX_GROUP_IDENTIFIER_REGEX = "\\+[A-Z0-9=_\\-./]+$DOMAIN_REGEX"
|
||||
val PATTERN_CONTAIN_MATRIX_GROUP_IDENTIFIER = Pattern.compile(MATRIX_GROUP_IDENTIFIER_REGEX, Pattern.CASE_INSENSITIVE)
|
||||
@ -116,7 +120,8 @@ object MatrixPatterns {
|
||||
* @return true if the string is a valid event id.
|
||||
*/
|
||||
fun isEventId(str: String?): Boolean {
|
||||
return str != null && PATTERN_CONTAIN_MATRIX_EVENT_IDENTIFIER.matcher(str).matches()
|
||||
return str != null
|
||||
&& (PATTERN_CONTAIN_MATRIX_EVENT_IDENTIFIER.matcher(str).matches() || PATTERN_CONTAIN_MATRIX_EVENT_IDENTIFIER_V3.matcher(str).matches())
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -19,7 +19,7 @@ package im.vector.matrix.android.api.failure
|
||||
import java.io.IOException
|
||||
|
||||
/**
|
||||
* This class allows to expose differents kind of error to be then handled by the application.
|
||||
* This class allows to expose different kinds of error to be then handled by the application.
|
||||
* As it is a sealed class, you typically use it like that :
|
||||
* when(failure) {
|
||||
* is NetworkConnection -> Unit
|
||||
|
@ -33,6 +33,7 @@ data class MatrixError(
|
||||
const val FORBIDDEN = "M_FORBIDDEN"
|
||||
const val UNKNOWN = "M_UNKNOWN"
|
||||
const val UNKNOWN_TOKEN = "M_UNKNOWN_TOKEN"
|
||||
const val MISSING_TOKEN = "M_MISSING_TOKEN"
|
||||
const val BAD_JSON = "M_BAD_JSON"
|
||||
const val NOT_JSON = "M_NOT_JSON"
|
||||
const val NOT_FOUND = "M_NOT_FOUND"
|
||||
|
@ -89,6 +89,4 @@ object MatrixLinkify {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
@ -18,15 +18,28 @@ package im.vector.matrix.android.api.session
|
||||
|
||||
import androidx.annotation.MainThread
|
||||
import im.vector.matrix.android.api.auth.data.SessionParams
|
||||
import im.vector.matrix.android.api.session.cache.CacheService
|
||||
import im.vector.matrix.android.api.session.content.ContentUploadStateTracker
|
||||
import im.vector.matrix.android.api.session.content.ContentUrlResolver
|
||||
import im.vector.matrix.android.api.session.crypto.CryptoService
|
||||
import im.vector.matrix.android.api.session.group.GroupService
|
||||
import im.vector.matrix.android.api.session.room.RoomService
|
||||
import im.vector.matrix.android.api.session.signout.SignOutService
|
||||
import im.vector.matrix.android.api.session.sync.FilterService
|
||||
import im.vector.matrix.android.api.session.user.UserService
|
||||
|
||||
/**
|
||||
* This interface defines interactions with a session.
|
||||
* An instance of a session will be provided by the SDK.
|
||||
*/
|
||||
interface Session : RoomService, GroupService {
|
||||
interface Session :
|
||||
RoomService,
|
||||
GroupService,
|
||||
UserService,
|
||||
CryptoService,
|
||||
CacheService,
|
||||
SignOutService,
|
||||
FilterService {
|
||||
|
||||
/**
|
||||
* The params associated to the session
|
||||
@ -39,6 +52,18 @@ interface Session : RoomService, GroupService {
|
||||
@MainThread
|
||||
fun open()
|
||||
|
||||
/**
|
||||
* This method start the sync thread.
|
||||
*/
|
||||
@MainThread
|
||||
fun startSync()
|
||||
|
||||
/**
|
||||
* This method stop the sync thread.
|
||||
*/
|
||||
@MainThread
|
||||
fun stopSync()
|
||||
|
||||
/**
|
||||
* This method allow to close a session. It does stop some services.
|
||||
*/
|
||||
@ -50,6 +75,11 @@ interface Session : RoomService, GroupService {
|
||||
*/
|
||||
fun contentUrlResolver(): ContentUrlResolver
|
||||
|
||||
/**
|
||||
* Returns the ContentUploadProgressTracker associated with the session
|
||||
*/
|
||||
fun contentUploadProgressTracker(): ContentUploadStateTracker
|
||||
|
||||
/**
|
||||
* Add a listener to the session.
|
||||
* @param listener the listener to add.
|
||||
@ -65,8 +95,11 @@ interface Session : RoomService, GroupService {
|
||||
/**
|
||||
* A global session listener to get notified for some events.
|
||||
*/
|
||||
// Not used at the moment
|
||||
interface Listener
|
||||
|
||||
interface Listener {
|
||||
/**
|
||||
* The access token is not valid anymore
|
||||
*/
|
||||
fun onInvalidToken()
|
||||
}
|
||||
|
||||
}
|
31
matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/cache/CacheService.kt
vendored
Normal file
@ -0,0 +1,31 @@
|
||||
/*
|
||||
* Copyright 2019 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.matrix.android.api.session.cache
|
||||
|
||||
import im.vector.matrix.android.api.MatrixCallback
|
||||
|
||||
/**
|
||||
* This interface defines a method to sign out. It's implemented at the session level.
|
||||
*/
|
||||
interface CacheService {
|
||||
|
||||
/**
|
||||
* Clear the whole cached data, except credentials. Once done, the session is closed and has to be opened again
|
||||
*/
|
||||
fun clearCache(callback: MatrixCallback<Unit>)
|
||||
|
||||
}
|
@ -0,0 +1,42 @@
|
||||
/*
|
||||
* Copyright 2019 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.matrix.android.api.session.content
|
||||
|
||||
import android.os.Parcelable
|
||||
import kotlinx.android.parcel.Parcelize
|
||||
|
||||
@Parcelize
|
||||
data class ContentAttachmentData(
|
||||
val size: Long = 0,
|
||||
val duration: Long? = 0,
|
||||
val date: Long = 0,
|
||||
val height: Long? = 0,
|
||||
val width: Long? = 0,
|
||||
val name: String? = null,
|
||||
val path: String,
|
||||
val mimeType: String,
|
||||
val type: Type
|
||||
) : Parcelable {
|
||||
|
||||
enum class Type {
|
||||
FILE,
|
||||
IMAGE,
|
||||
AUDIO,
|
||||
VIDEO
|
||||
}
|
||||
|
||||
}
|
@ -14,24 +14,29 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package im.vector.riotredesign.features.home.group
|
||||
package im.vector.matrix.android.api.session.content
|
||||
|
||||
import arrow.core.Option
|
||||
import com.jakewharton.rxrelay2.BehaviorRelay
|
||||
import im.vector.matrix.android.api.session.group.model.GroupSummary
|
||||
import io.reactivex.Observable
|
||||
interface ContentUploadStateTracker {
|
||||
|
||||
class SelectedGroupHolder {
|
||||
fun track(key: String, updateListener: UpdateListener)
|
||||
|
||||
private val selectedGroupStream = BehaviorRelay.createDefault<Option<GroupSummary>>(Option.empty())
|
||||
fun untrack(key: String, updateListener: UpdateListener)
|
||||
|
||||
fun setSelectedGroup(group: GroupSummary?) {
|
||||
val optionValue = Option.fromNullable(group)
|
||||
selectedGroupStream.accept(optionValue)
|
||||
fun setFailure(key: String)
|
||||
|
||||
fun setSuccess(key: String)
|
||||
|
||||
fun setProgress(key: String, current: Long, total: Long)
|
||||
|
||||
interface UpdateListener {
|
||||
fun onUpdate(state: State)
|
||||
}
|
||||
|
||||
fun selectedGroup(): Observable<Option<GroupSummary>> {
|
||||
return selectedGroupStream.hide()
|
||||
sealed class State {
|
||||
object Idle : State()
|
||||
data class ProgressData(val current: Long, val total: Long) : State()
|
||||
object Success : State()
|
||||
object Failure : State()
|
||||
}
|
||||
|
||||
|
@ -0,0 +1,23 @@
|
||||
/*
|
||||
* Copyright 2019 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.matrix.android.api.session.crypto
|
||||
|
||||
interface CryptoService {
|
||||
|
||||
// Not supported for the moment
|
||||
fun isCryptoEnabled() = false
|
||||
}
|
@ -0,0 +1,42 @@
|
||||
/*
|
||||
* Copyright 2019 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.matrix.android.api.session.events.model
|
||||
|
||||
import com.squareup.moshi.JsonClass
|
||||
|
||||
/**
|
||||
* <code>
|
||||
* {
|
||||
* "chunk": [
|
||||
* {
|
||||
* "type": "m.reaction",
|
||||
* "key": "👍",
|
||||
* "count": 3
|
||||
* }
|
||||
* ],
|
||||
* "limited": false,
|
||||
* "count": 1
|
||||
* },
|
||||
* </code>
|
||||
*/
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class AggregatedAnnotation (
|
||||
override val limited: Boolean? = false,
|
||||
override val count: Int? = 0,
|
||||
val chunk: List<RelationChunkInfo>? = null
|
||||
|
||||
) : UnsignedRelationInfo
|
@ -0,0 +1,53 @@
|
||||
/*
|
||||
* Copyright 2019 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.matrix.android.api.session.events.model
|
||||
|
||||
import com.squareup.moshi.Json
|
||||
import com.squareup.moshi.JsonClass
|
||||
|
||||
/**
|
||||
* <code>
|
||||
* {
|
||||
* "m.annotation": {
|
||||
* "chunk": [
|
||||
* {
|
||||
* "type": "m.reaction",
|
||||
* "key": "👍",
|
||||
* "count": 3
|
||||
* }
|
||||
* ],
|
||||
* "limited": false,
|
||||
* "count": 1
|
||||
* },
|
||||
* "m.reference": {
|
||||
* "chunk": [
|
||||
* {
|
||||
* "type": "m.room.message",
|
||||
* "event_id": "$some_event_id"
|
||||
* }
|
||||
* ],
|
||||
* "limited": false,
|
||||
* "count": 1
|
||||
* }
|
||||
* }
|
||||
* </code>
|
||||
*/
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class AggregatedRelations(
|
||||
@Json(name = "m.annotation") val annotations: AggregatedAnnotation? = null,
|
||||
@Json(name = "m.reference") val references: DefaultUnsignedRelationInfo? = null
|
||||
)
|
@ -0,0 +1,26 @@
|
||||
/*
|
||||
* Copyright 2019 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.matrix.android.api.session.events.model
|
||||
|
||||
import com.squareup.moshi.JsonClass
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class DefaultUnsignedRelationInfo(
|
||||
override val limited: Boolean? = false,
|
||||
override val count: Int? = 0,
|
||||
val chunk: List<Map<String, Any>>? = null
|
||||
|
||||
) : UnsignedRelationInfo
|
@ -35,6 +35,18 @@ inline fun <reified T> Content?.toModel(): T? {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This methods is a facility method to map a model to a json Content
|
||||
*/
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
inline fun <reified T> T?.toContent(): Content? {
|
||||
return this?.let {
|
||||
val moshi = MoshiProvider.providesMoshi()
|
||||
val moshiAdapter = moshi.adapter(T::class.java)
|
||||
return moshiAdapter.toJsonValue(it) as Content
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Generic event class with all possible fields for events.
|
||||
* The content and prevContent json fields can easily be mapped to a model with [toModel] method.
|
||||
@ -42,7 +54,7 @@ inline fun <reified T> Content?.toModel(): T? {
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class Event(
|
||||
@Json(name = "type") val type: String,
|
||||
@Json(name = "event_id") val eventId: String?,
|
||||
@Json(name = "event_id") val eventId: String? = null,
|
||||
@Json(name = "content") val content: Content? = null,
|
||||
@Json(name = "prev_content") val prevContent: Content? = null,
|
||||
@Json(name = "origin_server_ts") val originServerTs: Long? = null,
|
||||
|
@ -65,6 +65,11 @@ object EventType {
|
||||
const val CALL_ANSWER = "m.call.answer"
|
||||
const val CALL_HANGUP = "m.call.hangup"
|
||||
|
||||
// Relation Events
|
||||
|
||||
const val REACTION = "m.reaction"
|
||||
|
||||
|
||||
private val STATE_EVENTS = listOf(
|
||||
STATE_ROOM_NAME,
|
||||
STATE_ROOM_TOPIC,
|
||||
@ -86,4 +91,10 @@ object EventType {
|
||||
return STATE_EVENTS.contains(type)
|
||||
}
|
||||
|
||||
fun isCallEvent(type: String): Boolean {
|
||||
return type == CALL_INVITE
|
||||
|| type == CALL_CANDIDATES
|
||||
|| type == CALL_ANSWER
|
||||
|| type == CALL_HANGUP
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,35 @@
|
||||
/*
|
||||
* Copyright 2019 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.matrix.android.api.session.events.model
|
||||
|
||||
import com.squareup.moshi.JsonClass
|
||||
|
||||
/**
|
||||
* <code>
|
||||
* {
|
||||
* "type": "m.reaction",
|
||||
* "key": "👍",
|
||||
* "count": 3
|
||||
* }
|
||||
* </code>
|
||||
*/
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class RelationChunkInfo(
|
||||
val type: String,
|
||||
val key: String,
|
||||
val count: Int
|
||||
)
|
@ -0,0 +1,31 @@
|
||||
/*
|
||||
* Copyright 2019 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.matrix.android.api.session.events.model
|
||||
|
||||
|
||||
/**
|
||||
* Constants defining known event relation types from Matrix specifications.
|
||||
*/
|
||||
object RelationType {
|
||||
|
||||
/** Lets you define an event which annotates an existing event.*/
|
||||
const val ANNOTATION = "m.annotation"
|
||||
/** Lets you define an event which replaces an existing event.*/
|
||||
const val REPLACE = "m.replace"
|
||||
/** ets you define an event which references an existing event.*/
|
||||
const val REFERENCE = "m.reference"
|
||||
|
||||
}
|
@ -24,5 +24,6 @@ data class UnsignedData(
|
||||
@Json(name = "age") val age: Long?,
|
||||
@Json(name = "redacted_because") val redactedEvent: Event? = null,
|
||||
@Json(name = "transaction_id") val transactionId: String? = null,
|
||||
@Json(name = "prev_content") val prevContent: Map<String, Any>? = null
|
||||
@Json(name = "prev_content") val prevContent: Map<String, Any>? = null,
|
||||
@Json(name = "m.relations") val relations: AggregatedRelations? = null
|
||||
)
|
@ -13,10 +13,10 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package im.vector.riotredesign.core.extensions
|
||||
package im.vector.matrix.android.api.session.events.model
|
||||
|
||||
|
||||
fun CharSequence.firstCharAsString(): String {
|
||||
return if (isNotEmpty()) this[0].toString() else ""
|
||||
interface UnsignedRelationInfo {
|
||||
val limited : Boolean?
|
||||
val count: Int?
|
||||
}
|
@ -17,16 +17,18 @@
|
||||
package im.vector.matrix.android.api.session.room
|
||||
|
||||
import androidx.lifecycle.LiveData
|
||||
import im.vector.matrix.android.api.session.room.members.RoomMembersService
|
||||
import im.vector.matrix.android.api.session.room.members.MembershipService
|
||||
import im.vector.matrix.android.api.session.room.model.RoomSummary
|
||||
import im.vector.matrix.android.api.session.room.model.annotation.ReactionService
|
||||
import im.vector.matrix.android.api.session.room.read.ReadService
|
||||
import im.vector.matrix.android.api.session.room.send.SendService
|
||||
import im.vector.matrix.android.api.session.room.state.StateService
|
||||
import im.vector.matrix.android.api.session.room.timeline.TimelineService
|
||||
|
||||
/**
|
||||
* This interface defines methods to interact within a room.
|
||||
*/
|
||||
interface Room : TimelineService, SendService, ReadService, RoomMembersService {
|
||||
interface Room : TimelineService, SendService, ReadService, MembershipService, StateService , ReactionService{
|
||||
|
||||
/**
|
||||
* The roomId of this room
|
||||
@ -39,26 +41,4 @@ interface Room : TimelineService, SendService, ReadService, RoomMembersService {
|
||||
*/
|
||||
val roomSummary: LiveData<RoomSummary>
|
||||
|
||||
/**
|
||||
* Add a listener to the room.
|
||||
* @param listener the listener to add.
|
||||
*/
|
||||
fun addListener(listener: Listener)
|
||||
|
||||
/**
|
||||
* Remove a listener from the room.
|
||||
* @param listener the listener to remove.
|
||||
*/
|
||||
fun removeListener(listener: Listener)
|
||||
|
||||
/**
|
||||
* A listener defined at the room level to listen for some events from the different room services.
|
||||
*/
|
||||
interface Listener : ReadService.Listener {
|
||||
|
||||
override fun onReadReceiptsUpdated() {
|
||||
// no-op
|
||||
}
|
||||
}
|
||||
|
||||
}
|