mirror of
https://github.com/simplex-chat/simplex-chat.git
synced 2026-09-01 22:18:43 +00:00
android: New chat dialog (#1173)
* android: New chat dialog * Click indication was disabled * Color experiments * Padding * Padding * Second variant * Third variant * Another style * Another part * update buttons * Another try * Elevation * Empty view when no chats available * update buttons * do not hide "you have no chats" with new chat dialog Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
This commit is contained in:
co-authored by
Evgeny Poberezkin
parent
b263d7e547
commit
059f1e1e79
@@ -2,6 +2,7 @@ package chat.simplex.app.views.chatlist
|
||||
|
||||
import androidx.activity.compose.BackHandler
|
||||
import androidx.compose.foundation.*
|
||||
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.items
|
||||
@@ -25,88 +26,48 @@ import androidx.compose.ui.text.intl.Locale
|
||||
import androidx.compose.ui.unit.dp
|
||||
import chat.simplex.app.R
|
||||
import chat.simplex.app.model.*
|
||||
import chat.simplex.app.ui.theme.HighOrLowlight
|
||||
import chat.simplex.app.ui.theme.Indigo
|
||||
import chat.simplex.app.ui.theme.*
|
||||
import chat.simplex.app.views.helpers.*
|
||||
import chat.simplex.app.views.newchat.NewChatSheet
|
||||
import chat.simplex.app.views.usersettings.SettingsView
|
||||
import chat.simplex.app.views.usersettings.simplexTeamUri
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
class ScaffoldController(val scope: CoroutineScope) {
|
||||
lateinit var state: BottomSheetScaffoldState
|
||||
val expanded = mutableStateOf(false)
|
||||
|
||||
fun expand() {
|
||||
expanded.value = true
|
||||
scope.launch { state.bottomSheetState.expand() }
|
||||
}
|
||||
|
||||
fun collapse() {
|
||||
expanded.value = false
|
||||
scope.launch { state.bottomSheetState.collapse() }
|
||||
}
|
||||
|
||||
fun toggleSheet() {
|
||||
if (state.bottomSheetState.isExpanded) collapse() else expand()
|
||||
}
|
||||
|
||||
fun toggleDrawer() = scope.launch {
|
||||
state.drawerState.apply { if (isClosed) open() else close() }
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun scaffoldController(): ScaffoldController {
|
||||
val ctrl = ScaffoldController(scope = rememberCoroutineScope())
|
||||
val bottomSheetState = rememberBottomSheetState(
|
||||
BottomSheetValue.Collapsed,
|
||||
confirmStateChange = {
|
||||
ctrl.expanded.value = it == BottomSheetValue.Expanded
|
||||
true
|
||||
}
|
||||
)
|
||||
ctrl.state = rememberBottomSheetScaffoldState(bottomSheetState = bottomSheetState)
|
||||
return ctrl
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun ChatListView(chatModel: ChatModel, setPerformLA: (Boolean) -> Unit, stopped: Boolean) {
|
||||
val scaffoldCtrl = scaffoldController()
|
||||
val scaffoldState = rememberScaffoldState()
|
||||
var showNewChatDialog by rememberSaveable { mutableStateOf(false) }
|
||||
LaunchedEffect(chatModel.clearOverlays.value) {
|
||||
if (chatModel.clearOverlays.value && scaffoldCtrl.expanded.value) scaffoldCtrl.collapse()
|
||||
if (chatModel.clearOverlays.value && showNewChatDialog) showNewChatDialog = false
|
||||
}
|
||||
var searchInList by rememberSaveable { mutableStateOf("") }
|
||||
BottomSheetScaffold(
|
||||
topBar = { ChatListToolbar(chatModel, scaffoldCtrl, stopped) { searchInList = it.trim() } },
|
||||
scaffoldState = scaffoldCtrl.state,
|
||||
Scaffold (
|
||||
topBar = { ChatListToolbar(chatModel, scaffoldState.drawerState, stopped) { searchInList = it.trim() } },
|
||||
scaffoldState = scaffoldState,
|
||||
drawerContent = { SettingsView(chatModel, setPerformLA) },
|
||||
sheetPeekHeight = 0.dp,
|
||||
sheetContent = { NewChatSheet(chatModel, scaffoldCtrl) },
|
||||
floatingActionButton = {
|
||||
FloatingActionButton(
|
||||
onClick = {
|
||||
if (!stopped) {
|
||||
if (!scaffoldCtrl.expanded.value) scaffoldCtrl.expand() else scaffoldCtrl.collapse()
|
||||
}
|
||||
},
|
||||
Modifier.padding(bottom = 90.dp),
|
||||
elevation = FloatingActionButtonDefaults.elevation(
|
||||
defaultElevation = 0.dp,
|
||||
pressedElevation = 0.dp,
|
||||
hoveredElevation = 0.dp,
|
||||
focusedElevation = 0.dp,
|
||||
),
|
||||
backgroundColor = if (!stopped) MaterialTheme.colors.primary else HighOrLowlight,
|
||||
contentColor = Color.White
|
||||
) {
|
||||
Icon(Icons.Default.Edit, stringResource(R.string.add_contact_or_create_group))
|
||||
if (searchInList.isEmpty()) {
|
||||
FloatingActionButton(
|
||||
onClick = {
|
||||
if (!stopped) {
|
||||
showNewChatDialog = !showNewChatDialog
|
||||
}
|
||||
},
|
||||
elevation = FloatingActionButtonDefaults.elevation(
|
||||
defaultElevation = 0.dp,
|
||||
pressedElevation = 0.dp,
|
||||
hoveredElevation = 0.dp,
|
||||
focusedElevation = 0.dp,
|
||||
),
|
||||
backgroundColor = if (!stopped) MaterialTheme.colors.primary else HighOrLowlight,
|
||||
contentColor = Color.White
|
||||
) {
|
||||
Icon(if (!showNewChatDialog) Icons.Default.Edit else Icons.Default.Close, stringResource(R.string.add_contact_or_create_group))
|
||||
}
|
||||
}
|
||||
},
|
||||
sheetShape = RoundedCornerShape(topStart = 18.dp, topEnd = 18.dp),
|
||||
}
|
||||
) {
|
||||
Box {
|
||||
Box(Modifier.padding(it)) {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
@@ -115,69 +76,92 @@ fun ChatListView(chatModel: ChatModel, setPerformLA: (Boolean) -> Unit, stopped:
|
||||
if (chatModel.chats.isNotEmpty()) {
|
||||
ChatList(chatModel, search = searchInList)
|
||||
} else {
|
||||
if (!stopped) {
|
||||
OnboardingButtons(scaffoldCtrl)
|
||||
Box(Modifier.fillMaxSize()) {
|
||||
if (!stopped && !showNewChatDialog) {
|
||||
OnboardingButtons { showNewChatDialog = true }
|
||||
}
|
||||
Text(stringResource(R.string.you_have_no_chats), Modifier.align(Alignment.Center), color = HighOrLowlight)
|
||||
}
|
||||
}
|
||||
}
|
||||
if (scaffoldCtrl.expanded.value) {
|
||||
Surface(
|
||||
Modifier
|
||||
.fillMaxSize()
|
||||
.clickable { scaffoldCtrl.collapse() },
|
||||
color = Color.Black.copy(alpha = 0.12F)
|
||||
) {}
|
||||
}
|
||||
}
|
||||
if (showNewChatDialog && searchInList.isEmpty()) {
|
||||
Surface(
|
||||
Modifier
|
||||
.fillMaxSize()
|
||||
.clickable(remember { MutableInteractionSource() }, indication = null) { showNewChatDialog = false },
|
||||
color = if (isInDarkTheme()) MaterialTheme.colors.background.copy(ContentAlpha.disabled) else DrawerDefaults.scrimColor,
|
||||
) {
|
||||
Column(
|
||||
verticalArrangement = Arrangement.Bottom,
|
||||
horizontalAlignment = Alignment.End
|
||||
) {
|
||||
NewChatSheet(chatModel, showNewChatDialog) { showNewChatDialog = false }
|
||||
FloatingActionButton(
|
||||
onClick = {
|
||||
if (!stopped) { showNewChatDialog = !showNewChatDialog }
|
||||
},
|
||||
Modifier.padding(end = 16.dp, bottom = 16.dp),
|
||||
elevation = FloatingActionButtonDefaults.elevation(
|
||||
defaultElevation = 0.dp,
|
||||
pressedElevation = 0.dp,
|
||||
hoveredElevation = 0.dp,
|
||||
focusedElevation = 0.dp,
|
||||
),
|
||||
backgroundColor = if (!stopped) MaterialTheme.colors.primary else HighOrLowlight,
|
||||
contentColor = Color.White
|
||||
) {
|
||||
Icon(if (!showNewChatDialog) Icons.Default.Edit else Icons.Default.Close, stringResource(R.string.add_contact_or_create_group))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun OnboardingButtons(scaffoldCtrl: ScaffoldController) {
|
||||
Box {
|
||||
Column(Modifier.fillMaxSize().padding(6.dp), horizontalAlignment = Alignment.End, verticalArrangement = Arrangement.Bottom) {
|
||||
val uriHandler = LocalUriHandler.current
|
||||
ConnectButton(generalGetString(R.string.chat_with_developers)) {
|
||||
uriHandler.openUri(simplexTeamUri)
|
||||
}
|
||||
Spacer(Modifier.height(10.dp))
|
||||
ConnectButton(generalGetString(R.string.tap_to_start_new_chat)) {
|
||||
scaffoldCtrl.toggleSheet()
|
||||
}
|
||||
val color = MaterialTheme.colors.primary
|
||||
Canvas(modifier = Modifier.width(46.dp).height(10.dp), onDraw = {
|
||||
val trianglePath = Path().apply {
|
||||
moveTo(0.dp.toPx(), 0f)
|
||||
lineTo(16.dp.toPx(), 0.dp.toPx())
|
||||
lineTo(8.dp.toPx(), 10.dp.toPx())
|
||||
lineTo(0.dp.toPx(), 0.dp.toPx())
|
||||
}
|
||||
drawPath(
|
||||
color = color,
|
||||
path = trianglePath
|
||||
)
|
||||
})
|
||||
Spacer(Modifier.height(80.dp))
|
||||
private fun OnboardingButtons(openNewChatDialog: () -> Unit) {
|
||||
Column(Modifier.fillMaxSize().padding(DEFAULT_PADDING), horizontalAlignment = Alignment.End, verticalArrangement = Arrangement.Bottom) {
|
||||
val uriHandler = LocalUriHandler.current
|
||||
ConnectButton(generalGetString(R.string.chat_with_developers)) {
|
||||
uriHandler.openUri(simplexTeamUri)
|
||||
}
|
||||
Text(stringResource(R.string.you_have_no_chats), Modifier.align(Alignment.Center), color = HighOrLowlight)
|
||||
Spacer(Modifier.height(DEFAULT_PADDING))
|
||||
ConnectButton(generalGetString(R.string.tap_to_start_new_chat), openNewChatDialog)
|
||||
val color = MaterialTheme.colors.primary
|
||||
Canvas(modifier = Modifier.width(40.dp).height(10.dp), onDraw = {
|
||||
val trianglePath = Path().apply {
|
||||
moveTo(0.dp.toPx(), 0f)
|
||||
lineTo(16.dp.toPx(), 0.dp.toPx())
|
||||
lineTo(8.dp.toPx(), 10.dp.toPx())
|
||||
lineTo(0.dp.toPx(), 0.dp.toPx())
|
||||
}
|
||||
drawPath(
|
||||
color = color,
|
||||
path = trianglePath
|
||||
)
|
||||
})
|
||||
Spacer(Modifier.height(62.dp))
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun ConnectButton(text: String, onClick: () -> Unit) {
|
||||
Box(
|
||||
Modifier
|
||||
.clip(RoundedCornerShape(16.dp))
|
||||
.background(MaterialTheme.colors.primary)
|
||||
.clickable { onClick() }
|
||||
.padding(vertical = 10.dp, horizontal = 20.dp),
|
||||
Button(onClick,
|
||||
shape = RoundedCornerShape(21.dp),
|
||||
colors = ButtonDefaults.textButtonColors(
|
||||
backgroundColor = MaterialTheme.colors.primary
|
||||
),
|
||||
elevation = null,
|
||||
contentPadding = PaddingValues(horizontal = DEFAULT_PADDING, vertical = DEFAULT_PADDING_HALF),
|
||||
modifier = Modifier.height(42.dp)
|
||||
) {
|
||||
Text(text, color = Color.White)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun ChatListToolbar(chatModel: ChatModel, scaffoldCtrl: ScaffoldController, stopped: Boolean, onSearchValueChanged: (String) -> Unit) {
|
||||
private fun ChatListToolbar(chatModel: ChatModel, drawerState: DrawerState, stopped: Boolean, onSearchValueChanged: (String) -> Unit) {
|
||||
var showSearch by rememberSaveable { mutableStateOf(false) }
|
||||
val hideSearchOnBack = { onSearchValueChanged(""); showSearch = false }
|
||||
if (showSearch) {
|
||||
@@ -208,8 +192,12 @@ private fun ChatListToolbar(chatModel: ChatModel, scaffoldCtrl: ScaffoldControll
|
||||
}
|
||||
}
|
||||
|
||||
val scope = rememberCoroutineScope()
|
||||
DefaultTopAppBar(
|
||||
navigationButton = { if (showSearch) NavigationButtonBack(hideSearchOnBack) else NavigationButtonMenu { scaffoldCtrl.toggleDrawer() } },
|
||||
navigationButton = { if (showSearch)
|
||||
NavigationButtonBack(hideSearchOnBack)
|
||||
else
|
||||
NavigationButtonMenu { scope.launch { if (drawerState.isOpen) drawerState.close() else drawerState.open() } } },
|
||||
title = {
|
||||
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||
Text(
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
package chat.simplex.app.views.helpers
|
||||
|
||||
import android.R.attr.factor
|
||||
import android.R.color
|
||||
import android.content.Context
|
||||
import android.content.res.Resources
|
||||
import android.graphics.*
|
||||
@@ -429,6 +431,9 @@ fun directoryFileCountAndSize(dir: String): Pair<Int, Long> { // count, size in
|
||||
return fileCount to bytes
|
||||
}
|
||||
|
||||
fun Color.darker(factor: Float = 0.1f): Color =
|
||||
Color(max(red * (1 - factor), 0f), max(green * (1 - factor), 0f), max(blue * (1 - factor), 0f), alpha)
|
||||
|
||||
fun ByteArray.toBase64String() = Base64.encodeToString(this, Base64.DEFAULT)
|
||||
|
||||
fun String.toByteArrayFromBase64() = Base64.decode(this, Base64.DEFAULT)
|
||||
|
||||
@@ -3,13 +3,15 @@ package chat.simplex.app.views.newchat
|
||||
import androidx.activity.compose.BackHandler
|
||||
import androidx.compose.foundation.*
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material.*
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.outlined.*
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.vector.ImageVector
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
@@ -18,26 +20,23 @@ import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import chat.simplex.app.R
|
||||
import chat.simplex.app.model.ChatModel
|
||||
import chat.simplex.app.ui.theme.HighOrLowlight
|
||||
import chat.simplex.app.ui.theme.SimpleXTheme
|
||||
import chat.simplex.app.views.chatlist.ScaffoldController
|
||||
import chat.simplex.app.ui.theme.*
|
||||
import chat.simplex.app.views.helpers.ModalManager
|
||||
import chat.simplex.app.views.helpers.generalGetString
|
||||
|
||||
@Composable
|
||||
fun NewChatSheet(chatModel: ChatModel, newChatCtrl: ScaffoldController) {
|
||||
if (newChatCtrl.expanded.value) BackHandler { newChatCtrl.collapse() }
|
||||
fun NewChatSheet(chatModel: ChatModel, newChatDialogOpen: Boolean, closeNewChatDialog: () -> Unit) {
|
||||
if (newChatDialogOpen) BackHandler { closeNewChatDialog() }
|
||||
NewChatSheetLayout(
|
||||
addContact = {
|
||||
newChatCtrl.collapse()
|
||||
closeNewChatDialog()
|
||||
ModalManager.shared.showModal { CreateLinkView(chatModel, CreateLinkTab.ONE_TIME) }
|
||||
},
|
||||
connectViaLink = {
|
||||
newChatCtrl.collapse()
|
||||
closeNewChatDialog()
|
||||
ModalManager.shared.showModalCloseable { close -> ConnectViaLinkView(chatModel, close) }
|
||||
},
|
||||
createGroup = {
|
||||
newChatCtrl.collapse()
|
||||
closeNewChatDialog()
|
||||
ModalManager.shared.showCustomModal { close -> AddGroupView(chatModel, close) }
|
||||
}
|
||||
)
|
||||
@@ -49,72 +48,41 @@ fun NewChatSheetLayout(
|
||||
connectViaLink: () -> Unit,
|
||||
createGroup: () -> Unit
|
||||
) {
|
||||
Column(horizontalAlignment = Alignment.CenterHorizontally) {
|
||||
Text(
|
||||
stringResource(R.string.add_contact_or_create_group),
|
||||
modifier = Modifier.padding(horizontal = 4.dp).padding(top = 20.dp, bottom = 20.dp),
|
||||
style = MaterialTheme.typography.body2
|
||||
)
|
||||
val boxModifier = Modifier.fillMaxWidth().height(80.dp).padding(horizontal = 0.dp)
|
||||
Divider(Modifier.padding(horizontal = 8.dp))
|
||||
Box(boxModifier) {
|
||||
ActionRowButton(
|
||||
stringResource(R.string.share_one_time_link),
|
||||
stringResource(R.string.to_share_with_your_contact),
|
||||
Icons.Outlined.AddLink,
|
||||
click = addContact
|
||||
)
|
||||
}
|
||||
Divider(Modifier.padding(horizontal = 8.dp))
|
||||
Box(boxModifier) {
|
||||
ActionRowButton(
|
||||
stringResource(R.string.connect_via_link_or_qr),
|
||||
stringResource(R.string.connect_via_link_or_qr_from_clipboard_or_in_person),
|
||||
Icons.Outlined.QrCode,
|
||||
click = connectViaLink
|
||||
)
|
||||
}
|
||||
Divider(Modifier.padding(horizontal = 8.dp))
|
||||
Box(boxModifier) {
|
||||
ActionRowButton(
|
||||
stringResource(R.string.create_group),
|
||||
stringResource(R.string.only_stored_on_members_devices),
|
||||
icon = Icons.Outlined.Group,
|
||||
click = createGroup
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun ActionRowButton(
|
||||
text: String, comment: String? = null, icon: ImageVector, disabled: Boolean = false,
|
||||
click: () -> Unit = {}
|
||||
) {
|
||||
Surface(Modifier.fillMaxSize()) {
|
||||
Row(
|
||||
Modifier.clickable(onClick = click).size(48.dp).padding(8.dp),
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
val tint = if (disabled) HighOrLowlight else MaterialTheme.colors.primary
|
||||
Icon(icon, text, tint = tint, modifier = Modifier.size(48.dp).padding(start = 4.dp, end = 16.dp))
|
||||
|
||||
Column {
|
||||
Text(
|
||||
text,
|
||||
textAlign = TextAlign.Left,
|
||||
fontWeight = FontWeight.Bold,
|
||||
color = tint
|
||||
)
|
||||
|
||||
if (comment != null) {
|
||||
Text(
|
||||
comment,
|
||||
textAlign = TextAlign.Left,
|
||||
style = MaterialTheme.typography.body2
|
||||
)
|
||||
val actions = remember { listOf(addContact, connectViaLink, createGroup) }
|
||||
val titles = remember { listOf(R.string.share_one_time_link, R.string.connect_via_link_or_qr, R.string.create_group) }
|
||||
val icons = remember { listOf(Icons.Outlined.AddLink, Icons.Outlined.QrCode, Icons.Outlined.Group) }
|
||||
LazyColumn {
|
||||
items(3) { index ->
|
||||
Row {
|
||||
Spacer(Modifier.weight(1f))
|
||||
Box(contentAlignment = Alignment.CenterEnd) {
|
||||
Button(
|
||||
actions[index],
|
||||
shape = RoundedCornerShape(21.dp),
|
||||
colors = ButtonDefaults.textButtonColors(
|
||||
backgroundColor = if (isInDarkTheme()) DarkGray else MaterialTheme.colors.background
|
||||
),
|
||||
elevation = null,
|
||||
contentPadding = PaddingValues(horizontal = DEFAULT_PADDING_HALF, vertical = DEFAULT_PADDING_HALF),
|
||||
modifier = Modifier.height(42.dp)
|
||||
) {
|
||||
Text(
|
||||
stringResource(titles[index]),
|
||||
Modifier.padding(start = DEFAULT_PADDING_HALF),
|
||||
color = if (isInDarkTheme()) MaterialTheme.colors.primary else MaterialTheme.colors.primary,
|
||||
fontWeight = FontWeight.Medium,
|
||||
)
|
||||
Icon(
|
||||
icons[index],
|
||||
stringResource(titles[index]),
|
||||
Modifier.size(42.dp),
|
||||
tint = if (isInDarkTheme()) MaterialTheme.colors.primary else MaterialTheme.colors.primary
|
||||
)
|
||||
}
|
||||
}
|
||||
Spacer(Modifier.width(DEFAULT_PADDING))
|
||||
}
|
||||
Spacer(Modifier.height(DEFAULT_PADDING))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user