Merge branch 'master' into master-android

This commit is contained in:
Evgeny Poberezkin
2024-12-04 20:00:27 +00:00
30 changed files with 328 additions and 102 deletions
+6 -1
View File
@@ -2014,11 +2014,16 @@ func processReceivedMsg(_ res: ChatResponse) async {
m.removeChat(hostConn.id)
}
}
case let .businessLinkConnecting(user, groupInfo, hostMember, fromContact):
case let .businessLinkConnecting(user, groupInfo, _, fromContact):
if !active(user) { return }
await MainActor.run {
m.updateGroup(groupInfo)
}
if m.chatId == fromContact.id {
ItemsModel.shared.loadOpenChat(groupInfo.id)
}
await MainActor.run {
m.removeChat(fromContact.id)
}
case let .joinedGroupMemberConnecting(user, groupInfo, _, member):
@@ -65,6 +65,7 @@ extension AppSettings {
if let val = uiCurrentThemeIds { currentThemeIdsDefault.set(val) }
if let val = uiThemes { themeOverridesDefault.set(val.skipDuplicates()) }
if let val = oneHandUI { groupDefaults.setValue(val, forKey: GROUP_DEFAULT_ONE_HAND_UI) }
if let val = chatBottomBar { groupDefaults.setValue(val, forKey: GROUP_DEFAULT_CHAT_BOTTOM_BAR) }
}
public static var current: AppSettings {
@@ -100,6 +101,7 @@ extension AppSettings {
c.uiCurrentThemeIds = currentThemeIdsDefault.get()
c.uiThemes = themeOverridesDefault.get()
c.oneHandUI = groupDefaults.bool(forKey: GROUP_DEFAULT_ONE_HAND_UI)
c.chatBottomBar = groupDefaults.bool(forKey: GROUP_DEFAULT_CHAT_BOTTOM_BAR)
return c
}
}
+18 -18
View File
@@ -172,9 +172,9 @@
648010AB281ADD15009009B9 /* CIFileView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 648010AA281ADD15009009B9 /* CIFileView.swift */; };
648679AB2BC96A74006456E7 /* ChatItemForwardingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 648679AA2BC96A74006456E7 /* ChatItemForwardingView.swift */; };
649B28DD2CFE07CF00536B68 /* libffi.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 649B28D82CFE07CF00536B68 /* libffi.a */; };
649B28DE2CFE07CF00536B68 /* libHSsimplex-chat-6.2.0.3-ELfYrsBTXJJ5vBEIgQ1y2B-ghc9.6.3.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 649B28D92CFE07CF00536B68 /* libHSsimplex-chat-6.2.0.3-ELfYrsBTXJJ5vBEIgQ1y2B-ghc9.6.3.a */; };
649B28DE2CFE07CF00536B68 /* libHSsimplex-chat-6.2.0.4-3HGUY7rfU7IFvhj8CPEbqy-ghc9.6.3.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 649B28D92CFE07CF00536B68 /* libHSsimplex-chat-6.2.0.4-3HGUY7rfU7IFvhj8CPEbqy-ghc9.6.3.a */; };
649B28DF2CFE07CF00536B68 /* libgmpxx.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 649B28DA2CFE07CF00536B68 /* libgmpxx.a */; };
649B28E02CFE07CF00536B68 /* libHSsimplex-chat-6.2.0.3-ELfYrsBTXJJ5vBEIgQ1y2B.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 649B28DB2CFE07CF00536B68 /* libHSsimplex-chat-6.2.0.3-ELfYrsBTXJJ5vBEIgQ1y2B.a */; };
649B28E02CFE07CF00536B68 /* libHSsimplex-chat-6.2.0.4-3HGUY7rfU7IFvhj8CPEbqy.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 649B28DB2CFE07CF00536B68 /* libHSsimplex-chat-6.2.0.4-3HGUY7rfU7IFvhj8CPEbqy.a */; };
649B28E12CFE07CF00536B68 /* libgmp.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 649B28DC2CFE07CF00536B68 /* libgmp.a */; };
649BCDA0280460FD00C3A862 /* ComposeImageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 649BCD9F280460FD00C3A862 /* ComposeImageView.swift */; };
649BCDA22805D6EF00C3A862 /* CIImageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 649BCDA12805D6EF00C3A862 /* CIImageView.swift */; };
@@ -526,9 +526,9 @@
648679AA2BC96A74006456E7 /* ChatItemForwardingView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatItemForwardingView.swift; sourceTree = "<group>"; };
6493D667280ED77F007A76FB /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/Localizable.strings; sourceTree = "<group>"; };
649B28D82CFE07CF00536B68 /* libffi.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libffi.a; sourceTree = "<group>"; };
649B28D92CFE07CF00536B68 /* libHSsimplex-chat-6.2.0.3-ELfYrsBTXJJ5vBEIgQ1y2B-ghc9.6.3.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-6.2.0.3-ELfYrsBTXJJ5vBEIgQ1y2B-ghc9.6.3.a"; sourceTree = "<group>"; };
649B28D92CFE07CF00536B68 /* libHSsimplex-chat-6.2.0.4-3HGUY7rfU7IFvhj8CPEbqy-ghc9.6.3.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-6.2.0.4-3HGUY7rfU7IFvhj8CPEbqy-ghc9.6.3.a"; sourceTree = "<group>"; };
649B28DA2CFE07CF00536B68 /* libgmpxx.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmpxx.a; sourceTree = "<group>"; };
649B28DB2CFE07CF00536B68 /* libHSsimplex-chat-6.2.0.3-ELfYrsBTXJJ5vBEIgQ1y2B.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-6.2.0.3-ELfYrsBTXJJ5vBEIgQ1y2B.a"; sourceTree = "<group>"; };
649B28DB2CFE07CF00536B68 /* libHSsimplex-chat-6.2.0.4-3HGUY7rfU7IFvhj8CPEbqy.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-6.2.0.4-3HGUY7rfU7IFvhj8CPEbqy.a"; sourceTree = "<group>"; };
649B28DC2CFE07CF00536B68 /* libgmp.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmp.a; sourceTree = "<group>"; };
649BCD9F280460FD00C3A862 /* ComposeImageView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ComposeImageView.swift; sourceTree = "<group>"; };
649BCDA12805D6EF00C3A862 /* CIImageView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CIImageView.swift; sourceTree = "<group>"; };
@@ -681,9 +681,9 @@
5CE2BA93284534B000EC33A6 /* libiconv.tbd in Frameworks */,
649B28E12CFE07CF00536B68 /* libgmp.a in Frameworks */,
5CE2BA94284534BB00EC33A6 /* libz.tbd in Frameworks */,
649B28E02CFE07CF00536B68 /* libHSsimplex-chat-6.2.0.3-ELfYrsBTXJJ5vBEIgQ1y2B.a in Frameworks */,
649B28E02CFE07CF00536B68 /* libHSsimplex-chat-6.2.0.4-3HGUY7rfU7IFvhj8CPEbqy.a in Frameworks */,
CE38A29C2C3FCD72005ED185 /* SwiftyGif in Frameworks */,
649B28DE2CFE07CF00536B68 /* libHSsimplex-chat-6.2.0.3-ELfYrsBTXJJ5vBEIgQ1y2B-ghc9.6.3.a in Frameworks */,
649B28DE2CFE07CF00536B68 /* libHSsimplex-chat-6.2.0.4-3HGUY7rfU7IFvhj8CPEbqy-ghc9.6.3.a in Frameworks */,
649B28DD2CFE07CF00536B68 /* libffi.a in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
@@ -764,8 +764,8 @@
649B28D82CFE07CF00536B68 /* libffi.a */,
649B28DC2CFE07CF00536B68 /* libgmp.a */,
649B28DA2CFE07CF00536B68 /* libgmpxx.a */,
649B28D92CFE07CF00536B68 /* libHSsimplex-chat-6.2.0.3-ELfYrsBTXJJ5vBEIgQ1y2B-ghc9.6.3.a */,
649B28DB2CFE07CF00536B68 /* libHSsimplex-chat-6.2.0.3-ELfYrsBTXJJ5vBEIgQ1y2B.a */,
649B28D92CFE07CF00536B68 /* libHSsimplex-chat-6.2.0.4-3HGUY7rfU7IFvhj8CPEbqy-ghc9.6.3.a */,
649B28DB2CFE07CF00536B68 /* libHSsimplex-chat-6.2.0.4-3HGUY7rfU7IFvhj8CPEbqy.a */,
);
path = Libraries;
sourceTree = "<group>";
@@ -1941,7 +1941,7 @@
CLANG_TIDY_MISC_REDUNDANT_EXPRESSION = YES;
CODE_SIGN_ENTITLEMENTS = "SimpleX (iOS).entitlements";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 249;
CURRENT_PROJECT_VERSION = 250;
DEAD_CODE_STRIPPING = YES;
DEVELOPMENT_TEAM = 5NN7GUYB6T;
ENABLE_BITCODE = NO;
@@ -1990,7 +1990,7 @@
CLANG_TIDY_MISC_REDUNDANT_EXPRESSION = YES;
CODE_SIGN_ENTITLEMENTS = "SimpleX (iOS).entitlements";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 249;
CURRENT_PROJECT_VERSION = 250;
DEAD_CODE_STRIPPING = YES;
DEVELOPMENT_TEAM = 5NN7GUYB6T;
ENABLE_BITCODE = NO;
@@ -2031,7 +2031,7 @@
buildSettings = {
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 249;
CURRENT_PROJECT_VERSION = 250;
DEVELOPMENT_TEAM = 5NN7GUYB6T;
GENERATE_INFOPLIST_FILE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 15.0;
@@ -2051,7 +2051,7 @@
buildSettings = {
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 249;
CURRENT_PROJECT_VERSION = 250;
DEVELOPMENT_TEAM = 5NN7GUYB6T;
GENERATE_INFOPLIST_FILE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 15.0;
@@ -2076,7 +2076,7 @@
CODE_SIGN_ENTITLEMENTS = "SimpleX NSE/SimpleX NSE.entitlements";
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 249;
CURRENT_PROJECT_VERSION = 250;
DEVELOPMENT_TEAM = 5NN7GUYB6T;
ENABLE_BITCODE = NO;
GCC_OPTIMIZATION_LEVEL = s;
@@ -2113,7 +2113,7 @@
CODE_SIGN_ENTITLEMENTS = "SimpleX NSE/SimpleX NSE.entitlements";
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 249;
CURRENT_PROJECT_VERSION = 250;
DEVELOPMENT_TEAM = 5NN7GUYB6T;
ENABLE_BITCODE = NO;
ENABLE_CODE_COVERAGE = NO;
@@ -2150,7 +2150,7 @@
CLANG_TIDY_BUGPRONE_REDUNDANT_BRANCH_CONDITION = YES;
CLANG_TIDY_MISC_REDUNDANT_EXPRESSION = YES;
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 249;
CURRENT_PROJECT_VERSION = 250;
DEFINES_MODULE = YES;
DEVELOPMENT_TEAM = 5NN7GUYB6T;
DYLIB_COMPATIBILITY_VERSION = 1;
@@ -2201,7 +2201,7 @@
CLANG_TIDY_BUGPRONE_REDUNDANT_BRANCH_CONDITION = YES;
CLANG_TIDY_MISC_REDUNDANT_EXPRESSION = YES;
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 249;
CURRENT_PROJECT_VERSION = 250;
DEFINES_MODULE = YES;
DEVELOPMENT_TEAM = 5NN7GUYB6T;
DYLIB_COMPATIBILITY_VERSION = 1;
@@ -2252,7 +2252,7 @@
CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
CODE_SIGN_ENTITLEMENTS = "SimpleX SE/SimpleX SE.entitlements";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 249;
CURRENT_PROJECT_VERSION = 250;
DEVELOPMENT_TEAM = 5NN7GUYB6T;
ENABLE_USER_SCRIPT_SANDBOXING = YES;
GCC_C_LANGUAGE_STANDARD = gnu17;
@@ -2286,7 +2286,7 @@
CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
CODE_SIGN_ENTITLEMENTS = "SimpleX SE/SimpleX SE.entitlements";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 249;
CURRENT_PROJECT_VERSION = 250;
DEVELOPMENT_TEAM = 5NN7GUYB6T;
ENABLE_USER_SCRIPT_SANDBOXING = YES;
GCC_C_LANGUAGE_STANDARD = gnu17;
+4 -1
View File
@@ -2655,6 +2655,7 @@ public struct AppSettings: Codable, Equatable {
public var uiCurrentThemeIds: [String: String]? = nil
public var uiThemes: [ThemeOverrides]? = nil
public var oneHandUI: Bool? = nil
public var chatBottomBar: Bool? = nil
public func prepareForExport() -> AppSettings {
var empty = AppSettings()
@@ -2689,6 +2690,7 @@ public struct AppSettings: Codable, Equatable {
if uiCurrentThemeIds != def.uiCurrentThemeIds { empty.uiCurrentThemeIds = uiCurrentThemeIds }
if uiThemes != def.uiThemes { empty.uiThemes = uiThemes }
if oneHandUI != def.oneHandUI { empty.oneHandUI = oneHandUI }
if chatBottomBar != def.chatBottomBar { empty.chatBottomBar = chatBottomBar }
return empty
}
@@ -2723,7 +2725,8 @@ public struct AppSettings: Codable, Equatable {
uiDarkColorScheme: DefaultTheme.SIMPLEX.themeName,
uiCurrentThemeIds: nil as [String: String]?,
uiThemes: nil as [ThemeOverrides]?,
oneHandUI: false
oneHandUI: false,
chatBottomBar: true
)
}
}
+3 -1
View File
@@ -57,6 +57,7 @@ public let GROUP_DEFAULT_CONFIRM_DB_UPGRADES = "confirmDBUpgrades"
public let GROUP_DEFAULT_CALL_KIT_ENABLED = "callKitEnabled"
public let GROUP_DEFAULT_PQ_EXPERIMENTAL_ENABLED = "pqExperimentalEnabled" // no longer used
public let GROUP_DEFAULT_ONE_HAND_UI = "oneHandUI"
public let GROUP_DEFAULT_CHAT_BOTTOM_BAR = "chatBottomBar"
public let APP_GROUP_NAME = "group.chat.simplex.app"
@@ -94,7 +95,8 @@ public func registerGroupDefaults() {
GROUP_DEFAULT_CONFIRM_DB_UPGRADES: false,
GROUP_DEFAULT_CALL_KIT_ENABLED: true,
GROUP_DEFAULT_PQ_EXPERIMENTAL_ENABLED: false,
GROUP_DEFAULT_ONE_HAND_UI: true
GROUP_DEFAULT_ONE_HAND_UI: true,
GROUP_DEFAULT_CHAT_BOTTOM_BAR: true
])
}
@@ -29,6 +29,7 @@ actual fun LazyColumnWithScrollBar(
flingBehavior: FlingBehavior,
userScrollEnabled: Boolean,
additionalBarOffset: State<Dp>?,
chatBottomBar: State<Boolean>,
fillMaxSize: Boolean,
content: LazyListScope.() -> Unit
) {
@@ -91,6 +92,7 @@ actual fun LazyColumnWithScrollBarNoAppBar(
flingBehavior: FlingBehavior,
userScrollEnabled: Boolean,
additionalBarOffset: State<Dp>?,
chatBottomBar: State<Boolean>,
content: LazyListScope.() -> Unit
) {
val state = state ?: rememberLazyListState()
@@ -106,7 +106,12 @@ fun AppearanceScope.AppearanceLayout(
}
// }
SettingsPreferenceItem(icon = null, stringResource(MR.strings.one_hand_ui), ChatModel.controller.appPrefs.oneHandUI)
SettingsPreferenceItem(icon = null, stringResource(MR.strings.one_hand_ui), ChatModel.controller.appPrefs.oneHandUI) { enabled ->
if (enabled) appPrefs.chatBottomBar.set(true)
}
if (remember { appPrefs.oneHandUI.state }.value) {
SettingsPreferenceItem(icon = null, stringResource(MR.strings.chat_bottom_bar), ChatModel.controller.appPrefs.chatBottomBar)
}
}
SectionDividerSpaced()
@@ -23,6 +23,7 @@ import chat.simplex.common.platform.*
import chat.simplex.common.ui.theme.*
import chat.simplex.common.views.call.*
import chat.simplex.common.views.chat.item.showQuotedItemDoesNotExistAlert
import chat.simplex.common.views.chatlist.openGroupChat
import chat.simplex.common.views.migration.MigrationFileLinkData
import chat.simplex.common.views.onboarding.OnboardingStage
import chat.simplex.common.views.usersettings.*
@@ -257,6 +258,7 @@ class AppPreferences {
val iosCallKitCallsInRecents = mkBoolPreference(SHARED_PREFS_IOS_CALL_KIT_CALLS_IN_RECENTS, false)
val oneHandUI = mkBoolPreference(SHARED_PREFS_ONE_HAND_UI, true)
val chatBottomBar = mkBoolPreference(SHARED_PREFS_CHAT_BOTTOM_BAR, true)
val hintPreferences: List<Pair<SharedPreference<Boolean>, Boolean>> = listOf(
laNoticeShown to false,
@@ -431,6 +433,7 @@ class AppPreferences {
private const val SHARED_PREFS_SHOULD_IMPORT_APP_SETTINGS = "ShouldImportAppSettings"
private const val SHARED_PREFS_CONFIRM_DB_UPGRADES = "ConfirmDBUpgrades"
private const val SHARED_PREFS_ONE_HAND_UI = "OneHandUI"
private const val SHARED_PREFS_CHAT_BOTTOM_BAR = "ChatBottomBar"
private const val SHARED_PREFS_SELF_DESTRUCT = "LocalAuthenticationSelfDestruct"
private const val SHARED_PREFS_SELF_DESTRUCT_DISPLAY_NAME = "LocalAuthenticationSelfDestructDisplayName"
private const val SHARED_PREFS_PQ_EXPERIMENTAL_ENABLED = "PQExperimentalEnabled" // no longer used
@@ -438,7 +441,6 @@ class AppPreferences {
private const val SHARED_PREFS_CURRENT_THEME_IDs = "CurrentThemeIds"
private const val SHARED_PREFS_SYSTEM_DARK_THEME = "SystemDarkTheme"
private const val SHARED_PREFS_THEMES_OLD = "Themes"
private const val SHARED_PREFS_THEME_OVERRIDES = "ThemeOverrides"
private const val SHARED_PREFS_PROFILE_IMAGE_CORNER_RADIUS = "ProfileImageCornerRadius"
private const val SHARED_PREFS_CHAT_ITEM_ROUNDNESS = "ChatItemRoundness"
private const val SHARED_PREFS_CHAT_ITEM_TAIL = "ChatItemTail"
@@ -2531,6 +2533,11 @@ object ChatController {
withChats {
updateGroup(rhId, r.groupInfo)
}
if (chatModel.chatId.value == r.fromContact.id) {
openGroupChat(rhId, r.groupInfo.groupId)
}
withChats {
removeChat(rhId, r.fromContact.id)
}
}
@@ -6882,7 +6889,8 @@ data class AppSettings(
var uiDarkColorScheme: String? = null,
var uiCurrentThemeIds: Map<String, String>? = null,
var uiThemes: List<ThemeOverrides>? = null,
var oneHandUI: Boolean? = null
var oneHandUI: Boolean? = null,
var chatBottomBar: Boolean? = null
) {
fun prepareForExport(): AppSettings {
val empty = AppSettings()
@@ -6917,6 +6925,7 @@ data class AppSettings(
if (uiCurrentThemeIds != def.uiCurrentThemeIds) { empty.uiCurrentThemeIds = uiCurrentThemeIds }
if (uiThemes != def.uiThemes) { empty.uiThemes = uiThemes }
if (oneHandUI != def.oneHandUI) { empty.oneHandUI = oneHandUI }
if (chatBottomBar != def.chatBottomBar) { empty.chatBottomBar = chatBottomBar }
return empty
}
@@ -6962,6 +6971,7 @@ data class AppSettings(
uiCurrentThemeIds?.let { def.currentThemeIds.set(it) }
uiThemes?.let { def.themeOverrides.set(it.skipDuplicates()) }
oneHandUI?.let { def.oneHandUI.set(it) }
chatBottomBar?.let { if (appPlatform.isAndroid) def.chatBottomBar.set(it) else def.chatBottomBar.set(true) }
}
companion object {
@@ -6996,7 +7006,8 @@ data class AppSettings(
uiDarkColorScheme = DefaultTheme.SIMPLEX.themeName,
uiCurrentThemeIds = null,
uiThemes = null,
oneHandUI = true
oneHandUI = true,
chatBottomBar = true,
)
val current: AppSettings
@@ -7032,7 +7043,8 @@ data class AppSettings(
uiDarkColorScheme = def.systemDarkTheme.get() ?: DefaultTheme.SIMPLEX.themeName,
uiCurrentThemeIds = def.currentThemeIds.get(),
uiThemes = def.themeOverrides.get(),
oneHandUI = def.oneHandUI.get()
oneHandUI = def.oneHandUI.get(),
chatBottomBar = def.chatBottomBar.get()
)
}
}
@@ -23,6 +23,7 @@ expect fun LazyColumnWithScrollBar(
flingBehavior: FlingBehavior = ScrollableDefaults.flingBehavior(),
userScrollEnabled: Boolean = true,
additionalBarOffset: State<Dp>? = null,
chatBottomBar: State<Boolean> = remember { mutableStateOf(true) },
// by default, this function will include .fillMaxSize() without you doing anything. If you don't need it, pass `false` here
// maxSize (at least maxHeight) is needed for blur on appBars to work correctly
fillMaxSize: Boolean = true,
@@ -41,6 +42,7 @@ expect fun LazyColumnWithScrollBarNoAppBar(
flingBehavior: FlingBehavior = ScrollableDefaults.flingBehavior(),
userScrollEnabled: Boolean = true,
additionalBarOffset: State<Dp>? = null,
chatBottomBar: State<Boolean> = remember { mutableStateOf(true) },
content: LazyListScope.() -> Unit
)
@@ -156,7 +156,7 @@ fun TerminalLog(floating: Boolean, composeViewHeight: State<Dp>) {
LazyColumnWithScrollBar (
reverseLayout = true,
contentPadding = PaddingValues(
top = topPaddingToContent(),
top = topPaddingToContent(false),
bottom = composeViewHeight.value
),
state = listState,
@@ -658,6 +658,8 @@ fun ChatLayout(
Box(Modifier.fillMaxSize().chatViewBackgroundModifier(MaterialTheme.colors, MaterialTheme.wallpaper, LocalAppBarHandler.current?.backgroundGraphicsLayerSize, LocalAppBarHandler.current?.backgroundGraphicsLayer)) {
val remoteHostId = remember { remoteHostId }.value
val chatInfo = remember { chatInfo }.value
val oneHandUI = remember { appPrefs.oneHandUI.state }
val chatBottomBar = remember { appPrefs.chatBottomBar.state }
AdaptingBottomPaddingLayout(Modifier, CHAT_COMPOSE_LAYOUT_ID, composeViewHeight) {
if (chatInfo != null) {
Box(Modifier.fillMaxSize()) {
@@ -670,25 +672,23 @@ fun ChatLayout(
)
}
}
val oneHandUI = remember { appPrefs.oneHandUI.state }
Box(
Modifier
.layoutId(CHAT_COMPOSE_LAYOUT_ID)
.align(Alignment.BottomCenter)
.imePadding()
.navigationBarsPadding()
.then(if (oneHandUI.value) Modifier.padding(bottom = AppBarHeight * fontSizeSqrtMultiplier) else Modifier)
.then(if (oneHandUI.value && chatBottomBar.value) Modifier.padding(bottom = AppBarHeight * fontSizeSqrtMultiplier) else Modifier)
) {
composeView()
}
}
val oneHandUI = remember { appPrefs.oneHandUI.state }
if (oneHandUI.value) {
if (oneHandUI.value && chatBottomBar.value) {
StatusBarBackground()
} else {
NavigationBarBackground(true, oneHandUI.value, noAlpha = true)
}
Box(if (oneHandUI.value) Modifier.align(Alignment.BottomStart).imePadding() else Modifier) {
Box(if (oneHandUI.value && chatBottomBar.value) Modifier.align(Alignment.BottomStart).imePadding() else Modifier) {
if (selectedChatItems.value == null) {
if (chatInfo != null) {
ChatInfoToolbar(chatInfo, back, info, startCall, endCall, addMembers, openGroupLink, changeNtfsState, onSearchValueChanged, showSearch)
@@ -856,12 +856,13 @@ fun BoxScope.ChatInfoToolbar(
}
}
val oneHandUI = remember { appPrefs.oneHandUI.state }
val chatBottomBar = remember { appPrefs.chatBottomBar.state }
DefaultAppBar(
navigationButton = { if (appPlatform.isAndroid || showSearch.value) { NavigationButtonBack(onBackClicked) } },
title = { ChatInfoToolbarTitle(chatInfo) },
onTitleClick = if (chatInfo is ChatInfo.Local) null else info,
showSearch = showSearch.value,
onTop = !oneHandUI.value,
onTop = !oneHandUI.value || !chatBottomBar.value,
onSearchValueChanged = onSearchValueChanged,
buttons = { barButtons.forEach { it() } }
)
@@ -873,11 +874,11 @@ fun BoxScope.ChatInfoToolbar(
showMenu,
modifier = Modifier.onSizeChanged { with(density) {
width.value = it.width.toDp().coerceAtLeast(250.dp)
if (oneHandUI.value && (appPlatform.isDesktop || (platform.androidApiLevel ?: 0) >= 30)) height.value = it.height.toDp()
if (oneHandUI.value && chatBottomBar.value && (appPlatform.isDesktop || (platform.androidApiLevel ?: 0) >= 30)) height.value = it.height.toDp()
} },
offset = DpOffset(-width.value, if (oneHandUI.value) -height.value else AppBarHeight)
offset = DpOffset(-width.value, if (oneHandUI.value && chatBottomBar.value) -height.value else AppBarHeight)
) {
if (oneHandUI.value) {
if (oneHandUI.value && chatBottomBar.value) {
menuItems.asReversed().forEach { it() }
} else {
menuItems.forEach { it() }
@@ -964,7 +965,7 @@ fun BoxScope.ChatItemsList(
val reversedChatItems = remember { derivedStateOf { chatModel.chatItems.asReversed() } }
val revealedItems = rememberSaveable(stateSaver = serializableSaver()) { mutableStateOf(setOf<Long>()) }
val mergedItems = remember { derivedStateOf { MergedItems.create(reversedChatItems.value, unreadCount, revealedItems.value, chatModel.chatState) } }
val topPaddingToContentPx = rememberUpdatedState(with(LocalDensity.current) { topPaddingToContent().roundToPx() })
val topPaddingToContentPx = rememberUpdatedState(with(LocalDensity.current) { topPaddingToContent(true).roundToPx() })
/** determines height based on window info and static height of two AppBars. It's needed because in the first graphic frame height of
* [composeViewHeight] is unknown, but we need to set scroll position for unread messages already so it will be correct before the first frame appears
* */
@@ -1001,7 +1002,7 @@ fun BoxScope.ChatItemsList(
val chatInfoUpdated = rememberUpdatedState(chatInfo)
val highlightedItems = remember { mutableStateOf(setOf<Long>()) }
val scope = rememberCoroutineScope()
val scrollToItem: (Long) -> Unit = remember { scrollToItem(loadingMoreItems, highlightedItems, chatInfoUpdated, maxHeight, scope, reversedChatItems, mergedItems, listState, loadMessages) }
val scrollToItem: (Long) -> Unit = remember { scrollToItem(searchValue, loadingMoreItems, highlightedItems, chatInfoUpdated, maxHeight, scope, reversedChatItems, mergedItems, listState, loadMessages) }
val scrollToQuotedItemFromItem: (Long) -> Unit = remember { findQuotedItemFromItem(remoteHostIdUpdated, chatInfoUpdated, scope, scrollToItem) }
LoadLastItems(loadingMoreItems, remoteHostId, chatInfo)
@@ -1264,10 +1265,11 @@ fun BoxScope.ChatItemsList(
state = listState.value,
reverseLayout = true,
contentPadding = PaddingValues(
top = topPaddingToContent(),
top = topPaddingToContent(true),
bottom = composeViewHeight.value
),
additionalBarOffset = composeViewHeight
additionalBarOffset = composeViewHeight,
chatBottomBar = remember { appPrefs.chatBottomBar.state }
) {
val mergedItemsValue = mergedItems.value
itemsIndexed(mergedItemsValue.items, key = { _, merged -> keyForItem(merged.newest().item) }) { index, merged ->
@@ -1310,8 +1312,8 @@ fun BoxScope.ChatItemsList(
}
}
}
FloatingButtons(loadingMoreItems, mergedItems, unreadCount, maxHeight, composeViewHeight, remoteHostId, chatInfo, searchValue, markChatRead, listState)
FloatingDate(Modifier.padding(top = 10.dp + topPaddingToContent()).align(Alignment.TopCenter), mergedItems, listState)
FloatingButtons(loadingMoreItems, mergedItems, unreadCount, maxHeight, composeViewHeight, searchValue, markChatRead, listState)
FloatingDate(Modifier.padding(top = 10.dp + topPaddingToContent(true)).align(Alignment.TopCenter), mergedItems, listState)
LaunchedEffect(Unit) {
snapshotFlow { listState.value.isScrollInProgress }
@@ -1400,14 +1402,12 @@ fun BoxScope.FloatingButtons(
unreadCount: State<Int>,
maxHeight: State<Int>,
composeViewHeight: State<Dp>,
remoteHostId: Long?,
chatInfo: ChatInfo,
searchValue: State<String>,
markChatRead: () -> Unit,
listState: State<LazyListState>
) {
val scope = rememberCoroutineScope()
val topPaddingToContentPx = rememberUpdatedState(with(LocalDensity.current) { topPaddingToContent().roundToPx() })
val topPaddingToContentPx = rememberUpdatedState(with(LocalDensity.current) { topPaddingToContent(true).roundToPx() })
val bottomUnreadCount = remember {
derivedStateOf {
if (unreadCount.value == 0) return@derivedStateOf 0
@@ -1447,7 +1447,7 @@ fun BoxScope.FloatingButtons(
val showDropDown = remember { mutableStateOf(false) }
TopEndFloatingButton(
Modifier.padding(end = DEFAULT_PADDING, top = 24.dp + topPaddingToContent()).align(Alignment.TopEnd),
Modifier.padding(end = DEFAULT_PADDING, top = 24.dp + topPaddingToContent(true)).align(Alignment.TopEnd),
topUnreadCount,
onClick = {
val index = mergedItems.value.items.indexOfLast { it.hasUnread() }
@@ -1465,7 +1465,7 @@ fun BoxScope.FloatingButtons(
DefaultDropdownMenu(
showDropDown,
modifier = Modifier.onSizeChanged { with(density) { width.value = it.width.toDp().coerceAtLeast(250.dp) } },
offset = DpOffset(-DEFAULT_PADDING - width.value, 24.dp + fabSize + topPaddingToContent())
offset = DpOffset(-DEFAULT_PADDING - width.value, 24.dp + fabSize + topPaddingToContent(true))
) {
ItemAction(
generalGetString(MR.strings.mark_read),
@@ -1615,9 +1615,10 @@ private fun TopEndFloatingButton(
}
@Composable
fun topPaddingToContent(): Dp {
fun topPaddingToContent(chatView: Boolean): Dp {
val oneHandUI = remember { appPrefs.oneHandUI.state }
return if (oneHandUI.value) {
val chatBottomBar = remember { appPrefs.chatBottomBar.state }
return if (oneHandUI.value && (!chatView || chatBottomBar.value)) {
WindowInsets.statusBars.asPaddingValues().calculateTopPadding()
} else {
AppBarHeight * fontSizeSqrtMultiplier + WindowInsets.statusBars.asPaddingValues().calculateTopPadding()
@@ -1634,7 +1635,7 @@ private fun FloatingDate(
val nearBottomIndex = remember(chatModel.chatId) { mutableStateOf(if (isNearBottom.value) -1 else 0) }
val showDate = remember(chatModel.chatId) { mutableStateOf(false) }
val density = LocalDensity.current.density
val topPaddingToContentPx = rememberUpdatedState(with(LocalDensity.current) { topPaddingToContent().roundToPx() })
val topPaddingToContentPx = rememberUpdatedState(with(LocalDensity.current) { topPaddingToContent(true).roundToPx() })
val fontSizeSqrtMultiplier = fontSizeSqrtMultiplier
val lastVisibleItemDate = remember {
derivedStateOf {
@@ -1834,6 +1835,7 @@ private fun lastFullyVisibleIemInListState(topPaddingToContentPx: State<Int>, de
}
private fun scrollToItem(
searchValue: State<String>,
loadingMoreItems: MutableState<Boolean>,
highlightedItems: MutableState<Set<Long>>,
chatInfo: State<ChatInfo>,
@@ -1847,6 +1849,8 @@ private fun scrollToItem(
withApi {
try {
var index = mergedItems.value.indexInParentItems[itemId] ?: -1
// Don't try to load messages while in search
if (index == -1 && searchValue.value.isNotBlank()) return@withApi
// setting it to 'loading' even if the item is loaded because in rare cases when the resulting item is near the top, scrolling to
// it will trigger loading more items and will scroll to incorrect position (because of trimming)
loadingMoreItems.value = true
@@ -306,7 +306,7 @@ fun ModalData.GroupChatInfoLayout(
contentPadding = if (oneHandUI.value) {
PaddingValues(top = WindowInsets.statusBars.asPaddingValues().calculateTopPadding() + DEFAULT_PADDING + 5.dp, bottom = WindowInsets.navigationBars.asPaddingValues().calculateBottomPadding())
} else {
PaddingValues(top = topPaddingToContent())
PaddingValues(top = topPaddingToContent(false))
},
state = listState
) {
@@ -761,7 +761,7 @@ private fun BoxScope.ChatList(searchText: MutableState<TextFieldValue>, listStat
val searchShowingSimplexLink = remember { mutableStateOf(false) }
val searchChatFilteredBySimplexLink = remember { mutableStateOf<String?>(null) }
val chats = filteredChats(showUnreadAndFavorites, searchShowingSimplexLink, searchChatFilteredBySimplexLink, searchText.value.text, allChats.value.toList())
val topPaddingToContent = topPaddingToContent()
val topPaddingToContent = topPaddingToContent(false)
val blankSpaceSize = if (oneHandUI.value) WindowInsets.navigationBars.asPaddingValues().calculateBottomPadding() + AppBarHeight * fontSizeSqrtMultiplier else topPaddingToContent
LazyColumnWithScrollBar(
if (!oneHandUI.value) Modifier.imePadding() else Modifier,
@@ -194,7 +194,7 @@ private fun ShareList(
filteredChats(false, mutableStateOf(false), mutableStateOf(null), search, sorted)
}
}
val topPaddingToContent = topPaddingToContent()
val topPaddingToContent = topPaddingToContent(false)
LazyColumnWithScrollBar(
modifier = Modifier.then(if (oneHandUI.value) Modifier.consumeWindowInsets(WindowInsets.navigationBars.only(WindowInsetsSides.Vertical)) else Modifier).imePadding(),
contentPadding = PaddingValues(
@@ -334,7 +334,7 @@ private fun ModalData.NewChatSheetLayout(
@Composable
fun NonOneHandLazyColumn() {
val blankSpaceSize = topPaddingToContent()
val blankSpaceSize = topPaddingToContent(false)
LazyColumnWithScrollBar(
Modifier.imePadding(),
state = listState,
@@ -646,7 +646,7 @@ private fun ModalData.DeletedContactsView(rh: RemoteHostInfo?, closeDeletedChats
)
Box {
val topPaddingToContent = topPaddingToContent()
val topPaddingToContent = topPaddingToContent(false)
LazyColumnWithScrollBar(
if (!oneHandUI.value) Modifier.imePadding() else Modifier,
contentPadding = PaddingValues(
@@ -400,7 +400,7 @@ fun ActiveProfilePicker(
.fillMaxSize()
.alpha(if (progressByTimeout) 0.6f else 1f)
) {
LazyColumnWithScrollBar(Modifier.padding(top = topPaddingToContent()), userScrollEnabled = !switchingProfile.value) {
LazyColumnWithScrollBar(Modifier.padding(top = topPaddingToContent(false)), userScrollEnabled = !switchingProfile.value) {
item {
val oneHandUI = remember { appPrefs.oneHandUI.state }
if (oneHandUI.value) {
@@ -1396,7 +1396,8 @@
<string name="database_downgrade">Database downgrade</string>
<string name="incompatible_database_version">Incompatible database version</string>
<string name="confirm_database_upgrades">Confirm database upgrades</string>
<string name="one_hand_ui">Reachable chat toolbar</string>
<string name="one_hand_ui">Reachable app toolbars</string>
<string name="chat_bottom_bar">Reachable chat toolbar</string>
<string name="one_hand_ui_card_title">Toggle chat list:</string>
<string name="one_hand_ui_change_instruction">You can change it in Appearance settings.</string>
<string name="terminal_always_visible">Show console in new window</string>
@@ -36,6 +36,7 @@ actual fun LazyColumnWithScrollBar(
flingBehavior: FlingBehavior,
userScrollEnabled: Boolean,
additionalBarOffset: State<Dp>?,
chatBottomBar: State<Boolean>,
fillMaxSize: Boolean,
content: LazyListScope.() -> Unit
) {
@@ -92,7 +93,7 @@ actual fun LazyColumnWithScrollBar(
val modifier = if (fillMaxSize) Modifier.fillMaxSize().then(modifier) else modifier
Box(Modifier.copyViewToAppBar(remember { appPrefs.appearanceBarsBlurRadius.state }.value, LocalAppBarHandler.current?.graphicsLayer).nestedScroll(connection)) {
LazyColumn(modifier.then(scrollModifier), state, contentPadding, reverseLayout, verticalArrangement, horizontalAlignment, flingBehavior, userScrollEnabled, content)
ScrollBar(reverseLayout, state, scrollBarAlpha, scrollJob, scrollBarDraggingState, additionalBarOffset)
ScrollBar(reverseLayout, state, scrollBarAlpha, scrollJob, scrollBarDraggingState, additionalBarOffset, chatBottomBar)
}
}
@@ -107,6 +108,7 @@ actual fun LazyColumnWithScrollBarNoAppBar(
flingBehavior: FlingBehavior,
userScrollEnabled: Boolean,
additionalBarOffset: State<Dp>?,
chatBottomBar: State<Boolean>,
content: LazyListScope.() -> Unit
) {
val scope = rememberCoroutineScope()
@@ -133,7 +135,7 @@ actual fun LazyColumnWithScrollBarNoAppBar(
val scrollBarDraggingState = remember { mutableStateOf(false) }
Box {
LazyColumn(modifier.then(scrollModifier), state, contentPadding, reverseLayout, verticalArrangement, horizontalAlignment, flingBehavior, userScrollEnabled, content)
ScrollBar(reverseLayout, state, scrollBarAlpha, scrollJob, scrollBarDraggingState, additionalBarOffset)
ScrollBar(reverseLayout, state, scrollBarAlpha, scrollJob, scrollBarDraggingState, additionalBarOffset, chatBottomBar)
}
}
@@ -144,15 +146,16 @@ private fun ScrollBar(
scrollBarAlpha: Animatable<Float, AnimationVector1D>,
scrollJob: MutableState<Job>,
scrollBarDraggingState: MutableState<Boolean>,
additionalBarHeight: State<Dp>?
additionalBarHeight: State<Dp>?,
chatBottomBar: State<Boolean>,
) {
val oneHandUI = remember { appPrefs.oneHandUI.state }
val padding = if (additionalBarHeight != null) {
PaddingValues(top = if (oneHandUI.value) 0.dp else AppBarHeight * fontSizeSqrtMultiplier, bottom = additionalBarHeight.value)
PaddingValues(top = if (oneHandUI.value && chatBottomBar.value) 0.dp else AppBarHeight * fontSizeSqrtMultiplier, bottom = additionalBarHeight.value)
} else if (reverseLayout) {
PaddingValues(bottom = AppBarHeight * fontSizeSqrtMultiplier)
} else {
PaddingValues(top = if (oneHandUI.value) 0.dp else AppBarHeight * fontSizeSqrtMultiplier)
PaddingValues(top = if (oneHandUI.value && chatBottomBar.value) 0.dp else AppBarHeight * fontSizeSqrtMultiplier)
}
Box(Modifier.fillMaxSize().padding(padding), contentAlignment = Alignment.CenterEnd) {
DesktopScrollBar(rememberScrollbarAdapter(state), Modifier.fillMaxHeight(), scrollBarAlpha, scrollJob, reverseLayout, scrollBarDraggingState)
@@ -58,6 +58,9 @@ fun AppearanceScope.AppearanceLayout(
}
}
SettingsPreferenceItem(icon = null, stringResource(MR.strings.one_hand_ui), ChatModel.controller.appPrefs.oneHandUI)
if (remember { appPrefs.oneHandUI.state }.value && !remember { appPrefs.chatBottomBar.state }.value) {
SettingsPreferenceItem(icon = null, stringResource(MR.strings.chat_bottom_bar), ChatModel.controller.appPrefs.chatBottomBar)
}
}
SectionDividerSpaced()
ThemesSection(systemDarkTheme)
+4 -4
View File
@@ -24,11 +24,11 @@ android.nonTransitiveRClass=true
kotlin.mpp.androidSourceSetLayoutVersion=2
kotlin.jvm.target=11
android.version_name=6.2-beta.3
android.version_code=254
android.version_name=6.2-beta.4
android.version_code=255
desktop.version_name=6.2-beta.3
desktop.version_code=78
desktop.version_name=6.2-beta.4
desktop.version_code=79
kotlin.version=1.9.23
gradle.plugin.version=8.2.0
+1 -1
View File
@@ -12,7 +12,7 @@ constraints: zip +disable-bzip2 +disable-zstd
source-repository-package
type: git
location: https://github.com/simplex-chat/simplexmq.git
tag: 4b43cb805437dc0822eb81c57b2c85e77ad333ca
tag: 966b9990e0bf5fdb701f79b6efd722baddd1ee1d
source-repository-package
type: git
+67
View File
@@ -53,8 +53,75 @@ With all advantages in privacy and security of e2e encryption in SimpleX Chat, t
### Running SimpleX Chat in the cloud
To install SimpleX Chat CLI in the cloud, follow this:
1. Create dedicated user for CLI:
```sh
useradd -m -s /bin/bash simplex-cli
```
2. Create new tmux session
```sh
tmux new -s simplex-cli
```
3. Login to dedicated user:
```sh
su - simplex-cli
```
4. Install CLI:
```sh
curl -o- https://raw.githubusercontent.com/simplex-chat/simplex-chat/stable/install.sh | bash
```
5. Run the CLI:
```sh
simplex-chat
```
To deattach from running CLI simply press `Ctrl+B` and then `D`.
To reattach back to CLI, run: `tmux attach -t simplex-cli`.
### Using remote profiles via Desktop app
To use CLI from Desktop app, follow this:
1. Enable Developer tools in desktop app.
2. In the Desktop app, click - `Linked mobile` -> `+ Link a mobile`, choose local address `127.0.0.1`, enter some fixed port (can be any free port, e.g. 12345), and copy the link.
3. In the same machine where Desktop app is running, execute:
Change `PORT` to port, chosen in the previous step in Desktop app and `SERVER_IP` to your server.
```sh
ssh -R PORT:127.0.0.1:PORT -N root@SERVER_IP
```
4. In the CLI on the server:
Change `LINK` to link, copied in the first step and enter the following:
```sh
/crc LINK
```
CLI will print verification code:
```sh
Compare session code with controller and use:
/verify remote ctrl ...
```
Simply copy the whole line starting with `/verify ...` from the terminal and paste it. Now you can control the CLI from your Desktop app.
## Organizations using SimpleX Chat for customer service, support and sales
Please let us know if you use SimpleX Chat to communicate with your customers and want to be included in this list.
+1 -1
View File
@@ -1,5 +1,5 @@
name: simplex-chat
version: 6.2.0.4
version: 6.2.0.5
#synopsis:
#description:
homepage: https://github.com/simplex-chat/simplex-chat#readme
+1 -1
View File
@@ -1,5 +1,5 @@
{
"https://github.com/simplex-chat/simplexmq.git"."4b43cb805437dc0822eb81c57b2c85e77ad333ca" = "143cskyh3z6yxn6fnaw8biskbspa2cndc65rzziajlw13yd0wggg";
"https://github.com/simplex-chat/simplexmq.git"."966b9990e0bf5fdb701f79b6efd722baddd1ee1d" = "0gmycrmyrgy5wbhr3f7qy6hbpppsamfypq7y650dinpbqyrfs9fb";
"https://github.com/simplex-chat/hs-socks.git"."a30cc7a79a08d8108316094f8f2f82a0c5e1ac51" = "0yasvnr7g91k76mjkamvzab2kvlb1g5pspjyjn2fr6v83swjhj38";
"https://github.com/simplex-chat/direct-sqlcipher.git"."f814ee68b16a9447fbb467ccc8f29bdd3546bfd9" = "1ql13f4kfwkbaq7nygkxgw84213i0zm7c1a8hwvramayxl38dq5d";
"https://github.com/simplex-chat/sqlcipher-simple.git"."a46bd361a19376c5211f1058908fc0ae6bf42446" = "1z0r78d8f0812kxbgsm735qf6xx8lvaz27k1a0b4a2m0sshpd5gl";
+1 -1
View File
@@ -5,7 +5,7 @@ cabal-version: 1.12
-- see: https://github.com/sol/hpack
name: simplex-chat
version: 6.2.0.4
version: 6.2.0.5
category: Web, System, Services, Cryptography
homepage: https://github.com/simplex-chat/simplex-chat#readme
author: simplex.chat
+19 -10
View File
@@ -101,7 +101,7 @@ import qualified Simplex.FileTransfer.Transport as XFTP
import Simplex.FileTransfer.Types (FileErrorType (..), RcvFileId, SndFileId)
import Simplex.Messaging.Agent as Agent
import Simplex.Messaging.Agent.Client (SubInfo (..), agentClientStore, getAgentQueuesInfo, getAgentWorkersDetails, getAgentWorkersSummary, getFastNetworkConfig, ipAddressProtected, withLockMap)
import Simplex.Messaging.Agent.Env.SQLite (AgentConfig (..), InitialAgentServers (..), ServerCfg (..), ServerRoles (..), allRoles, createAgentStore, defaultAgentConfig)
import Simplex.Messaging.Agent.Env.SQLite (AgentConfig (..), InitialAgentServers (..), ServerCfg (..), ServerRoles (..), allRoles, createAgentStore, defaultAgentConfig, presetServerCfg)
import Simplex.Messaging.Agent.Lock (withLock)
import Simplex.Messaging.Agent.Protocol
import qualified Simplex.Messaging.Agent.Protocol as AP (AgentErrorType (..))
@@ -427,8 +427,12 @@ newChatController
ops <- getUpdateServerOperators db presetOps (null users)
let opDomains = operatorDomains $ mapMaybe snd ops
(smp', xftp') <- unzip <$> mapM (getServers ops opDomains) users
pure InitialAgentServers {smp = M.fromList smp', xftp = M.fromList xftp', ntf, netCfg}
pure InitialAgentServers {smp = M.fromList (optServers smp' smpServers), xftp = M.fromList (optServers xftp' xftpServers), ntf, netCfg}
where
optServers :: [(UserId, NonEmpty (ServerCfg p))] -> [ProtoServerWithAuth p] -> [(UserId, NonEmpty (ServerCfg p))]
optServers srvs overrides_ = case L.nonEmpty overrides_ of
Just overrides -> map (second $ const $ L.map (presetServerCfg True allRoles Nothing) overrides) srvs
Nothing -> srvs
getServers :: [(Maybe PresetOperator, Maybe ServerOperator)] -> [(Text, ServerOperator)] -> User -> IO ((UserId, NonEmpty (ServerCfg 'PSMP)), (UserId, NonEmpty (ServerCfg 'PXFTP)))
getServers ops opDomains user' = do
smpSrvs <- getProtocolServers db SPSMP user'
@@ -5110,7 +5114,7 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage =
void . sendGroupMessage user gInfo members . XGrpMemNew $ memberInfo m
sendIntroductions members
when (groupFeatureAllowed SGFHistory gInfo) sendHistory
when (connChatVersion < batchSend2Version) $ sendGroupAutoReply members
when (connChatVersion < batchSend2Version) sendGroupAutoReply
where
sendXGrpLinkMem = do
let profileMode = ExistingIncognito <$> incognitoMembershipProfile gInfo
@@ -5145,7 +5149,7 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage =
withStore' $ \db -> updateIntroStatus db introId GMIntroSent
sendHistory =
when (m `supportsVersion` batchSendVersion) $ do
(errs, items) <- partitionEithers <$> withStore' (\db -> getGroupHistoryItems db user gInfo 100)
(errs, items) <- partitionEithers <$> withStore' (\db -> getGroupHistoryItems db user gInfo m 100)
(errs', events) <- partitionEithers <$> mapM (tryChatError . itemForwardEvents) items
let errors = map ChatErrorStore errs <> errs'
unless (null errors) $ toView $ CRChatErrors (Just user) errors
@@ -5376,9 +5380,7 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage =
JOINED sqSecured ->
-- [async agent commands] continuation on receiving JOINED
when (corrId /= "") $ withCompletedCommand conn agentMsg $ \_cmdData ->
when sqSecured $ do
members <- withStore' $ \db -> getGroupMembers db vr user gInfo
when (connChatVersion >= batchSend2Version) $ sendGroupAutoReply members
when (sqSecured && connChatVersion >= batchSend2Version) sendGroupAutoReply
QCONT -> do
continued <- continueSending connEntity conn
when continued $ sendPendingGroupMessages user m conn
@@ -5406,7 +5408,7 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage =
updateGroupItemsErrorStatus db msgId groupMemberId newStatus = do
itemIds <- getChatItemIdsByAgentMsgId db connId msgId
forM_ itemIds $ \itemId -> updateGroupMemSndStatus' db itemId groupMemberId newStatus
sendGroupAutoReply members = autoReplyMC >>= mapM_ send
sendGroupAutoReply = autoReplyMC >>= mapM_ send
where
autoReplyMC = do
let GroupInfo {businessChat} = gInfo
@@ -5420,8 +5422,9 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage =
_ -> Nothing
_ -> pure Nothing
send mc = do
msg <- sendGroupMessage' user gInfo members (XMsgNew $ MCSimple (extMsgContent mc Nothing))
msg <- sendGroupMessage' user gInfo [m] (XMsgNew $ MCSimple (extMsgContent mc Nothing))
ci <- saveSndChatItem user (CDGroupSnd gInfo) msg (CISndMsgContent mc)
withStore' $ \db -> createGroupSndStatus db (chatItemId' ci) (groupMemberId' m) GSSNew
toView $ CRNewChatItems user [AChatItem SCTGroup SMDSnd (GroupChat gInfo) ci]
agentMsgDecryptError :: AgentCryptoError -> (MsgDecryptError, Word32)
@@ -6459,7 +6462,8 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage =
processMemberProfileUpdate :: GroupInfo -> GroupMember -> Profile -> Bool -> Maybe UTCTime -> CM GroupMember
processMemberProfileUpdate gInfo m@GroupMember {memberProfile = p, memberContactId} p' createItems itemTs_
| redactedMemberProfile (fromLocalProfile p) /= redactedMemberProfile p' =
| redactedMemberProfile (fromLocalProfile p) /= redactedMemberProfile p' = do
updateBusinessChatProfile gInfo m
case memberContactId of
Nothing -> do
m' <- withStore $ \db -> updateMemberProfile db user m p'
@@ -6485,6 +6489,11 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage =
| otherwise =
pure m
where
updateBusinessChatProfile g@GroupInfo {businessChat} GroupMember {memberId} = case businessChat of
Just BusinessChatInfo {memberId = mId} | mId == memberId -> do
g' <- withStore $ \db -> updateGroupProfileFromMember db user g p'
toView $ CRGroupUpdated user g g' (Just m)
_ -> pure ()
createProfileUpdatedItem m' =
when createItems $ do
let ciContent = CIRcvGroupEvent $ RGEMemberProfileUpdated (fromLocalProfile p) p'
+11 -5
View File
@@ -56,7 +56,8 @@ data AppSettings = AppSettings
uiDarkColorScheme :: Maybe DarkColorScheme,
uiCurrentThemeIds :: Maybe (Map ThemeColorScheme Text),
uiThemes :: Maybe [UITheme],
oneHandUI :: Maybe Bool
oneHandUI :: Maybe Bool,
chatBottomBar :: Maybe Bool
}
deriving (Show)
@@ -105,7 +106,8 @@ defaultAppSettings =
uiDarkColorScheme = Just DCSSimplex,
uiCurrentThemeIds = Nothing,
uiThemes = Nothing,
oneHandUI = Just True
oneHandUI = Just True,
chatBottomBar = Just True
}
defaultParseAppSettings :: AppSettings
@@ -141,7 +143,8 @@ defaultParseAppSettings =
uiDarkColorScheme = Nothing,
uiCurrentThemeIds = Nothing,
uiThemes = Nothing,
oneHandUI = Nothing
oneHandUI = Nothing,
chatBottomBar = Nothing
}
combineAppSettings :: AppSettings -> AppSettings -> AppSettings
@@ -177,7 +180,8 @@ combineAppSettings platformDefaults storedSettings =
uiDarkColorScheme = p uiDarkColorScheme,
uiCurrentThemeIds = p uiCurrentThemeIds,
uiThemes = p uiThemes,
oneHandUI = p oneHandUI
oneHandUI = p oneHandUI,
chatBottomBar = p chatBottomBar
}
where
p :: (AppSettings -> Maybe a) -> Maybe a
@@ -230,6 +234,7 @@ instance FromJSON AppSettings where
uiCurrentThemeIds <- p "uiCurrentThemeIds"
uiThemes <- p "uiThemes"
oneHandUI <- p "oneHandUI"
chatBottomBar <- p "chatBottomBar"
pure
AppSettings
{ appPlatform,
@@ -262,7 +267,8 @@ instance FromJSON AppSettings where
uiDarkColorScheme,
uiCurrentThemeIds,
uiThemes,
oneHandUI
oneHandUI,
chatBottomBar
}
where
p key = v .:? key <|> pure Nothing
+29 -8
View File
@@ -39,6 +39,7 @@ module Simplex.Chat.Store.Groups
getGroupInfoByUserContactLinkConnReq,
getGroupInfoByGroupLinkHash,
updateGroupProfile,
updateGroupProfileFromMember,
getGroupIdByName,
getGroupMemberIdByName,
getActiveMembersByName,
@@ -917,9 +918,10 @@ createBusinessRequestGroup
UserContactRequest {cReqChatVRange, xContactId, profile = Profile {displayName, fullName, image, contactLink, preferences}}
groupPreferences = do
currentTs <- liftIO getCurrentTime
groupInfo <- insertGroup_ currentTs
(groupMemberId, memberId) <- insertClientMember_ currentTs groupInfo
liftIO $ setBusinessMemberId groupInfo memberId
(groupId, membership) <- insertGroup_ currentTs
(groupMemberId, memberId) <- insertClientMember_ currentTs groupId membership
liftIO $ DB.execute db "UPDATE groups SET business_member_id = ? WHERE group_id = ?" (memberId, groupId)
groupInfo <- getGroupInfo db vr user groupId
clientMember <- getGroupMemberById db vr user groupMemberId
pure (groupInfo, clientMember)
where
@@ -942,10 +944,10 @@ createBusinessRequestGroup
(profileId, localDisplayName, userId, True, currentTs, currentTs, currentTs, currentTs, BCCustomer, xContactId)
insertedRowId db
memberId <- liftIO $ encodedRandomBytes gVar 12
void $ createContactMemberInv_ db user groupId Nothing user (MemberIdRole (MemberId memberId) GROwner) GCUserMember GSMemCreator IBUser Nothing currentTs vr
getGroupInfo db vr user groupId
membership <- createContactMemberInv_ db user groupId Nothing user (MemberIdRole (MemberId memberId) GROwner) GCUserMember GSMemCreator IBUser Nothing currentTs vr
pure (groupId, membership)
VersionRange minV maxV = cReqChatVRange
insertClientMember_ currentTs GroupInfo {groupId, membership} = ExceptT $ do
insertClientMember_ currentTs groupId membership = ExceptT $ do
withLocalDisplayName db userId displayName $ \localDisplayName -> runExceptT $ do
liftIO $
DB.execute
@@ -969,8 +971,6 @@ createBusinessRequestGroup
)
groupMemberId <- liftIO $ insertedRowId db
pure (groupMemberId, MemberId memId)
setBusinessMemberId GroupInfo {groupId} businessMemberId = do
DB.execute db "UPDATE groups SET business_member_id = ? WHERE group_id = ?" (businessMemberId, groupId)
getContactViaMember :: DB.Connection -> VersionRangeChat -> User -> GroupMember -> ExceptT StoreError IO Contact
getContactViaMember db vr user@User {userId} GroupMember {groupMemberId} = do
@@ -1456,6 +1456,27 @@ updateGroupProfile db user@User {userId} g@GroupInfo {groupId, localDisplayName,
(ldn, currentTs, userId, groupId)
safeDeleteLDN db user localDisplayName
updateGroupProfileFromMember :: DB.Connection -> User -> GroupInfo -> Profile -> ExceptT StoreError IO GroupInfo
updateGroupProfileFromMember db user g@GroupInfo {groupId} Profile {displayName = n, fullName = fn, image = img} = do
p <- getGroupProfile -- to avoid any race conditions with UI
let g' = g {groupProfile = p} :: GroupInfo
p' = p {displayName = n, fullName = fn, image = img} :: GroupProfile
updateGroupProfile db user g' p'
where
getGroupProfile =
ExceptT $ firstRow toGroupProfile (SEGroupNotFound groupId) $
DB.query
db
[sql|
SELECT gp.display_name, gp.full_name, gp.description, gp.image, gp.preferences
FROM group_profiles gp
JOIN groups g ON gp.group_profile_id = g.group_profile_id
WHERE g.group_id = ?
|]
(Only groupId)
toGroupProfile (displayName, fullName, description, image, groupPreferences) =
GroupProfile {displayName, fullName, description, image, groupPreferences}
getGroupInfo :: DB.Connection -> VersionRangeChat -> User -> Int64 -> ExceptT StoreError IO GroupInfo
getGroupInfo db vr User {userId, userContactId} groupId =
ExceptT . firstRow (toGroupInfo vr userContactId) (SEGroupNotFound groupId) $
+11 -9
View File
@@ -3034,8 +3034,8 @@ getGroupSndStatusCounts db itemId =
|]
(Only itemId)
getGroupHistoryItems :: DB.Connection -> User -> GroupInfo -> Int -> IO [Either StoreError (CChatItem 'CTGroup)]
getGroupHistoryItems db user@User {userId} GroupInfo {groupId} count = do
getGroupHistoryItems :: DB.Connection -> User -> GroupInfo -> GroupMember -> Int -> IO [Either StoreError (CChatItem 'CTGroup)]
getGroupHistoryItems db user@User {userId} GroupInfo {groupId} m count = do
ciIds <- getLastItemIds_
-- use getGroupCIWithReactions to read reactions data
reverse <$> mapM (runExceptT . getGroupChatItem db user groupId) ciIds
@@ -3046,12 +3046,14 @@ getGroupHistoryItems db user@User {userId} GroupInfo {groupId} count = do
<$> DB.query
db
[sql|
SELECT chat_item_id
FROM chat_items
WHERE user_id = ? AND group_id = ?
AND item_content_tag IN (?,?)
AND item_deleted = 0
ORDER BY item_ts DESC, chat_item_id DESC
SELECT i.chat_item_id
FROM chat_items i
LEFT JOIN group_snd_item_statuses s ON s.chat_item_id = i.chat_item_id AND s.group_member_id = ?
WHERE i.user_id = ? AND i.group_id = ?
AND i.item_content_tag IN (?,?)
AND i.item_deleted = 0
AND s.group_snd_item_status_id IS NULL
ORDER BY i.item_ts DESC, i.chat_item_id DESC
LIMIT ?
|]
(userId, groupId, rcvMsgContentTag, sndMsgContentTag, count)
(groupMemberId' m, userId, groupId, rcvMsgContentTag, sndMsgContentTag, count)
+77
View File
@@ -49,6 +49,7 @@ chatProfileTests = do
it "auto-reply message in incognito" testAutoReplyMessageInIncognito
describe "business address" $ do
it "create and connect via business address" testBusinessAddress
it "update profiles with business address" testBusinessUpdateProfiles
describe "contact address connection plan" $ do
it "contact address ok to connect; known contact" testPlanAddressOkKnown
it "own contact address" testPlanAddressOwn
@@ -732,6 +733,82 @@ testBusinessAddress = testChat3 businessProfile aliceProfile {fullName = "Alice
(alice <# "#bob bob_1> hey there")
(biz <# "#bob bob_1> hey there")
testBusinessUpdateProfiles :: HasCallStack => FilePath -> IO ()
testBusinessUpdateProfiles = testChat3 businessProfile aliceProfile bobProfile $
\biz alice bob -> do
biz ##> "/ad"
cLink <- getContactLink biz True
biz ##> "/auto_accept on business text Welcome"
biz <## "auto_accept on, business"
biz <## "auto reply:"
biz <## "Welcome"
alice ##> ("/c " <> cLink)
alice <## "connection request sent!"
biz <## "#alice (Alice): accepting business address request..."
alice <## "#biz: joining the group..."
biz <# "#alice Welcome" -- auto reply
biz <## "#alice: alice_1 joined the group"
alice <# "#biz biz_1> Welcome"
alice <## "#biz: you joined the group"
biz #> "#alice hi"
alice <# "#biz biz_1> hi"
alice #> "#biz hello"
biz <# "#alice alice_1> hello"
alice ##> "/p alisa"
alice <## "user profile is changed to alisa (your 0 contacts are notified)"
alice #> "#biz hello again" -- profile update is sent with message
biz <## "alice_1 updated group #alice:"
biz <## "changed to #alisa"
biz <# "#alisa alisa_1> hello again"
-- customer can invite members too, if business allows
biz ##> "/mr alisa alisa_1 admin"
biz <## "#alisa: you changed the role of alisa_1 from member to admin"
alice <## "#biz: biz_1 changed your role from member to admin"
connectUsers alice bob
alice ##> "/a #biz bob"
alice <## "invitation to join the group #biz sent to bob"
bob <## "#biz (Biz Inc): alisa invites you to join the group as member"
bob <## "use /j biz to accept"
bob ##> "/j biz"
concurrentlyN_
[ do
bob <## "#biz: you joined the group"
bob
<###
[ WithTime "#biz biz_1> Welcome [>>]",
WithTime "#biz biz_1> hi [>>]",
WithTime "#biz alisa> hello [>>]",
WithTime "#biz alisa> hello again [>>]"
]
bob <## "#biz: member biz_1 (Biz Inc) is connected",
alice <## "#biz: bob joined the group",
do
biz <## "#alisa: alisa_1 added bob (Bob) to the group (connecting...)"
biz <## "#alisa: new member bob is connected"
]
-- changing other member profiles does not change group profile
bob ##> "/p robert"
bob <## "user profile is changed to robert (your 1 contacts are notified)"
alice <## "contact bob changed to robert" -- only alice receives profile update
alice <## "use @robert <message> to send messages"
bob #> "#biz hi there" -- profile update is sent to group with message
alice <# "#biz robert> hi there"
biz <# "#alisa robert> hi there"
-- both customers receive business profile change
biz ##> "/p business"
biz <## "user profile is changed to business (your 0 contacts are notified)"
biz #> "#alisa hey"
concurrentlyN_
[ do
alice <## "biz_1 updated group #biz:"
alice <## "changed to #business"
alice <# "#business business_1> hey",
do
bob <## "biz_1 updated group #biz:"
bob <## "changed to #business"
bob <# "#business business_1> hey"
]
testPlanAddressOkKnown :: HasCallStack => FilePath -> IO ()
testPlanAddressOkKnown =
testChat2 aliceProfile bobProfile $