diff --git a/apps/android/app/src/main/java/chat/simplex/app/views/chatlist/ChatListView.kt b/apps/android/app/src/main/java/chat/simplex/app/views/chatlist/ChatListView.kt index 52762f234c..438e5f267f 100644 --- a/apps/android/app/src/main/java/chat/simplex/app/views/chatlist/ChatListView.kt +++ b/apps/android/app/src/main/java/chat/simplex/app/views/chatlist/ChatListView.kt @@ -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( diff --git a/apps/android/app/src/main/java/chat/simplex/app/views/helpers/Util.kt b/apps/android/app/src/main/java/chat/simplex/app/views/helpers/Util.kt index 374355582e..76b533ed1e 100644 --- a/apps/android/app/src/main/java/chat/simplex/app/views/helpers/Util.kt +++ b/apps/android/app/src/main/java/chat/simplex/app/views/helpers/Util.kt @@ -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 { // 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) diff --git a/apps/android/app/src/main/java/chat/simplex/app/views/newchat/NewChatSheet.kt b/apps/android/app/src/main/java/chat/simplex/app/views/newchat/NewChatSheet.kt index 6b386fc706..80478e7878 100644 --- a/apps/android/app/src/main/java/chat/simplex/app/views/newchat/NewChatSheet.kt +++ b/apps/android/app/src/main/java/chat/simplex/app/views/newchat/NewChatSheet.kt @@ -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)) } } }