Merge remote-tracking branch 'origin/develop' into develop

This commit is contained in:
Benoit Marty
2022-10-28 17:27:40 +02:00
95 changed files with 931 additions and 595 deletions
+29
View File
@@ -0,0 +1,29 @@
<component name="InspectionProjectProfileManager">
<profile version="1.0">
<option name="myName" value="Project Default" />
<inspection_tool class="PreviewAnnotationInFunctionWithParameters" enabled="true" level="ERROR" enabled_by_default="true">
<option name="composableFile" value="true" />
</inspection_tool>
<inspection_tool class="PreviewDimensionRespectsLimit" enabled="true" level="WARNING" enabled_by_default="true">
<option name="composableFile" value="true" />
</inspection_tool>
<inspection_tool class="PreviewFontScaleMustBeGreaterThanZero" enabled="true" level="ERROR" enabled_by_default="true">
<option name="composableFile" value="true" />
</inspection_tool>
<inspection_tool class="PreviewMultipleParameterProviders" enabled="true" level="ERROR" enabled_by_default="true">
<option name="composableFile" value="true" />
</inspection_tool>
<inspection_tool class="PreviewMustBeTopLevelFunction" enabled="true" level="ERROR" enabled_by_default="true">
<option name="composableFile" value="true" />
</inspection_tool>
<inspection_tool class="PreviewNeedsComposableAnnotation" enabled="true" level="ERROR" enabled_by_default="true">
<option name="composableFile" value="true" />
</inspection_tool>
<inspection_tool class="PreviewNotSupportedInUnitTestFiles" enabled="true" level="ERROR" enabled_by_default="true">
<option name="composableFile" value="true" />
</inspection_tool>
<inspection_tool class="PreviewPickerAnnotation" enabled="true" level="ERROR" enabled_by_default="true">
<option name="composableFile" value="true" />
</inspection_tool>
</profile>
</component>
+25
View File
@@ -0,0 +1,25 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="RemoteRepositoriesConfiguration">
<remote-repository>
<option name="id" value="central" />
<option name="name" value="Maven Central repository" />
<option name="url" value="https://repo1.maven.org/maven2" />
</remote-repository>
<remote-repository>
<option name="id" value="jboss.community" />
<option name="name" value="JBoss Community repository" />
<option name="url" value="https://repository.jboss.org/nexus/content/repositories/public/" />
</remote-repository>
<remote-repository>
<option name="id" value="MavenRepo" />
<option name="name" value="MavenRepo" />
<option name="url" value="https://repo.maven.apache.org/maven2/" />
</remote-repository>
<remote-repository>
<option name="id" value="Google" />
<option name="name" value="Google" />
<option name="url" value="https://dl.google.com/dl/android/maven2/" />
</remote-repository>
</component>
</project>
+2 -1
View File
@@ -1,6 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<project version="4"> <project version="4">
<component name="ProjectRootManager" version="2" languageLevel="JDK_11" default="true" project-jdk-name="Android Studio default JDK" project-jdk-type="JavaSDK"> <component name="ExternalStorageConfigurationManager" enabled="true" />
<component name="ProjectRootManager" version="2" languageLevel="JDK_11" default="true" project-jdk-name="11" project-jdk-type="JavaSDK">
<output url="file://$PROJECT_DIR$/build/classes" /> <output url="file://$PROJECT_DIR$/build/classes" />
</component> </component>
<component name="ProjectType"> <component name="ProjectType">
+29 -13
View File
@@ -1,6 +1,7 @@
plugins { plugins {
id 'com.android.application' id 'com.android.application'
id 'org.jetbrains.kotlin.android' id 'org.jetbrains.kotlin.android'
id 'com.google.devtools.ksp' version '1.7.20-1.0.7'
} }
android { android {
@@ -36,7 +37,7 @@ android {
compose true compose true
} }
composeOptions { composeOptions {
kotlinCompilerExtensionVersion compose_version kotlinCompilerExtensionVersion "1.3.2"
} }
packagingOptions { packagingOptions {
resources { resources {
@@ -44,6 +45,14 @@ android {
} }
} }
applicationVariants.all { variant ->
kotlin.sourceSets {
getByName(variant.name) {
kotlin.srcDir("build/generated/ksp/${variant.name}/kotlin")
}
}
}
signingConfigs { signingConfigs {
debug { debug {
keyAlias 'androiddebugkey' keyAlias 'androiddebugkey'
@@ -55,19 +64,26 @@ android {
} }
dependencies { dependencies {
implementation project(":libraries:ui:theme") implementation project(":libraries:designsystem")
implementation project(":libraries:ui:screens:login") implementation project(":libraries:matrix")
implementation project(":libraries:ui:screens:roomlist") implementation project(":features:login")
implementation project(":libraries:sdk:matrix") implementation project(":features:roomlist")
implementation 'io.github.raamcosta.compose-destinations:core:1.7.23-beta'
ksp 'io.github.raamcosta.compose-destinations:ksp:1.7.23-beta'
def composeBom = platform('androidx.compose:compose-bom:2022.10.00')
implementation composeBom
androidTestImplementation composeBom
implementation 'androidx.core:core-ktx:1.9.0' implementation 'androidx.core:core-ktx:1.9.0'
implementation "androidx.compose.ui:ui:$compose_version" implementation "androidx.compose.ui:ui"
implementation 'androidx.compose.material3:material3:1.0.0-rc01' implementation 'androidx.compose.material3:material3'
implementation "androidx.compose.ui:ui-tooling-preview:$compose_version" implementation "androidx.compose.ui:ui-tooling-preview"
implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.5.1' implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.5.1'
implementation 'androidx.activity:activity-compose:1.6.0' implementation 'androidx.activity:activity-compose:1.6.1'
debugImplementation "androidx.compose.ui:ui-tooling:$compose_version" implementation 'androidx.lifecycle:lifecycle-viewmodel-compose:2.5.1'
debugImplementation "androidx.compose.ui:ui-test-manifest:$compose_version" debugImplementation "androidx.compose.ui:ui-tooling"
debugImplementation "androidx.compose.ui:ui-test-manifest"
implementation 'com.airbnb.android:mavericks-compose:2.7.0' implementation 'com.airbnb.android:mavericks-compose:3.0.1'
} }
@@ -2,7 +2,7 @@ package io.element.android.x
import android.app.Application import android.app.Application
import com.airbnb.mvrx.Mavericks import com.airbnb.mvrx.Mavericks
import io.element.android.x.sdk.matrix.MatrixInstance import io.element.android.x.matrix.MatrixInstance
class ElementXApplication : Application() { class ElementXApplication : Application() {
@@ -1,41 +1,41 @@
package io.element.android.x package io.element.android.x
import android.content.Intent
import android.os.Bundle import android.os.Bundle
import androidx.activity.ComponentActivity import androidx.activity.ComponentActivity
import androidx.activity.result.contract.ActivityResultContracts import androidx.activity.compose.setContent
import androidx.activity.viewModels import androidx.activity.viewModels
import androidx.lifecycle.lifecycleScope import androidx.compose.runtime.Composable
import io.element.android.x.ui.screen.login.LoginActivity import com.ramcosta.composedestinations.DestinationsNavHost
import io.element.android.x.ui.screen.roomlist.RoomListActivity import com.ramcosta.composedestinations.rememberNavHostEngine
import kotlinx.coroutines.launch import io.element.android.x.designsystem.ElementXTheme
import io.element.android.x.destinations.LoginScreenNavigationDestination
import kotlinx.coroutines.runBlocking
class MainActivity : ComponentActivity() { class MainActivity : ComponentActivity() {
private val launcher =
registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
if (it.resultCode == RESULT_OK) {
// Launch the room Activity and finish
startRoomActivityAndFinish()
} else {
finish()
}
}
private val viewModel: MainViewModel by viewModels() private val viewModel: MainViewModel by viewModels()
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
lifecycleScope.launch { setContent {
if (viewModel.hasSession()) { ElementXTheme {
startRoomActivityAndFinish() MainScreen(viewModel = viewModel)
} else {
launcher.launch(Intent(this@MainActivity, LoginActivity::class.java))
} }
} }
} }
}
private fun startRoomActivityAndFinish() {
startActivity(Intent(this, RoomListActivity::class.java)) @Composable
finish() private fun MainScreen(viewModel: MainViewModel) {
} val engine = rememberNavHostEngine()
val navController = engine.rememberNavController()
val startRoute = runBlocking {
if (!viewModel.hasSession()) LoginScreenNavigationDestination else NavGraphs.root.startRoute
}
DestinationsNavHost(
engine = engine,
navController = navController,
navGraph = NavGraphs.root,
startRoute = startRoute
)
} }
@@ -1,7 +1,7 @@
package io.element.android.x package io.element.android.x
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import io.element.android.x.sdk.matrix.MatrixInstance import io.element.android.x.matrix.MatrixInstance
class MainViewModel : ViewModel() { class MainViewModel : ViewModel() {
private val matrix = MatrixInstance.getInstance() private val matrix = MatrixInstance.getInstance()
@@ -0,0 +1,28 @@
package io.element.android.x
import androidx.compose.runtime.Composable
import com.ramcosta.composedestinations.annotation.Destination
import com.ramcosta.composedestinations.annotation.RootNavGraph
import com.ramcosta.composedestinations.navigation.DestinationsNavigator
import io.element.android.x.destinations.RoomListScreenNavigationDestination
import io.element.android.x.features.login.LoginScreen
import io.element.android.x.features.roomlist.RoomListScreen
@Destination
@Composable
fun LoginScreenNavigation(navigator: DestinationsNavigator) {
LoginScreen(
onLoginWithSuccess = {
navigator.clearBackStack(RoomListScreenNavigationDestination)
}
)
}
@RootNavGraph(start = true)
@Destination
@Composable
fun RoomListScreenNavigation() {
RoomListScreen()
}

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 1.4 KiB

Before

Width:  |  Height:  |  Size: 2.8 KiB

After

Width:  |  Height:  |  Size: 2.8 KiB

Before

Width:  |  Height:  |  Size: 982 B

After

Width:  |  Height:  |  Size: 982 B

Before

Width:  |  Height:  |  Size: 1.7 KiB

After

Width:  |  Height:  |  Size: 1.7 KiB

Before

Width:  |  Height:  |  Size: 1.9 KiB

After

Width:  |  Height:  |  Size: 1.9 KiB

Before

Width:  |  Height:  |  Size: 3.8 KiB

After

Width:  |  Height:  |  Size: 3.8 KiB

Before

Width:  |  Height:  |  Size: 2.8 KiB

After

Width:  |  Height:  |  Size: 2.8 KiB

Before

Width:  |  Height:  |  Size: 5.8 KiB

After

Width:  |  Height:  |  Size: 5.8 KiB

Before

Width:  |  Height:  |  Size: 3.8 KiB

After

Width:  |  Height:  |  Size: 3.8 KiB

Before

Width:  |  Height:  |  Size: 7.6 KiB

After

Width:  |  Height:  |  Size: 7.6 KiB

+2 -6
View File
@@ -1,12 +1,8 @@
buildscript { // Top-level build file where you can add configuration options common to all sub-projects/modules.
ext {
compose_version = '1.3.0-rc01'
}
}// Top-level build file where you can add configuration options common to all sub-projects/modules.
plugins { plugins {
id 'com.android.application' version '7.3.0' apply false id 'com.android.application' version '7.3.0' apply false
id 'com.android.library' version '7.3.0' apply false id 'com.android.library' version '7.3.0' apply false
id 'org.jetbrains.kotlin.android' version '1.7.10' apply false id 'org.jetbrains.kotlin.android' version '1.7.20' apply false
} }
task clean(type: Delete) { task clean(type: Delete) {
+1
View File
@@ -0,0 +1 @@
/build
+18
View File
@@ -0,0 +1,18 @@
plugins {
id("io.element.android-compose")
}
android {
namespace = "io.element.android.x.features.login"
}
dependencies {
implementation(project(":libraries:core"))
implementation(project(":libraries:matrix"))
implementation(project(":libraries:designsystem"))
implementation("com.airbnb.android:mavericks-compose:3.0.1")
testImplementation("junit:junit:4.13.2")
androidTestImplementation("androidx.test.ext:junit:1.1.3")
androidTestImplementation("androidx.test.espresso:espresso-core:3.4.0")
}
View File
+21
View File
@@ -0,0 +1,21 @@
# Add project specific ProGuard rules here.
# You can control the set of applied configuration files using the
# proguardFiles setting in build.gradle.kts.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}
# Uncomment this to preserve the line number information for
# debugging stack traces.
#-keepattributes SourceFile,LineNumberTable
# If you keep the line number information, uncomment this to
# hide the original source file name.
#-renamesourcefileattribute SourceFile
@@ -0,0 +1,24 @@
package io.element.android.x.features.login
import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.ext.junit.runners.AndroidJUnit4
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.Assert.*
/**
* Instrumented test, which will execute on an Android device.
*
* See [testing documentation](http://d.android.com/tools/testing).
*/
@RunWith(AndroidJUnit4::class)
class ExampleInstrumentedTest {
@Test
fun useAppContext() {
// Context of the app under test.
val appContext = InstrumentationRegistry.getInstrumentation().targetContext
assertEquals("io.element.android.x.features.login.test", appContext.packageName)
}
}
@@ -1,4 +1,4 @@
package io.element.android.x.ui.screen.login package io.element.android.x.features.login
sealed interface LoginActions { sealed interface LoginActions {
data class SetHomeserver(val homeserver: String) : LoginActions data class SetHomeserver(val homeserver: String) : LoginActions
@@ -0,0 +1,133 @@
@file:OptIn(ExperimentalMaterial3Api::class)
package io.element.android.x.features.login
import androidx.compose.foundation.Image
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.material3.*
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.text.input.ImeAction
import androidx.compose.ui.text.input.KeyboardType
import androidx.compose.ui.unit.dp
import com.airbnb.mvrx.Fail
import com.airbnb.mvrx.Loading
import com.airbnb.mvrx.Success
import com.airbnb.mvrx.compose.collectAsState
import com.airbnb.mvrx.compose.mavericksViewModel
import io.element.android.x.designsystem.components.VectorButton
@Composable
fun LoginScreen(
viewModel: LoginViewModel = mavericksViewModel(),
onLoginWithSuccess: () -> Unit = { }
) {
val state: LoginViewState by viewModel.collectAsState()
LoginContent(
state = state,
onHomeserverChanged = { viewModel.handle(LoginActions.SetHomeserver(it)) },
onLoginChanged = { viewModel.handle(LoginActions.SetLogin(it)) },
onPasswordChanged = { viewModel.handle(LoginActions.SetPassword(it)) },
onSubmitClicked = { viewModel.handle(LoginActions.Submit) },
onLoginWithSuccess = onLoginWithSuccess
)
}
@Composable
fun LoginContent(
state: LoginViewState,
onHomeserverChanged: (String) -> Unit,
onLoginChanged: (String) -> Unit,
onPasswordChanged: (String) -> Unit,
onSubmitClicked: () -> Unit,
onLoginWithSuccess: () -> Unit
) {
Surface(color = MaterialTheme.colorScheme.background) {
Box(
modifier = Modifier
.fillMaxSize()
.padding(16.dp)
) {
Column(
modifier = Modifier.fillMaxSize(),
) {
val isError = state.isLoggedIn is Fail
Image(
painterResource(id = R.drawable.element_logo_green),
contentDescription = null,
modifier = Modifier
.align(Alignment.CenterHorizontally)
.padding(40.dp)
)
OutlinedTextField(
value = state.homeserver,
modifier = Modifier.fillMaxWidth(),
onValueChange = {
onHomeserverChanged(it)
},
keyboardOptions = KeyboardOptions(
keyboardType = KeyboardType.Uri,
),
)
OutlinedTextField(
value = state.login,
modifier = Modifier
.fillMaxWidth()
.padding(top = 8.dp),
onValueChange = {
onLoginChanged(it)
},
keyboardOptions = KeyboardOptions(
keyboardType = KeyboardType.Text,
),
)
OutlinedTextField(
value = state.password,
modifier = Modifier
.fillMaxWidth()
.padding(top = 8.dp),
onValueChange = {
onPasswordChanged(it)
},
isError = isError,
keyboardOptions = KeyboardOptions(
keyboardType = KeyboardType.Password,
imeAction = ImeAction.Send,
),
)
if (isError) {
Text(
text = (state.isLoggedIn as? Fail)?.toString().orEmpty(),
color = MaterialTheme.colorScheme.error,
style = MaterialTheme.typography.bodySmall,
modifier = Modifier.padding(start = 16.dp)
)
}
VectorButton(
text = "Submit",
onClick = {
onSubmitClicked()
},
enabled = state.submitEnabled,
modifier = Modifier
.align(Alignment.End)
.padding(top = 16.dp)
)
if (state.isLoggedIn is Loading) {
// FIXME This does not work, we never enter this if block
CircularProgressIndicator(
modifier = Modifier.align(Alignment.CenterHorizontally)
)
}
if (state.isLoggedIn is Success) {
onLoginWithSuccess()
}
}
}
}
}
@@ -1,11 +1,11 @@
package io.element.android.x.ui.screen.login package io.element.android.x.features.login
import android.util.Log import android.util.Log
import com.airbnb.mvrx.Fail import com.airbnb.mvrx.Fail
import com.airbnb.mvrx.Loading import com.airbnb.mvrx.Loading
import com.airbnb.mvrx.MavericksViewModel import com.airbnb.mvrx.MavericksViewModel
import com.airbnb.mvrx.Success import com.airbnb.mvrx.Success
import io.element.android.x.sdk.matrix.MatrixInstance import io.element.android.x.matrix.MatrixInstance
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
class LoginViewModel(initialState: LoginViewState) : class LoginViewModel(initialState: LoginViewState) :
@@ -13,28 +13,6 @@ class LoginViewModel(initialState: LoginViewState) :
private val matrix = MatrixInstance.getInstance() private val matrix = MatrixInstance.getInstance()
init {
observeState()
}
private fun observeState() {
onEach(
LoginViewState::homeserver,
LoginViewState::login,
LoginViewState::password,
LoginViewState::isLoggedIn,
) { homeserver, login, password, isLoggedIn ->
setState {
copy(
submitEnabled = homeserver.isNotEmpty() &&
login.isNotEmpty() &&
password.isNotEmpty() &&
isLoggedIn !is Loading
)
}
}
}
fun handle(action: LoginActions) { fun handle(action: LoginActions) {
when (action) { when (action) {
is LoginActions.SetHomeserver -> handleSetHomeserver(action) is LoginActions.SetHomeserver -> handleSetHomeserver(action)
@@ -44,14 +22,6 @@ class LoginViewModel(initialState: LoginViewState) :
} }
} }
private fun handleSetHomeserver(action: LoginActions.SetHomeserver) {
setState {
copy(
homeserver = action.homeserver
)
}
}
private fun handleSubmit() = withState { state -> private fun handleSubmit() = withState { state ->
viewModelScope.launch { viewModelScope.launch {
setState { copy(isLoggedIn = Loading()) } setState { copy(isLoggedIn = Loading()) }
@@ -65,20 +35,15 @@ class LoginViewModel(initialState: LoginViewState) :
} }
} }
private fun handleSetHomeserver(action: LoginActions.SetHomeserver) {
setState { copy(homeserver = action.homeserver) }
}
private fun handleSetPassword(action: LoginActions.SetPassword) { private fun handleSetPassword(action: LoginActions.SetPassword) {
setState { setState { copy(password = action.password) }
copy(
password = action.password
)
}
} }
private fun handleSetName(action: LoginActions.SetLogin) { private fun handleSetName(action: LoginActions.SetLogin) {
setState { setState { copy(login = action.login) }
copy(
login = action.login
)
}
} }
} }
@@ -1,4 +1,4 @@
package io.element.android.x.ui.screen.login package io.element.android.x.features.login
import com.airbnb.mvrx.Async import com.airbnb.mvrx.Async
import com.airbnb.mvrx.MavericksState import com.airbnb.mvrx.MavericksState
@@ -8,6 +8,10 @@ data class LoginViewState(
val homeserver: String = "matrix.org", val homeserver: String = "matrix.org",
val login: String = "", val login: String = "",
val password: String = "", val password: String = "",
val submitEnabled: Boolean = false,
val isLoggedIn: Async<Unit> = Uninitialized, val isLoggedIn: Async<Unit> = Uninitialized,
) : MavericksState ) : MavericksState {
val submitEnabled = homeserver.isNotEmpty() && login.isNotEmpty() && password.isNotEmpty()
}
@@ -0,0 +1,22 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="64dp"
android:height="64dp"
android:viewportWidth="64"
android:viewportHeight="64">
<path
android:pathData="M23.04,3.84C23.04,1.7192 24.7593,0 26.88,0C41.0185,0 52.48,11.4615 52.48,25.6C52.48,27.7208 50.7608,29.44 48.64,29.44C46.5193,29.44 44.8,27.7208 44.8,25.6C44.8,15.7031 36.777,7.68 26.88,7.68C24.7593,7.68 23.04,5.9608 23.04,3.84Z"
android:fillColor="#0DBD8B"
android:fillType="evenOdd"/>
<path
android:pathData="M40.96,60.16C40.96,62.2808 39.2407,64 37.12,64C22.9815,64 11.52,52.5385 11.52,38.4C11.52,36.2792 13.2392,34.56 15.36,34.56C17.4807,34.56 19.2,36.2792 19.2,38.4C19.2,48.2969 27.223,56.32 37.12,56.32C39.2407,56.32 40.96,58.0392 40.96,60.16Z"
android:fillColor="#0DBD8B"
android:fillType="evenOdd"/>
<path
android:pathData="M3.84,40.96C1.7192,40.96 -0,39.2407 -0,37.12C-0,22.9815 11.4615,11.52 25.6,11.52C27.7208,11.52 29.44,13.2392 29.44,15.36C29.44,17.4807 27.7208,19.2 25.6,19.2C15.7031,19.2 7.68,27.223 7.68,37.12C7.68,39.2407 5.9608,40.96 3.84,40.96Z"
android:fillColor="#0DBD8B"
android:fillType="evenOdd"/>
<path
android:pathData="M60.16,23.04C62.2808,23.04 64,24.7593 64,26.88C64,41.0185 52.5385,52.48 38.4,52.48C36.2792,52.48 34.56,50.7608 34.56,48.64C34.56,46.5193 36.2792,44.8 38.4,44.8C48.2969,44.8 56.32,36.777 56.32,26.88C56.32,24.7593 58.0392,23.04 60.16,23.04Z"
android:fillColor="#0DBD8B"
android:fillType="evenOdd"/>
</vector>
@@ -0,0 +1,17 @@
package io.element.android.x.features.login
import org.junit.Test
import org.junit.Assert.*
/**
* Example local unit test, which will execute on the development machine (host).
*
* See [testing documentation](http://d.android.com/tools/testing).
*/
class ExampleUnitTest {
@Test
fun addition_isCorrect() {
assertEquals(4, 2 + 2)
}
}
+1
View File
@@ -0,0 +1 @@
/build
+41
View File
@@ -0,0 +1,41 @@
plugins {
id 'com.android.library'
id 'org.jetbrains.kotlin.android'
}
android {
namespace 'io.element.android.x.features.messages'
compileSdk 32
defaultConfig {
minSdk 24
targetSdk 32
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
consumerProguardFiles "consumer-rules.pro"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
kotlinOptions {
jvmTarget = '1.8'
}
}
dependencies {
implementation 'androidx.core:core-ktx:1.7.0'
implementation 'androidx.appcompat:appcompat:1.4.1'
implementation 'com.google.android.material:material:1.5.0'
testImplementation 'junit:junit:4.13.2'
androidTestImplementation 'androidx.test.ext:junit:1.1.3'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
}
+21
View File
@@ -0,0 +1,21 @@
# Add project specific ProGuard rules here.
# You can control the set of applied configuration files using the
# proguardFiles setting in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}
# Uncomment this to preserve the line number information for
# debugging stack traces.
#-keepattributes SourceFile,LineNumberTable
# If you keep the line number information, uncomment this to
# hide the original source file name.
#-renamesourcefileattribute SourceFile
@@ -0,0 +1,24 @@
package io.element.android.x.features.messages
import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.ext.junit.runners.AndroidJUnit4
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.Assert.*
/**
* Instrumented test, which will execute on an Android device.
*
* See [testing documentation](http://d.android.com/tools/testing).
*/
@RunWith(AndroidJUnit4::class)
class ExampleInstrumentedTest {
@Test
fun useAppContext() {
// Context of the app under test.
val appContext = InstrumentationRegistry.getInstrumentation().targetContext
assertEquals("io.element.android.x.features.messages.test", appContext.packageName)
}
}
@@ -1,8 +1,4 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"> <manifest xmlns:android="http://schemas.android.com/apk/res/android">
<application>
<activity android:name="LoginActivity" />
</application>
</manifest> </manifest>
@@ -0,0 +1,17 @@
package io.element.android.x.features.messages
import org.junit.Test
import org.junit.Assert.*
/**
* Example local unit test, which will execute on the development machine (host).
*
* See [testing documentation](http://d.android.com/tools/testing).
*/
class ExampleUnitTest {
@Test
fun addition_isCorrect() {
assertEquals(4, 2 + 2)
}
}
+1
View File
@@ -0,0 +1 @@
/build
+18
View File
@@ -0,0 +1,18 @@
plugins {
id("io.element.android-compose")
}
android {
namespace = "io.element.android.x.features.roomlist"
}
dependencies {
implementation(project(":libraries:core"))
implementation(project(":libraries:matrix"))
implementation(project(":libraries:designsystem"))
implementation("com.airbnb.android:mavericks-compose:3.0.1")
testImplementation("junit:junit:4.13.2")
androidTestImplementation("androidx.test.ext:junit:1.1.3")
androidTestImplementation("androidx.test.espresso:espresso-core:3.4.0")
}
+21
View File
@@ -0,0 +1,21 @@
# Add project specific ProGuard rules here.
# You can control the set of applied configuration files using the
# proguardFiles setting in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}
# Uncomment this to preserve the line number information for
# debugging stack traces.
#-keepattributes SourceFile,LineNumberTable
# If you keep the line number information, uncomment this to
# hide the original source file name.
#-renamesourcefileattribute SourceFile
@@ -0,0 +1,24 @@
package io.element.android.x.features.roomlist
import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.ext.junit.runners.AndroidJUnit4
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.Assert.*
/**
* Instrumented test, which will execute on an Android device.
*
* See [testing documentation](http://d.android.com/tools/testing).
*/
@RunWith(AndroidJUnit4::class)
class ExampleInstrumentedTest {
@Test
fun useAppContext() {
// Context of the app under test.
val appContext = InstrumentationRegistry.getInstrumentation().targetContext
assertEquals("io.element.android.x.features.roomlist.test", appContext.packageName)
}
}
@@ -1,8 +1,4 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"> <manifest xmlns:android="http://schemas.android.com/apk/res/android">
<application>
<activity android:name="RoomListActivity" />
</application>
</manifest> </manifest>
@@ -1,4 +1,4 @@
package io.element.android.x.ui.screen.roomlist package io.element.android.x.features.roomlist
data class MatrixUser( data class MatrixUser(
val username: String? = null, val username: String? = null,
@@ -1,7 +1,6 @@
package io.element.android.x.ui.screen.roomlist package io.element.android.x.features.roomlist
sealed interface RoomListActions { sealed interface RoomListActions {
object Init : RoomListActions
object LoadMore : RoomListActions object LoadMore : RoomListActions
object Logout : RoomListActions object Logout : RoomListActions
} }
@@ -0,0 +1,70 @@
package io.element.android.x.features.roomlist
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import com.airbnb.mvrx.Success
import com.airbnb.mvrx.compose.collectAsState
import com.airbnb.mvrx.compose.mavericksViewModel
import org.matrix.rustcomponents.sdk.Room
@Composable
fun RoomListScreen(
viewModel: RoomListViewModel = mavericksViewModel(),
onRoomClicked: (String) -> Unit = { }
) {
val state by viewModel.collectAsState()
RoomListContent(state, onRoomClicked)
}
@Composable
fun RoomListContent(
state: RoomListViewState,
onRoomClicked: (String) -> Unit
) {
Surface(color = MaterialTheme.colorScheme.background) {
Column(
modifier = Modifier.fillMaxSize()
) {
val rooms = state.rooms
if (rooms is Success) {
LazyColumn {
items(rooms()) { room ->
RoomItem(room = room) {
onRoomClicked(it)
}
}
}
}
}
}
}
@Composable
private fun RoomItem(
modifier: Modifier = Modifier,
room: Room,
onClick: (String) -> Unit
) {
Row(verticalAlignment = Alignment.CenterVertically,
modifier = modifier
.clickable {
onClick(room.id())
}
.fillMaxWidth()
) {
Column(modifier = modifier.padding(8.dp)) {
Text(text = "Room: ${room.name() ?: room.id()}")
Text(text = if (room.isDirect()) "Direct" else "Room")
}
}
}
@@ -1,12 +1,12 @@
package io.element.android.x.ui.screen.roomlist package io.element.android.x.features.roomlist
import com.airbnb.mvrx.Fail import com.airbnb.mvrx.Fail
import com.airbnb.mvrx.Loading import com.airbnb.mvrx.Loading
import com.airbnb.mvrx.MavericksViewModel import com.airbnb.mvrx.MavericksViewModel
import com.airbnb.mvrx.Success import com.airbnb.mvrx.Success
import io.element.android.x.core.data.tryOrNull import io.element.android.x.core.data.tryOrNull
import io.element.android.x.sdk.matrix.MatrixClient import io.element.android.x.matrix.MatrixClient
import io.element.android.x.sdk.matrix.MatrixInstance import io.element.android.x.matrix.MatrixInstance
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import org.matrix.rustcomponents.sdk.Room import org.matrix.rustcomponents.sdk.Room
import org.matrix.rustcomponents.sdk.StoppableSpawn import org.matrix.rustcomponents.sdk.StoppableSpawn
@@ -19,9 +19,12 @@ class RoomListViewModel(initialState: RoomListViewState) :
private var sync: StoppableSpawn? = null private var sync: StoppableSpawn? = null
private val matrix = MatrixInstance.getInstance() private val matrix = MatrixInstance.getInstance()
init {
handleInit()
}
fun handle(action: RoomListActions) { fun handle(action: RoomListActions) {
when (action) { when (action) {
RoomListActions.Init -> handleInit()
RoomListActions.LoadMore -> TODO() RoomListActions.LoadMore -> TODO()
RoomListActions.Logout -> handleLogout() RoomListActions.Logout -> handleLogout()
} }
@@ -1,4 +1,4 @@
package io.element.android.x.ui.screen.roomlist package io.element.android.x.features.roomlist
import com.airbnb.mvrx.Async import com.airbnb.mvrx.Async
import com.airbnb.mvrx.MavericksState import com.airbnb.mvrx.MavericksState
@@ -0,0 +1,17 @@
package io.element.android.x.features.roomlist
import org.junit.Test
import org.junit.Assert.*
/**
* Example local unit test, which will execute on the development machine (host).
*
* See [testing documentation](http://d.android.com/tools/testing).
*/
class ExampleUnitTest {
@Test
fun addition_isCorrect() {
assertEquals(4, 2 + 2)
}
}
+67
View File
@@ -0,0 +1,67 @@
[versions]
# Project
android_gradle_plugin = "7.3.1"
kotlin = "1.7.20"
# AndroidX
material = "1.6.1"
corektx = "1.9.0"
datastore = "1.0.0"
# Compose
compose_compiler = "1.3.2"
compose_bom = "2022.10.00"
# Coroutines
coroutines = "1.6.4"
# Accompanist
accompanist = "0.27.0"
# Test
test_junit = "4.13.2"
test_runner = "1.4.0"
test_core = "1.4.0"
test_mockk = "1.13.2"
test_uiautomator = "2.2.0"
test_junitext = "1.1.3"
test_barista = "4.2.0"
test_hamcrest = "2.2"
test_orchestrator = "1.4.1"
[libraries]
# Project
android_gradle_plugin = { module = "com.android.tools.build:gradle", version.ref = "android_gradle_plugin" }
kotlin_gradle_plugin = { module = "org.jetbrains.kotlin:kotlin-gradle-plugin", version.ref = "kotlin" }
# AndroidX
androidx_material = { module = "com.google.android.material:material", version.ref = "material" }
androidx_corektx = { module = "androidx.core:core-ktx", version.ref = "corektx" }
androidx_datastore = { module = "androidx.datastore:datastore-preferences", version.ref = "datastore" }
androidx_compose_bom = { group = "androidx.compose", name = "compose-bom", version.ref = "compose_bom" }
androidx_compose_foundation = { group = "androidx.compose.foundation", name = "foundation" }
# Coroutines
coroutines_core = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", version.ref = "coroutines" }
coroutines_test = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-test", version.ref = "coroutines" }
# Accompanist
accompanist_animation = { module = "com.google.accompanist:accompanist-navigation-animation", version.ref = "accompanist" }
accompanist_permission = { module = "com.google.accompanist:accompanist-permissions", version.ref = "accompanist" }
accompanist_material = { module = "com.google.accompanist:accompanist-navigation-material", version.ref = "accompanist" }
# Test
test_junit = { module = "junit:junit", version.ref = "test_junit" }
test_runner = { module = "androidx.test:runner", version.ref = "test_runner" }
test_core = { module = "androidx.test:core", version.ref = "test_core" }
test_corektx = { module = "androidx.test:core-ktx", version.ref = "test_core" }
test_uiautomator = { module = "androidx.test.uiautomator:uiautomator", version.ref = "test_uiautomator" }
test_junitext = { module = "androidx.test.ext:junit", version.ref = "test_junitext" }
test_mockk = { module = "io.mockk:mockk", version.ref = "test_mockk" }
test_barista = { module = "com.adevinta.android:barista", version.ref = "test_barista" }
test_hamcrest = { module = "org.hamcrest:hamcrest", version.ref = "test_hamcrest" }
test_orchestrator = { module = "androidx.test:orchestrator", version.ref = "test_orchestrator" }
[bundles]
+4 -4
View File
@@ -28,7 +28,7 @@ android {
compose true compose true
} }
composeOptions { composeOptions {
kotlinCompilerExtensionVersion compose_version kotlinCompilerExtensionVersion "1.3.2"
} }
kotlinOptions { kotlinOptions {
jvmTarget = '1.8' jvmTarget = '1.8'
@@ -39,13 +39,13 @@ dependencies {
implementation 'androidx.core:core-ktx:1.9.0' implementation 'androidx.core:core-ktx:1.9.0'
implementation 'androidx.appcompat:appcompat:1.5.1' implementation 'androidx.appcompat:appcompat:1.5.1'
implementation 'com.google.android.material:material:1.6.1' implementation 'com.google.android.material:material:1.7.0'
implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.5.1' implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.5.1'
implementation "androidx.lifecycle:lifecycle-viewmodel-compose:2.5.1" implementation "androidx.lifecycle:lifecycle-viewmodel-compose:2.5.1"
implementation 'androidx.activity:activity-compose:1.6.0' implementation 'androidx.activity:activity-compose:1.6.1'
implementation 'androidx.fragment:fragment-ktx:1.5.3' implementation 'androidx.fragment:fragment-ktx:1.5.3'
implementation 'com.airbnb.android:mavericks-compose:2.7.0' implementation 'com.airbnb.android:mavericks-compose:3.0.1'
} }
+1
View File
@@ -0,0 +1 @@
/build
+12
View File
@@ -0,0 +1,12 @@
plugins {
id("io.element.android-compose")
}
android {
namespace = "io.element.android.x.libraries.designsystem"
dependencies {
// Should not be there, but this is a POC
implementation("io.coil-kt:coil-compose:2.2.1")
}
}
+21
View File
@@ -0,0 +1,21 @@
# Add project specific ProGuard rules here.
# You can control the set of applied configuration files using the
# proguardFiles setting in build.gradle.kts.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}
# Uncomment this to preserve the line number information for
# debugging stack traces.
#-keepattributes SourceFile,LineNumberTable
# If you keep the line number information, uncomment this to
# hide the original source file name.
#-renamesourcefileattribute SourceFile
@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
</manifest>
@@ -1,4 +1,4 @@
package io.element.android.x.ui.theme package io.element.android.x.designsystem
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
@@ -1,4 +1,4 @@
package io.element.android.x.ui.theme package io.element.android.x.designsystem
import android.app.Activity import android.app.Activity
import android.os.Build import android.os.Build
@@ -1,4 +1,4 @@
package io.element.android.x.ui.theme package io.element.android.x.designsystem
import androidx.compose.material3.Typography import androidx.compose.material3.Typography
import androidx.compose.ui.text.TextStyle import androidx.compose.ui.text.TextStyle
@@ -1,4 +1,4 @@
package io.element.android.x.ui.theme.components package io.element.android.x.designsystem.components
import androidx.compose.material3.Button import androidx.compose.material3.Button
import androidx.compose.material3.Text import androidx.compose.material3.Text
@@ -1,4 +1,4 @@
package io.element.android.x.ui.theme.components package io.element.android.x.designsystem.components
import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.ExperimentalMaterial3Api
@@ -0,0 +1,3 @@
package io.element.android.x.matrix
internal const val LOG_TAG = "Matrix"
@@ -1,8 +1,8 @@
package io.element.android.x.sdk.matrix package io.element.android.x.matrix
import android.content.Context import android.content.Context
import io.element.android.x.sdk.matrix.store.SessionStore import io.element.android.x.matrix.store.SessionStore
import io.element.android.x.sdk.matrix.util.logError import io.element.android.x.matrix.util.logError
import org.matrix.rustcomponents.sdk.AuthenticationService import org.matrix.rustcomponents.sdk.AuthenticationService
import org.matrix.rustcomponents.sdk.ClientBuilder import org.matrix.rustcomponents.sdk.ClientBuilder
import java.io.File import java.io.File
@@ -1,7 +1,7 @@
package io.element.android.x.sdk.matrix package io.element.android.x.matrix
import android.util.Log import android.util.Log
import io.element.android.x.sdk.matrix.store.SessionStore import io.element.android.x.matrix.store.SessionStore
import org.matrix.rustcomponents.sdk.* import org.matrix.rustcomponents.sdk.*
class MatrixClient internal constructor( class MatrixClient internal constructor(
@@ -1,4 +1,4 @@
package io.element.android.x.sdk.matrix package io.element.android.x.matrix
import android.annotation.SuppressLint import android.annotation.SuppressLint
import android.content.Context import android.content.Context
@@ -1,4 +1,4 @@
package io.element.android.x.sdk.matrix package io.element.android.x.matrix
import android.util.Log import android.util.Log
import org.matrix.rustcomponents.sdk.Client import org.matrix.rustcomponents.sdk.Client
@@ -1,4 +1,4 @@
package io.element.android.x.sdk.matrix.store package io.element.android.x.matrix.store
import android.content.Context import android.content.Context
import androidx.datastore.core.DataStore import androidx.datastore.core.DataStore
@@ -10,6 +10,10 @@ import kotlinx.coroutines.flow.firstOrNull
private val Context.dataStore: DataStore<Preferences> by preferencesDataStore(name = "elementx_sessions") private val Context.dataStore: DataStore<Preferences> by preferencesDataStore(name = "elementx_sessions")
private val userIdPreference = stringPreferencesKey("userId")
// TODO It contains the access token, so it has to be stored in a more secured storage.
// I would expect the Rust SDK to provide a more obscure token.
private val restoreTokenPreference = stringPreferencesKey("restoreToken")
internal class SessionStore( internal class SessionStore(
context: Context context: Context
@@ -21,10 +25,6 @@ internal class SessionStore(
private val store = context.dataStore private val store = context.dataStore
private val userIdPreference = stringPreferencesKey("userId")
// TODO It contains the access token, so it has to be stored in a more secured storage.
// I would expect the Rust SDK to provide a more obscure token.
private val restoreTokenPreference = stringPreferencesKey("restoreToken")
suspend fun storeData(sessionData: SessionData) { suspend fun storeData(sessionData: SessionData) {
store.edit { prefs -> store.edit { prefs ->
@@ -1,7 +1,7 @@
package io.element.android.x.sdk.matrix.util package io.element.android.x.matrix.util
import android.util.Log import android.util.Log
import io.element.android.x.sdk.matrix.LOG_TAG import io.element.android.x.matrix.LOG_TAG
import org.matrix.rustcomponents.sdk.ClientException import org.matrix.rustcomponents.sdk.ClientException
fun logError(throwable: Throwable) { fun logError(throwable: Throwable) {
@@ -1,3 +0,0 @@
package io.element.android.x.sdk.matrix
internal const val LOG_TAG = "Matrix"
-61
View File
@@ -1,61 +0,0 @@
plugins {
id 'com.android.library'
id 'org.jetbrains.kotlin.android'
}
android {
namespace 'io.element.android.x.ui.screen.login'
compileSdk 33
defaultConfig {
minSdk 29
targetSdk 33
consumerProguardFiles "consumer-rules.pro"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
buildFeatures {
compose true
}
composeOptions {
kotlinCompilerExtensionVersion compose_version
}
kotlinOptions {
jvmTarget = '1.8'
}
}
dependencies {
implementation project(":libraries:core")
implementation project(":libraries:ui:theme")
implementation project(":libraries:sdk:matrix")
implementation 'androidx.core:core-ktx:1.9.0'
implementation 'androidx.appcompat:appcompat:1.5.1'
implementation 'com.google.android.material:material:1.6.1'
implementation "androidx.compose.ui:ui:$compose_version"
implementation 'androidx.compose.material3:material3:1.0.0-rc01'
implementation "androidx.compose.ui:ui-tooling-preview:$compose_version"
debugImplementation "androidx.compose.ui:ui-tooling:$compose_version"
debugImplementation "androidx.compose.ui:ui-test-manifest:$compose_version"
implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.5.1'
implementation "androidx.lifecycle:lifecycle-viewmodel-compose:2.5.1"
implementation 'androidx.activity:activity-compose:1.6.0'
implementation 'androidx.fragment:fragment-ktx:1.5.3'
implementation 'com.airbnb.android:mavericks-compose:2.7.0'
}
@@ -1,129 +0,0 @@
package io.element.android.x.ui.screen.login
import android.app.Activity
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.foundation.Image
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.material3.*
import androidx.compose.runtime.getValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.text.input.ImeAction
import androidx.compose.ui.text.input.KeyboardType
import androidx.compose.ui.unit.dp
import com.airbnb.mvrx.Fail
import com.airbnb.mvrx.Loading
import com.airbnb.mvrx.Success
import com.airbnb.mvrx.compose.collectAsState
import com.airbnb.mvrx.compose.mavericksViewModel
import io.element.android.x.ui.theme.ElementXTheme
import io.element.android.x.ui.theme.components.VectorButton
class LoginActivity : ComponentActivity() {
@OptIn(ExperimentalMaterial3Api::class)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
ElementXTheme {
// A surface container using the 'background' color from the theme
Surface(
modifier = Modifier
.fillMaxSize()
.padding(16.dp),
color = MaterialTheme.colorScheme.background
) {
Box(modifier = Modifier.fillMaxSize()) {
Column(
modifier = Modifier.fillMaxSize(),
) {
val viewModel: LoginViewModel = mavericksViewModel()
val state by viewModel.collectAsState()
val isError = state.isLoggedIn is Fail
Image(
painterResource(id = R.drawable.element_logo_green),
contentDescription = null,
modifier = Modifier
.align(Alignment.CenterHorizontally)
.padding(40.dp)
)
OutlinedTextField(
value = state.homeserver,
modifier = Modifier.fillMaxWidth(),
onValueChange = {
viewModel.handle(LoginActions.SetHomeserver(it))
},
keyboardOptions = KeyboardOptions(
keyboardType = KeyboardType.Uri,
),
)
OutlinedTextField(
value = state.login,
modifier = Modifier
.fillMaxWidth()
.padding(top = 8.dp),
onValueChange = {
viewModel.handle(LoginActions.SetLogin(it))
},
keyboardOptions = KeyboardOptions(
keyboardType = KeyboardType.Text,
),
)
OutlinedTextField(
value = state.password,
modifier = Modifier
.fillMaxWidth()
.padding(top = 8.dp),
onValueChange = {
viewModel.handle(LoginActions.SetPassword(it))
},
isError = isError,
keyboardOptions = KeyboardOptions(
keyboardType = KeyboardType.Password,
imeAction = ImeAction.Send,
),
)
if (isError) {
Text(
text = (state.isLoggedIn as? Fail)?.toString().orEmpty(),
color = MaterialTheme.colorScheme.error,
style = MaterialTheme.typography.bodySmall,
modifier = Modifier.padding(start = 16.dp)
)
}
VectorButton(
text = "Submit",
onClick = {
viewModel.handle(LoginActions.Submit)
},
enabled = state.submitEnabled,
modifier = Modifier
.align(Alignment.End)
.padding(top = 16.dp)
)
if (state.isLoggedIn is Loading) {
// FIXME This does not work, we never enter this if block
CircularProgressIndicator(
modifier = Modifier.align(Alignment.CenterHorizontally)
)
}
if (state.isLoggedIn is Success) {
openRoomList()
}
}
}
}
}
}
}
private fun openRoomList() {
setResult(Activity.RESULT_OK)
finish()
}
}
@@ -1,22 +0,0 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="64dp"
android:height="64dp"
android:viewportWidth="64"
android:viewportHeight="64">
<path
android:pathData="M23.04,3.84C23.04,1.7192 24.7593,0 26.88,0C41.0185,0 52.48,11.4615 52.48,25.6C52.48,27.7208 50.7608,29.44 48.64,29.44C46.5193,29.44 44.8,27.7208 44.8,25.6C44.8,15.7031 36.777,7.68 26.88,7.68C24.7593,7.68 23.04,5.9608 23.04,3.84Z"
android:fillColor="#0DBD8B"
android:fillType="evenOdd"/>
<path
android:pathData="M40.96,60.16C40.96,62.2808 39.2407,64 37.12,64C22.9815,64 11.52,52.5385 11.52,38.4C11.52,36.2792 13.2392,34.56 15.36,34.56C17.4807,34.56 19.2,36.2792 19.2,38.4C19.2,48.2969 27.223,56.32 37.12,56.32C39.2407,56.32 40.96,58.0392 40.96,60.16Z"
android:fillColor="#0DBD8B"
android:fillType="evenOdd"/>
<path
android:pathData="M3.84,40.96C1.7192,40.96 -0,39.2407 -0,37.12C-0,22.9815 11.4615,11.52 25.6,11.52C27.7208,11.52 29.44,13.2392 29.44,15.36C29.44,17.4807 27.7208,19.2 25.6,19.2C15.7031,19.2 7.68,27.223 7.68,37.12C7.68,39.2407 5.9608,40.96 3.84,40.96Z"
android:fillColor="#0DBD8B"
android:fillType="evenOdd"/>
<path
android:pathData="M60.16,23.04C62.2808,23.04 64,24.7593 64,26.88C64,41.0185 52.5385,52.48 38.4,52.48C36.2792,52.48 34.56,50.7608 34.56,48.64C34.56,46.5193 36.2792,44.8 38.4,44.8C48.2969,44.8 56.32,36.777 56.32,26.88C56.32,24.7593 58.0392,23.04 60.16,23.04Z"
android:fillColor="#0DBD8B"
android:fillType="evenOdd"/>
</vector>
@@ -1,62 +0,0 @@
plugins {
id 'com.android.library'
id 'org.jetbrains.kotlin.android'
}
android {
namespace 'io.element.android.x.ui.screen.roomlist'
compileSdk 33
defaultConfig {
minSdk 29
targetSdk 33
consumerProguardFiles "consumer-rules.pro"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
buildFeatures {
compose true
}
composeOptions {
kotlinCompilerExtensionVersion compose_version
}
kotlinOptions {
jvmTarget = '1.8'
}
}
dependencies {
implementation project(":libraries:core")
implementation project(":libraries:ui:theme")
implementation project(":libraries:sdk:matrix")
implementation 'androidx.core:core-ktx:1.9.0'
implementation 'androidx.appcompat:appcompat:1.5.1'
implementation 'com.google.android.material:material:1.6.1'
implementation "androidx.compose.ui:ui:$compose_version"
implementation 'androidx.compose.material3:material3:1.0.0-rc01'
implementation "androidx.compose.ui:ui-tooling-preview:$compose_version"
debugImplementation "androidx.compose.ui:ui-tooling:$compose_version"
debugImplementation "androidx.compose.ui:ui-test-manifest:$compose_version"
implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.5.1'
implementation "androidx.lifecycle:lifecycle-viewmodel-compose:2.5.1"
implementation 'androidx.activity:activity-compose:1.6.0'
implementation 'androidx.fragment:fragment-ktx:1.5.3'
implementation 'com.airbnb.android:mavericks-compose:2.7.0'
implementation 'io.coil-kt:coil-compose:2.2.1'
}
@@ -1,126 +0,0 @@
package io.element.android.x.ui.screen.roomlist
import android.os.Bundle
import android.util.Log
import android.widget.Toast
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.foundation.Image
import androidx.compose.foundation.border
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.ExitToApp
import androidx.compose.material3.*
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.unit.dp
import coil.compose.rememberAsyncImagePainter
import com.airbnb.mvrx.Success
import com.airbnb.mvrx.compose.collectAsState
import com.airbnb.mvrx.compose.mavericksViewModel
import io.element.android.x.ui.theme.ElementXTheme
import io.element.android.x.ui.theme.components.Avatar
import org.matrix.rustcomponents.sdk.Room
class RoomListActivity : ComponentActivity() {
private var initDone = false
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
ElementXTheme {
val viewModel: RoomListViewModel = mavericksViewModel()
if (!initDone) {
initDone = true
viewModel.handle(RoomListActions.Init)
}
val state = viewModel.collectAsState()
// A surface container using the 'background' color from the theme
Surface(
modifier = Modifier
.fillMaxSize()
.padding(16.dp),
color = MaterialTheme.colorScheme.background
) {
Column(
modifier = Modifier.fillMaxSize()
) {
OptionMenu(state.value.user, viewModel)
val rooms = state.value.rooms
if (rooms is Success) {
LazyColumn {
items(rooms()) { room ->
RoomCompose(room) {
Toast.makeText(
this@RoomListActivity,
"Room $it clicked!",
Toast.LENGTH_SHORT
).show()
}
}
}
}
}
}
if (state.value.logoutAction is Success) {
finish()
}
}
}
}
@Composable
private fun RoomCompose(room: Room, onClick: (String) -> Unit) {
Row(
modifier = Modifier
.fillMaxWidth()
.clickable { onClick.invoke(room.id()) },
) {
Image(
painter = rememberAsyncImagePainter(model = room.avatarUrl()),
contentDescription = null,
modifier = Modifier.size(32.dp),
)
Spacer(modifier = Modifier.width(8.dp))
Column(
modifier = Modifier
.fillMaxWidth()
.height(32.dp),
) {
Text(text = "Room: ${room.name() ?: room.id()}")
Text(text = if (room.isDirect()) "Direct" else "Room")
}
}
}
@OptIn(ExperimentalMaterial3Api::class)
@Composable
private fun OptionMenu(matrixUser: MatrixUser, viewModel: RoomListViewModel) {
TopAppBar(
title = {
Row(
modifier = Modifier.fillMaxWidth()
) {
Avatar(data = matrixUser.avatarData)
Spacer(modifier = Modifier.width(8.dp))
Text("${matrixUser.username}")
}
},
actions = {
IconButton(
onClick = { viewModel.handle(RoomListActions.Logout) }
) {
Icon(Icons.Default.ExitToApp, contentDescription = "logout")
}
}
)
}
}
-52
View File
@@ -1,52 +0,0 @@
plugins {
id 'com.android.library'
id 'org.jetbrains.kotlin.android'
}
android {
namespace 'io.element.android.x.theme'
compileSdk 33
defaultConfig {
minSdk 29
targetSdk 33
consumerProguardFiles "consumer-rules.pro"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
buildFeatures {
compose true
}
composeOptions {
kotlinCompilerExtensionVersion compose_version
}
kotlinOptions {
jvmTarget = '1.8'
}
}
dependencies {
implementation 'androidx.core:core-ktx:1.9.0'
implementation 'androidx.appcompat:appcompat:1.5.1'
implementation 'com.google.android.material:material:1.6.1'
implementation "androidx.compose.ui:ui:$compose_version"
implementation 'androidx.compose.material3:material3:1.0.0-rc01'
implementation "androidx.compose.ui:ui-tooling-preview:$compose_version"
debugImplementation "androidx.compose.ui:ui-tooling:$compose_version"
debugImplementation "androidx.compose.ui:ui-test-manifest:$compose_version"
implementation "androidx.lifecycle:lifecycle-viewmodel-compose:2.5.1"
implementation 'io.coil-kt:coil-compose:2.2.1'
}
+14
View File
@@ -0,0 +1,14 @@
plugins {
`kotlin-dsl`
`kotlin-dsl-precompiled-script-plugins`
}
repositories {
mavenCentral()
google()
}
dependencies {
implementation(libs.android.gradle.plugin)
implementation(libs.kotlin.gradle.plugin)
}
+10
View File
@@ -0,0 +1,10 @@
dependencyResolutionManagement {
repositories {
mavenCentral()
}
versionCatalogs {
create("libs") {
from(files("../gradle/libs.versions.toml"))
}
}
}
+17
View File
@@ -0,0 +1,17 @@
import org.gradle.api.JavaVersion
import org.gradle.api.artifacts.VersionCatalog
import org.gradle.jvm.toolchain.JavaLanguageVersion
val VersionCatalog.composeVersion: String
get() = findVersion("compose_compiler").get().requiredVersion
object Versions {
const val versionCode = 100100
const val versionName = "0.1.0"
const val compileSdk = 33
const val targetSdk = 33
const val minSdk = 24
val javaCompileVersion = JavaVersion.VERSION_11
val javaLanguageVersion: JavaLanguageVersion = JavaLanguageVersion.of(11)
}
@@ -0,0 +1,50 @@
package extension
import Versions
import com.android.build.api.dsl.CommonExtension
import com.android.build.api.dsl.LibraryExtension
import composeVersion
import org.gradle.api.artifacts.VersionCatalog
fun CommonExtension<*, *, *, *>.androidConfig() {
defaultConfig {
compileSdk = Versions.compileSdk
minSdk = Versions.minSdk
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
}
testOptions {
unitTests.isReturnDefaultValues = true
}
}
fun CommonExtension<*, *, *, *>.composeConfig() {
buildFeatures {
compose = true
}
composeOptions {
kotlinCompilerExtensionVersion = "1.3.2"
}
packagingOptions {
resources.excludes.apply {
add("META-INF/AL2.0")
add("META-INF/LGPL2.1")
}
}
}
fun LibraryExtension.proguardConfig() {
buildTypes {
getByName("release") {
isMinifyEnabled = true
proguardFiles("proguard-android.txt", "proguard-rules.pro")
consumerProguardFiles("proguard-rules.pro")
}
}
}
@@ -0,0 +1,29 @@
import extension.androidConfig
import extension.composeConfig
import extension.proguardConfig
plugins {
id("com.android.library")
id("kotlin-android")
}
android {
androidConfig()
proguardConfig()
composeConfig()
}
dependencies {
implementation(platform("androidx.compose:compose-bom:2022.10.00"))
implementation("androidx.compose.ui:ui")
implementation("androidx.compose.material3:material3")
implementation("androidx.compose.ui:ui-tooling-preview")
implementation("androidx.lifecycle:lifecycle-runtime-ktx:2.5.1")
implementation("androidx.activity:activity-compose:1.6.1")
implementation("androidx.lifecycle:lifecycle-viewmodel-compose:2.5.1")
implementation("com.airbnb.android:mavericks-compose:3.0.1")
debugImplementation("androidx.compose.ui:ui-tooling")
debugImplementation("androidx.compose.ui:ui-test-manifest")
}
@@ -0,0 +1,12 @@
import extension.androidConfig
import extension.proguardConfig
plugins {
id("com.android.library")
id("kotlin-android")
}
android {
androidConfig()
proguardConfig()
}
+9 -7
View File
@@ -1,5 +1,6 @@
pluginManagement { pluginManagement {
repositories { repositories {
includeBuild("plugins")
gradlePluginPortal() gradlePluginPortal()
google() google()
mavenCentral() mavenCentral()
@@ -11,14 +12,15 @@ dependencyResolutionManagement {
google() google()
mavenCentral() mavenCentral()
flatDir { flatDir {
dirs 'libraries/sdk/matrix/libs' dirs("libraries/matrix/libs")
} }
} }
} }
rootProject.name = "ElementX" rootProject.name = "ElementX"
include ':app' include(":app")
include ':libraries:core' include(":libraries:core")
include ':libraries:ui:theme' include(":libraries:matrix")
include ':libraries:ui:screens:login' include(":features:login")
include ':libraries:ui:screens:roomlist' include(":features:roomlist")
include ':libraries:sdk:matrix' include(":features:messages")
include(":libraries:designsystem")