Browse Source

Introduce mavericks-compose and room list module - WIP

feature/bma/flipper
Benoit Marty 2 years ago
parent
commit
4ce0b62241
  1. 3
      app/build.gradle
  2. 2
      app/src/main/java/io/element/android/x/ElementXApplication.kt
  3. 18
      app/src/main/java/io/element/android/x/MainActivity.kt
  4. 51
      libraries/core/build.gradle
  5. 2
      libraries/core/src/main/AndroidManifest.xml
  6. 3
      libraries/ui/screens/login/build.gradle
  7. 47
      libraries/ui/screens/login/src/main/java/io/element/android/x/ui/screen/login/LoginActivity.kt
  8. 66
      libraries/ui/screens/login/src/main/java/io/element/android/x/ui/screen/login/LoginViewModel.kt
  9. 7
      libraries/ui/screens/login/src/main/java/io/element/android/x/ui/screen/login/LoginViewState.kt
  10. 61
      libraries/ui/screens/roomlist/build.gradle
  11. 8
      libraries/ui/screens/roomlist/src/main/AndroidManifest.xml
  12. 5
      libraries/ui/screens/roomlist/src/main/java/io/element/android/x/ui/screen/login/RoomListActions.kt
  13. 47
      libraries/ui/screens/roomlist/src/main/java/io/element/android/x/ui/screen/login/RoomListActivity.kt
  14. 28
      libraries/ui/screens/roomlist/src/main/java/io/element/android/x/ui/screen/login/RoomListViewModel.kt
  15. 6
      libraries/ui/screens/roomlist/src/main/java/io/element/android/x/ui/screen/login/RoomListViewState.kt
  16. 4
      libraries/ui/theme/src/main/java/io/element/android/x/ui/theme/components/VectorButton.kt
  17. 5
      libraries/ui/theme/src/main/java/io/element/android/x/ui/theme/components/VectorTextField.kt
  18. 2
      settings.gradle

3
app/build.gradle

@ -57,6 +57,7 @@ android { @@ -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 { @@ -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'
}

2
app/src/main/java/io/element/android/x/ElementXApplication.kt

@ -1,6 +1,7 @@ @@ -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() { @@ -8,5 +9,6 @@ class ElementXApplication : Application() {
override fun onCreate() {
super.onCreate()
MatrixInstance.init(this)
Mavericks.initialize(this)
}
}

18
app/src/main/java/io/element/android/x/MainActivity.kt

@ -3,14 +3,30 @@ package io.element.android.x @@ -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

@ -0,0 +1,51 @@ @@ -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

@ -0,0 +1,2 @@ @@ -0,0 +1,2 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest />

3
libraries/ui/screens/login/build.gradle

@ -36,6 +36,7 @@ android { @@ -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 { @@ -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'
}

47
libraries/ui/screens/login/src/main/java/io/element/android/x/ui/screen/login/LoginActivity.kt

@ -1,25 +1,31 @@ @@ -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() { @@ -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() { @@ -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()
}
}

66
libraries/ui/screens/login/src/main/java/io/element/android/x/ui/screen/login/LoginViewModel.kt

@ -1,26 +1,38 @@ @@ -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() { @@ -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
)
}
}
}

7
libraries/ui/screens/login/src/main/java/io/element/android/x/ui/screen/login/LoginViewState.kt

@ -1,8 +1,13 @@ @@ -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

@ -0,0 +1,61 @@ @@ -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'
}

8
libraries/ui/screens/roomlist/src/main/AndroidManifest.xml

@ -0,0 +1,8 @@ @@ -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>

5
libraries/ui/screens/roomlist/src/main/java/io/element/android/x/ui/screen/login/RoomListActions.kt

@ -0,0 +1,5 @@ @@ -0,0 +1,5 @@
package io.element.android.x.ui.screen.login
sealed interface RoomListActions {
object LoadMore : RoomListActions
}

47
libraries/ui/screens/roomlist/src/main/java/io/element/android/x/ui/screen/login/RoomListActivity.kt

@ -0,0 +1,47 @@ @@ -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()
*/
}
}
}
}
}
}

28
libraries/ui/screens/roomlist/src/main/java/io/element/android/x/ui/screen/login/RoomListViewModel.kt

@ -0,0 +1,28 @@ @@ -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()
}
}
}

6
libraries/ui/screens/roomlist/src/main/java/io/element/android/x/ui/screen/login/RoomListViewState.kt

@ -0,0 +1,6 @@ @@ -0,0 +1,6 @@
package io.element.android.x.ui.screen.login
data class RoomListViewState(
val list: List<String> = emptyList(),
val canLoadMore: Boolean = false,
)

4
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 @@ -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)
}

5
libraries/ui/theme/src/main/java/io/element/android/x/ui/theme/components/VectorTextField.kt

@ -9,10 +9,11 @@ import androidx.compose.ui.Modifier @@ -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
)
}

2
settings.gradle

@ -17,6 +17,8 @@ dependencyResolutionManagement { @@ -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'

Loading…
Cancel
Save