diff --git a/app/build.gradle b/app/build.gradle
index d4d4b98158..71c9701f6b 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -57,6 +57,7 @@ android {
dependencies {
implementation project(":libraries:ui:theme")
implementation project(":libraries:ui:screens:login")
+ implementation project(":libraries:ui:screens:roomlist")
implementation project(":libraries:sdk:matrix")
implementation 'androidx.core:core-ktx:1.9.0'
@@ -67,4 +68,6 @@ dependencies {
implementation 'androidx.activity:activity-compose:1.6.0'
debugImplementation "androidx.compose.ui:ui-tooling:$compose_version"
debugImplementation "androidx.compose.ui:ui-test-manifest:$compose_version"
+
+ implementation 'com.airbnb.android:mavericks-compose:2.7.0'
}
\ No newline at end of file
diff --git a/app/src/main/java/io/element/android/x/ElementXApplication.kt b/app/src/main/java/io/element/android/x/ElementXApplication.kt
index 6ed4dfc1c1..88136d002b 100644
--- a/app/src/main/java/io/element/android/x/ElementXApplication.kt
+++ b/app/src/main/java/io/element/android/x/ElementXApplication.kt
@@ -1,6 +1,7 @@
package io.element.android.x
import android.app.Application
+import com.airbnb.mvrx.Mavericks
import io.element.android.x.sdk.matrix.MatrixInstance
class ElementXApplication : Application() {
@@ -8,5 +9,6 @@ class ElementXApplication : Application() {
override fun onCreate() {
super.onCreate()
MatrixInstance.init(this)
+ Mavericks.initialize(this)
}
}
\ No newline at end of file
diff --git a/app/src/main/java/io/element/android/x/MainActivity.kt b/app/src/main/java/io/element/android/x/MainActivity.kt
index 83797b5271..d9097dca77 100644
--- a/app/src/main/java/io/element/android/x/MainActivity.kt
+++ b/app/src/main/java/io/element/android/x/MainActivity.kt
@@ -3,14 +3,30 @@ package io.element.android.x
import android.content.Intent
import android.os.Bundle
import androidx.activity.ComponentActivity
+import androidx.activity.result.contract.ActivityResultContracts
import io.element.android.x.ui.screen.login.LoginActivity
+import io.element.android.x.ui.screen.login.RoomListActivity
class MainActivity : ComponentActivity() {
+ private val launcher =
+ registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
+ if (it.resultCode == RESULT_OK) {
+ // Launch the room Activity and finish
+ startRoomActivityAndFinish()
+ } else {
+ finish()
+ }
+ }
+
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// Just start the LoginActivity for now.
// TODO if a session exist, start the room list
- startActivity(Intent(this, LoginActivity::class.java))
+ launcher.launch(Intent(this, LoginActivity::class.java))
+ }
+
+ private fun startRoomActivityAndFinish() {
+ startActivity(Intent(this, RoomListActivity::class.java))
finish()
}
}
diff --git a/libraries/core/build.gradle b/libraries/core/build.gradle
new file mode 100644
index 0000000000..b1ec9bdb80
--- /dev/null
+++ b/libraries/core/build.gradle
@@ -0,0 +1,51 @@
+plugins {
+ id 'com.android.library'
+ id 'org.jetbrains.kotlin.android'
+}
+
+android {
+ namespace 'io.element.android.x.core'
+ 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.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'
+}
\ No newline at end of file
diff --git a/libraries/core/src/main/AndroidManifest.xml b/libraries/core/src/main/AndroidManifest.xml
new file mode 100644
index 0000000000..568741e54f
--- /dev/null
+++ b/libraries/core/src/main/AndroidManifest.xml
@@ -0,0 +1,2 @@
+
+
\ No newline at end of file
diff --git a/libraries/ui/screens/login/build.gradle b/libraries/ui/screens/login/build.gradle
index 012c0f8161..14552fbb08 100644
--- a/libraries/ui/screens/login/build.gradle
+++ b/libraries/ui/screens/login/build.gradle
@@ -36,6 +36,7 @@ android {
}
dependencies {
+ implementation project(":libraries:core")
implementation project(":libraries:ui:theme")
implementation project(":libraries:sdk:matrix")
@@ -55,4 +56,6 @@ dependencies {
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'
}
\ No newline at end of file
diff --git a/libraries/ui/screens/login/src/main/java/io/element/android/x/ui/screen/login/LoginActivity.kt b/libraries/ui/screens/login/src/main/java/io/element/android/x/ui/screen/login/LoginActivity.kt
index 95c1b84a83..919db9c71c 100644
--- a/libraries/ui/screens/login/src/main/java/io/element/android/x/ui/screen/login/LoginActivity.kt
+++ b/libraries/ui/screens/login/src/main/java/io/element/android/x/ui/screen/login/LoginActivity.kt
@@ -1,25 +1,31 @@
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.activity.viewModels
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
+import androidx.compose.material3.CircularProgressIndicator
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface
-import androidx.compose.runtime.collectAsState
+import androidx.compose.material3.Text
+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.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
import io.element.android.x.ui.theme.components.VectorTextField
class LoginActivity : ComponentActivity() {
- private val viewModel: LoginViewModel by viewModels()
-
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
@@ -35,9 +41,10 @@ class LoginActivity : ComponentActivity() {
Column(
modifier = Modifier.fillMaxSize()
) {
- val state = viewModel.state.collectAsState().value
- VectorTextField(
- value = state.homeserver,
+ val viewModel: LoginViewModel = mavericksViewModel()
+ val state by viewModel.collectAsState()
+ val isError = state.isLoggedIn is Fail
+ VectorTextField(value = state.homeserver,
onValueChange = {
viewModel.handle(LoginActions.SetHomeserver(it))
})
@@ -50,18 +57,42 @@ class LoginActivity : ComponentActivity() {
value = state.password,
onValueChange = {
viewModel.handle(LoginActions.SetPassword(it))
- }
+ },
+ isError = isError
)
+ 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)
)
+ 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()
+ }
}
diff --git a/libraries/ui/screens/login/src/main/java/io/element/android/x/ui/screen/login/LoginViewModel.kt b/libraries/ui/screens/login/src/main/java/io/element/android/x/ui/screen/login/LoginViewModel.kt
index 2a8812e53a..efb486ca5e 100644
--- a/libraries/ui/screens/login/src/main/java/io/element/android/x/ui/screen/login/LoginViewModel.kt
+++ b/libraries/ui/screens/login/src/main/java/io/element/android/x/ui/screen/login/LoginViewModel.kt
@@ -1,26 +1,38 @@
package io.element.android.x.ui.screen.login
import android.util.Log
-import androidx.lifecycle.ViewModel
-import androidx.lifecycle.viewModelScope
+import com.airbnb.mvrx.Fail
+import com.airbnb.mvrx.Loading
+import com.airbnb.mvrx.MavericksViewModel
+import com.airbnb.mvrx.Success
import io.element.android.x.sdk.matrix.MatrixInstance
-import kotlinx.coroutines.flow.MutableStateFlow
-import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.launch
-class LoginViewModel : ViewModel() {
+class LoginViewModel(initialState: LoginViewState) :
+ MavericksViewModel(initialState) {
private val matrix = MatrixInstance.getInstance()
- private val _state = MutableStateFlow(LoginViewState())
- val state = _state.asStateFlow()
-
init {
observeState()
}
private fun observeState() {
- // TODO Update submitEnabled when other state members are updated.
+ 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) {
@@ -33,40 +45,40 @@ class LoginViewModel : ViewModel() {
}
private fun handleSetHomeserver(action: LoginActions.SetHomeserver) {
- _state.value = _state.value.copy(
- homeserver = action.homeserver,
- submitEnabled = _state.value.login.isNotEmpty() &&
- _state.value.password.isNotEmpty() &&
- action.homeserver.isNotEmpty()
- )
+ setState {
+ copy(
+ homeserver = action.homeserver
+ )
+ }
}
- private fun handleSubmit() {
+ private fun handleSubmit() = withState { state ->
viewModelScope.launch {
- val currentState = state.value
+ setState { copy(isLoggedIn = Loading()) }
try {
- matrix.login(currentState.homeserver, currentState.login, currentState.password)
+ matrix.login(state.homeserver, state.login, state.password)
+ setState { copy(isLoggedIn = Success(Unit)) }
} catch (throwable: Throwable) {
Log.e("Error", "Cannot login", throwable)
+ setState { copy(isLoggedIn = Fail(throwable)) }
}
}
}
private fun handleSetPassword(action: LoginActions.SetPassword) {
- _state.value = _state.value.copy(
- password = action.password,
- submitEnabled = _state.value.login.isNotEmpty() &&
- _state.value.homeserver.isNotEmpty() &&
- action.password.isNotEmpty()
- )
+ setState {
+ copy(
+ password = action.password
+ )
+ }
}
private fun handleSetName(action: LoginActions.SetLogin) {
- _state.value = _state.value.copy(
- login = action.login,
- submitEnabled = action.login.isNotEmpty() &&
- _state.value.homeserver.isNotEmpty() &&
- _state.value.password.isNotEmpty()
- )
+ setState {
+ copy(
+
+ login = action.login
+ )
+ }
}
}
\ No newline at end of file
diff --git a/libraries/ui/screens/login/src/main/java/io/element/android/x/ui/screen/login/LoginViewState.kt b/libraries/ui/screens/login/src/main/java/io/element/android/x/ui/screen/login/LoginViewState.kt
index 406fc55751..83c9ec295a 100644
--- a/libraries/ui/screens/login/src/main/java/io/element/android/x/ui/screen/login/LoginViewState.kt
+++ b/libraries/ui/screens/login/src/main/java/io/element/android/x/ui/screen/login/LoginViewState.kt
@@ -1,8 +1,13 @@
package io.element.android.x.ui.screen.login
+import com.airbnb.mvrx.Async
+import com.airbnb.mvrx.MavericksState
+import com.airbnb.mvrx.Uninitialized
+
data class LoginViewState(
val homeserver: String = "matrix.org",
val login: String = "",
val password: String = "",
val submitEnabled: Boolean = false,
-)
+ val isLoggedIn: Async = Uninitialized,
+) : MavericksState
diff --git a/libraries/ui/screens/roomlist/build.gradle b/libraries/ui/screens/roomlist/build.gradle
new file mode 100644
index 0000000000..3b9f08b932
--- /dev/null
+++ b/libraries/ui/screens/roomlist/build.gradle
@@ -0,0 +1,61 @@
+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'
+}
\ No newline at end of file
diff --git a/libraries/ui/screens/roomlist/src/main/AndroidManifest.xml b/libraries/ui/screens/roomlist/src/main/AndroidManifest.xml
new file mode 100644
index 0000000000..f292776d9a
--- /dev/null
+++ b/libraries/ui/screens/roomlist/src/main/AndroidManifest.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/libraries/ui/screens/roomlist/src/main/java/io/element/android/x/ui/screen/login/RoomListActions.kt b/libraries/ui/screens/roomlist/src/main/java/io/element/android/x/ui/screen/login/RoomListActions.kt
new file mode 100644
index 0000000000..6655529cf3
--- /dev/null
+++ b/libraries/ui/screens/roomlist/src/main/java/io/element/android/x/ui/screen/login/RoomListActions.kt
@@ -0,0 +1,5 @@
+package io.element.android.x.ui.screen.login
+
+sealed interface RoomListActions {
+ object LoadMore : RoomListActions
+}
diff --git a/libraries/ui/screens/roomlist/src/main/java/io/element/android/x/ui/screen/login/RoomListActivity.kt b/libraries/ui/screens/roomlist/src/main/java/io/element/android/x/ui/screen/login/RoomListActivity.kt
new file mode 100644
index 0000000000..12146e32b1
--- /dev/null
+++ b/libraries/ui/screens/roomlist/src/main/java/io/element/android/x/ui/screen/login/RoomListActivity.kt
@@ -0,0 +1,47 @@
+package io.element.android.x.ui.screen.login
+
+import android.os.Bundle
+import androidx.activity.ComponentActivity
+import androidx.activity.compose.setContent
+import androidx.activity.viewModels
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.padding
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Surface
+import androidx.compose.runtime.collectAsState
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.unit.dp
+import io.element.android.x.ui.theme.ElementXTheme
+
+class RoomListActivity : ComponentActivity() {
+
+ private val viewModel: RoomListViewModel by viewModels()
+
+ 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
+ ) {
+ Column(
+ modifier = Modifier.fillMaxSize()
+ ) {
+ /* TODO
+ val state = viewModel.state.collectAsState().value
+ RoomListHeader()
+ RoomList()
+
+ */
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/libraries/ui/screens/roomlist/src/main/java/io/element/android/x/ui/screen/login/RoomListViewModel.kt b/libraries/ui/screens/roomlist/src/main/java/io/element/android/x/ui/screen/login/RoomListViewModel.kt
new file mode 100644
index 0000000000..aa3634b94c
--- /dev/null
+++ b/libraries/ui/screens/roomlist/src/main/java/io/element/android/x/ui/screen/login/RoomListViewModel.kt
@@ -0,0 +1,28 @@
+package io.element.android.x.ui.screen.login
+
+import androidx.lifecycle.ViewModel
+import io.element.android.x.sdk.matrix.MatrixInstance
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.asStateFlow
+
+class RoomListViewModel : ViewModel() {
+
+ private val matrix = MatrixInstance.getInstance()
+
+ private val _state = MutableStateFlow(RoomListViewState())
+ val state = _state.asStateFlow()
+
+ init {
+ observeState()
+ }
+
+ private fun observeState() {
+ // TODO Update submitEnabled when other state members are updated.
+ }
+
+ fun handle(action: RoomListActions) {
+ when (action) {
+ RoomListActions.LoadMore -> TODO()
+ }
+ }
+}
\ No newline at end of file
diff --git a/libraries/ui/screens/roomlist/src/main/java/io/element/android/x/ui/screen/login/RoomListViewState.kt b/libraries/ui/screens/roomlist/src/main/java/io/element/android/x/ui/screen/login/RoomListViewState.kt
new file mode 100644
index 0000000000..7ed1906b89
--- /dev/null
+++ b/libraries/ui/screens/roomlist/src/main/java/io/element/android/x/ui/screen/login/RoomListViewState.kt
@@ -0,0 +1,6 @@
+package io.element.android.x.ui.screen.login
+
+data class RoomListViewState(
+ val list: List = emptyList(),
+ val canLoadMore: Boolean = false,
+)
diff --git a/libraries/ui/theme/src/main/java/io/element/android/x/ui/theme/components/VectorButton.kt b/libraries/ui/theme/src/main/java/io/element/android/x/ui/theme/components/VectorButton.kt
index 6a237fc857..03413a9b72 100644
--- a/libraries/ui/theme/src/main/java/io/element/android/x/ui/theme/components/VectorButton.kt
+++ b/libraries/ui/theme/src/main/java/io/element/android/x/ui/theme/components/VectorButton.kt
@@ -3,13 +3,15 @@ package io.element.android.x.ui.theme.components
import androidx.compose.material3.Button
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
@Composable
-fun VectorButton(text: String, enabled: Boolean, onClick: () -> Unit) {
+fun VectorButton(text: String, enabled: Boolean, onClick: () -> Unit, modifier: Modifier? = null) {
Button(
onClick = onClick,
enabled = enabled,
+ modifier = modifier ?: Modifier
) {
Text(text = text)
}
diff --git a/libraries/ui/theme/src/main/java/io/element/android/x/ui/theme/components/VectorTextField.kt b/libraries/ui/theme/src/main/java/io/element/android/x/ui/theme/components/VectorTextField.kt
index a46e61a1b5..7d2b61e734 100644
--- a/libraries/ui/theme/src/main/java/io/element/android/x/ui/theme/components/VectorTextField.kt
+++ b/libraries/ui/theme/src/main/java/io/element/android/x/ui/theme/components/VectorTextField.kt
@@ -9,10 +9,11 @@ import androidx.compose.ui.Modifier
@OptIn(ExperimentalMaterial3Api::class)
@Composable
-fun VectorTextField(value: String, onValueChange: (String) -> Unit) {
+fun VectorTextField(value: String, onValueChange: (String) -> Unit, isError: Boolean = false) {
OutlinedTextField(
value = value,
onValueChange = onValueChange,
- modifier = Modifier.fillMaxWidth()
+ modifier = Modifier.fillMaxWidth(),
+ isError = isError
)
}
diff --git a/settings.gradle b/settings.gradle
index 55bf47e307..aa27129117 100644
--- a/settings.gradle
+++ b/settings.gradle
@@ -17,6 +17,8 @@ dependencyResolutionManagement {
}
rootProject.name = "ElementX"
include ':app'
+include ':libraries:core'
include ':libraries:ui:theme'
include ':libraries:ui:screens:login'
+include ':libraries:ui:screens:roomlist'
include ':libraries:sdk:matrix'