android: user picker UI changes + share button (#1858)

* android: user picker UI changes + share button

* limit terminal items size in a different way
This commit is contained in:
Stanislav Dmitrenko
2023-01-30 11:19:26 +00:00
committed by GitHub
parent 9723c47b25
commit dfa0272065
10 changed files with 117 additions and 109 deletions
@@ -399,6 +399,13 @@ class ChatModel(val controller: ChatController) {
fun contactNetworkStatus(contact: Contact): NetworkStatus =
networkStatuses[contact.activeConn.agentConnId] ?: NetworkStatus.Unknown()
fun addTerminalItem(item: TerminalItem) {
if (terminalItems.size >= 500) {
terminalItems.removeAt(0)
}
terminalItems.add(item)
}
}
enum class ChatType(val type: String) {
@@ -343,7 +343,7 @@ open class ChatController(var ctrl: ChatCtrl?, val ntfManager: NtfManager, val a
return withContext(Dispatchers.IO) {
val c = cmd.cmdString
if (cmd !is CC.ApiParseMarkdown) {
chatModel.terminalItems.add(TerminalItem.cmd(cmd.obfuscated))
chatModel.addTerminalItem(TerminalItem.cmd(cmd.obfuscated))
Log.d(TAG, "sendCmd: ${cmd.cmdType}")
}
val json = chatSendCmd(ctrl, c)
@@ -353,7 +353,7 @@ open class ChatController(var ctrl: ChatCtrl?, val ntfManager: NtfManager, val a
Log.d(TAG, "sendCmd response json $json")
}
if (r.resp !is CR.ParsedMarkdown) {
chatModel.terminalItems.add(TerminalItem.resp(r.resp))
chatModel.addTerminalItem(TerminalItem.resp(r.resp))
}
r.resp
}
@@ -1143,7 +1143,7 @@ open class ChatController(var ctrl: ChatCtrl?, val ntfManager: NtfManager, val a
suspend fun processReceivedMsg(r: CR) {
lastMsgReceivedTimestamp = System.currentTimeMillis()
chatModel.terminalItems.add(TerminalItem.resp(r))
chatModel.addTerminalItem(TerminalItem.resp(r))
when (r) {
is CR.NewContactConnection -> {
if (active(r.user)) {
@@ -54,7 +54,7 @@ fun TerminalView(chatModel: ChatModel, close: () -> Unit) {
lastSuccessfulAuth.value = SystemClock.elapsedRealtime()
}
TerminalLayout(
chatModel.terminalItems,
remember { chatModel.terminalItems },
composeState,
sendCommand = { sendCommand(chatModel, composeState) },
close
@@ -103,8 +103,8 @@ private fun sendCommand(chatModel: ChatModel, composeState: MutableState<Compose
val s = composeState.value.message
if (s.startsWith("/sql") && (!prefPerformLA || !developerTools)) {
val resp = CR.ChatCmdError(null, ChatError.ChatErrorChat(ChatErrorType.СommandError("Failed reading: empty")))
chatModel.terminalItems.add(TerminalItem.cmd(CC.Console(s)))
chatModel.terminalItems.add(TerminalItem.resp(resp))
chatModel.addTerminalItem(TerminalItem.cmd(CC.Console(s)))
chatModel.addTerminalItem(TerminalItem.resp(resp))
composeState.value = ComposeState(useLinkPreviews = false)
} else {
withApi {
@@ -175,6 +175,7 @@ fun TerminalLog(terminalItems: List<TerminalItem>) {
onDispose { lazyListState = listState.firstVisibleItemIndex to listState.firstVisibleItemScrollOffset }
}
val reversedTerminalItems by remember { derivedStateOf { terminalItems.reversed().toList() } }
val context = LocalContext.current
LazyColumn(state = listState, reverseLayout = true) {
items(reversedTerminalItems) { item ->
Text(
@@ -185,7 +186,7 @@ fun TerminalLog(terminalItems: List<TerminalItem>) {
modifier = Modifier
.fillMaxWidth()
.clickable {
ModalManager.shared.showModal {
ModalManager.shared.showModal(endButtons = { ShareButton { shareText(context, item.details) } }) {
SelectionContainer(modifier = Modifier.verticalScroll(rememberScrollState())) {
Text(item.details, modifier = Modifier.padding(horizontal = DEFAULT_PADDING).padding(bottom = DEFAULT_PADDING))
}
@@ -65,6 +65,7 @@ fun ChatListView(chatModel: ChatModel, setPerformLA: (Boolean) -> Unit, stopped:
var searchInList by rememberSaveable { mutableStateOf("") }
val scaffoldState = rememberScaffoldState()
val scope = rememberCoroutineScope()
val switchingUsers = rememberSaveable { mutableStateOf(false) }
Scaffold(
topBar = { ChatListToolbar(chatModel, scaffoldState.drawerState, userPickerState, stopped) { searchInList = it.trim() } },
scaffoldState = scaffoldState,
@@ -99,7 +100,7 @@ fun ChatListView(chatModel: ChatModel, setPerformLA: (Boolean) -> Unit, stopped:
) {
if (chatModel.chats.isNotEmpty()) {
ChatList(chatModel, search = searchInList)
} else {
} else if (!switchingUsers.value) {
Box(Modifier.fillMaxSize()) {
if (!stopped && !newChatSheetState.collectAsState().value.isVisible()) {
OnboardingButtons(showNewChatSheet)
@@ -113,9 +114,17 @@ fun ChatListView(chatModel: ChatModel, setPerformLA: (Boolean) -> Unit, stopped:
if (searchInList.isEmpty()) {
NewChatSheet(chatModel, newChatSheetState, stopped, hideNewChatSheet)
}
UserPicker(chatModel, userPickerState) {
UserPicker(chatModel, userPickerState, switchingUsers) {
scope.launch { if (scaffoldState.drawerState.isOpen) scaffoldState.drawerState.close() else scaffoldState.drawerState.open() }
}
if (switchingUsers.value) {
Box(
Modifier.fillMaxSize().clickable(enabled = false, onClick = {}),
contentAlignment = Alignment.Center
) {
ProgressIndicator()
}
}
}
@Composable
@@ -267,6 +276,17 @@ private fun BoxScope.unreadBadge(text: String? = "") {
)
}
@Composable
private fun ProgressIndicator() {
CircularProgressIndicator(
Modifier
.padding(horizontal = 2.dp)
.size(30.dp),
color = HighOrLowlight,
strokeWidth = 2.5.dp
)
}
private var lazyListState = 0 to 0
@Composable
@@ -18,6 +18,7 @@ import androidx.compose.ui.graphics.graphicsLayer
import androidx.compose.ui.platform.LocalConfiguration
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.text.capitalize
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.intl.Locale
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.*
@@ -32,20 +33,11 @@ import kotlinx.coroutines.launch
import kotlin.math.roundToInt
@Composable
fun UserPicker(chatModel: ChatModel, userPickerState: MutableStateFlow<AnimatedViewState>, openSettings: () -> Unit) {
fun UserPicker(chatModel: ChatModel, userPickerState: MutableStateFlow<AnimatedViewState>, switchingUsers: MutableState<Boolean>, openSettings: () -> Unit) {
val scope = rememberCoroutineScope()
var newChat by remember { mutableStateOf(userPickerState.value) }
val users by remember { derivedStateOf { chatModel.users.sortedByDescending { it.user.activeUser } } }
val animatedFloat = remember { Animatable(if (newChat.isVisible()) 0f else 1f) }
var progressIndicator by remember { mutableStateOf(false) }
if (progressIndicator) {
Box(
Modifier.fillMaxSize().clickable(enabled = false, onClick = {}),
contentAlignment = Alignment.Center
) {
ProgressIndicator()
}
}
LaunchedEffect(Unit) {
launch {
userPickerState.collect {
@@ -113,11 +105,11 @@ fun UserPicker(chatModel: ChatModel, userPickerState: MutableStateFlow<AnimatedV
scope.launch {
val job = launch {
delay(500)
progressIndicator = true
switchingUsers.value = true
}
chatModel.controller.changeActiveUser(u.user.userId)
job.cancel()
progressIndicator = false
switchingUsers.value = false
}
}
}
@@ -137,7 +129,20 @@ fun UserPicker(chatModel: ChatModel, userPickerState: MutableStateFlow<AnimatedV
@Composable
fun UserProfilePickerItem(u: User, unreadCount: Int = 0, onLongClick: () -> Unit = {}, onClick: () -> Unit) {
SectionItemViewSpaceBetween(onClick, onLongClick, padding = PaddingValues(start = 8.dp, end = DEFAULT_PADDING)) {
Row(
Modifier
.fillMaxWidth()
.sizeIn(minHeight = 46.dp)
.combinedClickable(
onClick = if (!u.activeUser) onClick else { {} },
onLongClick = onLongClick,
interactionSource = remember { MutableInteractionSource() },
indication = if (!u.activeUser) LocalIndication.current else null
)
.padding(PaddingValues(start = 8.dp, end = DEFAULT_PADDING)),
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically
) {
Row(
Modifier
.widthIn(max = LocalConfiguration.current.screenWidthDp.dp * 0.7f)
@@ -149,26 +154,30 @@ fun UserProfilePickerItem(u: User, unreadCount: Int = 0, onLongClick: () -> Unit
size = 54.dp
)
Text(
u.chatViewName,
u.displayName,
modifier = Modifier
.padding(start = 8.dp, end = 8.dp)
.padding(start = 8.dp, end = 8.dp),
fontWeight = if (u.activeUser) FontWeight.Medium else FontWeight.Normal
)
}
if (u.activeUser) {
Icon(Icons.Filled.Done, null, Modifier.size(20.dp), tint = MaterialTheme.colors.primary)
Icon(Icons.Filled.Done, null, Modifier.size(20.dp), tint = MaterialTheme.colors.onBackground)
} else if (unreadCount > 0) {
Text(
unreadCountStr(unreadCount),
color = MaterialTheme.colors.onPrimary,
fontSize = 11.sp,
modifier = Modifier
.background(MaterialTheme.colors.primary, shape = CircleShape)
.sizeIn(minWidth = 20.dp, minHeight = 20.dp)
.padding(horizontal = 3.dp)
.padding(vertical = 1.dp),
textAlign = TextAlign.Center,
maxLines = 1
)
Row {
Text(
unreadCountStr(unreadCount),
color = MaterialTheme.colors.onPrimary,
fontSize = 11.sp,
modifier = Modifier
.background(MaterialTheme.colors.primary, shape = CircleShape)
.sizeIn(minWidth = 20.dp, minHeight = 20.dp)
.padding(horizontal = 3.dp)
.padding(vertical = 1.dp),
textAlign = TextAlign.Center,
maxLines = 1
)
Spacer(Modifier.width(2.dp))
}
} else {
Box(Modifier.size(20.dp))
}
@@ -186,14 +195,3 @@ private fun SettingsPickerItem(onClick: () -> Unit) {
Icon(Icons.Outlined.Settings, text, Modifier.size(20.dp), tint = MaterialTheme.colors.onBackground)
}
}
@Composable
private fun ProgressIndicator() {
CircularProgressIndicator(
Modifier
.padding(horizontal = 2.dp)
.size(30.dp),
color = HighOrLowlight,
strokeWidth = 2.5.dp
)
}
@@ -11,7 +11,7 @@ import androidx.compose.ui.unit.dp
import chat.simplex.app.ui.theme.*
@Composable
fun CloseSheetBar(close: () -> Unit) {
fun CloseSheetBar(close: () -> Unit, endButtons: @Composable RowScope.() -> Unit = {}) {
Column(
Modifier
.fillMaxWidth()
@@ -20,9 +20,15 @@ fun CloseSheetBar(close: () -> Unit) {
) {
Row(
Modifier
.width(TitleInsetWithIcon - AppBarHorizontalPadding)
.padding(top = 4.dp), // Like in DefaultAppBar
content = { NavigationButtonBack(close) }
content = {
Row(Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween) {
NavigationButtonBack(close)
Row {
endButtons()
}
}
}
)
}
}
@@ -53,6 +53,15 @@ fun NavigationButtonBack(onButtonClicked: () -> Unit) {
}
}
@Composable
fun ShareButton(onButtonClicked: () -> Unit) {
IconButton(onButtonClicked) {
Icon(
Icons.Outlined.Share, stringResource(R.string.share_verb), tint = MaterialTheme.colors.primary
)
}
}
@Composable
fun NavigationButtonMenu(onButtonClicked: () -> Unit) {
IconButton(onClick = onButtonClicked) {
@@ -20,12 +20,13 @@ fun ModalView(
close: () -> Unit,
background: Color = MaterialTheme.colors.background,
modifier: Modifier = Modifier,
endButtons: @Composable RowScope.() -> Unit = {},
content: @Composable () -> Unit,
) {
BackHandler(onBack = close)
Surface(Modifier.fillMaxSize()) {
Column(Modifier.background(background)) {
CloseSheetBar(close)
CloseSheetBar(close, endButtons)
Box(modifier) { content() }
}
}
@@ -37,9 +38,9 @@ class ModalManager {
private val toRemove = mutableSetOf<Int>()
private var oldViewChanging = AtomicBoolean(false)
fun showModal(settings: Boolean = false, content: @Composable () -> Unit) {
fun showModal(settings: Boolean = false, endButtons: @Composable RowScope.() -> Unit = {}, content: @Composable () -> Unit) {
showCustomModal { close ->
ModalView(close, if (!settings || isInDarkTheme()) MaterialTheme.colors.background else SettingsBackgroundLight, content = content)
ModalView(close, if (!settings || isInDarkTheme()) MaterialTheme.colors.background else SettingsBackgroundLight, endButtons = endButtons, content = content)
}
}
@@ -24,8 +24,6 @@ import androidx.compose.ui.graphics.*
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.capitalize
import androidx.compose.ui.text.intl.Locale
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.core.content.ContextCompat
@@ -65,11 +63,6 @@ fun AppearanceView() {
AppearanceLayout(
appIcon,
changeIcon = ::setAppIcon,
showThemeSelector = {
ModalManager.shared.showModal(true) {
ThemeSelectorView()
}
},
editPrimaryColor = { primary ->
ModalManager.shared.showModalCloseable { close ->
ColorEditor(primary, close)
@@ -81,7 +74,6 @@ fun AppearanceView() {
@Composable fun AppearanceLayout(
icon: MutableState<AppIcon>,
changeIcon: (AppIcon) -> Unit,
showThemeSelector: () -> Unit,
editPrimaryColor: (Color) -> Unit,
) {
Column(
@@ -115,8 +107,12 @@ fun AppearanceView() {
SectionSpacer()
val currentTheme by CurrentColors.collectAsState()
SectionView(stringResource(R.string.settings_section_title_themes)) {
SectionItemViewSpaceBetween(showThemeSelector) {
Text(generalGetString(R.string.theme))
SectionItemViewSpaceBetween {
val darkTheme = isSystemInDarkTheme()
val state = remember { derivedStateOf { currentTheme.second } }
ThemeSelector(state) {
ThemeManager.applyTheme(it.name, darkTheme)
}
}
SectionDivider()
SectionItemViewSpaceBetween({ editPrimaryColor(currentTheme.first.primary) }) {
@@ -183,6 +179,21 @@ fun ColorPicker(initialColor: Color, onColorChanged: (Color) -> Unit) {
)
}
@Composable
private fun ThemeSelector(state: State<DefaultTheme>, onSelected: (DefaultTheme) -> Unit) {
val darkTheme = isSystemInDarkTheme()
val values by remember { mutableStateOf(ThemeManager.allThemes(darkTheme).map { it.second to it.third }) }
ExposedDropDownSettingRow(
generalGetString(R.string.theme),
values,
state,
icon = null,
enabled = remember { mutableStateOf(true) },
onSelected = onSelected
)
}
private fun findEnabledIcon(): AppIcon = AppIcon.values().first { icon ->
SimplexApp.context.packageManager.getComponentEnabledSetting(
ComponentName(BuildConfig.APPLICATION_ID, "chat.simplex.app.MainActivity_${icon.name.lowercase()}")
@@ -196,7 +207,6 @@ fun PreviewAppearanceSettings() {
AppearanceLayout(
icon = remember { mutableStateOf(AppIcon.DARK_BLUE) },
changeIcon = {},
showThemeSelector = {},
editPrimaryColor = {},
)
}
@@ -1,44 +0,0 @@
package chat.simplex.app.views.usersettings
import SectionViewSelectable
import androidx.compose.foundation.*
import androidx.compose.foundation.layout.*
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.capitalize
import androidx.compose.ui.text.intl.Locale
import chat.simplex.app.R
import chat.simplex.app.ui.theme.*
import chat.simplex.app.views.helpers.*
@Composable
fun ThemeSelectorView() {
val darkTheme = isSystemInDarkTheme()
val allThemes by remember { mutableStateOf(ThemeManager.allThemes(darkTheme).map { ValueTitleDesc(it.second, it.third, "") }) }
ThemeSelectorLayout(
allThemes,
onSelectTheme = {
ThemeManager.applyTheme(it.name, darkTheme)
},
)
}
@Composable
private fun ThemeSelectorLayout(
allThemes: List<ValueTitleDesc<DefaultTheme>>,
onSelectTheme: (DefaultTheme) -> Unit,
) {
Column(
Modifier.fillMaxWidth(),
horizontalAlignment = Alignment.Start,
) {
AppBarTitle(stringResource(R.string.settings_section_title_themes).lowercase().capitalize(Locale.current))
val currentTheme by CurrentColors.collectAsState()
val state = remember { derivedStateOf { currentTheme.second } }
SectionViewSelectable(null, state, allThemes, onSelectTheme)
}
}