Merge branch 'master-ghc8107' into master-android
This commit is contained in:
commit
cdb3b6aafd
@ -11,20 +11,12 @@ import CoreImage.CIFilterBuiltins
|
||||
|
||||
struct MutableQRCode: View {
|
||||
@Binding var uri: String
|
||||
@State private var image: UIImage?
|
||||
var withLogo: Bool = true
|
||||
var tintColor = UIColor(red: 0.023, green: 0.176, blue: 0.337, alpha: 1)
|
||||
|
||||
var body: some View {
|
||||
ZStack {
|
||||
if let image = image {
|
||||
qrCodeImage(image)
|
||||
}
|
||||
}
|
||||
.onAppear {
|
||||
image = generateImage(uri)
|
||||
}
|
||||
.onChange(of: uri) { _ in
|
||||
image = generateImage(uri)
|
||||
}
|
||||
QRCode(uri: uri, withLogo: withLogo, tintColor: tintColor)
|
||||
.id("simplex-qrcode-view-for-\(uri)")
|
||||
}
|
||||
}
|
||||
|
||||
@ -49,7 +41,7 @@ struct QRCode: View {
|
||||
var withLogo: Bool = true
|
||||
var tintColor = UIColor(red: 0.023, green: 0.176, blue: 0.337, alpha: 1)
|
||||
@State private var image: UIImage? = nil
|
||||
@State private var makeScreenshotBinding: () -> Void = {}
|
||||
@State private var makeScreenshotFunc: () -> Void = {}
|
||||
|
||||
var body: some View {
|
||||
ZStack {
|
||||
@ -70,18 +62,18 @@ struct QRCode: View {
|
||||
}
|
||||
}
|
||||
.onAppear {
|
||||
makeScreenshotBinding = {
|
||||
makeScreenshotFunc = {
|
||||
let size = CGSizeMake(1024 / UIScreen.main.scale, 1024 / UIScreen.main.scale)
|
||||
showShareSheet(items: [makeScreenshot(geo.frame(in: .local).origin, size)])}
|
||||
showShareSheet(items: [makeScreenshot(geo.frame(in: .local).origin, size)])
|
||||
}
|
||||
}
|
||||
.frame(width: geo.size.width, height: geo.size.height)
|
||||
}
|
||||
}
|
||||
.onTapGesture(perform: makeScreenshotBinding)
|
||||
.onTapGesture(perform: makeScreenshotFunc)
|
||||
.onAppear {
|
||||
image = image ?? generateImage(uri)?.replaceColor(UIColor.black, tintColor)
|
||||
image = image ?? generateImage(uri, tintColor: tintColor)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@ -93,13 +85,13 @@ private func qrCodeImage(_ image: UIImage) -> some View {
|
||||
.textSelection(.enabled)
|
||||
}
|
||||
|
||||
private func generateImage(_ uri: String) -> UIImage? {
|
||||
private func generateImage(_ uri: String, tintColor: UIColor) -> UIImage? {
|
||||
let context = CIContext()
|
||||
let filter = CIFilter.qrCodeGenerator()
|
||||
filter.message = Data(uri.utf8)
|
||||
if let outputImage = filter.outputImage,
|
||||
let cgImage = context.createCGImage(outputImage, from: outputImage.extent) {
|
||||
return UIImage(cgImage: cgImage)
|
||||
return UIImage(cgImage: cgImage).replaceColor(UIColor.black, tintColor)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
@ -190,7 +190,8 @@ struct UserAddressView: View {
|
||||
|
||||
@ViewBuilder private func existingAddressView(_ userAddress: UserContactLink) -> some View {
|
||||
Section {
|
||||
MutableQRCode(uri: Binding.constant(simplexChatLink(userAddress.connReqContact)))
|
||||
SimpleXLinkQRCode(uri: userAddress.connReqContact)
|
||||
.id("simplex-contact-address-qrcode-\(userAddress.connReqContact)")
|
||||
shareQRCodeButton(userAddress)
|
||||
if MFMailComposeViewController.canSendMail() {
|
||||
shareViaEmailButton(userAddress)
|
||||
|
@ -43,6 +43,11 @@
|
||||
5C3F1D562842B68D00EC8A82 /* IntegrityErrorItemView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C3F1D552842B68D00EC8A82 /* IntegrityErrorItemView.swift */; };
|
||||
5C3F1D58284363C400EC8A82 /* PrivacySettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C3F1D57284363C400EC8A82 /* PrivacySettings.swift */; };
|
||||
5C4B3B0A285FB130003915F2 /* DatabaseView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C4B3B09285FB130003915F2 /* DatabaseView.swift */; };
|
||||
5C4BB4B82B1E7D75007981AA /* libgmp.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5C4BB4B32B1E7D75007981AA /* libgmp.a */; };
|
||||
5C4BB4B92B1E7D75007981AA /* libHSsimplex-chat-5.4.0.6-DWi9o3X1dc6Jx2FZ4ew1fo-ghc8.10.7.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5C4BB4B42B1E7D75007981AA /* libHSsimplex-chat-5.4.0.6-DWi9o3X1dc6Jx2FZ4ew1fo-ghc8.10.7.a */; };
|
||||
5C4BB4BA2B1E7D75007981AA /* libHSsimplex-chat-5.4.0.6-DWi9o3X1dc6Jx2FZ4ew1fo.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5C4BB4B52B1E7D75007981AA /* libHSsimplex-chat-5.4.0.6-DWi9o3X1dc6Jx2FZ4ew1fo.a */; };
|
||||
5C4BB4BB2B1E7D75007981AA /* libffi.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5C4BB4B62B1E7D75007981AA /* libffi.a */; };
|
||||
5C4BB4BC2B1E7D75007981AA /* libgmpxx.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5C4BB4B72B1E7D75007981AA /* libgmpxx.a */; };
|
||||
5C5346A827B59A6A004DF848 /* ChatHelp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C5346A727B59A6A004DF848 /* ChatHelp.swift */; };
|
||||
5C55A91F283AD0E400C4E99E /* CallManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C55A91E283AD0E400C4E99E /* CallManager.swift */; };
|
||||
5C55A921283CCCB700C4E99E /* IncomingCallView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C55A920283CCCB700C4E99E /* IncomingCallView.swift */; };
|
||||
@ -120,11 +125,6 @@
|
||||
5CCD403727A5F9A200368C90 /* ScanToConnectView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CCD403627A5F9A200368C90 /* ScanToConnectView.swift */; };
|
||||
5CD67B8F2B0E858A00C510B1 /* hs_init.h in Headers */ = {isa = PBXBuildFile; fileRef = 5CD67B8D2B0E858A00C510B1 /* hs_init.h */; settings = {ATTRIBUTES = (Public, ); }; };
|
||||
5CD67B902B0E858A00C510B1 /* hs_init.c in Sources */ = {isa = PBXBuildFile; fileRef = 5CD67B8E2B0E858A00C510B1 /* hs_init.c */; };
|
||||
5CD67BA02B120ADF00C510B1 /* libgmp.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5CD67B9B2B120ADF00C510B1 /* libgmp.a */; };
|
||||
5CD67BA12B120ADF00C510B1 /* libgmpxx.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5CD67B9C2B120ADF00C510B1 /* libgmpxx.a */; };
|
||||
5CD67BA22B120ADF00C510B1 /* libHSsimplex-chat-5.4.0.6-9DfazyElTA72omjHp0C93u.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5CD67B9D2B120ADF00C510B1 /* libHSsimplex-chat-5.4.0.6-9DfazyElTA72omjHp0C93u.a */; };
|
||||
5CD67BA32B120ADF00C510B1 /* libHSsimplex-chat-5.4.0.6-9DfazyElTA72omjHp0C93u-ghc8.10.7.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5CD67B9E2B120ADF00C510B1 /* libHSsimplex-chat-5.4.0.6-9DfazyElTA72omjHp0C93u-ghc8.10.7.a */; };
|
||||
5CD67BA42B120ADF00C510B1 /* libffi.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5CD67B9F2B120ADF00C510B1 /* libffi.a */; };
|
||||
5CDCAD482818589900503DA2 /* NotificationService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CDCAD472818589900503DA2 /* NotificationService.swift */; };
|
||||
5CE2BA702845308900EC33A6 /* SimpleXChat.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 5CE2BA682845308900EC33A6 /* SimpleXChat.framework */; };
|
||||
5CE2BA712845308900EC33A6 /* SimpleXChat.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 5CE2BA682845308900EC33A6 /* SimpleXChat.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
|
||||
@ -295,6 +295,11 @@
|
||||
5C3F1D57284363C400EC8A82 /* PrivacySettings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PrivacySettings.swift; sourceTree = "<group>"; };
|
||||
5C422A7C27A9A6FA0097A1E1 /* SimpleX (iOS).entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = "SimpleX (iOS).entitlements"; sourceTree = "<group>"; };
|
||||
5C4B3B09285FB130003915F2 /* DatabaseView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DatabaseView.swift; sourceTree = "<group>"; };
|
||||
5C4BB4B32B1E7D75007981AA /* libgmp.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmp.a; sourceTree = "<group>"; };
|
||||
5C4BB4B42B1E7D75007981AA /* libHSsimplex-chat-5.4.0.6-DWi9o3X1dc6Jx2FZ4ew1fo-ghc8.10.7.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-5.4.0.6-DWi9o3X1dc6Jx2FZ4ew1fo-ghc8.10.7.a"; sourceTree = "<group>"; };
|
||||
5C4BB4B52B1E7D75007981AA /* libHSsimplex-chat-5.4.0.6-DWi9o3X1dc6Jx2FZ4ew1fo.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-5.4.0.6-DWi9o3X1dc6Jx2FZ4ew1fo.a"; sourceTree = "<group>"; };
|
||||
5C4BB4B62B1E7D75007981AA /* libffi.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libffi.a; sourceTree = "<group>"; };
|
||||
5C4BB4B72B1E7D75007981AA /* libgmpxx.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmpxx.a; sourceTree = "<group>"; };
|
||||
5C5346A727B59A6A004DF848 /* ChatHelp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatHelp.swift; sourceTree = "<group>"; };
|
||||
5C55A91E283AD0E400C4E99E /* CallManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CallManager.swift; sourceTree = "<group>"; };
|
||||
5C55A920283CCCB700C4E99E /* IncomingCallView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IncomingCallView.swift; sourceTree = "<group>"; };
|
||||
@ -408,11 +413,6 @@
|
||||
5CCD403627A5F9A200368C90 /* ScanToConnectView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScanToConnectView.swift; sourceTree = "<group>"; };
|
||||
5CD67B8D2B0E858A00C510B1 /* hs_init.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = hs_init.h; sourceTree = "<group>"; };
|
||||
5CD67B8E2B0E858A00C510B1 /* hs_init.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = hs_init.c; sourceTree = "<group>"; };
|
||||
5CD67B9B2B120ADF00C510B1 /* libgmp.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmp.a; sourceTree = "<group>"; };
|
||||
5CD67B9C2B120ADF00C510B1 /* libgmpxx.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmpxx.a; sourceTree = "<group>"; };
|
||||
5CD67B9D2B120ADF00C510B1 /* libHSsimplex-chat-5.4.0.6-9DfazyElTA72omjHp0C93u.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-5.4.0.6-9DfazyElTA72omjHp0C93u.a"; sourceTree = "<group>"; };
|
||||
5CD67B9E2B120ADF00C510B1 /* libHSsimplex-chat-5.4.0.6-9DfazyElTA72omjHp0C93u-ghc8.10.7.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-5.4.0.6-9DfazyElTA72omjHp0C93u-ghc8.10.7.a"; sourceTree = "<group>"; };
|
||||
5CD67B9F2B120ADF00C510B1 /* libffi.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libffi.a; sourceTree = "<group>"; };
|
||||
5CDCAD452818589900503DA2 /* SimpleX NSE.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = "SimpleX NSE.appex"; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
5CDCAD472818589900503DA2 /* NotificationService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationService.swift; sourceTree = "<group>"; };
|
||||
5CDCAD492818589900503DA2 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||
@ -521,13 +521,13 @@
|
||||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
5CD67BA12B120ADF00C510B1 /* libgmpxx.a in Frameworks */,
|
||||
5CD67BA22B120ADF00C510B1 /* libHSsimplex-chat-5.4.0.6-9DfazyElTA72omjHp0C93u.a in Frameworks */,
|
||||
5CE2BA93284534B000EC33A6 /* libiconv.tbd in Frameworks */,
|
||||
5CD67BA02B120ADF00C510B1 /* libgmp.a in Frameworks */,
|
||||
5CD67BA42B120ADF00C510B1 /* libffi.a in Frameworks */,
|
||||
5CD67BA32B120ADF00C510B1 /* libHSsimplex-chat-5.4.0.6-9DfazyElTA72omjHp0C93u-ghc8.10.7.a in Frameworks */,
|
||||
5C4BB4BB2B1E7D75007981AA /* libffi.a in Frameworks */,
|
||||
5C4BB4BA2B1E7D75007981AA /* libHSsimplex-chat-5.4.0.6-DWi9o3X1dc6Jx2FZ4ew1fo.a in Frameworks */,
|
||||
5C4BB4B92B1E7D75007981AA /* libHSsimplex-chat-5.4.0.6-DWi9o3X1dc6Jx2FZ4ew1fo-ghc8.10.7.a in Frameworks */,
|
||||
5C4BB4B82B1E7D75007981AA /* libgmp.a in Frameworks */,
|
||||
5CE2BA94284534BB00EC33A6 /* libz.tbd in Frameworks */,
|
||||
5C4BB4BC2B1E7D75007981AA /* libgmpxx.a in Frameworks */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
@ -589,11 +589,11 @@
|
||||
5C764E5C279C70B7000C6508 /* Libraries */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
5CD67B9F2B120ADF00C510B1 /* libffi.a */,
|
||||
5CD67B9B2B120ADF00C510B1 /* libgmp.a */,
|
||||
5CD67B9C2B120ADF00C510B1 /* libgmpxx.a */,
|
||||
5CD67B9E2B120ADF00C510B1 /* libHSsimplex-chat-5.4.0.6-9DfazyElTA72omjHp0C93u-ghc8.10.7.a */,
|
||||
5CD67B9D2B120ADF00C510B1 /* libHSsimplex-chat-5.4.0.6-9DfazyElTA72omjHp0C93u.a */,
|
||||
5C4BB4B62B1E7D75007981AA /* libffi.a */,
|
||||
5C4BB4B32B1E7D75007981AA /* libgmp.a */,
|
||||
5C4BB4B72B1E7D75007981AA /* libgmpxx.a */,
|
||||
5C4BB4B42B1E7D75007981AA /* libHSsimplex-chat-5.4.0.6-DWi9o3X1dc6Jx2FZ4ew1fo-ghc8.10.7.a */,
|
||||
5C4BB4B52B1E7D75007981AA /* libHSsimplex-chat-5.4.0.6-DWi9o3X1dc6Jx2FZ4ew1fo.a */,
|
||||
);
|
||||
path = Libraries;
|
||||
sourceTree = "<group>";
|
||||
|
@ -12,7 +12,7 @@ android {
|
||||
|
||||
defaultConfig {
|
||||
applicationId = "chat.simplex.app"
|
||||
minSdkVersion(26)
|
||||
minSdkVersion(28)
|
||||
targetSdkVersion(33)
|
||||
// !!!
|
||||
// skip version code after release to F-Droid, as it uses two version codes
|
||||
|
@ -41,10 +41,8 @@ class MainActivity: FragmentActivity() {
|
||||
)
|
||||
}
|
||||
setContent {
|
||||
SimpleXTheme {
|
||||
AppScreen()
|
||||
}
|
||||
}
|
||||
SimplexApp.context.schedulePeriodicServiceRestartWorker()
|
||||
SimplexApp.context.schedulePeriodicWakeUp()
|
||||
}
|
||||
|
@ -32,7 +32,9 @@ class SimplexApp: Application(), LifecycleEventObserver {
|
||||
override fun onCreate() {
|
||||
super.onCreate()
|
||||
if (ProcessPhoenix.isPhoenixProcess(this)) {
|
||||
return;
|
||||
return
|
||||
} else {
|
||||
registerGlobalErrorHandler()
|
||||
}
|
||||
context = this
|
||||
initHaskell()
|
||||
|
@ -110,7 +110,7 @@ android {
|
||||
compileSdkVersion(34)
|
||||
sourceSets["main"].manifest.srcFile("src/androidMain/AndroidManifest.xml")
|
||||
defaultConfig {
|
||||
minSdkVersion(26)
|
||||
minSdkVersion(28)
|
||||
targetSdkVersion(33)
|
||||
}
|
||||
compileOptions {
|
||||
|
@ -8,10 +8,14 @@ import android.os.Build
|
||||
import android.view.*
|
||||
import android.view.inputmethod.InputMethodManager
|
||||
import android.widget.Toast
|
||||
import androidx.activity.compose.setContent
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.ui.platform.LocalView
|
||||
import chat.simplex.common.views.helpers.KeyboardState
|
||||
import chat.simplex.common.AppScreen
|
||||
import chat.simplex.common.ui.theme.SimpleXTheme
|
||||
import chat.simplex.common.views.helpers.*
|
||||
import androidx.compose.ui.platform.LocalContext as LocalContext1
|
||||
import chat.simplex.res.MR
|
||||
|
||||
actual fun showToast(text: String, timeout: Long) = Toast.makeText(androidAppContext, text, Toast.LENGTH_SHORT).show()
|
||||
|
||||
@ -71,3 +75,37 @@ actual fun hideKeyboard(view: Any?) {
|
||||
}
|
||||
|
||||
actual fun androidIsFinishingMainActivity(): Boolean = (mainActivity.get()?.isFinishing == true)
|
||||
|
||||
actual class GlobalExceptionsHandler: Thread.UncaughtExceptionHandler {
|
||||
actual override fun uncaughtException(thread: Thread, e: Throwable) {
|
||||
Log.e(TAG, "App crashed, thread name: " + thread.name + ", exception: " + e.stackTraceToString())
|
||||
if (ModalManager.start.hasModalsOpen()) {
|
||||
ModalManager.start.closeModal()
|
||||
} else if (chatModel.chatId.value != null) {
|
||||
// Since no modals are open, the problem is probably in ChatView
|
||||
chatModel.chatId.value = null
|
||||
chatModel.chatItems.clear()
|
||||
} else {
|
||||
// ChatList, nothing to do. Maybe to show other view except ChatList
|
||||
}
|
||||
chatModel.activeCall.value?.let {
|
||||
withBGApi {
|
||||
chatModel.callManager.endCall(it)
|
||||
}
|
||||
}
|
||||
AlertManager.shared.showAlertMsg(
|
||||
title = generalGetString(MR.strings.app_was_crashed),
|
||||
text = e.stackTraceToString()
|
||||
)
|
||||
//mainActivity.get()?.recreate()
|
||||
mainActivity.get()?.apply {
|
||||
window
|
||||
?.decorView
|
||||
?.findViewById<ViewGroup>(android.R.id.content)
|
||||
?.removeViewAt(0)
|
||||
setContent {
|
||||
AppScreen()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -42,12 +42,14 @@ data class SettingsViewState(
|
||||
|
||||
@Composable
|
||||
fun AppScreen() {
|
||||
SimpleXTheme {
|
||||
ProvideWindowInsets(windowInsetsAnimationsEnabled = true) {
|
||||
Surface(color = MaterialTheme.colors.background) {
|
||||
MainScreen()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun MainScreen() {
|
||||
|
@ -2,7 +2,6 @@ package chat.simplex.common.model
|
||||
|
||||
import androidx.compose.material.*
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.runtime.saveable.rememberSaveable
|
||||
import androidx.compose.runtime.snapshots.SnapshotStateMap
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.text.SpanStyle
|
||||
@ -68,6 +67,9 @@ object ChatModel {
|
||||
// set when app opened from external intent
|
||||
val clearOverlays = mutableStateOf<Boolean>(false)
|
||||
|
||||
// Only needed during onboarding when user skipped password setup (left as random password)
|
||||
val desktopOnboardingRandomPassword = mutableStateOf(false)
|
||||
|
||||
// set when app is opened via contact or invitation URI
|
||||
val appOpenUrl = mutableStateOf<URI?>(null)
|
||||
|
||||
|
@ -16,3 +16,11 @@ expect fun getKeyboardState(): State<KeyboardState>
|
||||
expect fun hideKeyboard(view: Any?)
|
||||
|
||||
expect fun androidIsFinishingMainActivity(): Boolean
|
||||
|
||||
fun registerGlobalErrorHandler() {
|
||||
Thread.setDefaultUncaughtExceptionHandler(GlobalExceptionsHandler())
|
||||
}
|
||||
|
||||
expect class GlobalExceptionsHandler(): Thread.UncaughtExceptionHandler {
|
||||
override fun uncaughtException(thread: Thread, e: Throwable)
|
||||
}
|
||||
|
@ -207,12 +207,12 @@ fun createProfileInProfiles(chatModel: ChatModel, displayName: String, close: ()
|
||||
|
||||
fun createProfileOnboarding(chatModel: ChatModel, displayName: String, close: () -> Unit) {
|
||||
withApi {
|
||||
chatModel.controller.apiCreateActiveUser(
|
||||
chatModel.currentUser.value = chatModel.controller.apiCreateActiveUser(
|
||||
null, Profile(displayName.trim(), "", null)
|
||||
) ?: return@withApi
|
||||
val onboardingStage = chatModel.controller.appPrefs.onboardingStage
|
||||
if (chatModel.users.isEmpty()) {
|
||||
onboardingStage.set(if (appPlatform.isDesktop && chatModel.controller.appPrefs.initialRandomDBPassphrase.get()) {
|
||||
onboardingStage.set(if (appPlatform.isDesktop && chatModel.controller.appPrefs.initialRandomDBPassphrase.get() && !chatModel.desktopOnboardingRandomPassword.value) {
|
||||
OnboardingStage.Step2_5_SetupDatabasePassphrase
|
||||
} else {
|
||||
OnboardingStage.Step3_CreateSimpleXAddress
|
||||
|
@ -1,8 +1,11 @@
|
||||
package chat.simplex.common.views.helpers
|
||||
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.shape.CornerSize
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.foundation.text.selection.SelectionContainer
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.material.*
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.ui.Alignment
|
||||
@ -233,6 +236,7 @@ private fun alertTitle(title: String): (@Composable () -> Unit)? {
|
||||
|
||||
@Composable
|
||||
private fun AlertContent(text: String?, hostDevice: Pair<Long?, String>?, extraPadding: Boolean = false, content: @Composable (() -> Unit)) {
|
||||
BoxWithConstraints {
|
||||
Column(
|
||||
Modifier
|
||||
.padding(bottom = if (appPlatform.isDesktop) DEFAULT_PADDING else DEFAULT_PADDING_HALF)
|
||||
@ -244,6 +248,10 @@ private fun AlertContent(text: String?, hostDevice: Pair<Long?, String>?, extraP
|
||||
}
|
||||
CompositionLocalProvider(LocalContentAlpha provides ContentAlpha.high) {
|
||||
if (text != null) {
|
||||
Column(Modifier.heightIn(max = this@BoxWithConstraints.maxHeight * 0.7f)
|
||||
.verticalScroll(rememberScrollState())
|
||||
) {
|
||||
SelectionContainer {
|
||||
Text(
|
||||
escapedHtmlToAnnotatedString(text, LocalDensity.current),
|
||||
Modifier.fillMaxWidth().padding(start = DEFAULT_PADDING, end = DEFAULT_PADDING, bottom = DEFAULT_PADDING * 1.5f),
|
||||
@ -253,14 +261,19 @@ private fun AlertContent(text: String?, hostDevice: Pair<Long?, String>?, extraP
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
content()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun AlertContent(text: AnnotatedString?, hostDevice: Pair<Long?, String>?, extraPadding: Boolean = false, content: @Composable (() -> Unit)) {
|
||||
BoxWithConstraints {
|
||||
Column(
|
||||
Modifier
|
||||
.verticalScroll(rememberScrollState())
|
||||
.padding(bottom = if (appPlatform.isDesktop) DEFAULT_PADDING else DEFAULT_PADDING_HALF)
|
||||
) {
|
||||
if (appPlatform.isDesktop) {
|
||||
@ -270,6 +283,11 @@ private fun AlertContent(text: AnnotatedString?, hostDevice: Pair<Long?, String>
|
||||
}
|
||||
CompositionLocalProvider(LocalContentAlpha provides ContentAlpha.high) {
|
||||
if (text != null) {
|
||||
Column(
|
||||
Modifier.heightIn(max = this@BoxWithConstraints.maxHeight * 0.7f)
|
||||
.verticalScroll(rememberScrollState())
|
||||
) {
|
||||
SelectionContainer {
|
||||
Text(
|
||||
text,
|
||||
Modifier.fillMaxWidth().padding(start = DEFAULT_PADDING, end = DEFAULT_PADDING, bottom = DEFAULT_PADDING * 1.5f),
|
||||
@ -279,9 +297,12 @@ private fun AlertContent(text: AnnotatedString?, hostDevice: Pair<Long?, String>
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
content()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun hostDevice(rhId: Long?): Pair<Long?, String>? = if (rhId == null && chatModel.remoteHosts.isNotEmpty()) {
|
||||
null to ChatModel.controller.appPrefs.deviceNameForRemoteAccess.get()!!
|
||||
|
@ -19,8 +19,8 @@ import dev.icerock.moko.resources.compose.painterResource
|
||||
import androidx.compose.ui.text.*
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.text.input.*
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.unit.*
|
||||
import chat.simplex.common.views.database.PassphraseStrength
|
||||
import chat.simplex.common.views.database.validKey
|
||||
import chat.simplex.res.MR
|
||||
@ -123,6 +123,7 @@ fun DefaultConfigurableTextField(
|
||||
isValid: (String) -> Boolean,
|
||||
keyboardActions: KeyboardActions = KeyboardActions(),
|
||||
keyboardType: KeyboardType = KeyboardType.Text,
|
||||
fontSize: TextUnit = 16.sp,
|
||||
dependsOn: State<Any?>? = null,
|
||||
) {
|
||||
var valid by remember { mutableStateOf(isValid(state.value.text)) }
|
||||
@ -175,14 +176,14 @@ fun DefaultConfigurableTextField(
|
||||
textStyle = TextStyle.Default.copy(
|
||||
color = color,
|
||||
fontWeight = FontWeight.Normal,
|
||||
fontSize = 16.sp
|
||||
fontSize = fontSize
|
||||
),
|
||||
interactionSource = interactionSource,
|
||||
decorationBox = @Composable { innerTextField ->
|
||||
TextFieldDefaults.TextFieldDecorationBox(
|
||||
value = state.value.text,
|
||||
innerTextField = innerTextField,
|
||||
placeholder = { Text(placeholder, color = MaterialTheme.colors.secondary) },
|
||||
placeholder = { Text(placeholder, color = MaterialTheme.colors.secondary, fontSize = fontSize, maxLines = 1, overflow = TextOverflow.Ellipsis) },
|
||||
singleLine = true,
|
||||
enabled = enabled,
|
||||
isError = !valid,
|
||||
|
@ -20,6 +20,7 @@ fun <T> ExposedDropDownSetting(
|
||||
values: List<Pair<T, String>>,
|
||||
selection: State<T>,
|
||||
textColor: Color = MaterialTheme.colors.secondary,
|
||||
fontSize: TextUnit = 16.sp,
|
||||
label: String? = null,
|
||||
enabled: State<Boolean> = mutableStateOf(true),
|
||||
minWidth: Dp = 200.dp,
|
||||
@ -43,7 +44,8 @@ fun <T> ExposedDropDownSetting(
|
||||
Modifier.widthIn(max = maxWidth),
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
color = textColor
|
||||
color = textColor,
|
||||
fontSize = fontSize,
|
||||
)
|
||||
Spacer(Modifier.size(12.dp))
|
||||
Icon(
|
||||
@ -69,6 +71,7 @@ fun <T> ExposedDropDownSetting(
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
color = if (isInDarkTheme()) MenuTextColorDark else Color.Black,
|
||||
fontSize = fontSize,
|
||||
)
|
||||
}
|
||||
}
|
||||
@ -91,6 +94,6 @@ fun <T> ExposedDropDownSettingRow(
|
||||
onSelected: (T) -> Unit
|
||||
) {
|
||||
SettingsActionItemWithContent(icon, title, iconColor = iconTint, disabled = !enabled.value) {
|
||||
ExposedDropDownSetting(values, selection ,textColor, label, enabled, minWidth, maxWidth, onSelected)
|
||||
ExposedDropDownSetting(values, selection ,textColor, label = label, enabled = enabled, minWidth = minWidth, maxWidth = maxWidth, onSelected = onSelected)
|
||||
}
|
||||
}
|
||||
|
@ -73,7 +73,8 @@ private fun LinkAMobileLayout(
|
||||
}
|
||||
Box(Modifier.weight(0.7f)) {
|
||||
AddingMobileDevice(false, staleQrCode, connecting) {
|
||||
if (chatModel.remoteHosts.isEmpty()) {
|
||||
// currentRemoteHost will be set instantly but remoteHosts may be delayed
|
||||
if (chatModel.remoteHosts.isEmpty() && chatModel.currentRemoteHost.value == null) {
|
||||
chatModel.controller.appPrefs.onboardingStage.set(OnboardingStage.Step1_SimpleXInfo)
|
||||
} else {
|
||||
chatModel.controller.appPrefs.onboardingStage.set(OnboardingStage.OnboardingComplete)
|
||||
|
@ -1,10 +1,7 @@
|
||||
package chat.simplex.common.views.onboarding
|
||||
|
||||
import SectionBottomSpacer
|
||||
import SectionItemView
|
||||
import SectionItemViewSpaceBetween
|
||||
import SectionTextFooter
|
||||
import SectionView
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.text.KeyboardActions
|
||||
@ -15,14 +12,12 @@ import androidx.compose.runtime.saveable.rememberSaveable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.focus.*
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.input.key.*
|
||||
import androidx.compose.ui.platform.LocalFocusManager
|
||||
import androidx.compose.ui.text.input.ImeAction
|
||||
import dev.icerock.moko.resources.compose.painterResource
|
||||
import dev.icerock.moko.resources.compose.stringResource
|
||||
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.platform.*
|
||||
@ -177,7 +172,10 @@ private fun SetupDatabasePassphraseLayout(
|
||||
}
|
||||
|
||||
Spacer(Modifier.weight(1f))
|
||||
SkipButton(progressIndicator.value, nextStep)
|
||||
SkipButton(progressIndicator.value) {
|
||||
chatModel.desktopOnboardingRandomPassword.value = true
|
||||
nextStep()
|
||||
}
|
||||
|
||||
SectionBottomSpacer()
|
||||
}
|
||||
|
@ -174,8 +174,6 @@ private fun ConnectMobileViewLayout(
|
||||
sessionCode: String?,
|
||||
port: String?,
|
||||
staleQrCode: Boolean = false,
|
||||
editEnabled: Boolean = false,
|
||||
editClicked: () -> Unit = {},
|
||||
refreshQrCode: () -> Unit = {},
|
||||
UnderQrLayout: @Composable () -> Unit = {},
|
||||
) {
|
||||
@ -201,16 +199,7 @@ private fun ConnectMobileViewLayout(
|
||||
}
|
||||
}
|
||||
SectionTextFooter(annotatedStringResource(MR.strings.open_on_mobile_and_scan_qr_code), textAlign = TextAlign.Center)
|
||||
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||
SectionTextFooter(annotatedStringResource(MR.strings.waiting_for_mobile_to_connect_on_port, port), textAlign = TextAlign.Center)
|
||||
if (editEnabled) {
|
||||
Spacer(Modifier.width(4.dp))
|
||||
IconButton(editClicked, Modifier.size(16.dp)) {
|
||||
Icon(painterResource(MR.images.ic_edit), stringResource(MR.strings.edit_verb), Modifier.size(16.dp), tint = MaterialTheme.colors.primary)
|
||||
}
|
||||
Spacer(Modifier.width(DEFAULT_PADDING))
|
||||
}
|
||||
}
|
||||
SectionTextFooter(annotatedStringResource(MR.strings.waiting_for_mobile_to_connect), textAlign = TextAlign.Center)
|
||||
|
||||
UnderQrLayout()
|
||||
|
||||
@ -249,9 +238,11 @@ private fun ConnectMobileViewLayout(
|
||||
}
|
||||
}
|
||||
}
|
||||
if (invitation != null) {
|
||||
SectionBottomSpacer()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun connectMobileDevice(rh: RemoteHostInfo, connecting: MutableState<Boolean>) {
|
||||
if (!rh.activeHost() && rh.sessionState is RemoteHostSessionState.Connected) {
|
||||
@ -275,10 +266,9 @@ private fun showAddingMobileDevice(connecting: MutableState<Boolean>) {
|
||||
|
||||
@Composable
|
||||
fun AddingMobileDevice(showTitle: Boolean, staleQrCode: MutableState<Boolean>, connecting: MutableState<Boolean>, close: () -> Unit) {
|
||||
val cachedR = remember { mutableStateOf<CR.RemoteHostStarted?>(null) }
|
||||
var cachedR by remember { mutableStateOf<CR.RemoteHostStarted?>(null) }
|
||||
val customAddress = rememberSaveable { mutableStateOf<RemoteCtrlAddress?>(null) }
|
||||
val customPort = rememberSaveable { mutableStateOf<Int?>(null) }
|
||||
var editing by rememberSaveable { mutableStateOf(false) }
|
||||
val startRemoteHost = suspend {
|
||||
val r = chatModel.controller.startRemoteHost(
|
||||
rhId = null,
|
||||
@ -287,9 +277,9 @@ fun AddingMobileDevice(showTitle: Boolean, staleQrCode: MutableState<Boolean>, c
|
||||
port = if (customPort.value != cachedR.port) customPort.value else cachedR.rh?.bindPort_
|
||||
)
|
||||
if (r != null) {
|
||||
cachedR.value = r
|
||||
cachedR = r
|
||||
connecting.value = true
|
||||
customAddress.value = cachedR.address
|
||||
customAddress.value = cachedR.addresses.firstOrNull()
|
||||
customPort.value = cachedR.port
|
||||
chatModel.remoteHostPairing.value = null to RemoteHostSessionState.Starting
|
||||
}
|
||||
@ -307,23 +297,20 @@ fun AddingMobileDevice(showTitle: Boolean, staleQrCode: MutableState<Boolean>, c
|
||||
val remoteDeviceName = pairing.value?.first?.hostDeviceName
|
||||
ConnectMobileViewLayout(
|
||||
title = if (!showTitle) null else if (cachedSessionCode == null) stringResource(MR.strings.link_a_mobile) else stringResource(MR.strings.verify_connection),
|
||||
invitation = cachedR.invitation,
|
||||
invitation = cachedR?.invitation,
|
||||
deviceName = remoteDeviceName,
|
||||
sessionCode = cachedSessionCode,
|
||||
port = cachedR.value?.ctrlPort,
|
||||
staleQrCode = staleQrCode.value || (cachedR.address != customAddress.value && customAddress.value != null) || (cachedR.port != customPort.value && customPort.value != null),
|
||||
editEnabled = !editing && cachedR.addresses.isNotEmpty(),
|
||||
editClicked = { editing = true },
|
||||
port = cachedR?.ctrlPort,
|
||||
staleQrCode = staleQrCode.value || (cachedR.address != customAddress.value && customAddress.value != null) || cachedR.port != customPort.value,
|
||||
refreshQrCode = {
|
||||
withBGApi {
|
||||
if (chatController.stopRemoteHost(null)) {
|
||||
startRemoteHost()
|
||||
staleQrCode.value = false
|
||||
editing = false
|
||||
}
|
||||
}
|
||||
},
|
||||
UnderQrLayout = { UnderQrLayout(editing, cachedR, customAddress, customPort) }
|
||||
UnderQrLayout = { UnderQrLayout(cachedR, customAddress, customPort) }
|
||||
)
|
||||
val oldRemoteHostId by remember { mutableStateOf(chatModel.currentRemoteHost.value?.remoteHostId) }
|
||||
LaunchedEffect(remember { chatModel.currentRemoteHost }.value) {
|
||||
@ -353,10 +340,9 @@ fun AddingMobileDevice(showTitle: Boolean, staleQrCode: MutableState<Boolean>, c
|
||||
|
||||
private fun showConnectMobileDevice(rh: RemoteHostInfo, connecting: MutableState<Boolean>) {
|
||||
ModalManager.start.showModalCloseable { close ->
|
||||
val cachedR = remember { mutableStateOf<CR.RemoteHostStarted?>(null) }
|
||||
var cachedR by remember { mutableStateOf<CR.RemoteHostStarted?>(null) }
|
||||
val customAddress = rememberSaveable { mutableStateOf<RemoteCtrlAddress?>(null) }
|
||||
val customPort = rememberSaveable { mutableStateOf<Int?>(null) }
|
||||
var editing by rememberSaveable { mutableStateOf(false) }
|
||||
val startRemoteHost = suspend {
|
||||
val r = chatModel.controller.startRemoteHost(
|
||||
rhId = rh.remoteHostId,
|
||||
@ -365,9 +351,9 @@ private fun showConnectMobileDevice(rh: RemoteHostInfo, connecting: MutableState
|
||||
port = if (customPort.value != cachedR.port) customPort.value else cachedR.rh?.bindPort_ ?: rh.bindPort_
|
||||
)
|
||||
if (r != null) {
|
||||
cachedR.value = r
|
||||
cachedR = r
|
||||
connecting.value = true
|
||||
customAddress.value = cachedR.address
|
||||
customAddress.value = cachedR.addresses.firstOrNull()
|
||||
customPort.value = cachedR.port
|
||||
chatModel.remoteHostPairing.value = null to RemoteHostSessionState.Starting
|
||||
}
|
||||
@ -384,22 +370,19 @@ private fun showConnectMobileDevice(rh: RemoteHostInfo, connecting: MutableState
|
||||
}
|
||||
ConnectMobileViewLayout(
|
||||
title = if (cachedSessionCode == null) stringResource(MR.strings.scan_from_mobile) else stringResource(MR.strings.verify_connection),
|
||||
invitation = cachedR.invitation,
|
||||
invitation = cachedR?.invitation,
|
||||
deviceName = pairing.value?.first?.hostDeviceName ?: rh.hostDeviceName,
|
||||
sessionCode = cachedSessionCode,
|
||||
port = cachedR.value?.ctrlPort,
|
||||
staleQrCode = (cachedR.address != customAddress.value && customAddress.value != null) || (cachedR.port != customPort.value && customPort.value != null),
|
||||
editEnabled = !editing && cachedR.addresses.isNotEmpty(),
|
||||
editClicked = { editing = true },
|
||||
port = cachedR?.ctrlPort,
|
||||
staleQrCode = (cachedR.address != customAddress.value && customAddress.value != null) || cachedR.port != customPort.value,
|
||||
refreshQrCode = {
|
||||
withBGApi {
|
||||
if (chatController.stopRemoteHost(rh.remoteHostId)) {
|
||||
startRemoteHost()
|
||||
editing = false
|
||||
}
|
||||
}
|
||||
},
|
||||
UnderQrLayout = { UnderQrLayout(editing, cachedR, customAddress, customPort) }
|
||||
UnderQrLayout = { UnderQrLayout(cachedR, customAddress, customPort) }
|
||||
)
|
||||
LaunchedEffect(remember { chatModel.currentRemoteHost }.value) {
|
||||
if (cachedR.remoteHostId != null && chatModel.currentRemoteHost.value?.remoteHostId == cachedR.remoteHostId) {
|
||||
@ -453,48 +436,75 @@ private fun showConnectedMobileDevice(rh: RemoteHostInfo, disconnectHost: () ->
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun UnderQrLayout(editing: Boolean, cachedR: State<CR.RemoteHostStarted?>, customAddress: MutableState<RemoteCtrlAddress?>, customPort: MutableState<Int?>) {
|
||||
if (editing) {
|
||||
private fun UnderQrLayout(cachedR: CR.RemoteHostStarted?, customAddress: MutableState<RemoteCtrlAddress?>, customPort: MutableState<Int?>) {
|
||||
Row(Modifier.fillMaxWidth(), verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.Center) {
|
||||
if (cachedR.addresses.size > 1) {
|
||||
ExposedDropDownSetting(
|
||||
cachedR.addresses.map { it to it.address + " (${it.`interface`})" },
|
||||
customAddress,
|
||||
textColor = MaterialTheme.colors.onBackground,
|
||||
fontSize = 14.sp,
|
||||
minWidth = 250.dp,
|
||||
maxWidth = with(LocalDensity.current) { 250.sp.toDp() },
|
||||
enabled = remember { mutableStateOf(cachedR.addresses.size > 1) },
|
||||
onSelected = {
|
||||
customAddress.value = it
|
||||
}
|
||||
)
|
||||
} else {
|
||||
Spacer(Modifier.width(10.dp))
|
||||
Text(customAddress.value?.address + " (${customAddress.value?.`interface`})", fontSize = 14.sp, color = MaterialTheme.colors.onBackground)
|
||||
}
|
||||
val portUnsaved = rememberSaveable(stateSaver = TextFieldValue.Saver) {
|
||||
mutableStateOf(TextFieldValue((customPort.value ?: cachedR.port!!).toString()))
|
||||
}
|
||||
Spacer(Modifier.width(DEFAULT_PADDING))
|
||||
Box {
|
||||
DefaultConfigurableTextField(
|
||||
portUnsaved,
|
||||
stringResource(MR.strings.port_verb),
|
||||
modifier = Modifier.widthIn(max = 100.dp),
|
||||
isValid = { validPort(it) && it.toInt() > 1023 },
|
||||
stringResource(MR.strings.random_port),
|
||||
modifier = Modifier.widthIn(max = 132.dp),
|
||||
isValid = { (validPort(it) && it.toInt() > 1023) || it.isBlank() },
|
||||
keyboardActions = KeyboardActions(onDone = { defaultKeyboardAction(ImeAction.Done) }),
|
||||
keyboardType = KeyboardType.Number,
|
||||
fontSize = 14.sp,
|
||||
)
|
||||
if (validPort(portUnsaved.value.text) && portUnsaved.value.text.toInt() > 1023) {
|
||||
Icon(painterResource(MR.images.ic_edit), stringResource(MR.strings.edit_verb), Modifier.padding(end = 56.dp).size(16.dp).align(Alignment.CenterEnd), tint = MaterialTheme.colors.secondary)
|
||||
IconButton(::showOpenPortAlert, Modifier.align(Alignment.TopEnd).padding(top = 2.dp)) {
|
||||
Icon(painterResource(MR.images.ic_info), null, tint = MaterialTheme.colors.primary)
|
||||
}
|
||||
}
|
||||
}
|
||||
LaunchedEffect(Unit) {
|
||||
snapshotFlow { portUnsaved.value.text }
|
||||
.distinctUntilChanged()
|
||||
.collect {
|
||||
if (validPort(it) && it.toInt() > 1023) {
|
||||
customPort.value = it.toInt()
|
||||
} else {
|
||||
customPort.value = null
|
||||
}
|
||||
}
|
||||
}
|
||||
KeyChangeEffect(customPort.value) {
|
||||
if (customPort.value != null) {
|
||||
portUnsaved.value = portUnsaved.value.copy(text = customPort.value.toString())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private val State<CR.RemoteHostStarted?>.rh: RemoteHostInfo? get() = value?.remoteHost_
|
||||
private val State<CR.RemoteHostStarted?>.remoteHostId: Long? get() = value?.remoteHost_?.remoteHostId
|
||||
private val State<CR.RemoteHostStarted?>.invitation: String? get() = value?.invitation
|
||||
private val State<CR.RemoteHostStarted?>.address: RemoteCtrlAddress? get() = value?.localAddrs?.firstOrNull()
|
||||
private val State<CR.RemoteHostStarted?>.addresses: List<RemoteCtrlAddress> get() =
|
||||
(if (controller.appPrefs.developerTools.get()) value?.localAddrs else value?.localAddrs?.filterNot { it.address == "127.0.0.1" }) ?: emptyList()
|
||||
private val State<CR.RemoteHostStarted?>.port: Int? get() = value?.ctrlPort?.toIntOrNull()
|
||||
private fun showOpenPortAlert() {
|
||||
AlertManager.shared.showAlertMsg(
|
||||
title = generalGetString(MR.strings.open_port_in_firewall_title),
|
||||
text = generalGetString(MR.strings.open_port_in_firewall_desc),
|
||||
)
|
||||
}
|
||||
|
||||
private val CR.RemoteHostStarted?.rh: RemoteHostInfo? get() = this?.remoteHost_
|
||||
private val CR.RemoteHostStarted?.remoteHostId: Long? get() = this?.remoteHost_?.remoteHostId
|
||||
private val CR.RemoteHostStarted?.address: RemoteCtrlAddress? get() = this?.localAddrs?.firstOrNull()
|
||||
private val CR.RemoteHostStarted?.addresses: List<RemoteCtrlAddress> get() =
|
||||
(if (controller.appPrefs.developerTools.get() || this?.localAddrs?.indexOfFirst { it.address == "127.0.0.1" } == 0) this?.localAddrs else this?.localAddrs?.filterNot { it.address == "127.0.0.1" }) ?: emptyList()
|
||||
private val CR.RemoteHostStarted?.port: Int? get() = this?.ctrlPort?.toIntOrNull()
|
||||
|
@ -18,6 +18,7 @@
|
||||
<string name="opening_database">Opening database…</string>
|
||||
<string name="non_content_uri_alert_title">Invalid file path</string>
|
||||
<string name="non_content_uri_alert_text">You shared an invalid file path. Report the issue to the app developers.</string>
|
||||
<string name="app_was_crashed">View crashed</string>
|
||||
|
||||
<!-- Server info - ChatModel.kt -->
|
||||
<string name="server_connected">connected</string>
|
||||
@ -1665,7 +1666,7 @@
|
||||
<string name="disconnect_desktop_question">Disconnect desktop?</string>
|
||||
<string name="only_one_device_can_work_at_the_same_time">Only one device can work at the same time</string>
|
||||
<string name="open_on_mobile_and_scan_qr_code"><![CDATA[Open <i>Use from desktop</i> in mobile app and scan QR code.]]></string>
|
||||
<string name="waiting_for_mobile_to_connect_on_port"><![CDATA[Waiting for mobile to connect on port <i>%s</i>]]></string>
|
||||
<string name="waiting_for_mobile_to_connect">Waiting for mobile to connect:</string>
|
||||
<string name="bad_desktop_address">Bad desktop address</string>
|
||||
<string name="desktop_incompatible_version">Incompatible version</string>
|
||||
<string name="desktop_app_version_is_incompatible">Desktop app version %s is not compatible with this app.</string>
|
||||
@ -1693,6 +1694,9 @@
|
||||
<string name="not_compatible">Not compatible!</string>
|
||||
<string name="refresh_qr_code">Refresh</string>
|
||||
<string name="no_connected_mobile">No connected mobile</string>
|
||||
<string name="random_port">Random</string>
|
||||
<string name="open_port_in_firewall_title">Open port in firewall</string>
|
||||
<string name="open_port_in_firewall_desc">To allow a mobile app to connect to the desktop, open this port in your firewall, if you have it enabled</string>
|
||||
|
||||
<!-- Under development -->
|
||||
<string name="in_developing_title">Coming soon!</string>
|
||||
|
@ -1581,7 +1581,7 @@
|
||||
<string name="v5_4_better_groups_descr">Schnellerer Gruppenbeitritt und zuverlässigere Nachrichtenzustellung.</string>
|
||||
<string name="linked_mobiles">Verknüpfte Mobiltelefone</string>
|
||||
<string name="this_device_name">Dieser Gerätename</string>
|
||||
<string name="waiting_for_mobile_to_connect_on_port"><![CDATA[Auf die Mobiltelefonverbindung über Port <i>%s</i> warten]]></string>
|
||||
<string name="waiting_for_mobile_to_connect">Auf die Mobiltelefonverbindung warten:</string>
|
||||
<string name="loading_remote_file_title">Laden der Datei</string>
|
||||
<string name="link_a_mobile">Zu einem Mobiltelefon verbinden</string>
|
||||
<string name="settings_section_title_use_from_desktop">Vom Desktop aus nutzen</string>
|
||||
|
@ -1486,7 +1486,7 @@
|
||||
<string name="desktop_device">Bureau</string>
|
||||
<string name="connected_to_desktop">Connecté au bureau</string>
|
||||
<string name="this_device_name">Ce nom d\'appareil</string>
|
||||
<string name="waiting_for_mobile_to_connect_on_port"><![CDATA[En attente d\'une connexion mobile sur le port <i>%s</i>]]></string>
|
||||
<string name="waiting_for_mobile_to_connect">En attente d\'une connexion mobile:</string>
|
||||
<string name="loading_remote_file_title">Chargement du fichier</string>
|
||||
<string name="connecting_to_desktop">Connexion au bureau</string>
|
||||
<string name="desktop_devices">Appareils de bureau</string>
|
||||
|
@ -1517,6 +1517,6 @@
|
||||
<string name="v5_4_more_things_descr">- avvisa facoltativamente i contatti eliminati.
|
||||
\n- nomi del profilo con spazi.
|
||||
\n- e molto altro!</string>
|
||||
<string name="waiting_for_mobile_to_connect_on_port"><![CDATA[In attesa che il cellulare si connetta alla porta <i>%s</i>]]></string>
|
||||
<string name="waiting_for_mobile_to_connect">In attesa che il cellulare si connette:</string>
|
||||
<string name="group_member_role_author">autore</string>
|
||||
</resources>
|
@ -1516,7 +1516,7 @@
|
||||
\n- en meer!</string>
|
||||
<string name="remote_host_was_disconnected_toast"><![CDATA[Mobiele verbinding <b>%s</b> is verbroken]]></string>
|
||||
<string name="group_member_role_author">auteur</string>
|
||||
<string name="waiting_for_mobile_to_connect_on_port"><![CDATA[Wachten tot mobiel verbinding maakt op poort <i>%s</i>]]></string>
|
||||
<string name="waiting_for_mobile_to_connect">Wachten tot mobiel verbinding maakt:</string>
|
||||
<string name="multicast_connect_automatically">Automatisch verbinden</string>
|
||||
<string name="waiting_for_desktop">Wachten op desktop…</string>
|
||||
<string name="found_desktop">Desktop gevonden</string>
|
||||
|
@ -1496,7 +1496,7 @@
|
||||
<string name="v5_4_better_groups_descr">Szybsze dołączenie i bardziej niezawodne wiadomości.</string>
|
||||
<string name="linked_mobiles">Połączone telefony</string>
|
||||
<string name="this_device_name">Nazwa tego urządzenia</string>
|
||||
<string name="waiting_for_mobile_to_connect_on_port"><![CDATA[Oczekiwanie na połączenie telefonu na port <i>%s</i>]]></string>
|
||||
<string name="waiting_for_mobile_to_connect">Oczekiwanie na połączenie telefonu:</string>
|
||||
<string name="loading_remote_file_title">Ładowanie pliku</string>
|
||||
<string name="found_desktop">Znaleziono komputer</string>
|
||||
<string name="desktop_devices">Urządzenia komputerowe</string>
|
||||
|
@ -1601,7 +1601,7 @@
|
||||
<string name="verify_connection">Проверить соединение</string>
|
||||
<string name="multicast_connect_automatically">Соединяться автоматически</string>
|
||||
<string name="waiting_for_desktop">Ожидается подключение…</string>
|
||||
<string name="waiting_for_mobile_to_connect_on_port"><![CDATA[Ожидается подключение мобильного через порт <i>%s</i>]]></string>
|
||||
<string name="waiting_for_mobile_to_connect">Ожидается подключение мобильного:</string>
|
||||
<string name="found_desktop">Компьютер найден</string>
|
||||
<string name="not_compatible">Несовместимая версия!</string>
|
||||
<string name="group_member_role_author">автор</string>
|
||||
|
@ -1517,7 +1517,7 @@
|
||||
<string name="v5_4_more_things_descr">- 可选择通知已删除的联系人。
|
||||
\n- 带空格的个人资料名称。
|
||||
\n- 以及更多!</string>
|
||||
<string name="waiting_for_mobile_to_connect_on_port"><![CDATA[正等待移动设备在端口 <i>%s</i> 进行连接]]></string>
|
||||
<string name="waiting_for_mobile_to_connect">正等待移动设备 进行连接:</string>
|
||||
<string name="group_member_role_author">作者</string>
|
||||
<string name="multicast_connect_automatically">自动连接</string>
|
||||
<string name="waiting_for_desktop">等待桌面中…</string>
|
||||
|
@ -9,30 +9,73 @@ import androidx.compose.material.Text
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.awt.ComposeWindow
|
||||
import androidx.compose.ui.input.key.*
|
||||
import androidx.compose.ui.platform.LocalDensity
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.window.*
|
||||
import chat.simplex.common.model.ChatController
|
||||
import chat.simplex.common.model.ChatModel
|
||||
import chat.simplex.common.platform.*
|
||||
import chat.simplex.common.ui.theme.DEFAULT_START_MODAL_WIDTH
|
||||
import chat.simplex.common.ui.theme.SimpleXTheme
|
||||
import chat.simplex.common.views.TerminalView
|
||||
import chat.simplex.common.views.helpers.FileDialogChooser
|
||||
import chat.simplex.common.views.helpers.escapedHtmlToAnnotatedString
|
||||
import chat.simplex.common.views.helpers.*
|
||||
import chat.simplex.res.MR
|
||||
import dev.icerock.moko.resources.compose.stringResource
|
||||
import kotlinx.coroutines.*
|
||||
import java.awt.event.WindowEvent
|
||||
import java.awt.event.WindowFocusListener
|
||||
import java.io.File
|
||||
import kotlin.system.exitProcess
|
||||
|
||||
val simplexWindowState = SimplexWindowState()
|
||||
|
||||
fun showApp() = application {
|
||||
fun showApp() {
|
||||
val closedByError = mutableStateOf(true)
|
||||
while (closedByError.value) {
|
||||
application(exitProcessOnExit = false) {
|
||||
CompositionLocalProvider(
|
||||
LocalWindowExceptionHandlerFactory provides WindowExceptionHandlerFactory { window ->
|
||||
WindowExceptionHandler { e ->
|
||||
AlertManager.shared.showAlertMsg(
|
||||
title = generalGetString(MR.strings.app_was_crashed),
|
||||
text = e.stackTraceToString()
|
||||
)
|
||||
Log.e(TAG, "App crashed, thread name: " + Thread.currentThread().name + ", exception: " + e.stackTraceToString())
|
||||
window.dispatchEvent(WindowEvent(window, WindowEvent.WINDOW_CLOSING))
|
||||
closedByError.value = true
|
||||
// If the left side of screen has open modal, it's probably caused the crash
|
||||
if (ModalManager.start.hasModalsOpen()) {
|
||||
ModalManager.start.closeModal()
|
||||
} else if (ModalManager.center.hasModalsOpen() || ModalManager.end.hasModalsOpen()) {
|
||||
ModalManager.center.closeModal()
|
||||
ModalManager.end.closeModal()
|
||||
// Better to not close fullscreen since it can contain passcode
|
||||
} else {
|
||||
// The last possible cause that can be closed
|
||||
chatModel.chatId.value = null
|
||||
chatModel.chatItems.clear()
|
||||
}
|
||||
chatModel.activeCall.value?.let {
|
||||
withBGApi {
|
||||
chatModel.callManager.endCall(it)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
) {
|
||||
AppWindow(closedByError)
|
||||
}
|
||||
}
|
||||
}
|
||||
exitProcess(0)
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun ApplicationScope.AppWindow(closedByError: MutableState<Boolean>) {
|
||||
// Creates file if not exists; comes with proper defaults
|
||||
val state = getStoredWindowState()
|
||||
|
||||
val windowState: WindowState = rememberWindowState(
|
||||
placement = WindowPlacement.Floating,
|
||||
width = state.width.dp,
|
||||
@ -46,25 +89,27 @@ fun showApp() = application {
|
||||
windowState.size.width.value,
|
||||
windowState.size.height.value
|
||||
) {
|
||||
storeWindowState(WindowPositionSize(
|
||||
storeWindowState(
|
||||
WindowPositionSize(
|
||||
x = windowState.position.x.value.toInt(),
|
||||
y = windowState.position.y.value.toInt(),
|
||||
width = windowState.size.width.value.toInt(),
|
||||
height = windowState.size.height.value.toInt()
|
||||
))
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
simplexWindowState.windowState = windowState
|
||||
// Reload all strings in all @Composable's after language change at runtime
|
||||
if (remember { ChatController.appPrefs.appLanguage.state }.value != "") {
|
||||
Window(state = windowState, onCloseRequest = ::exitApplication, onKeyEvent = {
|
||||
Window(state = windowState, onCloseRequest = { closedByError.value = false; exitApplication() }, onKeyEvent = {
|
||||
if (it.key == Key.Escape && it.type == KeyEventType.KeyUp) {
|
||||
simplexWindowState.backstack.lastOrNull()?.invoke() != null
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}, title = "SimpleX") {
|
||||
SimpleXTheme {
|
||||
simplexWindowState.window = window
|
||||
AppScreen()
|
||||
if (simplexWindowState.openDialog.isAwaiting) {
|
||||
FileDialogChooser(
|
||||
@ -99,6 +144,7 @@ fun showApp() = application {
|
||||
val toasts = remember { simplexWindowState.toasts }
|
||||
val toast = toasts.firstOrNull()
|
||||
if (toast != null) {
|
||||
SimpleXTheme {
|
||||
Box(Modifier.fillMaxSize().padding(bottom = 20.dp), contentAlignment = Alignment.BottomCenter) {
|
||||
Text(
|
||||
escapedHtmlToAnnotatedString(toast.first, LocalDensity.current),
|
||||
@ -107,13 +153,13 @@ fun showApp() = application {
|
||||
style = MaterialTheme.typography.body1
|
||||
)
|
||||
}
|
||||
}
|
||||
// Shows toast in insertion order with preferred delay per toast. New one will be shown once previous one expires
|
||||
LaunchedEffect(toast, toasts.size) {
|
||||
delay(toast.second)
|
||||
simplexWindowState.toasts.removeFirst()
|
||||
}
|
||||
}
|
||||
}
|
||||
var windowFocused by remember { simplexWindowState.windowFocused }
|
||||
LaunchedEffect(windowFocused) {
|
||||
val delay = ChatController.appPrefs.laLockDelay.get()
|
||||
@ -160,6 +206,7 @@ class SimplexWindowState {
|
||||
val saveDialog = DialogState<File?>()
|
||||
val toasts = mutableStateListOf<Pair<String, Long>>()
|
||||
var windowFocused = mutableStateOf(true)
|
||||
var window: ComposeWindow? = null
|
||||
}
|
||||
|
||||
data class DialogParams(
|
||||
@ -188,7 +235,5 @@ class DialogState<T> {
|
||||
@Preview
|
||||
@Composable
|
||||
fun AppPreview() {
|
||||
SimpleXTheme {
|
||||
AppScreen()
|
||||
}
|
||||
}
|
||||
|
@ -19,3 +19,9 @@ actual fun getKeyboardState(): State<KeyboardState> = remember { mutableStateOf(
|
||||
actual fun hideKeyboard(view: Any?) {}
|
||||
|
||||
actual fun androidIsFinishingMainActivity(): Boolean = false
|
||||
|
||||
actual class GlobalExceptionsHandler: Thread.UncaughtExceptionHandler {
|
||||
actual override fun uncaughtException(thread: Thread, e: Throwable) {
|
||||
Log.e(TAG, "App crashed, thread name: " + thread.name + ", exception: " + e.stackTraceToString())
|
||||
}
|
||||
}
|
||||
|
@ -6,6 +6,7 @@ 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.platform.chatModel
|
||||
import chat.simplex.common.ui.theme.DEFAULT_PADDING
|
||||
import chat.simplex.res.MR
|
||||
import dev.icerock.moko.resources.compose.painterResource
|
||||
@ -14,7 +15,7 @@ import dev.icerock.moko.resources.compose.painterResource
|
||||
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.link_a_mobile, onboarding = if (controller.appPrefs.initialRandomDBPassphrase.get() && !chatModel.desktopOnboardingRandomPassword.value) 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 {
|
||||
|
@ -3,10 +3,6 @@ package chat.simplex.desktop
|
||||
import chat.simplex.common.platform.*
|
||||
import chat.simplex.common.showApp
|
||||
import java.io.File
|
||||
import java.nio.file.*
|
||||
import java.nio.file.attribute.BasicFileAttributes
|
||||
import java.nio.file.attribute.FileTime
|
||||
import kotlin.io.path.setLastModifiedTime
|
||||
|
||||
fun main() {
|
||||
initHaskell()
|
||||
|
@ -11,7 +11,7 @@ constraints: zip +disable-bzip2 +disable-zstd
|
||||
source-repository-package
|
||||
type: git
|
||||
location: https://github.com/simplex-chat/simplexmq.git
|
||||
tag: eaf5317834b069144b5f4897f9c79831983e54dd
|
||||
tag: a860936072172e261480fa6bdd95203976e366b2
|
||||
|
||||
source-repository-package
|
||||
type: git
|
||||
|
@ -17,7 +17,7 @@ Group Chat — Users have the option to create a secret group, share their conta
|
||||
|
||||
You can [create an optional long term address](./app-settings.md#your-simplex-contact-address) for other people to connect with you. Unlike 1-time invitation links, these addresses can be used many times, that makes them good to share online, e.g. on social media platforms, or in email signatures. That helps more people discover SimpleX Chat, so please do it!
|
||||
|
||||
When people connect to you via this address, you will receive a connection request that you can accept or reject. You can configure an automatic acceptance of connection request and an automatic welcome message that will be sent to the new contacts. You can also share this address as part of your SimpleX profile, so group members can connect to you, and you contacts can share it with others - if this is something that you want.
|
||||
When people connect to you via this address, you will receive a connection request that you can accept or reject. You can configure an automatic acceptance of connection request and an automatic welcome message that will be sent to the new contacts. You can also share this address as part of your SimpleX profile, so group members can connect to you, and your contacts can share it with others - if this is something that you want.
|
||||
|
||||
If you start receiving too many requests via this address it is always safe to remove it – all the connections you created via this address will remain active, as this address is not used to deliver the messages.
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
name: simplex-chat
|
||||
version: 5.4.0.6
|
||||
version: 5.4.0.7
|
||||
#synopsis:
|
||||
#description:
|
||||
homepage: https://github.com/simplex-chat/simplex-chat#readme
|
||||
|
@ -1,5 +1,5 @@
|
||||
{
|
||||
"https://github.com/simplex-chat/simplexmq.git"."eaf5317834b069144b5f4897f9c79831983e54dd" = "0jlic1q08mq9p9sgvigmc59r6x1r5fa1zsfqvvrwd97pwain36mj";
|
||||
"https://github.com/simplex-chat/simplexmq.git"."a860936072172e261480fa6bdd95203976e366b2" = "16rwnh5zzphmw8d8ypvps6xjvzbmf5ljr6zzy15gz2g0jyh7hd91";
|
||||
"https://github.com/simplex-chat/hs-socks.git"."a30cc7a79a08d8108316094f8f2f82a0c5e1ac51" = "0yasvnr7g91k76mjkamvzab2kvlb1g5pspjyjn2fr6v83swjhj38";
|
||||
"https://github.com/kazu-yamamoto/http2.git"."f5525b755ff2418e6e6ecc69e877363b0d0bcaeb" = "0fyx0047gvhm99ilp212mmz37j84cwrfnpmssib5dw363fyb88b6";
|
||||
"https://github.com/simplex-chat/direct-sqlcipher.git"."f814ee68b16a9447fbb467ccc8f29bdd3546bfd9" = "0kiwhvml42g9anw4d2v0zd1fpc790pj9syg5x3ik4l97fnkbbwpp";
|
||||
|
@ -5,7 +5,7 @@ cabal-version: 1.12
|
||||
-- see: https://github.com/sol/hpack
|
||||
|
||||
name: simplex-chat
|
||||
version: 5.4.0.6
|
||||
version: 5.4.0.7
|
||||
category: Web, System, Services, Cryptography
|
||||
homepage: https://github.com/simplex-chat/simplex-chat#readme
|
||||
author: simplex.chat
|
||||
|
@ -189,6 +189,7 @@ mobileChatOpts dbFilePrefix dbKey =
|
||||
allowInstantFiles = True,
|
||||
autoAcceptFileSize = 0,
|
||||
muteNotifications = True,
|
||||
markRead = False,
|
||||
maintenance = True
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user