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
|
/build
|
||||||
/captures
|
/captures
|
||||||
.externalNativeBuild
|
.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.
|
// Top-level build file where you can add configuration options common to all sub-projects/modules.
|
||||||
|
|
||||||
buildscript {
|
buildscript {
|
||||||
ext.kotlin_version = '1.3.11'
|
ext.kotlin_version = '1.3.21'
|
||||||
ext.koin_version = '1.0.2'
|
ext.koin_version = '1.0.2'
|
||||||
repositories {
|
repositories {
|
||||||
google()
|
google()
|
||||||
jcenter()
|
jcenter()
|
||||||
|
maven {
|
||||||
|
url "https://plugins.gradle.org/m2/"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
dependencies {
|
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 "com.airbnb.okreplay:gradle-plugin:1.4.0"
|
||||||
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
|
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
|
// NOTE: Do not place your application dependencies here; they belong
|
||||||
// in the individual module build.gradle files
|
// in the individual module build.gradle files
|
||||||
@ -19,12 +24,42 @@ buildscript {
|
|||||||
|
|
||||||
allprojects {
|
allprojects {
|
||||||
repositories {
|
repositories {
|
||||||
|
maven { url "http://dl.bintray.com/piasy/maven" }
|
||||||
|
maven { url 'https://jitpack.io' }
|
||||||
|
maven { url 'https://oss.sonatype.org/content/repositories/snapshots/' }
|
||||||
google()
|
google()
|
||||||
jcenter()
|
jcenter()
|
||||||
maven { url 'https://jitpack.io' }
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
task clean(type: Delete) {
|
task clean(type: Delete) {
|
||||||
delete rootProject.buildDir
|
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
|
distributionBase=GRADLE_USER_HOME
|
||||||
distributionPath=wrapper/dists
|
distributionPath=wrapper/dists
|
||||||
zipStoreBase=GRADLE_USER_HOME
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
zipStorePath=wrapper/dists
|
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"`
|
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.
|
# 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.
|
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
||||||
MAX_FD="maximum"
|
MAX_FD="maximum"
|
||||||
|
2
gradlew.bat
vendored
@ -14,7 +14,7 @@ set APP_BASE_NAME=%~n0
|
|||||||
set APP_HOME=%DIRNAME%
|
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.
|
@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
|
@rem Find java.exe
|
||||||
if defined JAVA_HOME goto findJavaFromJavaHome
|
if defined JAVA_HOME goto findJavaFromJavaHome
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
|
|
||||||
/*
|
/*
|
||||||
* Copyright 2019 New Vector Ltd
|
* Copyright 2019 New Vector Ltd
|
||||||
*
|
*
|
||||||
@ -18,9 +17,7 @@
|
|||||||
package im.vector.matrix.rx
|
package im.vector.matrix.rx
|
||||||
|
|
||||||
import im.vector.matrix.android.api.session.room.Room
|
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.model.RoomSummary
|
||||||
import im.vector.matrix.android.api.session.room.timeline.TimelineData
|
|
||||||
import io.reactivex.Observable
|
import io.reactivex.Observable
|
||||||
|
|
||||||
class RxRoom(private val room: Room) {
|
class RxRoom(private val room: Room) {
|
||||||
@ -29,12 +26,8 @@ class RxRoom(private val room: Room) {
|
|||||||
return room.roomSummary.asObservable()
|
return room.roomSummary.asObservable()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun timeline(eventId: String? = null): Observable<TimelineData> {
|
fun liveRoomMemberIds(): Observable<List<String>> {
|
||||||
return room.timeline(eventId).asObservable()
|
return room.getRoomMemberIdsLive().asObservable()
|
||||||
}
|
|
||||||
|
|
||||||
fun liveReadReceipts(): Observable<List<ReadReceipt>> {
|
|
||||||
return room.readReceipts().asObservable()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
apply plugin: 'com.android.library'
|
apply plugin: 'com.android.library'
|
||||||
apply plugin: 'kotlin-android'
|
apply plugin: 'kotlin-android'
|
||||||
apply plugin: 'kotlin-kapt'
|
apply plugin: 'kotlin-kapt'
|
||||||
|
apply plugin: 'kotlin-android-extensions'
|
||||||
apply plugin: 'realm-android'
|
apply plugin: 'realm-android'
|
||||||
apply plugin: 'okreplay'
|
apply plugin: 'okreplay'
|
||||||
|
|
||||||
@ -19,6 +20,10 @@ repositories {
|
|||||||
jcenter()
|
jcenter()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
androidExtensions {
|
||||||
|
experimental = true
|
||||||
|
}
|
||||||
|
|
||||||
android {
|
android {
|
||||||
compileSdkVersion 28
|
compileSdkVersion 28
|
||||||
testOptions.unitTests.includeAndroidResources = true
|
testOptions.unitTests.includeAndroidResources = true
|
||||||
@ -28,12 +33,28 @@ android {
|
|||||||
targetSdkVersion 28
|
targetSdkVersion 28
|
||||||
versionCode 1
|
versionCode 1
|
||||||
versionName "1.0"
|
versionName "1.0"
|
||||||
|
multiDexEnabled true
|
||||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
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 {
|
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 {
|
release {
|
||||||
|
buildConfigField "boolean", "LOG_PRIVATE_DATA", "false"
|
||||||
|
buildConfigField "okhttp3.logging.HttpLoggingInterceptor.Level", "OKHTTP_LOGGING_LEVEL", "okhttp3.logging.HttpLoggingInterceptor.Level.NONE"
|
||||||
|
|
||||||
minifyEnabled false
|
minifyEnabled false
|
||||||
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
|
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
|
||||||
}
|
}
|
||||||
@ -42,12 +63,31 @@ android {
|
|||||||
adbOptions {
|
adbOptions {
|
||||||
installOptions "-g"
|
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 {
|
dependencies {
|
||||||
|
|
||||||
def arrow_version = "0.8.0"
|
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 moshi_version = '1.8.0'
|
||||||
def lifecycle_version = '2.0.0'
|
def lifecycle_version = '2.0.0'
|
||||||
def coroutines_version = "1.0.1"
|
def coroutines_version = "1.0.1"
|
||||||
@ -66,7 +106,7 @@ dependencies {
|
|||||||
// Network
|
// Network
|
||||||
implementation 'com.squareup.retrofit2:retrofit:2.4.0'
|
implementation 'com.squareup.retrofit2:retrofit:2.4.0'
|
||||||
implementation 'com.squareup.retrofit2:converter-moshi: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.squareup.okhttp3:logging-interceptor:3.10.0'
|
||||||
implementation 'com.novoda:merlin:1.1.6'
|
implementation 'com.novoda:merlin:1.1.6'
|
||||||
implementation "com.squareup.moshi:moshi-adapters:$moshi_version"
|
implementation "com.squareup.moshi:moshi-adapters:$moshi_version"
|
||||||
@ -76,11 +116,8 @@ dependencies {
|
|||||||
implementation 'com.github.Zhuinden:realm-monarchy:0.5.1'
|
implementation 'com.github.Zhuinden:realm-monarchy:0.5.1'
|
||||||
kapt 'dk.ilios:realmfieldnameshelper:1.1.1'
|
kapt 'dk.ilios:realmfieldnameshelper:1.1.1'
|
||||||
|
|
||||||
// Paging
|
|
||||||
implementation 'androidx.paging:paging-runtime:2.0.0'
|
|
||||||
|
|
||||||
// Work
|
// Work
|
||||||
implementation "android.arch.work:work-runtime-ktx:1.0.0-beta02"
|
implementation "android.arch.work:work-runtime-ktx:1.0.0"
|
||||||
|
|
||||||
// FP
|
// FP
|
||||||
implementation "io.arrow-kt:arrow-core:$arrow_version"
|
implementation "io.arrow-kt:arrow-core:$arrow_version"
|
||||||
@ -95,6 +132,7 @@ dependencies {
|
|||||||
|
|
||||||
// Logging
|
// Logging
|
||||||
implementation 'com.jakewharton.timber:timber:4.7.1'
|
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'
|
debugImplementation 'com.airbnb.okreplay:okreplay:1.4.0'
|
||||||
releaseImplementation 'com.airbnb.okreplay:noop:1.4.0'
|
releaseImplementation 'com.airbnb.okreplay:noop:1.4.0'
|
||||||
@ -103,20 +141,20 @@ dependencies {
|
|||||||
testImplementation 'junit:junit:4.12'
|
testImplementation 'junit:junit:4.12'
|
||||||
testImplementation 'org.robolectric:robolectric:4.0.2'
|
testImplementation 'org.robolectric:robolectric:4.0.2'
|
||||||
testImplementation "org.koin:koin-test:$koin_version"
|
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 "io.mockk:mockk:1.8.13.kotlin13"
|
||||||
testImplementation 'org.amshove.kluent:kluent-android:1.44'
|
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"
|
testImplementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutines_version"
|
||||||
|
|
||||||
androidTestImplementation "org.koin:koin-test:$koin_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:runner:1.1.1'
|
||||||
androidTestImplementation 'androidx.test:rules: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 'androidx.test.espresso:espresso-core:3.1.1'
|
||||||
androidTestImplementation 'org.amshove.kluent:kluent-android:1.44'
|
androidTestImplementation 'org.amshove.kluent:kluent-android:1.44'
|
||||||
androidTestImplementation "io.mockk:mockk-android:1.8.13.kotlin13"
|
androidTestImplementation "io.mockk:mockk-android:1.8.13.kotlin13"
|
||||||
androidTestImplementation "androidx.arch.core:core-testing:$lifecycle_version"
|
androidTestImplementation "androidx.arch.core:core-testing:$lifecycle_version"
|
||||||
androidTestImplementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutines_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
|
package im.vector.matrix.android.session.room.timeline
|
||||||
|
|
||||||
|
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||||
import com.zhuinden.monarchy.Monarchy
|
import com.zhuinden.monarchy.Monarchy
|
||||||
import im.vector.matrix.android.InstrumentedTest
|
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.add
|
||||||
import im.vector.matrix.android.internal.database.helper.addAll
|
import im.vector.matrix.android.internal.database.helper.addAll
|
||||||
import im.vector.matrix.android.internal.database.helper.isUnlinked
|
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.helper.merge
|
||||||
import im.vector.matrix.android.internal.database.model.ChunkEntity
|
import im.vector.matrix.android.internal.database.model.ChunkEntity
|
||||||
import im.vector.matrix.android.internal.session.room.timeline.PaginationDirection
|
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.Realm
|
||||||
import io.realm.RealmConfiguration
|
import io.realm.RealmConfiguration
|
||||||
import io.realm.kotlin.createObject
|
import io.realm.kotlin.createObject
|
||||||
@ -35,9 +37,10 @@ import org.amshove.kluent.shouldBeTrue
|
|||||||
import org.amshove.kluent.shouldEqual
|
import org.amshove.kluent.shouldEqual
|
||||||
import org.junit.Before
|
import org.junit.Before
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
import kotlin.random.Random
|
import org.junit.runner.RunWith
|
||||||
|
|
||||||
|
|
||||||
|
@RunWith(AndroidJUnit4::class)
|
||||||
internal class ChunkEntityTest : InstrumentedTest {
|
internal class ChunkEntityTest : InstrumentedTest {
|
||||||
|
|
||||||
private lateinit var monarchy: Monarchy
|
private lateinit var monarchy: Monarchy
|
||||||
@ -54,7 +57,7 @@ internal class ChunkEntityTest : InstrumentedTest {
|
|||||||
fun add_shouldAdd_whenNotAlreadyIncluded() {
|
fun add_shouldAdd_whenNotAlreadyIncluded() {
|
||||||
monarchy.runTransactionSync { realm ->
|
monarchy.runTransactionSync { realm ->
|
||||||
val chunk: ChunkEntity = realm.createObject()
|
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
|
chunk.events.size shouldEqual 1
|
||||||
}
|
}
|
||||||
@ -64,7 +67,7 @@ internal class ChunkEntityTest : InstrumentedTest {
|
|||||||
fun add_shouldNotAdd_whenAlreadyIncluded() {
|
fun add_shouldNotAdd_whenAlreadyIncluded() {
|
||||||
monarchy.runTransactionSync { realm ->
|
monarchy.runTransactionSync { realm ->
|
||||||
val chunk: ChunkEntity = realm.createObject()
|
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.add("roomId", fakeEvent, PaginationDirection.FORWARDS)
|
chunk.add("roomId", fakeEvent, PaginationDirection.FORWARDS)
|
||||||
chunk.events.size shouldEqual 1
|
chunk.events.size shouldEqual 1
|
||||||
@ -75,7 +78,7 @@ internal class ChunkEntityTest : InstrumentedTest {
|
|||||||
fun add_shouldStateIndexIncremented_whenStateEventIsAddedForward() {
|
fun add_shouldStateIndexIncremented_whenStateEventIsAddedForward() {
|
||||||
monarchy.runTransactionSync { realm ->
|
monarchy.runTransactionSync { realm ->
|
||||||
val chunk: ChunkEntity = realm.createObject()
|
val chunk: ChunkEntity = realm.createObject()
|
||||||
val fakeEvent = createFakeEvent(true)
|
val fakeEvent = createFakeRoomMemberEvent()
|
||||||
chunk.add("roomId", fakeEvent, PaginationDirection.FORWARDS)
|
chunk.add("roomId", fakeEvent, PaginationDirection.FORWARDS)
|
||||||
chunk.lastStateIndex(PaginationDirection.FORWARDS) shouldEqual 1
|
chunk.lastStateIndex(PaginationDirection.FORWARDS) shouldEqual 1
|
||||||
}
|
}
|
||||||
@ -85,7 +88,7 @@ internal class ChunkEntityTest : InstrumentedTest {
|
|||||||
fun add_shouldStateIndexNotIncremented_whenNoStateEventIsAdded() {
|
fun add_shouldStateIndexNotIncremented_whenNoStateEventIsAdded() {
|
||||||
monarchy.runTransactionSync { realm ->
|
monarchy.runTransactionSync { realm ->
|
||||||
val chunk: ChunkEntity = realm.createObject()
|
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.lastStateIndex(PaginationDirection.FORWARDS) shouldEqual 0
|
chunk.lastStateIndex(PaginationDirection.FORWARDS) shouldEqual 0
|
||||||
}
|
}
|
||||||
@ -134,13 +137,13 @@ internal class ChunkEntityTest : InstrumentedTest {
|
|||||||
val chunk2: ChunkEntity = realm.createObject()
|
val chunk2: ChunkEntity = realm.createObject()
|
||||||
val eventsForChunk1 = createFakeListOfEvents(30)
|
val eventsForChunk1 = createFakeListOfEvents(30)
|
||||||
val eventsForChunk2 = eventsForChunk1 + createFakeListOfEvents(10)
|
val eventsForChunk2 = eventsForChunk1 + createFakeListOfEvents(10)
|
||||||
chunk1.isLast = true
|
chunk1.isLastForward = true
|
||||||
chunk2.isLast = false
|
chunk2.isLastForward = false
|
||||||
chunk1.addAll("roomId", eventsForChunk1, PaginationDirection.FORWARDS)
|
chunk1.addAll("roomId", eventsForChunk1, PaginationDirection.FORWARDS)
|
||||||
chunk2.addAll("roomId", eventsForChunk2, PaginationDirection.BACKWARDS)
|
chunk2.addAll("roomId", eventsForChunk2, PaginationDirection.BACKWARDS)
|
||||||
chunk1.merge("roomId", chunk2, PaginationDirection.BACKWARDS)
|
chunk1.merge("roomId", chunk2, PaginationDirection.BACKWARDS)
|
||||||
chunk1.events.size shouldEqual 40
|
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 {
|
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 fakeEvents = RoomDataHelper.createFakeListOfEvents(30)
|
||||||
val tokenChunkEvent = FakeTokenChunkEvent(
|
val tokenChunkEvent = FakeTokenChunkEvent(
|
||||||
Random.nextLong(System.currentTimeMillis()).toString(),
|
Random.nextLong(System.currentTimeMillis()).toString(),
|
||||||
@ -33,7 +33,6 @@ internal class FakeGetContextOfEventTask(private val tokenChunkEventPersistor: T
|
|||||||
fakeEvents
|
fakeEvents
|
||||||
)
|
)
|
||||||
return tokenChunkEventPersistor.insertInDb(tokenChunkEvent, params.roomId, PaginationDirection.BACKWARDS)
|
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 arrow.core.Try
|
||||||
import im.vector.matrix.android.internal.session.room.timeline.PaginationTask
|
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 im.vector.matrix.android.internal.session.room.timeline.TokenChunkEventPersistor
|
||||||
import kotlin.random.Random
|
import kotlin.random.Random
|
||||||
|
|
||||||
internal class FakePaginationTask(private val tokenChunkEventPersistor: TokenChunkEventPersistor) : PaginationTask {
|
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 fakeEvents = RoomDataHelper.createFakeListOfEvents(30)
|
||||||
val tokenChunkEvent = FakeTokenChunkEvent(params.from, Random.nextLong(System.currentTimeMillis()).toString(), fakeEvents)
|
val tokenChunkEvent = FakeTokenChunkEvent(params.from, Random.nextLong(System.currentTimeMillis()).toString(), fakeEvents)
|
||||||
return tokenChunkEventPersistor.insertInDb(tokenChunkEvent, params.roomId, params.direction)
|
return tokenChunkEventPersistor.insertInDb(tokenChunkEvent, params.roomId, params.direction)
|
||||||
.map { tokenChunkEvent }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -17,9 +17,14 @@
|
|||||||
package im.vector.matrix.android.session.room.timeline
|
package im.vector.matrix.android.session.room.timeline
|
||||||
|
|
||||||
import com.zhuinden.monarchy.Monarchy
|
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.Event
|
||||||
import im.vector.matrix.android.api.session.events.model.EventType
|
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.addAll
|
||||||
import im.vector.matrix.android.internal.database.helper.addOrUpdate
|
import im.vector.matrix.android.internal.database.helper.addOrUpdate
|
||||||
import im.vector.matrix.android.internal.database.model.ChunkEntity
|
import im.vector.matrix.android.internal.database.model.ChunkEntity
|
||||||
@ -30,27 +35,56 @@ import kotlin.random.Random
|
|||||||
|
|
||||||
object RoomDataHelper {
|
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> {
|
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 {
|
fun createFakeEvent(type: String,
|
||||||
val eventId = Random.nextLong(System.currentTimeMillis()).toString()
|
content: Content? = null,
|
||||||
val type = if (asStateEvent) EventType.STATE_ROOM_NAME else EventType.MESSAGE
|
prevContent: Content? = null,
|
||||||
return Event(type, eventId)
|
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) {
|
fun fakeInitialSync(monarchy: Monarchy, roomId: String) {
|
||||||
monarchy.runTransactionSync { realm ->
|
monarchy.runTransactionSync { realm ->
|
||||||
val roomEntity = realm.createObject<RoomEntity>(roomId)
|
val roomEntity = realm.createObject<RoomEntity>(roomId)
|
||||||
roomEntity.membership = MyMembership.JOINED
|
roomEntity.membership = Membership.JOIN
|
||||||
val eventList = createFakeListOfEvents(30)
|
val eventList = createFakeListOfEvents(10)
|
||||||
val chunkEntity = realm.createObject<ChunkEntity>().apply {
|
val chunkEntity = realm.createObject<ChunkEntity>().apply {
|
||||||
nextToken = null
|
nextToken = null
|
||||||
prevToken = Random.nextLong(System.currentTimeMillis()).toString()
|
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)
|
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 com.zhuinden.monarchy.Monarchy
|
||||||
import im.vector.matrix.android.api.auth.Authenticator
|
import im.vector.matrix.android.api.auth.Authenticator
|
||||||
import im.vector.matrix.android.api.session.Session
|
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.auth.AuthModule
|
||||||
import im.vector.matrix.android.internal.di.MatrixKoinComponent
|
import im.vector.matrix.android.internal.di.MatrixKoinComponent
|
||||||
import im.vector.matrix.android.internal.di.MatrixKoinHolder
|
import im.vector.matrix.android.internal.di.MatrixKoinHolder
|
||||||
import im.vector.matrix.android.internal.di.MatrixModule
|
import im.vector.matrix.android.internal.di.MatrixModule
|
||||||
import im.vector.matrix.android.internal.di.NetworkModule
|
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 im.vector.matrix.android.internal.util.BackgroundDetectionObserver
|
||||||
import org.koin.standalone.inject
|
import org.koin.standalone.inject
|
||||||
import java.util.concurrent.atomic.AtomicBoolean
|
import java.util.concurrent.atomic.AtomicBoolean
|
||||||
@ -38,8 +40,9 @@ import java.util.concurrent.atomic.AtomicBoolean
|
|||||||
class Matrix private constructor(context: Context) : MatrixKoinComponent {
|
class Matrix private constructor(context: Context) : MatrixKoinComponent {
|
||||||
|
|
||||||
private val authenticator by inject<Authenticator>()
|
private val authenticator by inject<Authenticator>()
|
||||||
|
private val userAgentHolder by inject<UserAgentHolder>()
|
||||||
private val backgroundDetectionObserver by inject<BackgroundDetectionObserver>()
|
private val backgroundDetectionObserver by inject<BackgroundDetectionObserver>()
|
||||||
lateinit var currentSession: Session
|
var currentSession: Session? = null
|
||||||
|
|
||||||
init {
|
init {
|
||||||
Monarchy.init(context)
|
Monarchy.init(context)
|
||||||
@ -48,10 +51,11 @@ class Matrix private constructor(context: Context) : MatrixKoinComponent {
|
|||||||
val authModule = AuthModule().definition
|
val authModule = AuthModule().definition
|
||||||
MatrixKoinHolder.instance.loadModules(listOf(matrixModule, networkModule, authModule))
|
MatrixKoinHolder.instance.loadModules(listOf(matrixModule, networkModule, authModule))
|
||||||
ProcessLifecycleOwner.get().lifecycle.addObserver(backgroundDetectionObserver)
|
ProcessLifecycleOwner.get().lifecycle.addObserver(backgroundDetectionObserver)
|
||||||
val lastActiveSession = authenticator.getLastActiveSession()
|
authenticator.getLastActiveSession()?.also {
|
||||||
if (lastActiveSession != null) {
|
currentSession = it
|
||||||
currentSession = lastActiveSession
|
it.open()
|
||||||
currentSession.open()
|
it.setFilter(FilterService.FilterPreset.RiotFilter)
|
||||||
|
it.startSync()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -59,6 +63,15 @@ class Matrix private constructor(context: Context) : MatrixKoinComponent {
|
|||||||
return authenticator
|
return authenticator
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set application flavor, to alter user agent.
|
||||||
|
*/
|
||||||
|
fun setApplicationFlavor(flavor: String) {
|
||||||
|
userAgentHolder.setApplicationFlavor(flavor)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getUserAgent() = userAgentHolder.userAgent
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
private lateinit var instance: Matrix
|
private lateinit var instance: Matrix
|
||||||
private val isInit = AtomicBoolean(false)
|
private val isInit = AtomicBoolean(false)
|
||||||
|
@ -24,7 +24,7 @@ interface MatrixCallback<in T> {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* On success method, default to no-op
|
* 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) {
|
fun onSuccess(data: T) {
|
||||||
//no-op
|
//no-op
|
||||||
|
@ -44,6 +44,10 @@ object MatrixPatterns {
|
|||||||
private val MATRIX_EVENT_IDENTIFIER_REGEX = "\\$[A-Z0-9]+$DOMAIN_REGEX"
|
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)
|
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.
|
// regex pattern to find group ids in a string.
|
||||||
private val MATRIX_GROUP_IDENTIFIER_REGEX = "\\+[A-Z0-9=_\\-./]+$DOMAIN_REGEX"
|
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)
|
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.
|
* @return true if the string is a valid event id.
|
||||||
*/
|
*/
|
||||||
fun isEventId(str: String?): Boolean {
|
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
|
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 :
|
* As it is a sealed class, you typically use it like that :
|
||||||
* when(failure) {
|
* when(failure) {
|
||||||
* is NetworkConnection -> Unit
|
* is NetworkConnection -> Unit
|
||||||
|
@ -33,6 +33,7 @@ data class MatrixError(
|
|||||||
const val FORBIDDEN = "M_FORBIDDEN"
|
const val FORBIDDEN = "M_FORBIDDEN"
|
||||||
const val UNKNOWN = "M_UNKNOWN"
|
const val UNKNOWN = "M_UNKNOWN"
|
||||||
const val UNKNOWN_TOKEN = "M_UNKNOWN_TOKEN"
|
const val UNKNOWN_TOKEN = "M_UNKNOWN_TOKEN"
|
||||||
|
const val MISSING_TOKEN = "M_MISSING_TOKEN"
|
||||||
const val BAD_JSON = "M_BAD_JSON"
|
const val BAD_JSON = "M_BAD_JSON"
|
||||||
const val NOT_JSON = "M_NOT_JSON"
|
const val NOT_JSON = "M_NOT_JSON"
|
||||||
const val NOT_FOUND = "M_NOT_FOUND"
|
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 androidx.annotation.MainThread
|
||||||
import im.vector.matrix.android.api.auth.data.SessionParams
|
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.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.group.GroupService
|
||||||
import im.vector.matrix.android.api.session.room.RoomService
|
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.
|
* This interface defines interactions with a session.
|
||||||
* An instance of a session will be provided by the SDK.
|
* 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
|
* The params associated to the session
|
||||||
@ -39,6 +52,18 @@ interface Session : RoomService, GroupService {
|
|||||||
@MainThread
|
@MainThread
|
||||||
fun open()
|
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.
|
* This method allow to close a session. It does stop some services.
|
||||||
*/
|
*/
|
||||||
@ -50,6 +75,11 @@ interface Session : RoomService, GroupService {
|
|||||||
*/
|
*/
|
||||||
fun contentUrlResolver(): ContentUrlResolver
|
fun contentUrlResolver(): ContentUrlResolver
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the ContentUploadProgressTracker associated with the session
|
||||||
|
*/
|
||||||
|
fun contentUploadProgressTracker(): ContentUploadStateTracker
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Add a listener to the session.
|
* Add a listener to the session.
|
||||||
* @param listener the listener to add.
|
* @param listener the listener to add.
|
||||||
@ -65,8 +95,11 @@ interface Session : RoomService, GroupService {
|
|||||||
/**
|
/**
|
||||||
* A global session listener to get notified for some events.
|
* 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.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package im.vector.riotredesign.features.home.group
|
package im.vector.matrix.android.api.session.content
|
||||||
|
|
||||||
import arrow.core.Option
|
interface ContentUploadStateTracker {
|
||||||
import com.jakewharton.rxrelay2.BehaviorRelay
|
|
||||||
import im.vector.matrix.android.api.session.group.model.GroupSummary
|
|
||||||
import io.reactivex.Observable
|
|
||||||
|
|
||||||
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?) {
|
fun setFailure(key: String)
|
||||||
val optionValue = Option.fromNullable(group)
|
|
||||||
selectedGroupStream.accept(optionValue)
|
fun setSuccess(key: String)
|
||||||
|
|
||||||
|
fun setProgress(key: String, current: Long, total: Long)
|
||||||
|
|
||||||
|
interface UpdateListener {
|
||||||
|
fun onUpdate(state: State)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun selectedGroup(): Observable<Option<GroupSummary>> {
|
sealed class State {
|
||||||
return selectedGroupStream.hide()
|
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.
|
* 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.
|
* 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)
|
@JsonClass(generateAdapter = true)
|
||||||
data class Event(
|
data class Event(
|
||||||
@Json(name = "type") val type: String,
|
@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 = "content") val content: Content? = null,
|
||||||
@Json(name = "prev_content") val prevContent: Content? = null,
|
@Json(name = "prev_content") val prevContent: Content? = null,
|
||||||
@Json(name = "origin_server_ts") val originServerTs: Long? = 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_ANSWER = "m.call.answer"
|
||||||
const val CALL_HANGUP = "m.call.hangup"
|
const val CALL_HANGUP = "m.call.hangup"
|
||||||
|
|
||||||
|
// Relation Events
|
||||||
|
|
||||||
|
const val REACTION = "m.reaction"
|
||||||
|
|
||||||
|
|
||||||
private val STATE_EVENTS = listOf(
|
private val STATE_EVENTS = listOf(
|
||||||
STATE_ROOM_NAME,
|
STATE_ROOM_NAME,
|
||||||
STATE_ROOM_TOPIC,
|
STATE_ROOM_TOPIC,
|
||||||
@ -86,4 +91,10 @@ object EventType {
|
|||||||
return STATE_EVENTS.contains(type)
|
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 = "age") val age: Long?,
|
||||||
@Json(name = "redacted_because") val redactedEvent: Event? = null,
|
@Json(name = "redacted_because") val redactedEvent: Event? = null,
|
||||||
@Json(name = "transaction_id") val transactionId: String? = 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
|
* See the License for the specific language governing permissions and
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
package im.vector.matrix.android.api.session.events.model
|
||||||
package im.vector.riotredesign.core.extensions
|
|
||||||
|
|
||||||
|
|
||||||
fun CharSequence.firstCharAsString(): String {
|
interface UnsignedRelationInfo {
|
||||||
return if (isNotEmpty()) this[0].toString() else ""
|
val limited : Boolean?
|
||||||
|
val count: Int?
|
||||||
}
|
}
|
@ -17,16 +17,18 @@
|
|||||||
package im.vector.matrix.android.api.session.room
|
package im.vector.matrix.android.api.session.room
|
||||||
|
|
||||||
import androidx.lifecycle.LiveData
|
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.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.read.ReadService
|
||||||
import im.vector.matrix.android.api.session.room.send.SendService
|
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
|
import im.vector.matrix.android.api.session.room.timeline.TimelineService
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This interface defines methods to interact within a room.
|
* 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
|
* The roomId of this room
|
||||||
@ -39,26 +41,4 @@ interface Room : TimelineService, SendService, ReadService, RoomMembersService {
|
|||||||
*/
|
*/
|
||||||
val roomSummary: LiveData<RoomSummary>
|
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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|