SettingsScreen: Started working on settings screen
Still need to do auth
This commit is contained in:
@@ -20,9 +20,13 @@ import net.buzzert.kordophonedroid.data.AppContainer
|
||||
import net.buzzert.kordophonedroid.ui.theme.KordophoneTheme
|
||||
import net.buzzert.kordophonedroid.ui.conversationlist.ConversationListScreen
|
||||
import net.buzzert.kordophonedroid.ui.messagelist.MessageListScreen
|
||||
import net.buzzert.kordophonedroid.ui.settings.SettingsScreen
|
||||
|
||||
sealed class Destination(val route: String) {
|
||||
object ConversationList : Destination("conversations")
|
||||
|
||||
object Settings : Destination("settings")
|
||||
|
||||
object MessageList : Destination("messages/{id}") {
|
||||
fun createRoute(data: String) = "messages/$data"
|
||||
}
|
||||
@@ -64,6 +68,8 @@ fun KordophoneApp(
|
||||
composable(route = Destination.ConversationList.route) {
|
||||
ConversationListScreen(onConversationSelected = {
|
||||
navController.navigate(Destination.MessageList.createRoute(it))
|
||||
}, onSettingsInvoked = {
|
||||
navController.navigate(Destination.Settings.route)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -73,6 +79,12 @@ fun KordophoneApp(
|
||||
navController.popBackStack()
|
||||
})
|
||||
}
|
||||
|
||||
composable(Destination.Settings.route) {
|
||||
SettingsScreen(backAction = {
|
||||
navController.popBackStack()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
errorVisible.value?.let {
|
||||
|
||||
@@ -10,6 +10,7 @@ import androidx.compose.foundation.shape.CircleShape
|
||||
import androidx.compose.material.*
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.rounded.Info
|
||||
import androidx.compose.material.icons.rounded.Settings
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.runtime.getValue
|
||||
@@ -45,25 +46,30 @@ fun formatDateTime(dateTime: LocalDateTime): String {
|
||||
@Composable
|
||||
fun ConversationListScreen(
|
||||
viewModel: ConversationListViewModel = hiltViewModel(),
|
||||
onConversationSelected: (conversationID: String) -> Unit
|
||||
onConversationSelected: (conversationID: String) -> Unit,
|
||||
onSettingsInvoked: () -> Unit,
|
||||
) {
|
||||
val conversations by viewModel.conversations.collectAsStateWithLifecycle(initialValue = emptyList())
|
||||
ConversationListView(conversations = conversations, onConversationSelected = onConversationSelected)
|
||||
ConversationListView(
|
||||
conversations = conversations,
|
||||
onConversationSelected = onConversationSelected,
|
||||
onSettingsInvoked = onSettingsInvoked,
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun ConversationListView(
|
||||
conversations: List<Conversation>,
|
||||
onConversationSelected: (conversationID: String) -> Unit
|
||||
onConversationSelected: (conversationID: String) -> Unit,
|
||||
onSettingsInvoked: () -> Unit,
|
||||
) {
|
||||
|
||||
val listState = rememberLazyListState()
|
||||
|
||||
Scaffold(
|
||||
topBar = {
|
||||
TopAppBar(title = { Text("Conversations") }, actions = {
|
||||
IconButton(onClick = { /*TODO*/ }) {
|
||||
Icon(Icons.Rounded.Info, contentDescription = "Info")
|
||||
IconButton(onClick = onSettingsInvoked) {
|
||||
Icon(Icons.Rounded.Settings, contentDescription = "Settings")
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -173,5 +179,5 @@ fun ConversationListItemPreview() {
|
||||
@Preview
|
||||
@Composable
|
||||
fun ConversationListScreenPreview() {
|
||||
ConversationListScreen(onConversationSelected = {})
|
||||
ConversationListScreen(onConversationSelected = {}, onSettingsInvoked = {})
|
||||
}
|
||||
@@ -48,6 +48,7 @@ import androidx.hilt.navigation.compose.hiltViewModel
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import net.buzzert.kordophone.backend.model.GUID
|
||||
import net.buzzert.kordophone.backend.model.Message
|
||||
import net.buzzert.kordophonedroid.ui.theme.KordophoneTopAppBar
|
||||
|
||||
private val IncomingChatBubbleShape = RoundedCornerShape(4.dp, 20.dp, 20.dp, 20.dp)
|
||||
private val OutgoingChatBubbleShape = RoundedCornerShape(20.dp, 4.dp, 20.dp, 20.dp)
|
||||
@@ -82,7 +83,7 @@ fun MessageListScreen(
|
||||
)
|
||||
}
|
||||
|
||||
Scaffold(topBar = { MessagesListTopAppBar(title = viewModel.title, backAction = backAction) }) { padding ->
|
||||
Scaffold(topBar = { KordophoneTopAppBar(title = viewModel.title, backAction = backAction) }) { padding ->
|
||||
MessageTranscript(
|
||||
messages = messageItems,
|
||||
paddingValues = padding,
|
||||
@@ -93,19 +94,6 @@ fun MessageListScreen(
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun MessagesListTopAppBar(title: String, backAction: () -> Unit) {
|
||||
TopAppBar(
|
||||
title = { Text(title) },
|
||||
navigationIcon = {
|
||||
IconButton(onClick = backAction) {
|
||||
Icon(Icons.Filled.ArrowBack, null)
|
||||
}
|
||||
},
|
||||
actions = {}
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun MessageTranscript(
|
||||
messages: List<MessageListViewItem>,
|
||||
|
||||
@@ -0,0 +1,222 @@
|
||||
package net.buzzert.kordophonedroid.ui.settings
|
||||
|
||||
import android.provider.Settings
|
||||
import androidx.annotation.DrawableRes
|
||||
import androidx.annotation.StringRes
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.foundation.layout.wrapContentHeight
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.material.Button
|
||||
import androidx.compose.material.Divider
|
||||
import androidx.compose.material.ExperimentalMaterialApi
|
||||
import androidx.compose.material.Icon
|
||||
import androidx.compose.material.MaterialTheme
|
||||
import androidx.compose.material.Scaffold
|
||||
import androidx.compose.material.Surface
|
||||
import androidx.compose.material.Text
|
||||
import androidx.compose.material.TextField
|
||||
import androidx.compose.material.TopAppBar
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.AccountBox
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.State
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.saveable.rememberSaveable
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.input.TextFieldValue
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.window.Dialog
|
||||
import androidx.hilt.navigation.compose.hiltViewModel
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.flow.mapNotNull
|
||||
import net.buzzert.kordophonedroid.R
|
||||
import net.buzzert.kordophonedroid.ui.theme.KordophoneTopAppBar
|
||||
|
||||
@Composable
|
||||
fun SettingsScreen(
|
||||
viewModel: SettingsViewModel = hiltViewModel(),
|
||||
backAction: () -> Unit,
|
||||
) {
|
||||
Scaffold(
|
||||
topBar = {
|
||||
KordophoneTopAppBar(
|
||||
title = "Settings",
|
||||
backAction = backAction,
|
||||
)
|
||||
},
|
||||
|
||||
) {
|
||||
SettingsFormView(
|
||||
viewModel = viewModel,
|
||||
modifier = Modifier
|
||||
.verticalScroll(rememberScrollState())
|
||||
.padding(it)
|
||||
.padding(6.dp)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun SettingsFormView(
|
||||
viewModel: SettingsViewModel,
|
||||
modifier: Modifier = Modifier
|
||||
) {
|
||||
val serverName = viewModel.serverPreference.collectAsState()
|
||||
val userName = viewModel.usernamePreference.collectAsState()
|
||||
|
||||
Column(modifier) {
|
||||
var serverNameInput by remember { mutableStateOf(TextFieldValue(serverName.value)) }
|
||||
SettingsTextField(
|
||||
name = "Server",
|
||||
icon = R.drawable.storage,
|
||||
state = serverName,
|
||||
onSave = { viewModel.saveServerPreference(serverNameInput.text) }
|
||||
) { state ->
|
||||
TextField(serverNameInput, onValueChange = {
|
||||
serverNameInput = it
|
||||
})
|
||||
}
|
||||
|
||||
SettingsTextField(
|
||||
name = "Authentication",
|
||||
icon = R.drawable.account_circle,
|
||||
state = userName,
|
||||
onSave = { /* TODO */ }
|
||||
) {
|
||||
// TODO
|
||||
Text("Hoohah!")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalMaterialApi::class)
|
||||
@Composable
|
||||
fun <T> SettingsTextField(
|
||||
name: String,
|
||||
@DrawableRes icon: Int,
|
||||
state: State<T>,
|
||||
onSave: () -> Unit,
|
||||
dialogContent: @Composable (State<T>) -> Unit,
|
||||
) {
|
||||
var showingDialog by remember { mutableStateOf(false) }
|
||||
|
||||
if (showingDialog) {
|
||||
Dialog(
|
||||
onDismissRequest = { showingDialog = false }
|
||||
) {
|
||||
EditDialog(
|
||||
name = name,
|
||||
onDismiss = {
|
||||
onSave()
|
||||
showingDialog = false
|
||||
},
|
||||
content = { dialogContent(state) }
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
Surface(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = 16.dp)
|
||||
,
|
||||
onClick = {
|
||||
showingDialog = true
|
||||
},
|
||||
) {
|
||||
val valueString = state.value.toString().ifEmpty { "(Not set)" }
|
||||
|
||||
Column {
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.Start,
|
||||
modifier = Modifier.padding(vertical = 8.dp)
|
||||
) {
|
||||
Icon(
|
||||
painterResource(id = icon),
|
||||
contentDescription = "",
|
||||
modifier = Modifier.size(24.dp)
|
||||
)
|
||||
Spacer(modifier = Modifier.width(16.dp))
|
||||
Column(modifier = Modifier.padding(8.dp)) {
|
||||
// Title
|
||||
Text(
|
||||
text = name,
|
||||
style = MaterialTheme.typography.body1,
|
||||
textAlign = TextAlign.Start,
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.height(4.dp))
|
||||
|
||||
// Value
|
||||
Text(
|
||||
text = valueString,
|
||||
style = MaterialTheme.typography.body2,
|
||||
textAlign = TextAlign.Start,
|
||||
color = MaterialTheme.colors.onSurface.copy(alpha = 0.5f),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
Divider()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun EditDialog(
|
||||
name: String,
|
||||
onDismiss: () -> Unit,
|
||||
content: @Composable () -> Unit,
|
||||
) {
|
||||
Surface() {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.wrapContentHeight()
|
||||
.fillMaxWidth()
|
||||
.padding(16.dp)
|
||||
) {
|
||||
Text(name)
|
||||
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
|
||||
content()
|
||||
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
|
||||
Row {
|
||||
Spacer(modifier = Modifier.weight(1f))
|
||||
Button(onClick = {
|
||||
onDismiss()
|
||||
}) {
|
||||
Text("Save")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
fun SettingsPreview() {
|
||||
SettingsScreen(backAction = {})
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
package net.buzzert.kordophonedroid.ui.settings
|
||||
|
||||
import androidx.compose.runtime.MutableState
|
||||
import androidx.lifecycle.ViewModel
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.asSharedFlow
|
||||
import kotlinx.coroutines.flow.asStateFlow
|
||||
import javax.inject.Inject
|
||||
|
||||
@HiltViewModel
|
||||
class SettingsViewModel @Inject constructor() : ViewModel() {
|
||||
private val _serverPreference: MutableStateFlow<String> = MutableStateFlow("")
|
||||
var serverPreference = _serverPreference.asStateFlow()
|
||||
|
||||
private val _usernamePreference: MutableStateFlow<String> = MutableStateFlow("")
|
||||
var usernamePreference = _usernamePreference.asStateFlow()
|
||||
|
||||
private val _passwordPreference: MutableStateFlow<String> = MutableStateFlow("")
|
||||
var passwordPreference = _passwordPreference.asStateFlow()
|
||||
|
||||
fun saveServerPreference(serverName: String) {
|
||||
_serverPreference.value = serverName
|
||||
}
|
||||
|
||||
fun saveAuthenticationPreferences(username: String, password: String) {
|
||||
_usernamePreference.value = username
|
||||
_passwordPreference.value = password
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,14 @@
|
||||
package net.buzzert.kordophonedroid.ui.theme
|
||||
|
||||
import androidx.compose.foundation.isSystemInDarkTheme
|
||||
import androidx.compose.material.Icon
|
||||
import androidx.compose.material.IconButton
|
||||
import androidx.compose.material.MaterialTheme
|
||||
import androidx.compose.material.Text
|
||||
import androidx.compose.material.TopAppBar
|
||||
import androidx.compose.material.darkColors
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.ArrowBack
|
||||
import androidx.compose.material.lightColors
|
||||
import androidx.compose.runtime.Composable
|
||||
|
||||
@@ -44,4 +50,18 @@ fun KordophoneTheme(
|
||||
shapes = Shapes,
|
||||
content = content
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@Composable
|
||||
fun KordophoneTopAppBar(title: String, backAction: () -> Unit) {
|
||||
TopAppBar(
|
||||
title = { Text(title) },
|
||||
navigationIcon = {
|
||||
IconButton(onClick = backAction) {
|
||||
Icon(Icons.Filled.ArrowBack, null)
|
||||
}
|
||||
},
|
||||
actions = {}
|
||||
)
|
||||
}
|
||||
10
app/src/main/res/drawable/account_circle.xml
Normal file
10
app/src/main/res/drawable/account_circle.xml
Normal file
@@ -0,0 +1,10 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="960"
|
||||
android:viewportHeight="960"
|
||||
android:tint="?attr/colorControlNormal">
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="M234,684Q285,645 348,622.5Q411,600 480,600Q549,600 612,622.5Q675,645 726,684Q761,643 780.5,591Q800,539 800,480Q800,347 706.5,253.5Q613,160 480,160Q347,160 253.5,253.5Q160,347 160,480Q160,539 179.5,591Q199,643 234,684ZM480,520Q421,520 380.5,479.5Q340,439 340,380Q340,321 380.5,280.5Q421,240 480,240Q539,240 579.5,280.5Q620,321 620,380Q620,439 579.5,479.5Q539,520 480,520ZM480,880Q397,880 324,848.5Q251,817 197,763Q143,709 111.5,636Q80,563 80,480Q80,397 111.5,324Q143,251 197,197Q251,143 324,111.5Q397,80 480,80Q563,80 636,111.5Q709,143 763,197Q817,251 848.5,324Q880,397 880,480Q880,563 848.5,636Q817,709 763,763Q709,817 636,848.5Q563,880 480,880ZM480,800Q533,800 580,784.5Q627,769 666,740Q627,711 580,695.5Q533,680 480,680Q427,680 380,695.5Q333,711 294,740Q333,769 380,784.5Q427,800 480,800ZM480,440Q506,440 523,423Q540,406 540,380Q540,354 523,337Q506,320 480,320Q454,320 437,337Q420,354 420,380Q420,406 437,423Q454,440 480,440ZM480,380Q480,380 480,380Q480,380 480,380Q480,380 480,380Q480,380 480,380Q480,380 480,380Q480,380 480,380Q480,380 480,380Q480,380 480,380ZM480,740Q480,740 480,740Q480,740 480,740Q480,740 480,740Q480,740 480,740Q480,740 480,740Q480,740 480,740Q480,740 480,740Q480,740 480,740Z"/>
|
||||
</vector>
|
||||
10
app/src/main/res/drawable/storage.xml
Normal file
10
app/src/main/res/drawable/storage.xml
Normal file
@@ -0,0 +1,10 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="960"
|
||||
android:viewportHeight="960"
|
||||
android:tint="?attr/colorControlNormal">
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="M120,800L120,640L840,640L840,800L120,800ZM200,760L280,760L280,680L200,680L200,760ZM120,320L120,160L840,160L840,320L120,320ZM200,280L280,280L280,200L200,200L200,280ZM120,560L120,400L840,400L840,560L120,560ZM200,520L280,520L280,440L200,440L200,520Z"/>
|
||||
</vector>
|
||||
Reference in New Issue
Block a user