Introduce mavericks-compose and room list module - WIP
This commit is contained in:
@@ -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'
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
|
||||
51
libraries/core/build.gradle
Normal file
51
libraries/core/build.gradle
Normal file
@@ -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'
|
||||
}
|
||||
2
libraries/core/src/main/AndroidManifest.xml
Normal file
2
libraries/core/src/main/AndroidManifest.xml
Normal file
@@ -0,0 +1,2 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest />
|
||||
@@ -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'
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<LoginViewState>(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
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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<Unit> = Uninitialized,
|
||||
) : MavericksState
|
||||
|
||||
61
libraries/ui/screens/roomlist/build.gradle
Normal file
61
libraries/ui/screens/roomlist/build.gradle
Normal file
@@ -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'
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
|
||||
<application>
|
||||
<activity android:name="RoomListActivity" />
|
||||
</application>
|
||||
|
||||
</manifest>
|
||||
@@ -0,0 +1,5 @@
|
||||
package io.element.android.x.ui.screen.login
|
||||
|
||||
sealed interface RoomListActions {
|
||||
object LoadMore : RoomListActions
|
||||
}
|
||||
@@ -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()
|
||||
|
||||
*/
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
package io.element.android.x.ui.screen.login
|
||||
|
||||
data class RoomListViewState(
|
||||
val list: List<String> = emptyList(),
|
||||
val canLoadMore: Boolean = false,
|
||||
)
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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
|
||||
)
|
||||
}
|
||||
|
||||
@@ -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'
|
||||
|
||||
Reference in New Issue
Block a user