desktop: adapting onboarding process to linking devices (#3490)
* desktop: adapting onboarding process to linking devices * show progress on long operations * changes * clearing chat cache logic * lines --------- Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
This commit is contained in:
parent
a4b44254bc
commit
0e18b13bea
@ -0,0 +1,15 @@
|
|||||||
|
package chat.simplex.common.views.onboarding
|
||||||
|
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import chat.simplex.common.model.SharedPreference
|
||||||
|
import chat.simplex.common.model.User
|
||||||
|
import chat.simplex.res.MR
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
actual fun OnboardingActionButton(user: User?, onboardingStage: SharedPreference<OnboardingStage>, onclick: (() -> Unit)?) {
|
||||||
|
if (user == null) {
|
||||||
|
OnboardingActionButton(MR.strings.create_your_profile, onboarding = OnboardingStage.Step2_CreateProfile, true, onclick = onclick)
|
||||||
|
} else {
|
||||||
|
OnboardingActionButton(MR.strings.make_private_connection, onboarding = OnboardingStage.OnboardingComplete, true, onclick = onclick)
|
||||||
|
}
|
||||||
|
}
|
@ -37,8 +37,7 @@ import kotlinx.coroutines.flow.*
|
|||||||
|
|
||||||
data class SettingsViewState(
|
data class SettingsViewState(
|
||||||
val userPickerState: MutableStateFlow<AnimatedViewState>,
|
val userPickerState: MutableStateFlow<AnimatedViewState>,
|
||||||
val scaffoldState: ScaffoldState,
|
val scaffoldState: ScaffoldState
|
||||||
val switchingUsersAndHosts: MutableState<Boolean>
|
|
||||||
)
|
)
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
@ -102,11 +101,8 @@ fun MainScreen() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Box {
|
Box {
|
||||||
var onboarding by remember { mutableStateOf(chatModel.controller.appPrefs.onboardingStage.get()) }
|
val onboarding by remember { chatModel.controller.appPrefs.onboardingStage.state }
|
||||||
LaunchedEffect(Unit) {
|
val localUserCreated = chatModel.localUserCreated.value
|
||||||
snapshotFlow { chatModel.controller.appPrefs.onboardingStage.state.value }.distinctUntilChanged().collect { onboarding = it }
|
|
||||||
}
|
|
||||||
val userCreated = chatModel.userCreated.value
|
|
||||||
var showInitializationView by remember { mutableStateOf(false) }
|
var showInitializationView by remember { mutableStateOf(false) }
|
||||||
when {
|
when {
|
||||||
chatModel.chatDbStatus.value == null && showInitializationView -> InitializationView()
|
chatModel.chatDbStatus.value == null && showInitializationView -> InitializationView()
|
||||||
@ -115,14 +111,18 @@ fun MainScreen() {
|
|||||||
DatabaseErrorView(chatModel.chatDbStatus, chatModel.controller.appPrefs)
|
DatabaseErrorView(chatModel.chatDbStatus, chatModel.controller.appPrefs)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
remember { chatModel.chatDbEncrypted }.value == null || userCreated == null -> SplashView()
|
remember { chatModel.chatDbEncrypted }.value == null || localUserCreated == null -> SplashView()
|
||||||
onboarding == OnboardingStage.OnboardingComplete && userCreated -> {
|
onboarding == OnboardingStage.OnboardingComplete -> {
|
||||||
Box {
|
Box {
|
||||||
showAdvertiseLAAlert = true
|
showAdvertiseLAAlert = true
|
||||||
val userPickerState by rememberSaveable(stateSaver = AnimatedViewState.saver()) { mutableStateOf(MutableStateFlow(AnimatedViewState.GONE)) }
|
val userPickerState by rememberSaveable(stateSaver = AnimatedViewState.saver()) { mutableStateOf(MutableStateFlow(if (chatModel.desktopNoUserNoRemote()) AnimatedViewState.VISIBLE else AnimatedViewState.GONE)) }
|
||||||
|
KeyChangeEffect(chatModel.desktopNoUserNoRemote) {
|
||||||
|
if (chatModel.desktopNoUserNoRemote() && !ModalManager.start.hasModalsOpen()) {
|
||||||
|
userPickerState.value = AnimatedViewState.VISIBLE
|
||||||
|
}
|
||||||
|
}
|
||||||
val scaffoldState = rememberScaffoldState()
|
val scaffoldState = rememberScaffoldState()
|
||||||
val switchingUsersAndHosts = rememberSaveable { mutableStateOf(false) }
|
val settingsState = remember { SettingsViewState(userPickerState, scaffoldState) }
|
||||||
val settingsState = remember { SettingsViewState(userPickerState, scaffoldState, switchingUsersAndHosts) }
|
|
||||||
if (appPlatform.isAndroid) {
|
if (appPlatform.isAndroid) {
|
||||||
AndroidScreen(settingsState)
|
AndroidScreen(settingsState)
|
||||||
} else {
|
} else {
|
||||||
@ -137,12 +137,14 @@ fun MainScreen() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
onboarding == OnboardingStage.Step2_CreateProfile -> CreateFirstProfile(chatModel) {}
|
onboarding == OnboardingStage.Step2_CreateProfile -> CreateFirstProfile(chatModel) {}
|
||||||
|
onboarding == OnboardingStage.LinkAMobile -> LinkAMobile()
|
||||||
onboarding == OnboardingStage.Step2_5_SetupDatabasePassphrase -> SetupDatabasePassphrase(chatModel)
|
onboarding == OnboardingStage.Step2_5_SetupDatabasePassphrase -> SetupDatabasePassphrase(chatModel)
|
||||||
onboarding == OnboardingStage.Step3_CreateSimpleXAddress -> CreateSimpleXAddress(chatModel, null)
|
onboarding == OnboardingStage.Step3_CreateSimpleXAddress -> CreateSimpleXAddress(chatModel, null)
|
||||||
onboarding == OnboardingStage.Step4_SetNotificationsMode -> SetNotificationsMode(chatModel)
|
onboarding == OnboardingStage.Step4_SetNotificationsMode -> SetNotificationsMode(chatModel)
|
||||||
}
|
}
|
||||||
if (appPlatform.isAndroid) {
|
if (appPlatform.isAndroid) {
|
||||||
ModalManager.fullscreen.showInView()
|
ModalManager.fullscreen.showInView()
|
||||||
|
SwitchingUsersView()
|
||||||
}
|
}
|
||||||
|
|
||||||
val unauthorized = remember { derivedStateOf { AppLock.userAuthorized.value != true } }
|
val unauthorized = remember { derivedStateOf { AppLock.userAuthorized.value != true } }
|
||||||
@ -262,7 +264,7 @@ fun CenterPartOfScreen() {
|
|||||||
.background(MaterialTheme.colors.background),
|
.background(MaterialTheme.colors.background),
|
||||||
contentAlignment = Alignment.Center
|
contentAlignment = Alignment.Center
|
||||||
) {
|
) {
|
||||||
Text(stringResource(MR.strings.no_selected_chat))
|
Text(stringResource(if (chatModel.desktopNoUserNoRemote) MR.strings.no_connected_mobile else MR.strings.no_selected_chat))
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
ModalManager.center.showInView()
|
ModalManager.center.showInView()
|
||||||
@ -286,6 +288,7 @@ fun DesktopScreen(settingsState: SettingsViewState) {
|
|||||||
}
|
}
|
||||||
Box(Modifier.widthIn(max = DEFAULT_START_MODAL_WIDTH)) {
|
Box(Modifier.widthIn(max = DEFAULT_START_MODAL_WIDTH)) {
|
||||||
ModalManager.start.showInView()
|
ModalManager.start.showInView()
|
||||||
|
SwitchingUsersView()
|
||||||
}
|
}
|
||||||
Row(Modifier.padding(start = DEFAULT_START_MODAL_WIDTH).clipToBounds()) {
|
Row(Modifier.padding(start = DEFAULT_START_MODAL_WIDTH).clipToBounds()) {
|
||||||
Box(Modifier.widthIn(min = DEFAULT_MIN_CENTER_MODAL_WIDTH).weight(1f)) {
|
Box(Modifier.widthIn(min = DEFAULT_MIN_CENTER_MODAL_WIDTH).weight(1f)) {
|
||||||
@ -298,7 +301,7 @@ fun DesktopScreen(settingsState: SettingsViewState) {
|
|||||||
EndPartOfScreen()
|
EndPartOfScreen()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
val (userPickerState, scaffoldState, switchingUsersAndHosts ) = settingsState
|
val (userPickerState, scaffoldState ) = settingsState
|
||||||
val scope = rememberCoroutineScope()
|
val scope = rememberCoroutineScope()
|
||||||
if (scaffoldState.drawerState.isOpen) {
|
if (scaffoldState.drawerState.isOpen) {
|
||||||
Box(
|
Box(
|
||||||
@ -312,7 +315,7 @@ fun DesktopScreen(settingsState: SettingsViewState) {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
VerticalDivider(Modifier.padding(start = DEFAULT_START_MODAL_WIDTH))
|
VerticalDivider(Modifier.padding(start = DEFAULT_START_MODAL_WIDTH))
|
||||||
UserPicker(chatModel, userPickerState, switchingUsersAndHosts) {
|
UserPicker(chatModel, userPickerState) {
|
||||||
scope.launch { if (scaffoldState.drawerState.isOpen) scaffoldState.drawerState.close() else scaffoldState.drawerState.open() }
|
scope.launch { if (scaffoldState.drawerState.isOpen) scaffoldState.drawerState.close() else scaffoldState.drawerState.open() }
|
||||||
userPickerState.value = AnimatedViewState.GONE
|
userPickerState.value = AnimatedViewState.GONE
|
||||||
}
|
}
|
||||||
@ -335,3 +338,26 @@ fun InitializationView() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun SwitchingUsersView() {
|
||||||
|
if (remember { chatModel.switchingUsersAndHosts }.value) {
|
||||||
|
Box(
|
||||||
|
Modifier.fillMaxSize().clickable(enabled = false, onClick = {}),
|
||||||
|
contentAlignment = Alignment.Center
|
||||||
|
) {
|
||||||
|
ProgressIndicator()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun ProgressIndicator() {
|
||||||
|
CircularProgressIndicator(
|
||||||
|
Modifier
|
||||||
|
.padding(horizontal = 2.dp)
|
||||||
|
.size(30.dp),
|
||||||
|
color = MaterialTheme.colors.secondary,
|
||||||
|
strokeWidth = 2.5.dp
|
||||||
|
)
|
||||||
|
}
|
||||||
|
@ -2,7 +2,7 @@ package chat.simplex.common.model
|
|||||||
|
|
||||||
import androidx.compose.material.*
|
import androidx.compose.material.*
|
||||||
import androidx.compose.runtime.*
|
import androidx.compose.runtime.*
|
||||||
import androidx.compose.runtime.snapshots.SnapshotStateList
|
import androidx.compose.runtime.saveable.rememberSaveable
|
||||||
import androidx.compose.runtime.snapshots.SnapshotStateMap
|
import androidx.compose.runtime.snapshots.SnapshotStateMap
|
||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
import androidx.compose.ui.text.SpanStyle
|
import androidx.compose.ui.text.SpanStyle
|
||||||
@ -43,7 +43,7 @@ object ChatModel {
|
|||||||
val setDeliveryReceipts = mutableStateOf(false)
|
val setDeliveryReceipts = mutableStateOf(false)
|
||||||
val currentUser = mutableStateOf<User?>(null)
|
val currentUser = mutableStateOf<User?>(null)
|
||||||
val users = mutableStateListOf<UserInfo>()
|
val users = mutableStateListOf<UserInfo>()
|
||||||
val userCreated = mutableStateOf<Boolean?>(null)
|
val localUserCreated = mutableStateOf<Boolean?>(null)
|
||||||
val chatRunning = mutableStateOf<Boolean?>(null)
|
val chatRunning = mutableStateOf<Boolean?>(null)
|
||||||
val chatDbChanged = mutableStateOf<Boolean>(false)
|
val chatDbChanged = mutableStateOf<Boolean>(false)
|
||||||
val chatDbEncrypted = mutableStateOf<Boolean?>(false)
|
val chatDbEncrypted = mutableStateOf<Boolean?>(false)
|
||||||
@ -51,6 +51,7 @@ object ChatModel {
|
|||||||
val chats = mutableStateListOf<Chat>()
|
val chats = mutableStateListOf<Chat>()
|
||||||
// map of connections network statuses, key is agent connection id
|
// map of connections network statuses, key is agent connection id
|
||||||
val networkStatuses = mutableStateMapOf<String, NetworkStatus>()
|
val networkStatuses = mutableStateMapOf<String, NetworkStatus>()
|
||||||
|
val switchingUsersAndHosts = mutableStateOf(false)
|
||||||
|
|
||||||
// current chat
|
// current chat
|
||||||
val chatId = mutableStateOf<String?>(null)
|
val chatId = mutableStateOf<String?>(null)
|
||||||
@ -108,6 +109,9 @@ object ChatModel {
|
|||||||
|
|
||||||
var updatingChatsMutex: Mutex = Mutex()
|
var updatingChatsMutex: Mutex = Mutex()
|
||||||
|
|
||||||
|
val desktopNoUserNoRemote: Boolean @Composable get() = appPlatform.isDesktop && currentUser.value == null && currentRemoteHost.value == null
|
||||||
|
fun desktopNoUserNoRemote(): Boolean = appPlatform.isDesktop && currentUser.value == null && currentRemoteHost.value == null
|
||||||
|
|
||||||
// remote controller
|
// remote controller
|
||||||
val remoteHosts = mutableStateListOf<RemoteHostInfo>()
|
val remoteHosts = mutableStateListOf<RemoteHostInfo>()
|
||||||
val currentRemoteHost = mutableStateOf<RemoteHostInfo?>(null)
|
val currentRemoteHost = mutableStateOf<RemoteHostInfo?>(null)
|
||||||
@ -620,6 +624,7 @@ object ChatModel {
|
|||||||
terminalItems.add(item)
|
terminalItems.add(item)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val connectedToRemote: Boolean @Composable get() = currentRemoteHost.value != null || remoteCtrlSession.value?.active == true
|
||||||
fun connectedToRemote(): Boolean = currentRemoteHost.value != null || remoteCtrlSession.value?.active == true
|
fun connectedToRemote(): Boolean = currentRemoteHost.value != null || remoteCtrlSession.value?.active == true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -362,7 +362,7 @@ object ChatController {
|
|||||||
chatModel.users.addAll(users)
|
chatModel.users.addAll(users)
|
||||||
if (justStarted) {
|
if (justStarted) {
|
||||||
chatModel.currentUser.value = user
|
chatModel.currentUser.value = user
|
||||||
chatModel.userCreated.value = true
|
chatModel.localUserCreated.value = true
|
||||||
getUserChatData(null)
|
getUserChatData(null)
|
||||||
appPrefs.chatLastStart.set(Clock.System.now())
|
appPrefs.chatLastStart.set(Clock.System.now())
|
||||||
chatModel.chatRunning.value = true
|
chatModel.chatRunning.value = true
|
||||||
@ -382,6 +382,31 @@ object ChatController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
suspend fun startChatWithoutUser() {
|
||||||
|
Log.d(TAG, "user: null")
|
||||||
|
try {
|
||||||
|
if (chatModel.chatRunning.value == true) return
|
||||||
|
apiSetTempFolder(coreTmpDir.absolutePath)
|
||||||
|
apiSetFilesFolder(appFilesDir.absolutePath)
|
||||||
|
if (appPlatform.isDesktop) {
|
||||||
|
apiSetRemoteHostsFolder(remoteHostsDir.absolutePath)
|
||||||
|
}
|
||||||
|
apiSetXFTPConfig(getXFTPCfg())
|
||||||
|
apiSetEncryptLocalFiles(appPrefs.privacyEncryptLocalFiles.get())
|
||||||
|
chatModel.users.clear()
|
||||||
|
chatModel.currentUser.value = null
|
||||||
|
chatModel.localUserCreated.value = false
|
||||||
|
appPrefs.chatLastStart.set(Clock.System.now())
|
||||||
|
chatModel.chatRunning.value = true
|
||||||
|
startReceiver()
|
||||||
|
setLocalDeviceName(appPrefs.deviceNameForRemoteAccess.get()!!)
|
||||||
|
Log.d(TAG, "startChat: started without user")
|
||||||
|
} catch (e: Error) {
|
||||||
|
Log.e(TAG, "failed starting chat without user $e")
|
||||||
|
throw e
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
suspend fun changeActiveUser(rhId: Long?, toUserId: Long, viewPwd: String?) {
|
suspend fun changeActiveUser(rhId: Long?, toUserId: Long, viewPwd: String?) {
|
||||||
try {
|
try {
|
||||||
changeActiveUser_(rhId, toUserId, viewPwd)
|
changeActiveUser_(rhId, toUserId, viewPwd)
|
||||||
@ -475,7 +500,9 @@ object ChatController {
|
|||||||
val r = sendCmd(rh, CC.ShowActiveUser())
|
val r = sendCmd(rh, CC.ShowActiveUser())
|
||||||
if (r is CR.ActiveUser) return r.user.updateRemoteHostId(rh)
|
if (r is CR.ActiveUser) return r.user.updateRemoteHostId(rh)
|
||||||
Log.d(TAG, "apiGetActiveUser: ${r.responseType} ${r.details}")
|
Log.d(TAG, "apiGetActiveUser: ${r.responseType} ${r.details}")
|
||||||
chatModel.userCreated.value = false
|
if (rh == null) {
|
||||||
|
chatModel.localUserCreated.value = false
|
||||||
|
}
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1990,7 +2017,7 @@ object ChatController {
|
|||||||
chatModel.setContactNetworkStatus(contact, NetworkStatus.Error(err))
|
chatModel.setContactNetworkStatus(contact, NetworkStatus.Error(err))
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun switchUIRemoteHost(rhId: Long?) {
|
suspend fun switchUIRemoteHost(rhId: Long?) = showProgressIfNeeded {
|
||||||
// TODO lock the switch so that two switches can't run concurrently?
|
// TODO lock the switch so that two switches can't run concurrently?
|
||||||
chatModel.chatId.value = null
|
chatModel.chatId.value = null
|
||||||
ModalManager.center.closeModals()
|
ModalManager.center.closeModals()
|
||||||
@ -2003,7 +2030,10 @@ object ChatController {
|
|||||||
chatModel.users.clear()
|
chatModel.users.clear()
|
||||||
chatModel.users.addAll(users)
|
chatModel.users.addAll(users)
|
||||||
chatModel.currentUser.value = user
|
chatModel.currentUser.value = user
|
||||||
chatModel.userCreated.value = true
|
if (user == null) {
|
||||||
|
chatModel.chatItems.clear()
|
||||||
|
chatModel.chats.clear()
|
||||||
|
}
|
||||||
val statuses = apiGetNetworkStatuses(rhId)
|
val statuses = apiGetNetworkStatuses(rhId)
|
||||||
if (statuses != null) {
|
if (statuses != null) {
|
||||||
chatModel.networkStatuses.clear()
|
chatModel.networkStatuses.clear()
|
||||||
@ -2013,6 +2043,23 @@ object ChatController {
|
|||||||
getUserChatData(rhId)
|
getUserChatData(rhId)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
suspend fun showProgressIfNeeded(block: suspend () -> Unit) {
|
||||||
|
val job = withBGApi {
|
||||||
|
try {
|
||||||
|
delay(500)
|
||||||
|
chatModel.switchingUsersAndHosts.value = true
|
||||||
|
} catch (e: Throwable) {
|
||||||
|
chatModel.switchingUsersAndHosts.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
block()
|
||||||
|
} finally {
|
||||||
|
job.cancel()
|
||||||
|
chatModel.switchingUsersAndHosts.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fun getXFTPCfg(): XFTPFileConfig {
|
fun getXFTPCfg(): XFTPFileConfig {
|
||||||
return XFTPFileConfig(minFileSize = 0)
|
return XFTPFileConfig(minFileSize = 0)
|
||||||
}
|
}
|
||||||
|
@ -55,10 +55,22 @@ suspend fun initChatController(useKey: String? = null, confirmMigrations: Migrat
|
|||||||
if (appPreferences.encryptionStartedAt.get() != null) appPreferences.encryptionStartedAt.set(null)
|
if (appPreferences.encryptionStartedAt.get() != null) appPreferences.encryptionStartedAt.set(null)
|
||||||
val user = chatController.apiGetActiveUser(null)
|
val user = chatController.apiGetActiveUser(null)
|
||||||
if (user == null) {
|
if (user == null) {
|
||||||
chatModel.controller.appPrefs.onboardingStage.set(OnboardingStage.Step1_SimpleXInfo)
|
|
||||||
chatModel.controller.appPrefs.privacyDeliveryReceiptsSet.set(true)
|
chatModel.controller.appPrefs.privacyDeliveryReceiptsSet.set(true)
|
||||||
chatModel.currentUser.value = null
|
chatModel.currentUser.value = null
|
||||||
chatModel.users.clear()
|
chatModel.users.clear()
|
||||||
|
if (appPlatform.isDesktop) {
|
||||||
|
/**
|
||||||
|
* Setting it here to null because otherwise the screen will flash in [MainScreen] after the first start
|
||||||
|
* because of default value of [OnboardingStage.OnboardingComplete]
|
||||||
|
* */
|
||||||
|
chatModel.localUserCreated.value = null
|
||||||
|
if (chatController.listRemoteHosts()?.isEmpty() == true) {
|
||||||
|
chatController.appPrefs.onboardingStage.set(OnboardingStage.Step1_SimpleXInfo)
|
||||||
|
}
|
||||||
|
chatController.startChatWithoutUser()
|
||||||
|
} else {
|
||||||
|
chatController.appPrefs.onboardingStage.set(OnboardingStage.Step1_SimpleXInfo)
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
val savedOnboardingStage = appPreferences.onboardingStage.get()
|
val savedOnboardingStage = appPreferences.onboardingStage.get()
|
||||||
appPreferences.onboardingStage.set(if (listOf(OnboardingStage.Step1_SimpleXInfo, OnboardingStage.Step2_CreateProfile).contains(savedOnboardingStage) && chatModel.users.size == 1) {
|
appPreferences.onboardingStage.set(if (listOf(OnboardingStage.Step1_SimpleXInfo, OnboardingStage.Step2_CreateProfile).contains(savedOnboardingStage) && chatModel.users.size == 1) {
|
||||||
|
@ -59,7 +59,9 @@ abstract class NtfManager {
|
|||||||
awaitChatStartedIfNeeded(chatModel)
|
awaitChatStartedIfNeeded(chatModel)
|
||||||
if (userId != null && userId != chatModel.currentUser.value?.userId && chatModel.currentUser.value != null) {
|
if (userId != null && userId != chatModel.currentUser.value?.userId && chatModel.currentUser.value != null) {
|
||||||
// TODO include remote host ID in desktop notifications?
|
// TODO include remote host ID in desktop notifications?
|
||||||
chatModel.controller.changeActiveUser(null, userId, null)
|
chatModel.controller.showProgressIfNeeded {
|
||||||
|
chatModel.controller.changeActiveUser(null, userId, null)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
val cInfo = chatModel.getChat(chatId)?.chatInfo
|
val cInfo = chatModel.getChat(chatId)?.chatInfo
|
||||||
chatModel.clearOverlays.value = true
|
chatModel.clearOverlays.value = true
|
||||||
@ -72,7 +74,9 @@ abstract class NtfManager {
|
|||||||
awaitChatStartedIfNeeded(chatModel)
|
awaitChatStartedIfNeeded(chatModel)
|
||||||
if (userId != null && userId != chatModel.currentUser.value?.userId && chatModel.currentUser.value != null) {
|
if (userId != null && userId != chatModel.currentUser.value?.userId && chatModel.currentUser.value != null) {
|
||||||
// TODO include remote host ID in desktop notifications?
|
// TODO include remote host ID in desktop notifications?
|
||||||
chatModel.controller.changeActiveUser(null, userId, null)
|
chatModel.controller.showProgressIfNeeded {
|
||||||
|
chatModel.controller.changeActiveUser(null, userId, null)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
chatModel.chatId.value = null
|
chatModel.chatId.value = null
|
||||||
chatModel.clearOverlays.value = true
|
chatModel.clearOverlays.value = true
|
||||||
|
@ -21,8 +21,8 @@ import androidx.compose.ui.text.font.FontWeight
|
|||||||
import androidx.compose.ui.text.style.*
|
import androidx.compose.ui.text.style.*
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.compose.ui.unit.sp
|
import androidx.compose.ui.unit.sp
|
||||||
import chat.simplex.common.model.ChatModel
|
import chat.simplex.common.model.*
|
||||||
import chat.simplex.common.model.Profile
|
import chat.simplex.common.model.ChatModel.controller
|
||||||
import chat.simplex.common.platform.*
|
import chat.simplex.common.platform.*
|
||||||
import chat.simplex.common.ui.theme.*
|
import chat.simplex.common.ui.theme.*
|
||||||
import chat.simplex.common.views.helpers.*
|
import chat.simplex.common.views.helpers.*
|
||||||
@ -76,7 +76,13 @@ fun CreateProfile(chatModel: ChatModel, close: () -> Unit) {
|
|||||||
disabled = !canCreateProfile(displayName.value),
|
disabled = !canCreateProfile(displayName.value),
|
||||||
textColor = MaterialTheme.colors.primary,
|
textColor = MaterialTheme.colors.primary,
|
||||||
iconColor = MaterialTheme.colors.primary,
|
iconColor = MaterialTheme.colors.primary,
|
||||||
click = { createProfileInProfiles(chatModel, displayName.value, close) },
|
click = {
|
||||||
|
if (chatModel.localUserCreated.value == true) {
|
||||||
|
createProfileInProfiles(chatModel, displayName.value, close)
|
||||||
|
} else {
|
||||||
|
createProfileInNoProfileSetup(displayName.value, close)
|
||||||
|
}
|
||||||
|
},
|
||||||
)
|
)
|
||||||
SectionTextFooter(generalGetString(MR.strings.your_profile_is_stored_on_your_device))
|
SectionTextFooter(generalGetString(MR.strings.your_profile_is_stored_on_your_device))
|
||||||
SectionTextFooter(generalGetString(MR.strings.profile_is_only_shared_with_your_contacts))
|
SectionTextFooter(generalGetString(MR.strings.profile_is_only_shared_with_your_contacts))
|
||||||
@ -168,6 +174,17 @@ fun CreateFirstProfile(chatModel: ChatModel, close: () -> Unit) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun createProfileInNoProfileSetup(displayName: String, close: () -> Unit) {
|
||||||
|
withApi {
|
||||||
|
val user = controller.apiCreateActiveUser(null, Profile(displayName.trim(), "", null)) ?: return@withApi
|
||||||
|
controller.appPrefs.onboardingStage.set(OnboardingStage.Step3_CreateSimpleXAddress)
|
||||||
|
chatModel.chatRunning.value = false
|
||||||
|
controller.startChat(user)
|
||||||
|
controller.switchUIRemoteHost(null)
|
||||||
|
close()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fun createProfileInProfiles(chatModel: ChatModel, displayName: String, close: () -> Unit) {
|
fun createProfileInProfiles(chatModel: ChatModel, displayName: String, close: () -> Unit) {
|
||||||
withApi {
|
withApi {
|
||||||
val rhId = chatModel.remoteHostId()
|
val rhId = chatModel.remoteHostId()
|
||||||
|
@ -68,14 +68,14 @@ fun ChatListView(chatModel: ChatModel, settingsState: SettingsViewState, setPerf
|
|||||||
val endPadding = if (appPlatform.isDesktop) 56.dp else 0.dp
|
val endPadding = if (appPlatform.isDesktop) 56.dp else 0.dp
|
||||||
var searchInList by rememberSaveable { mutableStateOf("") }
|
var searchInList by rememberSaveable { mutableStateOf("") }
|
||||||
val scope = rememberCoroutineScope()
|
val scope = rememberCoroutineScope()
|
||||||
val (userPickerState, scaffoldState, switchingUsersAndHosts ) = settingsState
|
val (userPickerState, scaffoldState ) = settingsState
|
||||||
Scaffold(topBar = { Box(Modifier.padding(end = endPadding)) { ChatListToolbar(chatModel, scaffoldState.drawerState, userPickerState, stopped) { searchInList = it.trim() } } },
|
Scaffold(topBar = { Box(Modifier.padding(end = endPadding)) { ChatListToolbar(chatModel, scaffoldState.drawerState, userPickerState, stopped) { searchInList = it.trim() } } },
|
||||||
scaffoldState = scaffoldState,
|
scaffoldState = scaffoldState,
|
||||||
drawerContent = { SettingsView(chatModel, setPerformLA, scaffoldState.drawerState) },
|
drawerContent = { SettingsView(chatModel, setPerformLA, scaffoldState.drawerState) },
|
||||||
drawerScrimColor = MaterialTheme.colors.onSurface.copy(alpha = if (isInDarkTheme()) 0.16f else 0.32f),
|
drawerScrimColor = MaterialTheme.colors.onSurface.copy(alpha = if (isInDarkTheme()) 0.16f else 0.32f),
|
||||||
drawerGesturesEnabled = appPlatform.isAndroid,
|
drawerGesturesEnabled = appPlatform.isAndroid,
|
||||||
floatingActionButton = {
|
floatingActionButton = {
|
||||||
if (searchInList.isEmpty()) {
|
if (searchInList.isEmpty() && !chatModel.desktopNoUserNoRemote) {
|
||||||
FloatingActionButton(
|
FloatingActionButton(
|
||||||
onClick = {
|
onClick = {
|
||||||
if (!stopped) {
|
if (!stopped) {
|
||||||
@ -104,7 +104,7 @@ fun ChatListView(chatModel: ChatModel, settingsState: SettingsViewState, setPerf
|
|||||||
) {
|
) {
|
||||||
if (chatModel.chats.isNotEmpty()) {
|
if (chatModel.chats.isNotEmpty()) {
|
||||||
ChatList(chatModel, search = searchInList)
|
ChatList(chatModel, search = searchInList)
|
||||||
} else if (!switchingUsersAndHosts.value) {
|
} else if (!chatModel.switchingUsersAndHosts.value && !chatModel.desktopNoUserNoRemote) {
|
||||||
Box(Modifier.fillMaxSize()) {
|
Box(Modifier.fillMaxSize()) {
|
||||||
if (!stopped && !newChatSheetState.collectAsState().value.isVisible()) {
|
if (!stopped && !newChatSheetState.collectAsState().value.isVisible()) {
|
||||||
OnboardingButtons(showNewChatSheet)
|
OnboardingButtons(showNewChatSheet)
|
||||||
@ -121,19 +121,11 @@ fun ChatListView(chatModel: ChatModel, settingsState: SettingsViewState, setPerf
|
|||||||
NewChatSheet(chatModel, newChatSheetState, stopped, hideNewChatSheet)
|
NewChatSheet(chatModel, newChatSheetState, stopped, hideNewChatSheet)
|
||||||
}
|
}
|
||||||
if (appPlatform.isAndroid) {
|
if (appPlatform.isAndroid) {
|
||||||
UserPicker(chatModel, userPickerState, switchingUsersAndHosts) {
|
UserPicker(chatModel, userPickerState) {
|
||||||
scope.launch { if (scaffoldState.drawerState.isOpen) scaffoldState.drawerState.close() else scaffoldState.drawerState.open() }
|
scope.launch { if (scaffoldState.drawerState.isOpen) scaffoldState.drawerState.close() else scaffoldState.drawerState.open() }
|
||||||
userPickerState.value = AnimatedViewState.GONE
|
userPickerState.value = AnimatedViewState.GONE
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (switchingUsersAndHosts.value) {
|
|
||||||
Box(
|
|
||||||
Modifier.fillMaxSize().clickable(enabled = false, onClick = {}),
|
|
||||||
contentAlignment = Alignment.Center
|
|
||||||
) {
|
|
||||||
ProgressIndicator()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
@ -209,7 +201,7 @@ private fun ChatListToolbar(chatModel: ChatModel, drawerState: DrawerState, user
|
|||||||
navigationButton = {
|
navigationButton = {
|
||||||
if (showSearch) {
|
if (showSearch) {
|
||||||
NavigationButtonBack(hideSearchOnBack)
|
NavigationButtonBack(hideSearchOnBack)
|
||||||
} else if (chatModel.users.isEmpty()) {
|
} else if (chatModel.users.isEmpty() && !chatModel.desktopNoUserNoRemote) {
|
||||||
NavigationButtonMenu { scope.launch { if (drawerState.isOpen) drawerState.close() else drawerState.open() } }
|
NavigationButtonMenu { scope.launch { if (drawerState.isOpen) drawerState.close() else drawerState.open() } }
|
||||||
} else {
|
} else {
|
||||||
val users by remember { derivedStateOf { chatModel.users.filter { u -> u.user.activeUser || !u.user.hidden } } }
|
val users by remember { derivedStateOf { chatModel.users.filter { u -> u.user.activeUser || !u.user.hidden } } }
|
||||||
@ -304,17 +296,6 @@ private fun ToggleFilterButton() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
|
||||||
private fun ProgressIndicator() {
|
|
||||||
CircularProgressIndicator(
|
|
||||||
Modifier
|
|
||||||
.padding(horizontal = 2.dp)
|
|
||||||
.size(30.dp),
|
|
||||||
color = MaterialTheme.colors.secondary,
|
|
||||||
strokeWidth = 2.5.dp
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
expect fun DesktopActiveCallOverlayLayout(newChatSheetState: MutableStateFlow<AnimatedViewState>)
|
expect fun DesktopActiveCallOverlayLayout(newChatSheetState: MutableStateFlow<AnimatedViewState>)
|
||||||
|
|
||||||
|
@ -26,7 +26,7 @@ import kotlinx.coroutines.flow.MutableStateFlow
|
|||||||
@Composable
|
@Composable
|
||||||
fun ShareListView(chatModel: ChatModel, settingsState: SettingsViewState, stopped: Boolean) {
|
fun ShareListView(chatModel: ChatModel, settingsState: SettingsViewState, stopped: Boolean) {
|
||||||
var searchInList by rememberSaveable { mutableStateOf("") }
|
var searchInList by rememberSaveable { mutableStateOf("") }
|
||||||
val (userPickerState, scaffoldState, switchingUsersAndHosts) = settingsState
|
val (userPickerState, scaffoldState) = settingsState
|
||||||
val endPadding = if (appPlatform.isDesktop) 56.dp else 0.dp
|
val endPadding = if (appPlatform.isDesktop) 56.dp else 0.dp
|
||||||
Scaffold(
|
Scaffold(
|
||||||
Modifier.padding(end = endPadding),
|
Modifier.padding(end = endPadding),
|
||||||
@ -47,7 +47,7 @@ fun ShareListView(chatModel: ChatModel, settingsState: SettingsViewState, stoppe
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (appPlatform.isAndroid) {
|
if (appPlatform.isAndroid) {
|
||||||
UserPicker(chatModel, userPickerState, switchingUsersAndHosts, showSettings = false, showCancel = true, cancelClicked = {
|
UserPicker(chatModel, userPickerState, showSettings = false, showCancel = true, cancelClicked = {
|
||||||
chatModel.sharedContent.value = null
|
chatModel.sharedContent.value = null
|
||||||
userPickerState.value = AnimatedViewState.GONE
|
userPickerState.value = AnimatedViewState.GONE
|
||||||
})
|
})
|
||||||
|
@ -26,7 +26,9 @@ import chat.simplex.common.model.ChatModel.controller
|
|||||||
import chat.simplex.common.ui.theme.*
|
import chat.simplex.common.ui.theme.*
|
||||||
import chat.simplex.common.views.helpers.*
|
import chat.simplex.common.views.helpers.*
|
||||||
import chat.simplex.common.platform.*
|
import chat.simplex.common.platform.*
|
||||||
|
import chat.simplex.common.views.CreateProfile
|
||||||
import chat.simplex.common.views.remote.*
|
import chat.simplex.common.views.remote.*
|
||||||
|
import chat.simplex.common.views.usersettings.doWithAuth
|
||||||
import chat.simplex.res.MR
|
import chat.simplex.res.MR
|
||||||
import dev.icerock.moko.resources.compose.stringResource
|
import dev.icerock.moko.resources.compose.stringResource
|
||||||
import kotlinx.coroutines.delay
|
import kotlinx.coroutines.delay
|
||||||
@ -38,7 +40,6 @@ import kotlin.math.roundToInt
|
|||||||
fun UserPicker(
|
fun UserPicker(
|
||||||
chatModel: ChatModel,
|
chatModel: ChatModel,
|
||||||
userPickerState: MutableStateFlow<AnimatedViewState>,
|
userPickerState: MutableStateFlow<AnimatedViewState>,
|
||||||
switchingUsersAndHosts: MutableState<Boolean>,
|
|
||||||
showSettings: Boolean = true,
|
showSettings: Boolean = true,
|
||||||
showCancel: Boolean = false,
|
showCancel: Boolean = false,
|
||||||
cancelClicked: () -> Unit = {},
|
cancelClicked: () -> Unit = {},
|
||||||
@ -123,14 +124,10 @@ fun UserPicker(
|
|||||||
userPickerState.value = AnimatedViewState.HIDING
|
userPickerState.value = AnimatedViewState.HIDING
|
||||||
if (!u.user.activeUser) {
|
if (!u.user.activeUser) {
|
||||||
scope.launch {
|
scope.launch {
|
||||||
val job = launch {
|
controller.showProgressIfNeeded {
|
||||||
delay(500)
|
ModalManager.closeAllModalsEverywhere()
|
||||||
switchingUsersAndHosts.value = true
|
chatModel.controller.changeActiveUser(u.user.remoteHostId, u.user.userId, null)
|
||||||
}
|
}
|
||||||
ModalManager.closeAllModalsEverywhere()
|
|
||||||
chatModel.controller.changeActiveUser(u.user.remoteHostId, u.user.userId, null)
|
|
||||||
job.cancel()
|
|
||||||
switchingUsersAndHosts.value = false
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -162,13 +159,13 @@ fun UserPicker(
|
|||||||
val currentRemoteHost = remember { chatModel.currentRemoteHost }.value
|
val currentRemoteHost = remember { chatModel.currentRemoteHost }.value
|
||||||
Column(Modifier.weight(1f).verticalScroll(rememberScrollState())) {
|
Column(Modifier.weight(1f).verticalScroll(rememberScrollState())) {
|
||||||
if (remoteHosts.isNotEmpty()) {
|
if (remoteHosts.isNotEmpty()) {
|
||||||
if (currentRemoteHost == null) {
|
if (currentRemoteHost == null && chatModel.localUserCreated.value == true) {
|
||||||
LocalDevicePickerItem(true) {
|
LocalDevicePickerItem(true) {
|
||||||
userPickerState.value = AnimatedViewState.HIDING
|
userPickerState.value = AnimatedViewState.HIDING
|
||||||
switchToLocalDevice()
|
switchToLocalDevice()
|
||||||
}
|
}
|
||||||
Divider(Modifier.requiredHeight(1.dp))
|
Divider(Modifier.requiredHeight(1.dp))
|
||||||
} else {
|
} else if (currentRemoteHost != null) {
|
||||||
val connecting = rememberSaveable { mutableStateOf(false) }
|
val connecting = rememberSaveable { mutableStateOf(false) }
|
||||||
RemoteHostPickerItem(currentRemoteHost,
|
RemoteHostPickerItem(currentRemoteHost,
|
||||||
actionButtonClick = {
|
actionButtonClick = {
|
||||||
@ -176,7 +173,7 @@ fun UserPicker(
|
|||||||
stopRemoteHostAndReloadHosts(currentRemoteHost, true)
|
stopRemoteHostAndReloadHosts(currentRemoteHost, true)
|
||||||
}) {
|
}) {
|
||||||
userPickerState.value = AnimatedViewState.HIDING
|
userPickerState.value = AnimatedViewState.HIDING
|
||||||
switchToRemoteHost(currentRemoteHost, switchingUsersAndHosts, connecting)
|
switchToRemoteHost(currentRemoteHost, connecting)
|
||||||
}
|
}
|
||||||
Divider(Modifier.requiredHeight(1.dp))
|
Divider(Modifier.requiredHeight(1.dp))
|
||||||
}
|
}
|
||||||
@ -184,7 +181,7 @@ fun UserPicker(
|
|||||||
|
|
||||||
UsersView()
|
UsersView()
|
||||||
|
|
||||||
if (remoteHosts.isNotEmpty() && currentRemoteHost != null) {
|
if (remoteHosts.isNotEmpty() && currentRemoteHost != null && chatModel.localUserCreated.value == true) {
|
||||||
LocalDevicePickerItem(false) {
|
LocalDevicePickerItem(false) {
|
||||||
userPickerState.value = AnimatedViewState.HIDING
|
userPickerState.value = AnimatedViewState.HIDING
|
||||||
switchToLocalDevice()
|
switchToLocalDevice()
|
||||||
@ -199,7 +196,7 @@ fun UserPicker(
|
|||||||
stopRemoteHostAndReloadHosts(h, false)
|
stopRemoteHostAndReloadHosts(h, false)
|
||||||
}) {
|
}) {
|
||||||
userPickerState.value = AnimatedViewState.HIDING
|
userPickerState.value = AnimatedViewState.HIDING
|
||||||
switchToRemoteHost(h, switchingUsersAndHosts, connecting)
|
switchToRemoteHost(h, connecting)
|
||||||
}
|
}
|
||||||
Divider(Modifier.requiredHeight(1.dp))
|
Divider(Modifier.requiredHeight(1.dp))
|
||||||
}
|
}
|
||||||
@ -220,6 +217,18 @@ fun UserPicker(
|
|||||||
userPickerState.value = AnimatedViewState.GONE
|
userPickerState.value = AnimatedViewState.GONE
|
||||||
}
|
}
|
||||||
Divider(Modifier.requiredHeight(1.dp))
|
Divider(Modifier.requiredHeight(1.dp))
|
||||||
|
} else if (chatModel.desktopNoUserNoRemote) {
|
||||||
|
CreateInitialProfile {
|
||||||
|
doWithAuth(generalGetString(MR.strings.auth_open_chat_profiles), generalGetString(MR.strings.auth_log_in_using_credential)) {
|
||||||
|
ModalManager.center.showModalCloseable { close ->
|
||||||
|
LaunchedEffect(Unit) {
|
||||||
|
userPickerState.value = AnimatedViewState.HIDING
|
||||||
|
}
|
||||||
|
CreateProfile(chat.simplex.common.platform.chatModel, close)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Divider(Modifier.requiredHeight(1.dp))
|
||||||
}
|
}
|
||||||
if (showSettings) {
|
if (showSettings) {
|
||||||
SettingsPickerItem(settingsClicked)
|
SettingsPickerItem(settingsClicked)
|
||||||
@ -401,6 +410,16 @@ private fun LinkAMobilePickerItem(onClick: () -> Unit) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun CreateInitialProfile(onClick: () -> Unit) {
|
||||||
|
SectionItemView(onClick, padding = PaddingValues(start = DEFAULT_PADDING + 7.dp, end = DEFAULT_PADDING), minHeight = 68.dp) {
|
||||||
|
val text = generalGetString(MR.strings.create_chat_profile)
|
||||||
|
Icon(painterResource(MR.images.ic_manage_accounts), text, Modifier.size(20.dp), tint = MaterialTheme.colors.onBackground)
|
||||||
|
Spacer(Modifier.width(DEFAULT_PADDING + 6.dp))
|
||||||
|
Text(text, color = if (isInDarkTheme()) MenuTextColorDark else Color.Black)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun SettingsPickerItem(onClick: () -> Unit) {
|
private fun SettingsPickerItem(onClick: () -> Unit) {
|
||||||
SectionItemView(onClick, padding = PaddingValues(start = DEFAULT_PADDING + 7.dp, end = DEFAULT_PADDING), minHeight = 68.dp) {
|
SectionItemView(onClick, padding = PaddingValues(start = DEFAULT_PADDING + 7.dp, end = DEFAULT_PADDING), minHeight = 68.dp) {
|
||||||
@ -441,21 +460,15 @@ private fun switchToLocalDevice() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun switchToRemoteHost(h: RemoteHostInfo, switchingUsersAndHosts: MutableState<Boolean>, connecting: MutableState<Boolean>) {
|
private fun switchToRemoteHost(h: RemoteHostInfo, connecting: MutableState<Boolean>) {
|
||||||
if (!h.activeHost()) {
|
if (!h.activeHost()) {
|
||||||
withBGApi {
|
withBGApi {
|
||||||
val job = launch {
|
|
||||||
delay(500)
|
|
||||||
switchingUsersAndHosts.value = true
|
|
||||||
}
|
|
||||||
ModalManager.closeAllModalsEverywhere()
|
ModalManager.closeAllModalsEverywhere()
|
||||||
if (h.sessionState != null) {
|
if (h.sessionState != null) {
|
||||||
chatModel.controller.switchUIRemoteHost(h.remoteHostId)
|
chatModel.controller.switchUIRemoteHost(h.remoteHostId)
|
||||||
} else {
|
} else {
|
||||||
connectMobileDevice(h, connecting)
|
connectMobileDevice(h, connecting)
|
||||||
}
|
}
|
||||||
job.cancel()
|
|
||||||
switchingUsersAndHosts.value = false
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
connectMobileDevice(h, connecting)
|
connectMobileDevice(h, connecting)
|
||||||
|
@ -10,6 +10,7 @@ import androidx.compose.ui.graphics.painter.Painter
|
|||||||
import androidx.compose.ui.platform.LocalDensity
|
import androidx.compose.ui.platform.LocalDensity
|
||||||
import dev.icerock.moko.resources.compose.painterResource
|
import dev.icerock.moko.resources.compose.painterResource
|
||||||
import androidx.compose.ui.text.AnnotatedString
|
import androidx.compose.ui.text.AnnotatedString
|
||||||
|
import androidx.compose.ui.text.style.TextAlign
|
||||||
import androidx.compose.ui.text.style.TextOverflow
|
import androidx.compose.ui.text.style.TextOverflow
|
||||||
import androidx.compose.ui.unit.*
|
import androidx.compose.ui.unit.*
|
||||||
import chat.simplex.common.platform.onRightClick
|
import chat.simplex.common.platform.onRightClick
|
||||||
@ -202,13 +203,14 @@ fun SectionTextFooter(text: String) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun SectionTextFooter(text: AnnotatedString) {
|
fun SectionTextFooter(text: AnnotatedString, textAlign: TextAlign = TextAlign.Start) {
|
||||||
Text(
|
Text(
|
||||||
text,
|
text,
|
||||||
Modifier.padding(start = DEFAULT_PADDING, end = DEFAULT_PADDING, top = DEFAULT_PADDING_HALF).fillMaxWidth(0.9F),
|
Modifier.padding(start = DEFAULT_PADDING, end = DEFAULT_PADDING, top = DEFAULT_PADDING_HALF).fillMaxWidth(0.9F),
|
||||||
color = MaterialTheme.colors.secondary,
|
color = MaterialTheme.colors.secondary,
|
||||||
lineHeight = 18.sp,
|
lineHeight = 18.sp,
|
||||||
fontSize = 14.sp
|
fontSize = 14.sp,
|
||||||
|
textAlign = textAlign
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -182,6 +182,10 @@ private fun prepareChatBeforeAddressCreation(rhId: Long?) {
|
|||||||
val user = chatModel.controller.apiGetActiveUser(rhId) ?: return@withApi
|
val user = chatModel.controller.apiGetActiveUser(rhId) ?: return@withApi
|
||||||
chatModel.currentUser.value = user
|
chatModel.currentUser.value = user
|
||||||
if (chatModel.users.isEmpty()) {
|
if (chatModel.users.isEmpty()) {
|
||||||
|
if (appPlatform.isDesktop) {
|
||||||
|
// Make possible to use chat after going to remote device linking and returning back to local profile creation
|
||||||
|
chatModel.chatRunning.value = false
|
||||||
|
}
|
||||||
chatModel.controller.startChat(user)
|
chatModel.controller.startChat(user)
|
||||||
} else {
|
} else {
|
||||||
val users = chatModel.controller.listUsers(rhId)
|
val users = chatModel.controller.listUsers(rhId)
|
||||||
|
@ -0,0 +1,91 @@
|
|||||||
|
package chat.simplex.common.views.onboarding
|
||||||
|
|
||||||
|
import SectionTextFooter
|
||||||
|
import SectionView
|
||||||
|
import androidx.compose.foundation.layout.*
|
||||||
|
import androidx.compose.runtime.*
|
||||||
|
import androidx.compose.runtime.saveable.rememberSaveable
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
|
import androidx.compose.ui.text.style.TextDecoration
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import chat.simplex.common.model.ChatModel
|
||||||
|
import chat.simplex.common.platform.chatModel
|
||||||
|
import chat.simplex.common.ui.theme.DEFAULT_PADDING
|
||||||
|
import chat.simplex.common.views.helpers.*
|
||||||
|
import chat.simplex.common.views.remote.AddingMobileDevice
|
||||||
|
import chat.simplex.common.views.remote.DeviceNameField
|
||||||
|
import chat.simplex.common.views.usersettings.PreferenceToggle
|
||||||
|
import chat.simplex.res.MR
|
||||||
|
import dev.icerock.moko.resources.compose.painterResource
|
||||||
|
import dev.icerock.moko.resources.compose.stringResource
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun LinkAMobile() {
|
||||||
|
val connecting = rememberSaveable { mutableStateOf(false) }
|
||||||
|
val deviceName = chatModel.controller.appPrefs.deviceNameForRemoteAccess
|
||||||
|
var deviceNameInQrCode by remember { mutableStateOf(chatModel.controller.appPrefs.deviceNameForRemoteAccess.get()) }
|
||||||
|
val staleQrCode = remember { mutableStateOf(false) }
|
||||||
|
|
||||||
|
LinkAMobileLayout(
|
||||||
|
deviceName = remember { deviceName.state },
|
||||||
|
connecting,
|
||||||
|
staleQrCode,
|
||||||
|
updateDeviceName = {
|
||||||
|
withBGApi {
|
||||||
|
if (it != "" && it != deviceName.get()) {
|
||||||
|
chatModel.controller.setLocalDeviceName(it)
|
||||||
|
deviceName.set(it)
|
||||||
|
staleQrCode.value = deviceName.get() != deviceNameInQrCode
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
KeyChangeEffect(staleQrCode.value) {
|
||||||
|
if (!staleQrCode.value) {
|
||||||
|
deviceNameInQrCode = deviceName.get()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun LinkAMobileLayout(
|
||||||
|
deviceName: State<String?>,
|
||||||
|
connecting: MutableState<Boolean>,
|
||||||
|
staleQrCode: MutableState<Boolean>,
|
||||||
|
updateDeviceName: (String) -> Unit,
|
||||||
|
) {
|
||||||
|
Column(Modifier.padding(top = 20.dp)) {
|
||||||
|
AppBarTitle(stringResource(if (remember { chatModel.remoteHosts }.isEmpty()) MR.strings.link_a_mobile else MR.strings.linked_mobiles))
|
||||||
|
Row(Modifier.weight(1f).padding(horizontal = DEFAULT_PADDING * 2), verticalAlignment = Alignment.CenterVertically) {
|
||||||
|
Column(
|
||||||
|
Modifier.weight(0.3f),
|
||||||
|
verticalArrangement = Arrangement.spacedBy(8.dp)
|
||||||
|
) {
|
||||||
|
SectionView(generalGetString(MR.strings.this_device_name).uppercase()) {
|
||||||
|
DeviceNameField(deviceName.value ?: "") { updateDeviceName(it) }
|
||||||
|
SectionTextFooter(generalGetString(MR.strings.this_device_name_shared_with_mobile))
|
||||||
|
PreferenceToggle(stringResource(MR.strings.multicast_discoverable_via_local_network), remember { ChatModel.controller.appPrefs.offerRemoteMulticast.state }.value) {
|
||||||
|
ChatModel.controller.appPrefs.offerRemoteMulticast.set(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Box(Modifier.weight(0.7f)) {
|
||||||
|
AddingMobileDevice(false, staleQrCode, connecting) {
|
||||||
|
if (chatModel.remoteHosts.isEmpty()) {
|
||||||
|
chatModel.controller.appPrefs.onboardingStage.set(OnboardingStage.Step1_SimpleXInfo)
|
||||||
|
} else {
|
||||||
|
chatModel.controller.appPrefs.onboardingStage.set(OnboardingStage.OnboardingComplete)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
SimpleButtonDecorated(
|
||||||
|
text = stringResource(MR.strings.about_simplex),
|
||||||
|
icon = painterResource(MR.images.ic_arrow_back_ios_new),
|
||||||
|
textDecoration = TextDecoration.None,
|
||||||
|
fontWeight = FontWeight.Medium
|
||||||
|
) { chatModel.controller.appPrefs.onboardingStage.set(OnboardingStage.Step1_SimpleXInfo) }
|
||||||
|
}
|
||||||
|
}
|
@ -3,6 +3,7 @@ package chat.simplex.common.views.onboarding
|
|||||||
enum class OnboardingStage {
|
enum class OnboardingStage {
|
||||||
Step1_SimpleXInfo,
|
Step1_SimpleXInfo,
|
||||||
Step2_CreateProfile,
|
Step2_CreateProfile,
|
||||||
|
LinkAMobile,
|
||||||
Step2_5_SetupDatabasePassphrase,
|
Step2_5_SetupDatabasePassphrase,
|
||||||
Step3_CreateSimpleXAddress,
|
Step3_CreateSimpleXAddress,
|
||||||
Step4_SetNotificationsMode,
|
Step4_SetNotificationsMode,
|
||||||
|
@ -43,7 +43,11 @@ fun SetupDatabasePassphrase(m: ChatModel) {
|
|||||||
val newKey = rememberSaveable { mutableStateOf("") }
|
val newKey = rememberSaveable { mutableStateOf("") }
|
||||||
val confirmNewKey = rememberSaveable { mutableStateOf("") }
|
val confirmNewKey = rememberSaveable { mutableStateOf("") }
|
||||||
fun nextStep() {
|
fun nextStep() {
|
||||||
m.controller.appPrefs.onboardingStage.set(OnboardingStage.Step3_CreateSimpleXAddress)
|
if (appPlatform.isAndroid || chatModel.currentUser.value != null) {
|
||||||
|
m.controller.appPrefs.onboardingStage.set(OnboardingStage.Step3_CreateSimpleXAddress)
|
||||||
|
} else {
|
||||||
|
m.controller.appPrefs.onboardingStage.set(OnboardingStage.LinkAMobile)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
SetupDatabasePassphraseLayout(
|
SetupDatabasePassphraseLayout(
|
||||||
currentKey,
|
currentKey,
|
||||||
@ -159,10 +163,7 @@ private fun SetupDatabasePassphraseLayout(
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
isValid = { confirmNewKey.value == "" || newKey.value == confirmNewKey.value },
|
isValid = { confirmNewKey.value == "" || newKey.value == confirmNewKey.value },
|
||||||
keyboardActions = KeyboardActions(onDone = {
|
keyboardActions = KeyboardActions(onDone = { defaultKeyboardAction(ImeAction.Done) }),
|
||||||
if (!disabled) onClickUpdate()
|
|
||||||
defaultKeyboardAction(ImeAction.Done)
|
|
||||||
}),
|
|
||||||
)
|
)
|
||||||
|
|
||||||
Box(Modifier.align(Alignment.CenterHorizontally).padding(vertical = DEFAULT_PADDING)) {
|
Box(Modifier.align(Alignment.CenterHorizontally).padding(vertical = DEFAULT_PADDING)) {
|
||||||
|
@ -8,6 +8,7 @@ import androidx.compose.material.*
|
|||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.graphics.Color
|
||||||
import androidx.compose.ui.graphics.painter.Painter
|
import androidx.compose.ui.graphics.painter.Painter
|
||||||
import dev.icerock.moko.resources.compose.painterResource
|
import dev.icerock.moko.resources.compose.painterResource
|
||||||
import dev.icerock.moko.resources.compose.stringResource
|
import dev.icerock.moko.resources.compose.stringResource
|
||||||
@ -99,26 +100,22 @@ private fun InfoRow(icon: Painter, titleId: StringResource, textId: StringResour
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun OnboardingActionButton(user: User?, onboardingStage: SharedPreference<OnboardingStage>, onclick: (() -> Unit)? = null) {
|
expect fun OnboardingActionButton(user: User?, onboardingStage: SharedPreference<OnboardingStage>, onclick: (() -> Unit)? = null)
|
||||||
if (user == null) {
|
|
||||||
OnboardingActionButton(MR.strings.create_your_profile, onboarding = OnboardingStage.Step2_CreateProfile, true, onclick)
|
|
||||||
} else {
|
|
||||||
OnboardingActionButton(MR.strings.make_private_connection, onboarding = OnboardingStage.OnboardingComplete, true, onclick)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun OnboardingActionButton(
|
fun OnboardingActionButton(
|
||||||
labelId: StringResource,
|
labelId: StringResource,
|
||||||
onboarding: OnboardingStage?,
|
onboarding: OnboardingStage?,
|
||||||
border: Boolean,
|
border: Boolean,
|
||||||
|
icon: Painter? = null,
|
||||||
|
iconColor: Color = MaterialTheme.colors.primary,
|
||||||
onclick: (() -> Unit)?
|
onclick: (() -> Unit)?
|
||||||
) {
|
) {
|
||||||
val modifier = if (border) {
|
val modifier = if (border) {
|
||||||
Modifier
|
Modifier
|
||||||
.border(border = BorderStroke(1.dp, MaterialTheme.colors.primary), shape = RoundedCornerShape(50))
|
.border(border = BorderStroke(1.dp, MaterialTheme.colors.primary), shape = RoundedCornerShape(50))
|
||||||
.padding(
|
.padding(
|
||||||
horizontal = DEFAULT_PADDING * 2,
|
horizontal = if (icon == null) DEFAULT_PADDING * 2 else DEFAULT_PADDING_HALF,
|
||||||
vertical = 4.dp
|
vertical = 4.dp
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
@ -131,6 +128,9 @@ fun OnboardingActionButton(
|
|||||||
ChatController.appPrefs.onboardingStage.set(onboarding)
|
ChatController.appPrefs.onboardingStage.set(onboarding)
|
||||||
}
|
}
|
||||||
}, modifier) {
|
}, modifier) {
|
||||||
|
if (icon != null) {
|
||||||
|
Icon(icon, stringResource(labelId), Modifier.padding(end = DEFAULT_PADDING_HALF), tint = iconColor)
|
||||||
|
}
|
||||||
Text(stringResource(labelId), style = MaterialTheme.typography.h2, color = MaterialTheme.colors.primary, fontSize = 20.sp)
|
Text(stringResource(labelId), style = MaterialTheme.typography.h2, color = MaterialTheme.colors.primary, fontSize = 20.sp)
|
||||||
Icon(
|
Icon(
|
||||||
painterResource(MR.images.ic_arrow_forward_ios), "next stage", tint = MaterialTheme.colors.primary,
|
painterResource(MR.images.ic_arrow_forward_ios), "next stage", tint = MaterialTheme.colors.primary,
|
||||||
|
@ -13,12 +13,14 @@ import androidx.compose.foundation.text.selection.SelectionContainer
|
|||||||
import androidx.compose.material.*
|
import androidx.compose.material.*
|
||||||
import androidx.compose.runtime.*
|
import androidx.compose.runtime.*
|
||||||
import androidx.compose.runtime.saveable.rememberSaveable
|
import androidx.compose.runtime.saveable.rememberSaveable
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
import androidx.compose.ui.platform.LocalClipboardManager
|
import androidx.compose.ui.platform.LocalClipboardManager
|
||||||
import androidx.compose.ui.text.TextStyle
|
import androidx.compose.ui.text.TextStyle
|
||||||
import androidx.compose.ui.text.font.*
|
import androidx.compose.ui.text.font.*
|
||||||
import androidx.compose.ui.text.input.*
|
import androidx.compose.ui.text.input.*
|
||||||
|
import androidx.compose.ui.text.style.TextAlign
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.compose.ui.unit.sp
|
import androidx.compose.ui.unit.sp
|
||||||
import chat.simplex.common.model.*
|
import chat.simplex.common.model.*
|
||||||
@ -97,9 +99,11 @@ fun ConnectMobileLayout(
|
|||||||
SectionDividerSpaced(maxBottomPadding = false)
|
SectionDividerSpaced(maxBottomPadding = false)
|
||||||
}
|
}
|
||||||
SectionView(stringResource(MR.strings.devices).uppercase()) {
|
SectionView(stringResource(MR.strings.devices).uppercase()) {
|
||||||
SettingsActionItemWithContent(text = stringResource(MR.strings.this_device), icon = painterResource(MR.images.ic_desktop), click = connectDesktop) {
|
if (chatModel.localUserCreated.value == true) {
|
||||||
if (connectedHost.value == null) {
|
SettingsActionItemWithContent(text = stringResource(MR.strings.this_device), icon = painterResource(MR.images.ic_desktop), click = connectDesktop) {
|
||||||
Icon(painterResource(MR.images.ic_done_filled), null, Modifier.size(20.dp), tint = MaterialTheme.colors.onBackground)
|
if (connectedHost.value == null) {
|
||||||
|
Icon(painterResource(MR.images.ic_done_filled), null, Modifier.size(20.dp), tint = MaterialTheme.colors.onBackground)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -162,26 +166,37 @@ fun DeviceNameField(
|
|||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun ConnectMobileViewLayout(
|
private fun ConnectMobileViewLayout(
|
||||||
title: String,
|
title: String?,
|
||||||
invitation: String?,
|
invitation: String?,
|
||||||
deviceName: String?,
|
deviceName: String?,
|
||||||
sessionCode: String?,
|
sessionCode: String?,
|
||||||
port: String?
|
port: String?,
|
||||||
|
staleQrCode: Boolean = false,
|
||||||
|
refreshQrCode: () -> Unit = {}
|
||||||
) {
|
) {
|
||||||
Column(
|
Column(
|
||||||
Modifier.fillMaxWidth().verticalScroll(rememberScrollState()),
|
Modifier.fillMaxWidth().verticalScroll(rememberScrollState()),
|
||||||
verticalArrangement = Arrangement.spacedBy(8.dp)
|
verticalArrangement = Arrangement.spacedBy(8.dp)
|
||||||
) {
|
) {
|
||||||
AppBarTitle(title)
|
if (title != null) {
|
||||||
|
AppBarTitle(title)
|
||||||
|
}
|
||||||
SectionView {
|
SectionView {
|
||||||
if (invitation != null && sessionCode == null && port != null) {
|
if (invitation != null && sessionCode == null && port != null) {
|
||||||
QRCode(
|
Box {
|
||||||
invitation, Modifier
|
QRCode(
|
||||||
.padding(start = DEFAULT_PADDING, top = DEFAULT_PADDING_HALF, end = DEFAULT_PADDING, bottom = DEFAULT_PADDING_HALF)
|
invitation, Modifier
|
||||||
.aspectRatio(1f)
|
.padding(start = DEFAULT_PADDING, top = DEFAULT_PADDING_HALF, end = DEFAULT_PADDING, bottom = DEFAULT_PADDING_HALF)
|
||||||
)
|
.aspectRatio(1f)
|
||||||
SectionTextFooter(annotatedStringResource(MR.strings.open_on_mobile_and_scan_qr_code))
|
)
|
||||||
SectionTextFooter(annotatedStringResource(MR.strings.waiting_for_mobile_to_connect_on_port, port))
|
if (staleQrCode) {
|
||||||
|
Box(Modifier.matchParentSize().background(MaterialTheme.colors.background.copy(alpha = 0.9f)), contentAlignment = Alignment.Center) {
|
||||||
|
SimpleButtonDecorated(stringResource(MR.strings.refresh_qr_code), painterResource(MR.images.ic_refresh), click = refreshQrCode)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
SectionTextFooter(annotatedStringResource(MR.strings.open_on_mobile_and_scan_qr_code), textAlign = TextAlign.Center)
|
||||||
|
SectionTextFooter(annotatedStringResource(MR.strings.waiting_for_mobile_to_connect_on_port, port), textAlign = TextAlign.Center)
|
||||||
|
|
||||||
if (remember { controller.appPrefs.developerTools.state }.value) {
|
if (remember { controller.appPrefs.developerTools.state }.value) {
|
||||||
val clipboard = LocalClipboardManager.current
|
val clipboard = LocalClipboardManager.current
|
||||||
@ -237,55 +252,72 @@ fun connectMobileDevice(rh: RemoteHostInfo, connecting: MutableState<Boolean>) {
|
|||||||
|
|
||||||
private fun showAddingMobileDevice(connecting: MutableState<Boolean>) {
|
private fun showAddingMobileDevice(connecting: MutableState<Boolean>) {
|
||||||
ModalManager.start.showModalCloseable { close ->
|
ModalManager.start.showModalCloseable { close ->
|
||||||
val invitation = rememberSaveable { mutableStateOf<String?>(null) }
|
AddingMobileDevice(true, remember { mutableStateOf(false) }, connecting, close)
|
||||||
val port = rememberSaveable { mutableStateOf<String?>(null) }
|
}
|
||||||
val pairing = remember { chatModel.remoteHostPairing }
|
}
|
||||||
val sessionCode = when (val state = pairing.value?.second) {
|
|
||||||
is RemoteHostSessionState.PendingConfirmation -> state.sessionCode
|
@Composable
|
||||||
else -> null
|
fun AddingMobileDevice(showTitle: Boolean, staleQrCode: MutableState<Boolean>, connecting: MutableState<Boolean>, close: () -> Unit) {
|
||||||
|
val invitation = rememberSaveable { mutableStateOf<String?>(null) }
|
||||||
|
val port = rememberSaveable { mutableStateOf<String?>(null) }
|
||||||
|
val startRemoteHost = suspend {
|
||||||
|
val r = chatModel.controller.startRemoteHost(null, controller.appPrefs.offerRemoteMulticast.get())
|
||||||
|
if (r != null) {
|
||||||
|
connecting.value = true
|
||||||
|
invitation.value = r.second
|
||||||
|
port.value = r.third
|
||||||
|
chatModel.remoteHostPairing.value = null to RemoteHostSessionState.Starting
|
||||||
}
|
}
|
||||||
/** It's needed to prevent screen flashes when [chatModel.newRemoteHostPairing] sets to null in background */
|
}
|
||||||
var cachedSessionCode by remember { mutableStateOf<String?>(null) }
|
val pairing = remember { chatModel.remoteHostPairing }
|
||||||
if (cachedSessionCode == null && sessionCode != null) {
|
val sessionCode = when (val state = pairing.value?.second) {
|
||||||
cachedSessionCode = sessionCode
|
is RemoteHostSessionState.PendingConfirmation -> state.sessionCode
|
||||||
}
|
else -> null
|
||||||
val remoteDeviceName = pairing.value?.first?.hostDeviceName
|
}
|
||||||
ConnectMobileViewLayout(
|
/** It's needed to prevent screen flashes when [chatModel.newRemoteHostPairing] sets to null in background */
|
||||||
title = if (cachedSessionCode == null) stringResource(MR.strings.link_a_mobile) else stringResource(MR.strings.verify_connection),
|
var cachedSessionCode by remember { mutableStateOf<String?>(null) }
|
||||||
invitation = invitation.value,
|
if (cachedSessionCode == null && sessionCode != null) {
|
||||||
deviceName = remoteDeviceName,
|
cachedSessionCode = sessionCode
|
||||||
sessionCode = cachedSessionCode,
|
}
|
||||||
port = port.value
|
val remoteDeviceName = pairing.value?.first?.hostDeviceName
|
||||||
)
|
ConnectMobileViewLayout(
|
||||||
val oldRemoteHostId by remember { mutableStateOf(chatModel.currentRemoteHost.value?.remoteHostId) }
|
title = if (!showTitle) null else if (cachedSessionCode == null) stringResource(MR.strings.link_a_mobile) else stringResource(MR.strings.verify_connection),
|
||||||
LaunchedEffect(remember { chatModel.currentRemoteHost }.value) {
|
invitation = invitation.value,
|
||||||
if (chatModel.currentRemoteHost.value?.remoteHostId != null && chatModel.currentRemoteHost.value?.remoteHostId != oldRemoteHostId) {
|
deviceName = remoteDeviceName,
|
||||||
close()
|
sessionCode = cachedSessionCode,
|
||||||
}
|
port = port.value,
|
||||||
}
|
staleQrCode = staleQrCode.value,
|
||||||
KeyChangeEffect(pairing.value) {
|
refreshQrCode = {
|
||||||
if (pairing.value == null) {
|
|
||||||
close()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
DisposableEffect(Unit) {
|
|
||||||
withBGApi {
|
withBGApi {
|
||||||
val r = chatModel.controller.startRemoteHost(null, controller.appPrefs.offerRemoteMulticast.get())
|
if (chatController.stopRemoteHost(null)) {
|
||||||
if (r != null) {
|
startRemoteHost()
|
||||||
connecting.value = true
|
staleQrCode.value = false
|
||||||
invitation.value = r.second
|
|
||||||
port.value = r.third
|
|
||||||
chatModel.remoteHostPairing.value = null to RemoteHostSessionState.Starting
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
onDispose {
|
},
|
||||||
if (chatModel.currentRemoteHost.value?.remoteHostId == oldRemoteHostId) {
|
)
|
||||||
withBGApi {
|
val oldRemoteHostId by remember { mutableStateOf(chatModel.currentRemoteHost.value?.remoteHostId) }
|
||||||
chatController.stopRemoteHost(null)
|
LaunchedEffect(remember { chatModel.currentRemoteHost }.value) {
|
||||||
}
|
if (chatModel.currentRemoteHost.value?.remoteHostId != null && chatModel.currentRemoteHost.value?.remoteHostId != oldRemoteHostId) {
|
||||||
|
close()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
KeyChangeEffect(pairing.value) {
|
||||||
|
if (pairing.value == null) {
|
||||||
|
close()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
DisposableEffect(Unit) {
|
||||||
|
withBGApi {
|
||||||
|
startRemoteHost()
|
||||||
|
}
|
||||||
|
onDispose {
|
||||||
|
if (chatModel.currentRemoteHost.value?.remoteHostId == oldRemoteHostId) {
|
||||||
|
withBGApi {
|
||||||
|
chatController.stopRemoteHost(null)
|
||||||
}
|
}
|
||||||
chatModel.remoteHostPairing.value = null
|
|
||||||
}
|
}
|
||||||
|
chatModel.remoteHostPairing.value = null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -25,6 +25,7 @@ import androidx.compose.ui.platform.LocalDensity
|
|||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.compose.ui.unit.sp
|
import androidx.compose.ui.unit.sp
|
||||||
import chat.simplex.common.model.*
|
import chat.simplex.common.model.*
|
||||||
|
import chat.simplex.common.platform.chatModel
|
||||||
import chat.simplex.common.ui.theme.*
|
import chat.simplex.common.ui.theme.*
|
||||||
import chat.simplex.common.views.chat.item.ClickableText
|
import chat.simplex.common.views.chat.item.ClickableText
|
||||||
import chat.simplex.common.views.helpers.*
|
import chat.simplex.common.views.helpers.*
|
||||||
@ -169,18 +170,20 @@ fun NetworkAndServersView(
|
|||||||
verticalArrangement = Arrangement.spacedBy(8.dp)
|
verticalArrangement = Arrangement.spacedBy(8.dp)
|
||||||
) {
|
) {
|
||||||
AppBarTitle(stringResource(MR.strings.network_and_servers))
|
AppBarTitle(stringResource(MR.strings.network_and_servers))
|
||||||
SectionView(generalGetString(MR.strings.settings_section_title_messages)) {
|
if (!chatModel.desktopNoUserNoRemote) {
|
||||||
SettingsActionItem(painterResource(MR.images.ic_dns), stringResource(MR.strings.smp_servers), showCustomModal { m, close -> ProtocolServersView(m, m.remoteHostId, ServerProtocol.SMP, close) })
|
SectionView(generalGetString(MR.strings.settings_section_title_messages)) {
|
||||||
|
SettingsActionItem(painterResource(MR.images.ic_dns), stringResource(MR.strings.smp_servers), showCustomModal { m, close -> ProtocolServersView(m, m.remoteHostId, ServerProtocol.SMP, close) })
|
||||||
|
|
||||||
SettingsActionItem(painterResource(MR.images.ic_dns), stringResource(MR.strings.xftp_servers), showCustomModal { m, close -> ProtocolServersView(m, m.remoteHostId, ServerProtocol.XFTP, close) })
|
SettingsActionItem(painterResource(MR.images.ic_dns), stringResource(MR.strings.xftp_servers), showCustomModal { m, close -> ProtocolServersView(m, m.remoteHostId, ServerProtocol.XFTP, close) })
|
||||||
|
|
||||||
if (currentRemoteHost == null) {
|
if (currentRemoteHost == null) {
|
||||||
UseSocksProxySwitch(networkUseSocksProxy, proxyPort, toggleSocksProxy, showSettingsModal)
|
UseSocksProxySwitch(networkUseSocksProxy, proxyPort, toggleSocksProxy, showSettingsModal)
|
||||||
UseOnionHosts(onionHosts, networkUseSocksProxy, showSettingsModal, useOnion)
|
UseOnionHosts(onionHosts, networkUseSocksProxy, showSettingsModal, useOnion)
|
||||||
if (developerTools) {
|
if (developerTools) {
|
||||||
SessionModePicker(sessionMode, showSettingsModal, updateSessionMode)
|
SessionModePicker(sessionMode, showSettingsModal, updateSessionMode)
|
||||||
|
}
|
||||||
|
SettingsActionItem(painterResource(MR.images.ic_cable), stringResource(MR.strings.network_settings), showSettingsModal { AdvancedNetworkSettingsView(it) })
|
||||||
}
|
}
|
||||||
SettingsActionItem(painterResource(MR.images.ic_cable), stringResource(MR.strings.network_settings), showSettingsModal { AdvancedNetworkSettingsView(it) })
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (currentRemoteHost == null && networkUseSocksProxy.value) {
|
if (currentRemoteHost == null && networkUseSocksProxy.value) {
|
||||||
@ -192,7 +195,7 @@ fun NetworkAndServersView(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
Divider(Modifier.padding(start = DEFAULT_PADDING_HALF, top = 32.dp, end = DEFAULT_PADDING_HALF, bottom = 30.dp))
|
Divider(Modifier.padding(start = DEFAULT_PADDING_HALF, top = 32.dp, end = DEFAULT_PADDING_HALF, bottom = 30.dp))
|
||||||
} else {
|
} else if (!chatModel.desktopNoUserNoRemote) {
|
||||||
Divider(Modifier.padding(start = DEFAULT_PADDING_HALF, top = 24.dp, end = DEFAULT_PADDING_HALF, bottom = 30.dp))
|
Divider(Modifier.padding(start = DEFAULT_PADDING_HALF, top = 24.dp, end = DEFAULT_PADDING_HALF, bottom = 30.dp))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -92,7 +92,6 @@ fun PrivacySettingsView(
|
|||||||
chatModel.simplexLinkMode.value = it
|
chatModel.simplexLinkMode.value = it
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
SectionDividerSpaced()
|
|
||||||
|
|
||||||
val currentUser = chatModel.currentUser.value
|
val currentUser = chatModel.currentUser.value
|
||||||
if (currentUser != null) {
|
if (currentUser != null) {
|
||||||
@ -142,39 +141,42 @@ fun PrivacySettingsView(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
DeliveryReceiptsSection(
|
if (!chatModel.desktopNoUserNoRemote) {
|
||||||
currentUser = currentUser,
|
SectionDividerSpaced()
|
||||||
setOrAskSendReceiptsContacts = { enable ->
|
DeliveryReceiptsSection(
|
||||||
val contactReceiptsOverrides = chatModel.chats.fold(0) { count, chat ->
|
currentUser = currentUser,
|
||||||
if (chat.chatInfo is ChatInfo.Direct) {
|
setOrAskSendReceiptsContacts = { enable ->
|
||||||
val sendRcpts = chat.chatInfo.contact.chatSettings.sendRcpts
|
val contactReceiptsOverrides = chatModel.chats.fold(0) { count, chat ->
|
||||||
count + (if (sendRcpts == null || sendRcpts == enable) 0 else 1)
|
if (chat.chatInfo is ChatInfo.Direct) {
|
||||||
|
val sendRcpts = chat.chatInfo.contact.chatSettings.sendRcpts
|
||||||
|
count + (if (sendRcpts == null || sendRcpts == enable) 0 else 1)
|
||||||
|
} else {
|
||||||
|
count
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (contactReceiptsOverrides == 0) {
|
||||||
|
setSendReceiptsContacts(enable, clearOverrides = false)
|
||||||
} else {
|
} else {
|
||||||
count
|
showUserContactsReceiptsAlert(enable, contactReceiptsOverrides, ::setSendReceiptsContacts)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
setOrAskSendReceiptsGroups = { enable ->
|
||||||
|
val groupReceiptsOverrides = chatModel.chats.fold(0) { count, chat ->
|
||||||
|
if (chat.chatInfo is ChatInfo.Group) {
|
||||||
|
val sendRcpts = chat.chatInfo.groupInfo.chatSettings.sendRcpts
|
||||||
|
count + (if (sendRcpts == null || sendRcpts == enable) 0 else 1)
|
||||||
|
} else {
|
||||||
|
count
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (groupReceiptsOverrides == 0) {
|
||||||
|
setSendReceiptsGroups(enable, clearOverrides = false)
|
||||||
|
} else {
|
||||||
|
showUserGroupsReceiptsAlert(enable, groupReceiptsOverrides, ::setSendReceiptsGroups)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (contactReceiptsOverrides == 0) {
|
)
|
||||||
setSendReceiptsContacts(enable, clearOverrides = false)
|
}
|
||||||
} else {
|
|
||||||
showUserContactsReceiptsAlert(enable, contactReceiptsOverrides, ::setSendReceiptsContacts)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
setOrAskSendReceiptsGroups = { enable ->
|
|
||||||
val groupReceiptsOverrides = chatModel.chats.fold(0) { count, chat ->
|
|
||||||
if (chat.chatInfo is ChatInfo.Group) {
|
|
||||||
val sendRcpts = chat.chatInfo.groupInfo.chatSettings.sendRcpts
|
|
||||||
count + (if (sendRcpts == null || sendRcpts == enable) 0 else 1)
|
|
||||||
} else {
|
|
||||||
count
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (groupReceiptsOverrides == 0) {
|
|
||||||
setSendReceiptsGroups(enable, clearOverrides = false)
|
|
||||||
} else {
|
|
||||||
showUserGroupsReceiptsAlert(enable, groupReceiptsOverrides, ::setSendReceiptsGroups)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
SectionBottomSpacer()
|
SectionBottomSpacer()
|
||||||
}
|
}
|
||||||
|
@ -25,6 +25,7 @@ import androidx.compose.ui.unit.*
|
|||||||
import chat.simplex.common.model.*
|
import chat.simplex.common.model.*
|
||||||
import chat.simplex.common.platform.*
|
import chat.simplex.common.platform.*
|
||||||
import chat.simplex.common.ui.theme.*
|
import chat.simplex.common.ui.theme.*
|
||||||
|
import chat.simplex.common.views.CreateProfile
|
||||||
import chat.simplex.common.views.database.DatabaseView
|
import chat.simplex.common.views.database.DatabaseView
|
||||||
import chat.simplex.common.views.helpers.*
|
import chat.simplex.common.views.helpers.*
|
||||||
import chat.simplex.common.views.onboarding.SimpleXInfo
|
import chat.simplex.common.views.onboarding.SimpleXInfo
|
||||||
@ -38,76 +39,39 @@ import kotlinx.coroutines.launch
|
|||||||
fun SettingsView(chatModel: ChatModel, setPerformLA: (Boolean) -> Unit, drawerState: DrawerState) {
|
fun SettingsView(chatModel: ChatModel, setPerformLA: (Boolean) -> Unit, drawerState: DrawerState) {
|
||||||
val user = chatModel.currentUser.value
|
val user = chatModel.currentUser.value
|
||||||
val stopped = chatModel.chatRunning.value == false
|
val stopped = chatModel.chatRunning.value == false
|
||||||
|
SettingsLayout(
|
||||||
if (user != null) {
|
profile = user?.profile,
|
||||||
val requireAuth = remember { chatModel.controller.appPrefs.performLA.state }
|
stopped,
|
||||||
SettingsLayout(
|
chatModel.chatDbEncrypted.value == true,
|
||||||
profile = user.profile,
|
remember { chatModel.controller.appPrefs.storeDBPassphrase.state }.value,
|
||||||
stopped,
|
remember { chatModel.controller.appPrefs.notificationsMode.state },
|
||||||
chatModel.chatDbEncrypted.value == true,
|
user?.displayName,
|
||||||
remember { chatModel.controller.appPrefs.storeDBPassphrase.state }.value,
|
setPerformLA = setPerformLA,
|
||||||
remember { chatModel.controller.appPrefs.notificationsMode.state },
|
showModal = { modalView -> { ModalManager.start.showModal { modalView(chatModel) } } },
|
||||||
user.displayName,
|
showSettingsModal = { modalView -> { ModalManager.start.showModal(true) { modalView(chatModel) } } },
|
||||||
setPerformLA = setPerformLA,
|
showSettingsModalWithSearch = { modalView ->
|
||||||
showModal = { modalView -> { ModalManager.start.showModal { modalView(chatModel) } } },
|
ModalManager.start.showCustomModal { close ->
|
||||||
showSettingsModal = { modalView -> { ModalManager.start.showModal(true) { modalView(chatModel) } } },
|
val search = rememberSaveable { mutableStateOf("") }
|
||||||
showSettingsModalWithSearch = { modalView ->
|
ModalView(
|
||||||
ModalManager.start.showCustomModal { close ->
|
{ close() },
|
||||||
val search = rememberSaveable { mutableStateOf("") }
|
endButtons = {
|
||||||
ModalView(
|
SearchTextField(Modifier.fillMaxWidth(), placeholder = stringResource(MR.strings.search_verb), alwaysVisible = true) { search.value = it }
|
||||||
{ close() },
|
},
|
||||||
endButtons = {
|
content = { modalView(chatModel, search) })
|
||||||
SearchTextField(Modifier.fillMaxWidth(), placeholder = stringResource(MR.strings.search_verb), alwaysVisible = true) { search.value = it }
|
}
|
||||||
},
|
},
|
||||||
content = { modalView(chatModel, search) })
|
showCustomModal = { modalView -> { ModalManager.start.showCustomModal { close -> modalView(chatModel, close) } } },
|
||||||
|
showVersion = {
|
||||||
|
withApi {
|
||||||
|
val info = chatModel.controller.apiGetVersion()
|
||||||
|
if (info != null) {
|
||||||
|
ModalManager.start.showModal { VersionInfoView(info) }
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
showCustomModal = { modalView -> { ModalManager.start.showCustomModal { close -> modalView(chatModel, close) } } },
|
},
|
||||||
showVersion = {
|
withAuth = ::doWithAuth,
|
||||||
withApi {
|
drawerState = drawerState,
|
||||||
val info = chatModel.controller.apiGetVersion()
|
)
|
||||||
if (info != null) {
|
|
||||||
ModalManager.start.showModal { VersionInfoView(info) }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
withAuth = { title, desc, block ->
|
|
||||||
if (!requireAuth.value) {
|
|
||||||
block()
|
|
||||||
} else {
|
|
||||||
var autoShow = true
|
|
||||||
ModalManager.fullscreen.showModalCloseable { close ->
|
|
||||||
val onFinishAuth = { success: Boolean ->
|
|
||||||
if (success) {
|
|
||||||
close()
|
|
||||||
block()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
LaunchedEffect(Unit) {
|
|
||||||
if (autoShow) {
|
|
||||||
autoShow = false
|
|
||||||
runAuth(title, desc, onFinishAuth)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Box(
|
|
||||||
Modifier.fillMaxSize().background(MaterialTheme.colors.background),
|
|
||||||
contentAlignment = Alignment.Center
|
|
||||||
) {
|
|
||||||
SimpleButton(
|
|
||||||
stringResource(MR.strings.auth_unlock),
|
|
||||||
icon = painterResource(MR.images.ic_lock),
|
|
||||||
click = {
|
|
||||||
runAuth(title, desc, onFinishAuth)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
drawerState = drawerState,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
val simplexTeamUri =
|
val simplexTeamUri =
|
||||||
@ -115,12 +79,12 @@ val simplexTeamUri =
|
|||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun SettingsLayout(
|
fun SettingsLayout(
|
||||||
profile: LocalProfile,
|
profile: LocalProfile?,
|
||||||
stopped: Boolean,
|
stopped: Boolean,
|
||||||
encrypted: Boolean,
|
encrypted: Boolean,
|
||||||
passphraseSaved: Boolean,
|
passphraseSaved: Boolean,
|
||||||
notificationsMode: State<NotificationsMode>,
|
notificationsMode: State<NotificationsMode>,
|
||||||
userDisplayName: String,
|
userDisplayName: String?,
|
||||||
setPerformLA: (Boolean) -> Unit,
|
setPerformLA: (Boolean) -> Unit,
|
||||||
showModal: (@Composable (ChatModel) -> Unit) -> (() -> Unit),
|
showModal: (@Composable (ChatModel) -> Unit) -> (() -> Unit),
|
||||||
showSettingsModal: (@Composable (ChatModel) -> Unit) -> (() -> Unit),
|
showSettingsModal: (@Composable (ChatModel) -> Unit) -> (() -> Unit),
|
||||||
@ -150,13 +114,22 @@ fun SettingsLayout(
|
|||||||
AppBarTitle(stringResource(MR.strings.your_settings))
|
AppBarTitle(stringResource(MR.strings.your_settings))
|
||||||
|
|
||||||
SectionView(stringResource(MR.strings.settings_section_title_you)) {
|
SectionView(stringResource(MR.strings.settings_section_title_you)) {
|
||||||
SectionItemView(showCustomModal { chatModel, close -> UserProfileView(chatModel, close) }, 80.dp, padding = PaddingValues(start = 16.dp, end = DEFAULT_PADDING), disabled = stopped) {
|
|
||||||
ProfilePreview(profile, stopped = stopped)
|
|
||||||
}
|
|
||||||
val profileHidden = rememberSaveable { mutableStateOf(false) }
|
val profileHidden = rememberSaveable { mutableStateOf(false) }
|
||||||
SettingsActionItem(painterResource(MR.images.ic_manage_accounts), stringResource(MR.strings.your_chat_profiles), { withAuth(generalGetString(MR.strings.auth_open_chat_profiles), generalGetString(MR.strings.auth_log_in_using_credential)) { showSettingsModalWithSearch { it, search -> UserProfilesView(it, search, profileHidden) } } }, disabled = stopped, extraPadding = true)
|
if (profile != null) {
|
||||||
SettingsActionItem(painterResource(MR.images.ic_qr_code), stringResource(MR.strings.your_simplex_contact_address), showCustomModal { it, close -> UserAddressView(it, shareViaProfile = it.currentUser.value!!.addressShared, close = close) }, disabled = stopped, extraPadding = true)
|
SectionItemView(showCustomModal { chatModel, close -> UserProfileView(chatModel, close) }, 80.dp, padding = PaddingValues(start = 16.dp, end = DEFAULT_PADDING), disabled = stopped) {
|
||||||
ChatPreferencesItem(showCustomModal, stopped = stopped)
|
ProfilePreview(profile, stopped = stopped)
|
||||||
|
}
|
||||||
|
SettingsActionItem(painterResource(MR.images.ic_manage_accounts), stringResource(MR.strings.your_chat_profiles), { withAuth(generalGetString(MR.strings.auth_open_chat_profiles), generalGetString(MR.strings.auth_log_in_using_credential)) { showSettingsModalWithSearch { it, search -> UserProfilesView(it, search, profileHidden) } } }, disabled = stopped, extraPadding = true)
|
||||||
|
SettingsActionItem(painterResource(MR.images.ic_qr_code), stringResource(MR.strings.your_simplex_contact_address), showCustomModal { it, close -> UserAddressView(it, shareViaProfile = it.currentUser.value!!.addressShared, close = close) }, disabled = stopped, extraPadding = true)
|
||||||
|
ChatPreferencesItem(showCustomModal, stopped = stopped)
|
||||||
|
} else if (chatModel.localUserCreated.value == false) {
|
||||||
|
SettingsActionItem(painterResource(MR.images.ic_manage_accounts), stringResource(MR.strings.create_chat_profile), { withAuth(generalGetString(MR.strings.auth_open_chat_profiles), generalGetString(MR.strings.auth_log_in_using_credential)) { ModalManager.center.showModalCloseable { close ->
|
||||||
|
LaunchedEffect(Unit) {
|
||||||
|
closeSettings()
|
||||||
|
}
|
||||||
|
CreateProfile(chatModel, close)
|
||||||
|
} } }, disabled = stopped, extraPadding = true)
|
||||||
|
}
|
||||||
if (appPlatform.isDesktop) {
|
if (appPlatform.isDesktop) {
|
||||||
SettingsActionItem(painterResource(MR.images.ic_smartphone), stringResource(if (remember { chatModel.remoteHosts }.isEmpty()) MR.strings.link_a_mobile else MR.strings.linked_mobiles), showModal { ConnectMobileView() }, disabled = stopped, extraPadding = true)
|
SettingsActionItem(painterResource(MR.images.ic_smartphone), stringResource(if (remember { chatModel.remoteHosts }.isEmpty()) MR.strings.link_a_mobile else MR.strings.linked_mobiles), showModal { ConnectMobileView() }, disabled = stopped, extraPadding = true)
|
||||||
} else {
|
} else {
|
||||||
@ -171,15 +144,19 @@ fun SettingsLayout(
|
|||||||
SettingsActionItem(painterResource(MR.images.ic_videocam), stringResource(MR.strings.settings_audio_video_calls), showSettingsModal { CallSettingsView(it, showModal) }, disabled = stopped, extraPadding = true)
|
SettingsActionItem(painterResource(MR.images.ic_videocam), stringResource(MR.strings.settings_audio_video_calls), showSettingsModal { CallSettingsView(it, showModal) }, disabled = stopped, extraPadding = true)
|
||||||
SettingsActionItem(painterResource(MR.images.ic_lock), stringResource(MR.strings.privacy_and_security), showSettingsModal { PrivacySettingsView(it, showSettingsModal, setPerformLA) }, disabled = stopped, extraPadding = true)
|
SettingsActionItem(painterResource(MR.images.ic_lock), stringResource(MR.strings.privacy_and_security), showSettingsModal { PrivacySettingsView(it, showSettingsModal, setPerformLA) }, disabled = stopped, extraPadding = true)
|
||||||
SettingsActionItem(painterResource(MR.images.ic_light_mode), stringResource(MR.strings.appearance_settings), showSettingsModal { AppearanceView(it, showSettingsModal) }, extraPadding = true)
|
SettingsActionItem(painterResource(MR.images.ic_light_mode), stringResource(MR.strings.appearance_settings), showSettingsModal { AppearanceView(it, showSettingsModal) }, extraPadding = true)
|
||||||
DatabaseItem(encrypted, passphraseSaved, showSettingsModal { DatabaseView(it, showSettingsModal) }, stopped)
|
if (!chatModel.desktopNoUserNoRemote) {
|
||||||
|
DatabaseItem(encrypted, passphraseSaved, showSettingsModal { DatabaseView(it, showSettingsModal) }, stopped)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
SectionDividerSpaced()
|
SectionDividerSpaced()
|
||||||
|
|
||||||
SectionView(stringResource(MR.strings.settings_section_title_help)) {
|
SectionView(stringResource(MR.strings.settings_section_title_help)) {
|
||||||
SettingsActionItem(painterResource(MR.images.ic_help), stringResource(MR.strings.how_to_use_simplex_chat), showModal { HelpView(userDisplayName) }, disabled = stopped, extraPadding = true)
|
SettingsActionItem(painterResource(MR.images.ic_help), stringResource(MR.strings.how_to_use_simplex_chat), showModal { HelpView(userDisplayName ?: "") }, disabled = stopped, extraPadding = true)
|
||||||
SettingsActionItem(painterResource(MR.images.ic_add), stringResource(MR.strings.whats_new), showCustomModal { _, close -> WhatsNewView(viaSettings = true, close) }, disabled = stopped, extraPadding = true)
|
SettingsActionItem(painterResource(MR.images.ic_add), stringResource(MR.strings.whats_new), showCustomModal { _, close -> WhatsNewView(viaSettings = true, close) }, disabled = stopped, extraPadding = true)
|
||||||
SettingsActionItem(painterResource(MR.images.ic_info), stringResource(MR.strings.about_simplex_chat), showModal { SimpleXInfo(it, onboarding = false) }, extraPadding = true)
|
SettingsActionItem(painterResource(MR.images.ic_info), stringResource(MR.strings.about_simplex_chat), showModal { SimpleXInfo(it, onboarding = false) }, extraPadding = true)
|
||||||
SettingsActionItem(painterResource(MR.images.ic_tag), stringResource(MR.strings.chat_with_the_founder), { uriHandler.openVerifiedSimplexUri(simplexTeamUri) }, textColor = MaterialTheme.colors.primary, disabled = stopped, extraPadding = true)
|
if (!chatModel.desktopNoUserNoRemote) {
|
||||||
|
SettingsActionItem(painterResource(MR.images.ic_tag), stringResource(MR.strings.chat_with_the_founder), { uriHandler.openVerifiedSimplexUri(simplexTeamUri) }, textColor = MaterialTheme.colors.primary, disabled = stopped, extraPadding = true)
|
||||||
|
}
|
||||||
SettingsActionItem(painterResource(MR.images.ic_mail), stringResource(MR.strings.send_us_an_email), { uriHandler.openUriCatching("mailto:chat@simplex.chat") }, textColor = MaterialTheme.colors.primary, extraPadding = true)
|
SettingsActionItem(painterResource(MR.images.ic_mail), stringResource(MR.strings.send_us_an_email), { uriHandler.openUriCatching("mailto:chat@simplex.chat") }, textColor = MaterialTheme.colors.primary, extraPadding = true)
|
||||||
}
|
}
|
||||||
SectionDividerSpaced()
|
SectionDividerSpaced()
|
||||||
@ -469,6 +446,42 @@ fun PreferenceToggleWithIcon(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun doWithAuth(title: String, desc: String, block: () -> Unit) {
|
||||||
|
val requireAuth = chatModel.controller.appPrefs.performLA.get()
|
||||||
|
if (!requireAuth) {
|
||||||
|
block()
|
||||||
|
} else {
|
||||||
|
var autoShow = true
|
||||||
|
ModalManager.fullscreen.showModalCloseable { close ->
|
||||||
|
val onFinishAuth = { success: Boolean ->
|
||||||
|
if (success) {
|
||||||
|
close()
|
||||||
|
block()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
LaunchedEffect(Unit) {
|
||||||
|
if (autoShow) {
|
||||||
|
autoShow = false
|
||||||
|
runAuth(title, desc, onFinishAuth)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Box(
|
||||||
|
Modifier.fillMaxSize().background(MaterialTheme.colors.background),
|
||||||
|
contentAlignment = Alignment.Center
|
||||||
|
) {
|
||||||
|
SimpleButton(
|
||||||
|
stringResource(MR.strings.auth_unlock),
|
||||||
|
icon = painterResource(MR.images.ic_lock),
|
||||||
|
click = {
|
||||||
|
runAuth(title, desc, onFinishAuth)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private fun runAuth(title: String, desc: String, onFinish: (success: Boolean) -> Unit) {
|
private fun runAuth(title: String, desc: String, onFinish: (success: Boolean) -> Unit) {
|
||||||
authenticate(
|
authenticate(
|
||||||
title,
|
title,
|
||||||
|
@ -21,6 +21,7 @@ import androidx.compose.ui.text.font.FontWeight
|
|||||||
import androidx.compose.ui.text.style.TextAlign
|
import androidx.compose.ui.text.style.TextAlign
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import chat.simplex.common.model.*
|
import chat.simplex.common.model.*
|
||||||
|
import chat.simplex.common.model.ChatModel.controller
|
||||||
import chat.simplex.common.platform.*
|
import chat.simplex.common.platform.*
|
||||||
import chat.simplex.common.ui.theme.*
|
import chat.simplex.common.ui.theme.*
|
||||||
import chat.simplex.common.views.chat.item.ItemAction
|
import chat.simplex.common.views.chat.item.ItemAction
|
||||||
@ -56,7 +57,9 @@ fun UserProfilesView(m: ChatModel, search: MutableState<String>, profileHidden:
|
|||||||
ModalManager.end.closeModals()
|
ModalManager.end.closeModals()
|
||||||
}
|
}
|
||||||
withBGApi {
|
withBGApi {
|
||||||
m.controller.changeActiveUser(user.remoteHostId, user.userId, userViewPassword(user, searchTextOrPassword.value.trim()))
|
controller.showProgressIfNeeded {
|
||||||
|
m.controller.changeActiveUser(user.remoteHostId, user.userId, userViewPassword(user, searchTextOrPassword.value.trim()))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
removeUser = { user ->
|
removeUser = { user ->
|
||||||
|
@ -565,6 +565,7 @@
|
|||||||
<string name="your_settings">Your settings</string>
|
<string name="your_settings">Your settings</string>
|
||||||
<string name="your_simplex_contact_address">Your SimpleX address</string>
|
<string name="your_simplex_contact_address">Your SimpleX address</string>
|
||||||
<string name="your_chat_profiles">Your chat profiles</string>
|
<string name="your_chat_profiles">Your chat profiles</string>
|
||||||
|
<string name="create_chat_profile">Create chat profile</string>
|
||||||
<string name="database_passphrase_and_export">Database passphrase & export</string>
|
<string name="database_passphrase_and_export">Database passphrase & export</string>
|
||||||
<string name="about_simplex_chat">About SimpleX Chat</string>
|
<string name="about_simplex_chat">About SimpleX Chat</string>
|
||||||
<string name="how_to_use_simplex_chat">How to use it</string>
|
<string name="how_to_use_simplex_chat">How to use it</string>
|
||||||
@ -1689,6 +1690,8 @@
|
|||||||
<string name="paste_desktop_address">Paste desktop address</string>
|
<string name="paste_desktop_address">Paste desktop address</string>
|
||||||
<string name="desktop_device">Desktop</string>
|
<string name="desktop_device">Desktop</string>
|
||||||
<string name="not_compatible">Not compatible!</string>
|
<string name="not_compatible">Not compatible!</string>
|
||||||
|
<string name="refresh_qr_code">Refresh</string>
|
||||||
|
<string name="no_connected_mobile">No connected mobile</string>
|
||||||
|
|
||||||
<!-- Under development -->
|
<!-- Under development -->
|
||||||
<string name="in_developing_title">Coming soon!</string>
|
<string name="in_developing_title">Coming soon!</string>
|
||||||
|
@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 -960 960 960" width="24"><path d="M480.433-164.5q-131.583 0-223.758-92.216Q164.5-348.932 164.5-480.082q0-131.149 92.175-223.284Q348.85-795.5 480.5-795.5q84 0 147.75 34.25T738.5-666.5v-129H796V-547H547.5v-57.5H713q-38.032-60.033-96.537-96.767Q557.959-738 480.539-738 372-738 297-663.015q-75 74.986-75 183.25 0 108.265 74.875 183.015Q371.75-222 480.331-222q82.298 0 150.734-47 68.435-47 95.623-124.5H786q-28.5 103-113.49 166-84.991 63-192.077 63Z"/></svg>
|
After Width: | Height: | Size: 516 B |
@ -0,0 +1,23 @@
|
|||||||
|
package chat.simplex.common.views.onboarding
|
||||||
|
|
||||||
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
|
import androidx.compose.foundation.layout.Row
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import chat.simplex.common.model.ChatModel.controller
|
||||||
|
import chat.simplex.common.model.SharedPreference
|
||||||
|
import chat.simplex.common.model.User
|
||||||
|
import chat.simplex.common.ui.theme.DEFAULT_PADDING
|
||||||
|
import chat.simplex.res.MR
|
||||||
|
import dev.icerock.moko.resources.compose.painterResource
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
actual fun OnboardingActionButton(user: User?, onboardingStage: SharedPreference<OnboardingStage>, onclick: (() -> Unit)?) {
|
||||||
|
if (user == null) {
|
||||||
|
Row(horizontalArrangement = Arrangement.spacedBy(DEFAULT_PADDING * 2.5f)) {
|
||||||
|
OnboardingActionButton(MR.strings.link_a_mobile, onboarding = if (controller.appPrefs.initialRandomDBPassphrase.get()) OnboardingStage.Step2_5_SetupDatabasePassphrase else OnboardingStage.LinkAMobile, true, icon = painterResource(MR.images.ic_smartphone_300), onclick = onclick)
|
||||||
|
OnboardingActionButton(MR.strings.create_your_profile, onboarding = OnboardingStage.Step2_CreateProfile, true, icon = painterResource(MR.images.ic_desktop), onclick = onclick)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
OnboardingActionButton(MR.strings.make_private_connection, onboarding = OnboardingStage.OnboardingComplete, true, onclick = onclick)
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user