Compare commits

..

6 Commits

Author SHA1 Message Date
Avently 0cc1fc2dbf rename 2024-12-12 19:01:18 +07:00
Avently 7ba960021f changes 2024-12-12 18:57:57 +07:00
Avently 46d7b8ddd9 unused 2024-12-12 18:05:49 +07:00
Avently eb4290ef72 enhancements 2024-12-12 17:49:01 +07:00
Avently 507b0c5f2a Merge branch 'master' into av/multiplatform-saving-prefs 2024-12-12 15:12:33 +07:00
Avently 7122721af7 desktop: saving settings in a safer way to handle process death 2024-08-14 21:50:09 +09:00
26 changed files with 172 additions and 225 deletions
+21 -39
View File
@@ -440,7 +440,6 @@ struct ChatView: View {
maxWidth: maxWidth, maxWidth: maxWidth,
composeState: $composeState, composeState: $composeState,
selectedMember: $selectedMember, selectedMember: $selectedMember,
showChatInfoSheet: $showChatInfoSheet,
revealedChatItem: $revealedChatItem, revealedChatItem: $revealedChatItem,
selectedChatItems: $selectedChatItems, selectedChatItems: $selectedChatItems,
forwardedChatItems: $forwardedChatItems forwardedChatItems: $forwardedChatItems
@@ -894,14 +893,12 @@ struct ChatView: View {
private struct ChatItemWithMenu: View { private struct ChatItemWithMenu: View {
@EnvironmentObject var m: ChatModel @EnvironmentObject var m: ChatModel
@EnvironmentObject var theme: AppTheme @EnvironmentObject var theme: AppTheme
@AppStorage(DEFAULT_PROFILE_IMAGE_CORNER_RADIUS) private var profileRadius = defaultProfileImageCorner
@Binding @ObservedObject var chat: Chat @Binding @ObservedObject var chat: Chat
@ObservedObject var dummyModel: ChatItemDummyModel = .shared @ObservedObject var dummyModel: ChatItemDummyModel = .shared
let chatItem: ChatItem let chatItem: ChatItem
let maxWidth: CGFloat let maxWidth: CGFloat
@Binding var composeState: ComposeState @Binding var composeState: ComposeState
@Binding var selectedMember: GMember? @Binding var selectedMember: GMember?
@Binding var showChatInfoSheet: Bool
@Binding var revealedChatItem: ChatItem? @Binding var revealedChatItem: ChatItem?
@State private var deletingItem: ChatItem? = nil @State private var deletingItem: ChatItem? = nil
@@ -1258,22 +1255,16 @@ struct ChatView: View {
setReaction(ci, add: !r.userReacted, reaction: r.reaction) setReaction(ci, add: !r.userReacted, reaction: r.reaction)
} }
} }
switch chat.chatInfo { if case let .group(groupInfo) = chat.chatInfo {
case let .group(groupInfo):
v.contextMenu { v.contextMenu {
ReactionContextMenu( ReactionContextMenu(
groupInfo: groupInfo, groupInfo: groupInfo,
itemId: ci.id, itemId: ci.id,
reactionCount: r, reactionCount: r,
selectedMember: $selectedMember, selectedMember: $selectedMember
profileRadius: profileRadius
) )
} }
case let .direct(contact): } else {
v.contextMenu {
contactReactionMenu(contact, r)
}
default:
v v
} }
} }
@@ -1777,20 +1768,6 @@ struct ChatView: View {
} }
} }
@ViewBuilder private func contactReactionMenu(_ contact: Contact, _ r: CIReactionCount) -> some View {
if !r.userReacted || r.totalReacted > 1 {
Button { showChatInfoSheet = true } label: {
profileMenuItem(Text(contact.displayName), contact.image, radius: profileRadius)
}
}
if r.userReacted {
Button {} label: {
profileMenuItem(Text("you"), m.currentUser?.profile.image, radius: profileRadius)
}
.disabled(true)
}
}
private struct SelectedChatItem: View { private struct SelectedChatItem: View {
@EnvironmentObject var theme: AppTheme @EnvironmentObject var theme: AppTheme
var ciId: Int64 var ciId: Int64
@@ -1882,12 +1859,13 @@ struct ReactionContextMenu: View {
var itemId: Int64 var itemId: Int64
var reactionCount: CIReactionCount var reactionCount: CIReactionCount
@Binding var selectedMember: GMember? @Binding var selectedMember: GMember?
var profileRadius: CGFloat
@State private var memberReactions: [MemberReaction] = [] @State private var memberReactions: [MemberReaction] = []
@AppStorage(DEFAULT_PROFILE_IMAGE_CORNER_RADIUS) private var radius = defaultProfileImageCorner
var body: some View { var body: some View {
groupMemberReactionList() groupMemberReactionList()
.task { .task {
logger.debug("ReactionContextMenu task \(radius)")
await loadChatItemReaction() await loadChatItemReaction()
} }
} }
@@ -1911,13 +1889,28 @@ struct ReactionContextMenu: View {
selectedMember = member selectedMember = member
} }
} label: { } label: {
profileMenuItem(Text(mem.displayName), mem.image, radius: profileRadius) HStack {
Text(mem.displayName)
if let img = cropImage(mem.image) {
Image(uiImage: img)
} else {
Image(systemName: "person.crop.circle")
}
}
} }
.disabled(userMember) .disabled(userMember)
} }
} }
} }
private func cropImage(_ img: String?) -> UIImage? {
return if let originalImage = imageFromBase64(img) {
maskToCustomShape(originalImage, size: 30, radius: radius)
} else {
nil
}
}
private func loadChatItemReaction() async { private func loadChatItemReaction() async {
do { do {
let memberReactions = try await apiGetReactionMembers( let memberReactions = try await apiGetReactionMembers(
@@ -1934,17 +1927,6 @@ struct ReactionContextMenu: View {
} }
} }
func profileMenuItem(_ nameText: Text, _ image: String?, radius: CGFloat) -> some View {
HStack {
nameText
if let image, let img = imageFromBase64(image) {
Image(uiImage: maskToCustomShape(img, size: 30, radius: radius))
} else {
Image(systemName: "person.crop.circle")
}
}
}
func maskToCustomShape(_ image: UIImage, size: CGFloat, radius: CGFloat) -> UIImage { func maskToCustomShape(_ image: UIImage, size: CGFloat, radius: CGFloat) -> UIImage {
let path = Path { path in let path = Path { path in
if radius >= 50 { if radius >= 50 {
+20 -20
View File
@@ -1931,7 +1931,7 @@
CLANG_TIDY_MISC_REDUNDANT_EXPRESSION = YES; CLANG_TIDY_MISC_REDUNDANT_EXPRESSION = YES;
CODE_SIGN_ENTITLEMENTS = "SimpleX (iOS).entitlements"; CODE_SIGN_ENTITLEMENTS = "SimpleX (iOS).entitlements";
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 255; CURRENT_PROJECT_VERSION = 254;
DEAD_CODE_STRIPPING = YES; DEAD_CODE_STRIPPING = YES;
DEVELOPMENT_TEAM = 5NN7GUYB6T; DEVELOPMENT_TEAM = 5NN7GUYB6T;
ENABLE_BITCODE = NO; ENABLE_BITCODE = NO;
@@ -1956,7 +1956,7 @@
"@executable_path/Frameworks", "@executable_path/Frameworks",
); );
LLVM_LTO = YES_THIN; LLVM_LTO = YES_THIN;
MARKETING_VERSION = 6.2.1; MARKETING_VERSION = 6.2;
PRODUCT_BUNDLE_IDENTIFIER = chat.simplex.app; PRODUCT_BUNDLE_IDENTIFIER = chat.simplex.app;
PRODUCT_NAME = SimpleX; PRODUCT_NAME = SimpleX;
SDKROOT = iphoneos; SDKROOT = iphoneos;
@@ -1980,7 +1980,7 @@
CLANG_TIDY_MISC_REDUNDANT_EXPRESSION = YES; CLANG_TIDY_MISC_REDUNDANT_EXPRESSION = YES;
CODE_SIGN_ENTITLEMENTS = "SimpleX (iOS).entitlements"; CODE_SIGN_ENTITLEMENTS = "SimpleX (iOS).entitlements";
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 255; CURRENT_PROJECT_VERSION = 254;
DEAD_CODE_STRIPPING = YES; DEAD_CODE_STRIPPING = YES;
DEVELOPMENT_TEAM = 5NN7GUYB6T; DEVELOPMENT_TEAM = 5NN7GUYB6T;
ENABLE_BITCODE = NO; ENABLE_BITCODE = NO;
@@ -2005,7 +2005,7 @@
"@executable_path/Frameworks", "@executable_path/Frameworks",
); );
LLVM_LTO = YES; LLVM_LTO = YES;
MARKETING_VERSION = 6.2.1; MARKETING_VERSION = 6.2;
PRODUCT_BUNDLE_IDENTIFIER = chat.simplex.app; PRODUCT_BUNDLE_IDENTIFIER = chat.simplex.app;
PRODUCT_NAME = SimpleX; PRODUCT_NAME = SimpleX;
SDKROOT = iphoneos; SDKROOT = iphoneos;
@@ -2021,11 +2021,11 @@
buildSettings = { buildSettings = {
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 255; CURRENT_PROJECT_VERSION = 254;
DEVELOPMENT_TEAM = 5NN7GUYB6T; DEVELOPMENT_TEAM = 5NN7GUYB6T;
GENERATE_INFOPLIST_FILE = YES; GENERATE_INFOPLIST_FILE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 15.0; IPHONEOS_DEPLOYMENT_TARGET = 15.0;
MARKETING_VERSION = 6.2.1; MARKETING_VERSION = 6.2;
PRODUCT_BUNDLE_IDENTIFIER = "chat.simplex.Tests-iOS"; PRODUCT_BUNDLE_IDENTIFIER = "chat.simplex.Tests-iOS";
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
SDKROOT = iphoneos; SDKROOT = iphoneos;
@@ -2041,11 +2041,11 @@
buildSettings = { buildSettings = {
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 255; CURRENT_PROJECT_VERSION = 254;
DEVELOPMENT_TEAM = 5NN7GUYB6T; DEVELOPMENT_TEAM = 5NN7GUYB6T;
GENERATE_INFOPLIST_FILE = YES; GENERATE_INFOPLIST_FILE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 15.0; IPHONEOS_DEPLOYMENT_TARGET = 15.0;
MARKETING_VERSION = 6.2.1; MARKETING_VERSION = 6.2;
PRODUCT_BUNDLE_IDENTIFIER = "chat.simplex.Tests-iOS"; PRODUCT_BUNDLE_IDENTIFIER = "chat.simplex.Tests-iOS";
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
SDKROOT = iphoneos; SDKROOT = iphoneos;
@@ -2066,7 +2066,7 @@
CODE_SIGN_ENTITLEMENTS = "SimpleX NSE/SimpleX NSE.entitlements"; CODE_SIGN_ENTITLEMENTS = "SimpleX NSE/SimpleX NSE.entitlements";
CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 255; CURRENT_PROJECT_VERSION = 254;
DEVELOPMENT_TEAM = 5NN7GUYB6T; DEVELOPMENT_TEAM = 5NN7GUYB6T;
ENABLE_BITCODE = NO; ENABLE_BITCODE = NO;
GCC_OPTIMIZATION_LEVEL = s; GCC_OPTIMIZATION_LEVEL = s;
@@ -2081,7 +2081,7 @@
"@executable_path/../../Frameworks", "@executable_path/../../Frameworks",
); );
LLVM_LTO = YES; LLVM_LTO = YES;
MARKETING_VERSION = 6.2.1; MARKETING_VERSION = 6.2;
PRODUCT_BUNDLE_IDENTIFIER = "chat.simplex.app.SimpleX-NSE"; PRODUCT_BUNDLE_IDENTIFIER = "chat.simplex.app.SimpleX-NSE";
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = ""; PROVISIONING_PROFILE_SPECIFIER = "";
@@ -2103,7 +2103,7 @@
CODE_SIGN_ENTITLEMENTS = "SimpleX NSE/SimpleX NSE.entitlements"; CODE_SIGN_ENTITLEMENTS = "SimpleX NSE/SimpleX NSE.entitlements";
CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 255; CURRENT_PROJECT_VERSION = 254;
DEVELOPMENT_TEAM = 5NN7GUYB6T; DEVELOPMENT_TEAM = 5NN7GUYB6T;
ENABLE_BITCODE = NO; ENABLE_BITCODE = NO;
ENABLE_CODE_COVERAGE = NO; ENABLE_CODE_COVERAGE = NO;
@@ -2118,7 +2118,7 @@
"@executable_path/../../Frameworks", "@executable_path/../../Frameworks",
); );
LLVM_LTO = YES; LLVM_LTO = YES;
MARKETING_VERSION = 6.2.1; MARKETING_VERSION = 6.2;
PRODUCT_BUNDLE_IDENTIFIER = "chat.simplex.app.SimpleX-NSE"; PRODUCT_BUNDLE_IDENTIFIER = "chat.simplex.app.SimpleX-NSE";
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = ""; PROVISIONING_PROFILE_SPECIFIER = "";
@@ -2140,7 +2140,7 @@
CLANG_TIDY_BUGPRONE_REDUNDANT_BRANCH_CONDITION = YES; CLANG_TIDY_BUGPRONE_REDUNDANT_BRANCH_CONDITION = YES;
CLANG_TIDY_MISC_REDUNDANT_EXPRESSION = YES; CLANG_TIDY_MISC_REDUNDANT_EXPRESSION = YES;
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 255; CURRENT_PROJECT_VERSION = 254;
DEFINES_MODULE = YES; DEFINES_MODULE = YES;
DEVELOPMENT_TEAM = 5NN7GUYB6T; DEVELOPMENT_TEAM = 5NN7GUYB6T;
DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_COMPATIBILITY_VERSION = 1;
@@ -2166,7 +2166,7 @@
"$(PROJECT_DIR)/Libraries/sim", "$(PROJECT_DIR)/Libraries/sim",
); );
LLVM_LTO = YES; LLVM_LTO = YES;
MARKETING_VERSION = 6.2.1; MARKETING_VERSION = 6.2;
PRODUCT_BUNDLE_IDENTIFIER = chat.simplex.SimpleXChat; PRODUCT_BUNDLE_IDENTIFIER = chat.simplex.SimpleXChat;
PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)";
SDKROOT = iphoneos; SDKROOT = iphoneos;
@@ -2191,7 +2191,7 @@
CLANG_TIDY_BUGPRONE_REDUNDANT_BRANCH_CONDITION = YES; CLANG_TIDY_BUGPRONE_REDUNDANT_BRANCH_CONDITION = YES;
CLANG_TIDY_MISC_REDUNDANT_EXPRESSION = YES; CLANG_TIDY_MISC_REDUNDANT_EXPRESSION = YES;
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 255; CURRENT_PROJECT_VERSION = 254;
DEFINES_MODULE = YES; DEFINES_MODULE = YES;
DEVELOPMENT_TEAM = 5NN7GUYB6T; DEVELOPMENT_TEAM = 5NN7GUYB6T;
DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_COMPATIBILITY_VERSION = 1;
@@ -2217,7 +2217,7 @@
"$(PROJECT_DIR)/Libraries/sim", "$(PROJECT_DIR)/Libraries/sim",
); );
LLVM_LTO = YES; LLVM_LTO = YES;
MARKETING_VERSION = 6.2.1; MARKETING_VERSION = 6.2;
PRODUCT_BUNDLE_IDENTIFIER = chat.simplex.SimpleXChat; PRODUCT_BUNDLE_IDENTIFIER = chat.simplex.SimpleXChat;
PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)";
SDKROOT = iphoneos; SDKROOT = iphoneos;
@@ -2242,7 +2242,7 @@
CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
CODE_SIGN_ENTITLEMENTS = "SimpleX SE/SimpleX SE.entitlements"; CODE_SIGN_ENTITLEMENTS = "SimpleX SE/SimpleX SE.entitlements";
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 255; CURRENT_PROJECT_VERSION = 254;
DEVELOPMENT_TEAM = 5NN7GUYB6T; DEVELOPMENT_TEAM = 5NN7GUYB6T;
ENABLE_USER_SCRIPT_SANDBOXING = YES; ENABLE_USER_SCRIPT_SANDBOXING = YES;
GCC_C_LANGUAGE_STANDARD = gnu17; GCC_C_LANGUAGE_STANDARD = gnu17;
@@ -2257,7 +2257,7 @@
"@executable_path/../../Frameworks", "@executable_path/../../Frameworks",
); );
LOCALIZATION_PREFERS_STRING_CATALOGS = YES; LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
MARKETING_VERSION = 6.2.1; MARKETING_VERSION = 6.2;
PRODUCT_BUNDLE_IDENTIFIER = "chat.simplex.app.SimpleX-SE"; PRODUCT_BUNDLE_IDENTIFIER = "chat.simplex.app.SimpleX-SE";
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
SDKROOT = iphoneos; SDKROOT = iphoneos;
@@ -2276,7 +2276,7 @@
CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
CODE_SIGN_ENTITLEMENTS = "SimpleX SE/SimpleX SE.entitlements"; CODE_SIGN_ENTITLEMENTS = "SimpleX SE/SimpleX SE.entitlements";
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 255; CURRENT_PROJECT_VERSION = 254;
DEVELOPMENT_TEAM = 5NN7GUYB6T; DEVELOPMENT_TEAM = 5NN7GUYB6T;
ENABLE_USER_SCRIPT_SANDBOXING = YES; ENABLE_USER_SCRIPT_SANDBOXING = YES;
GCC_C_LANGUAGE_STANDARD = gnu17; GCC_C_LANGUAGE_STANDARD = gnu17;
@@ -2291,7 +2291,7 @@
"@executable_path/../../Frameworks", "@executable_path/../../Frameworks",
); );
LOCALIZATION_PREFERS_STRING_CATALOGS = YES; LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
MARKETING_VERSION = 6.2.1; MARKETING_VERSION = 6.2;
PRODUCT_BUNDLE_IDENTIFIER = "chat.simplex.app.SimpleX-SE"; PRODUCT_BUNDLE_IDENTIFIER = "chat.simplex.app.SimpleX-SE";
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
SDKROOT = iphoneos; SDKROOT = iphoneos;
@@ -19,6 +19,8 @@ actual val wallpapersDir: File = File(filesDir.absolutePath + File.separator + "
actual val coreTmpDir: File = File(filesDir.absolutePath + File.separator + "temp_files") actual val coreTmpDir: File = File(filesDir.absolutePath + File.separator + "temp_files")
actual val dbAbsolutePrefixPath: String = dataDir.absolutePath + File.separator + "files" actual val dbAbsolutePrefixPath: String = dataDir.absolutePath + File.separator + "files"
actual val preferencesDir = File(dataDir.absolutePath + File.separator + "shared_prefs") actual val preferencesDir = File(dataDir.absolutePath + File.separator + "shared_prefs")
actual val preferencesTmpDir = File(tmpDir, "prefs_tmp")
.also { it.deleteRecursively() }
actual val chatDatabaseFileName: String = "files_chat.db" actual val chatDatabaseFileName: String = "files_chat.db"
actual val agentDatabaseFileName: String = "files_agent.db" actual val agentDatabaseFileName: String = "files_agent.db"
@@ -1,11 +1,10 @@
package chat.simplex.common.platform package chat.simplex.common.platform
import android.util.Log import android.util.Log
import chat.simplex.common.model.ChatController.appPrefs
actual object Log { actual object Log {
actual fun d(tag: String, text: String) { if (appPrefs.logLevel.get() <= LogLevel.DEBUG && appPrefs.developerTools.get()) Log.d(tag, text) } actual fun d(tag: String, text: String) = Log.d(tag, text).run{}
actual fun e(tag: String, text: String) { if (appPrefs.logLevel.get() <= LogLevel.ERROR || !appPrefs.developerTools.get()) Log.e(tag, text) } actual fun e(tag: String, text: String) = Log.e(tag, text).run{}
actual fun i(tag: String, text: String) { if (appPrefs.logLevel.get() <= LogLevel.INFO && appPrefs.developerTools.get()) Log.i(tag, text) } actual fun i(tag: String, text: String) = Log.i(tag, text).run{}
actual fun w(tag: String, text: String) { if (appPrefs.logLevel.get() <= LogLevel.WARNING || !appPrefs.developerTools.get()) Log.w(tag, text) } actual fun w(tag: String, text: String) = Log.w(tag, text).run{}
} }
@@ -132,7 +132,6 @@ class AppPreferences {
val chatLastStart = mkDatePreference(SHARED_PREFS_CHAT_LAST_START, null) val chatLastStart = mkDatePreference(SHARED_PREFS_CHAT_LAST_START, null)
val chatStopped = mkBoolPreference(SHARED_PREFS_CHAT_STOPPED, false) val chatStopped = mkBoolPreference(SHARED_PREFS_CHAT_STOPPED, false)
val developerTools = mkBoolPreference(SHARED_PREFS_DEVELOPER_TOOLS, false) val developerTools = mkBoolPreference(SHARED_PREFS_DEVELOPER_TOOLS, false)
val logLevel = mkEnumPreference(SHARED_PREFS_LOG_LEVEL, LogLevel.WARNING) { LogLevel.entries.firstOrNull { it.name == this } }
val showInternalErrors = mkBoolPreference(SHARED_PREFS_SHOW_INTERNAL_ERRORS, false) val showInternalErrors = mkBoolPreference(SHARED_PREFS_SHOW_INTERNAL_ERRORS, false)
val showSlowApiCalls = mkBoolPreference(SHARED_PREFS_SHOW_SLOW_API_CALLS, false) val showSlowApiCalls = mkBoolPreference(SHARED_PREFS_SHOW_SLOW_API_CALLS, false)
val terminalAlwaysVisible = mkBoolPreference(SHARED_PREFS_TERMINAL_ALWAYS_VISIBLE, false) val terminalAlwaysVisible = mkBoolPreference(SHARED_PREFS_TERMINAL_ALWAYS_VISIBLE, false)
@@ -394,7 +393,6 @@ class AppPreferences {
private const val SHARED_PREFS_CHAT_LAST_START = "ChatLastStart" private const val SHARED_PREFS_CHAT_LAST_START = "ChatLastStart"
private const val SHARED_PREFS_CHAT_STOPPED = "ChatStopped" private const val SHARED_PREFS_CHAT_STOPPED = "ChatStopped"
private const val SHARED_PREFS_DEVELOPER_TOOLS = "DeveloperTools" private const val SHARED_PREFS_DEVELOPER_TOOLS = "DeveloperTools"
private const val SHARED_PREFS_LOG_LEVEL = "LogLevel"
private const val SHARED_PREFS_SHOW_INTERNAL_ERRORS = "ShowInternalErrors" private const val SHARED_PREFS_SHOW_INTERNAL_ERRORS = "ShowInternalErrors"
private const val SHARED_PREFS_SHOW_SLOW_API_CALLS = "ShowSlowApiCalls" private const val SHARED_PREFS_SHOW_SLOW_API_CALLS = "ShowSlowApiCalls"
private const val SHARED_PREFS_TERMINAL_ALWAYS_VISIBLE = "TerminalAlwaysVisible" private const val SHARED_PREFS_TERMINAL_ALWAYS_VISIBLE = "TerminalAlwaysVisible"
@@ -3117,8 +3115,12 @@ class SharedPreference<T>(val get: () -> T, set: (T) -> Unit) {
init { init {
this.set = { value -> this.set = { value ->
set(value) try {
_state.value = value set(value)
_state.value = value
} catch (e: Exception) {
Log.e(TAG, "Error saving settings: ${e.stackTraceToString()}")
}
} }
} }
} }
@@ -3,7 +3,7 @@ package chat.simplex.common.platform
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import chat.simplex.common.model.* import chat.simplex.common.model.*
import chat.simplex.common.ui.theme.* import chat.simplex.common.ui.theme.*
import chat.simplex.common.views.helpers.generalGetString import chat.simplex.common.views.helpers.*
import chat.simplex.res.MR import chat.simplex.res.MR
import com.charleskorn.kaml.* import com.charleskorn.kaml.*
import kotlinx.serialization.encodeToString import kotlinx.serialization.encodeToString
@@ -11,6 +11,8 @@ import java.io.*
import java.net.URI import java.net.URI
import java.net.URLDecoder import java.net.URLDecoder
import java.net.URLEncoder import java.net.URLEncoder
import java.nio.file.Files
import java.nio.file.StandardCopyOption
expect val dataDir: File expect val dataDir: File
expect val tmpDir: File expect val tmpDir: File
@@ -20,6 +22,7 @@ expect val wallpapersDir: File
expect val coreTmpDir: File expect val coreTmpDir: File
expect val dbAbsolutePrefixPath: String expect val dbAbsolutePrefixPath: String
expect val preferencesDir: File expect val preferencesDir: File
expect val preferencesTmpDir: File
expect val chatDatabaseFileName: String expect val chatDatabaseFileName: String
expect val agentDatabaseFileName: String expect val agentDatabaseFileName: String
@@ -142,16 +145,23 @@ fun readThemeOverrides(): List<ThemeOverrides> {
} }
} }
private const val lock = "themesWriter"
fun writeThemeOverrides(overrides: List<ThemeOverrides>): Boolean = fun writeThemeOverrides(overrides: List<ThemeOverrides>): Boolean =
try { synchronized(lock) {
File(getPreferenceFilePath("themes.yaml")).outputStream().use { try {
val string = yaml.encodeToString(ThemesFile(themes = overrides)) val themesFile = File(getPreferenceFilePath("themes.yaml"))
it.bufferedWriter().use { it.write(string) } createTmpFileAndDelete(preferencesTmpDir) { tmpFile ->
val string = yaml.encodeToString(ThemesFile(themes = overrides))
tmpFile.bufferedWriter().use { it.write(string) }
themesFile.parentFile.mkdirs()
Files.move(tmpFile.toPath(), themesFile.toPath(), StandardCopyOption.REPLACE_EXISTING)
}
true
} catch (e: Exception) {
Log.e(TAG, "Error writing themes file: ${e.stackTraceToString()}")
false
} }
true
} catch (e: Throwable) {
Log.e(TAG, "Error while writing themes file: ${e.stackTraceToString()}")
false
} }
private fun fileReady(file: CIFile, filePath: String) = private fun fileReady(file: CIFile, filePath: String) =
@@ -2,10 +2,6 @@ package chat.simplex.common.platform
const val TAG = "SIMPLEX" const val TAG = "SIMPLEX"
enum class LogLevel {
DEBUG, INFO, WARNING, ERROR
}
expect object Log { expect object Log {
fun d(tag: String, text: String) fun d(tag: String, text: String)
fun e(tag: String, text: String) fun e(tag: String, text: String)
@@ -102,7 +102,9 @@ object ThemeManager {
} }
fun applyTheme(theme: String) { fun applyTheme(theme: String) {
appPrefs.currentTheme.set(theme) if (appPrefs.currentTheme.get() != theme) {
appPrefs.currentTheme.set(theme)
}
CurrentColors.value = currentColors(null, null, chatModel.currentUser.value?.uiThemes, appPrefs.themeOverrides.get()) CurrentColors.value = currentColors(null, null, chatModel.currentUser.value?.uiThemes, appPrefs.themeOverrides.get())
platform.androidSetNightModeIfSupported() platform.androidSetNightModeIfSupported()
val c = CurrentColors.value.colors val c = CurrentColors.value.colors
@@ -296,7 +296,6 @@ fun ChatItemInfoView(chatRh: Long?, ci: ChatItem, ciInfo: ChatItemInfo, devTools
} }
} }
SectionBottomSpacer() SectionBottomSpacer()
SectionBottomSpacer()
} }
} }
@@ -310,7 +309,6 @@ fun ChatItemInfoView(chatRh: Long?, ci: ChatItem, ciInfo: ChatItemInfo, devTools
QuotedMsgView(qi) QuotedMsgView(qi)
} }
SectionBottomSpacer() SectionBottomSpacer()
SectionBottomSpacer()
} }
} }
@@ -326,7 +324,6 @@ fun ChatItemInfoView(chatRh: Long?, ci: ChatItem, ciInfo: ChatItemInfo, devTools
ForwardedFromView(forwardedFromItem) ForwardedFromView(forwardedFromItem)
} }
SectionBottomSpacer() SectionBottomSpacer()
SectionBottomSpacer()
} }
} }
@@ -398,7 +395,6 @@ fun ChatItemInfoView(chatRh: Long?, ci: ChatItem, ciInfo: ChatItemInfo, devTools
} }
} }
SectionBottomSpacer() SectionBottomSpacer()
SectionBottomSpacer()
} }
} }
@@ -437,11 +433,12 @@ fun ChatItemInfoView(chatRh: Long?, ci: ChatItem, ciInfo: ChatItemInfo, devTools
Column { Column {
if (numTabs() > 1) { if (numTabs() > 1) {
Box( Column(
Modifier Modifier
.fillMaxHeight() .fillMaxHeight(),
verticalArrangement = Arrangement.SpaceBetween
) { ) {
Column { Column(Modifier.weight(1f)) {
when (val sel = selection.value) { when (val sel = selection.value) {
is CIInfoTab.Delivery -> { is CIInfoTab.Delivery -> {
DeliveryTab(sel.memberDeliveryStatuses) DeliveryTab(sel.memberDeliveryStatuses)
@@ -482,7 +479,7 @@ fun ChatItemInfoView(chatRh: Long?, ci: ChatItem, ciInfo: ChatItemInfo, devTools
} }
} }
val oneHandUI = remember { appPrefs.oneHandUI.state } val oneHandUI = remember { appPrefs.oneHandUI.state }
Box(Modifier.align(Alignment.BottomCenter).navigationBarsPadding().offset(x = 0.dp, y = if (oneHandUI.value) -AppBarHeight * fontSizeSqrtMultiplier else 0.dp)) { Box(Modifier.offset(x = 0.dp, y = if (oneHandUI.value) -AppBarHeight * fontSizeSqrtMultiplier else 0.dp)) {
TabRow( TabRow(
selectedTabIndex = availableTabs.indexOfFirst { it::class == selection.value::class }, selectedTabIndex = availableTabs.indexOfFirst { it::class == selection.value::class },
Modifier.height(AppBarHeight * fontSizeSqrtMultiplier), Modifier.height(AppBarHeight * fontSizeSqrtMultiplier),
@@ -665,18 +665,13 @@ fun ChatLayout(
AdaptingBottomPaddingLayout(Modifier, CHAT_COMPOSE_LAYOUT_ID, composeViewHeight) { AdaptingBottomPaddingLayout(Modifier, CHAT_COMPOSE_LAYOUT_ID, composeViewHeight) {
if (chatInfo != null) { if (chatInfo != null) {
Box(Modifier.fillMaxSize()) { Box(Modifier.fillMaxSize()) {
// disables scrolling to top of chat item on click inside the bubble ChatItemsList(
CompositionLocalProvider(LocalBringIntoViewSpec provides object : BringIntoViewSpec { remoteHostId, chatInfo, unreadCount, composeState, composeViewHeight, searchValue,
override fun calculateScrollDistance(offset: Float, size: Float, containerSize: Float): Float = 0f useLinkPreviews, linkMode, selectedChatItems, showMemberInfo, loadMessages, deleteMessage, deleteMessages,
}) { receiveFile, cancelFile, joinGroup, acceptCall, acceptFeature, openDirectChat, forwardItem,
ChatItemsList( updateContactStats, updateMemberStats, syncContactConnection, syncMemberConnection, findModelChat, findModelMember,
remoteHostId, chatInfo, unreadCount, composeState, composeViewHeight, searchValue, setReaction, showItemDetails, markItemsRead, markChatRead, remember { { onComposed(it) } }, developerTools, showViaProxy,
useLinkPreviews, linkMode, selectedChatItems, showMemberInfo, showChatInfo = info, loadMessages, deleteMessage, deleteMessages, )
receiveFile, cancelFile, joinGroup, acceptCall, acceptFeature, openDirectChat, forwardItem,
updateContactStats, updateMemberStats, syncContactConnection, syncMemberConnection, findModelChat, findModelMember,
setReaction, showItemDetails, markItemsRead, markChatRead, remember { { onComposed(it) } }, developerTools, showViaProxy,
)
}
} }
} }
Box( Box(
@@ -944,7 +939,6 @@ fun BoxScope.ChatItemsList(
linkMode: SimplexLinkMode, linkMode: SimplexLinkMode,
selectedChatItems: MutableState<Set<Long>?>, selectedChatItems: MutableState<Set<Long>?>,
showMemberInfo: (GroupInfo, GroupMember) -> Unit, showMemberInfo: (GroupInfo, GroupMember) -> Unit,
showChatInfo: () -> Unit,
loadMessages: suspend (ChatId, ChatPagination, ActiveChatState, visibleItemIndexesNonReversed: () -> IntRange) -> Unit, loadMessages: suspend (ChatId, ChatPagination, ActiveChatState, visibleItemIndexesNonReversed: () -> IntRange) -> Unit,
deleteMessage: (Long, CIDeleteMode) -> Unit, deleteMessage: (Long, CIDeleteMode) -> Unit,
deleteMessages: (List<Long>) -> Unit, deleteMessages: (List<Long>) -> Unit,
@@ -1072,7 +1066,7 @@ fun BoxScope.ChatItemsList(
highlightedItems.value = setOf() highlightedItems.value = setOf()
} }
} }
ChatItemView(remoteHostId, chatInfo, cItem, composeState, provider, useLinkPreviews = useLinkPreviews, linkMode = linkMode, revealed = revealed, highlighted = highlighted, range = range, fillMaxWidth = fillMaxWidth, selectedChatItems = selectedChatItems, selectChatItem = { selectUnselectChatItem(true, cItem, revealed, selectedChatItems) }, deleteMessage = deleteMessage, deleteMessages = deleteMessages, receiveFile = receiveFile, cancelFile = cancelFile, joinGroup = joinGroup, acceptCall = acceptCall, acceptFeature = acceptFeature, openDirectChat = openDirectChat, forwardItem = forwardItem, updateContactStats = updateContactStats, updateMemberStats = updateMemberStats, syncContactConnection = syncContactConnection, syncMemberConnection = syncMemberConnection, findModelChat = findModelChat, findModelMember = findModelMember, scrollToItem = scrollToItem, scrollToQuotedItemFromItem = scrollToQuotedItemFromItem, setReaction = setReaction, showItemDetails = showItemDetails, reveal = reveal, showMemberInfo = showMemberInfo, showChatInfo = showChatInfo, developerTools = developerTools, showViaProxy = showViaProxy, itemSeparation = itemSeparation, showTimestamp = itemSeparation.timestamp) ChatItemView(remoteHostId, chatInfo, cItem, composeState, provider, useLinkPreviews = useLinkPreviews, linkMode = linkMode, revealed = revealed, highlighted = highlighted, range = range, fillMaxWidth = fillMaxWidth, selectedChatItems = selectedChatItems, selectChatItem = { selectUnselectChatItem(true, cItem, revealed, selectedChatItems) }, deleteMessage = deleteMessage, deleteMessages = deleteMessages, receiveFile = receiveFile, cancelFile = cancelFile, joinGroup = joinGroup, acceptCall = acceptCall, acceptFeature = acceptFeature, openDirectChat = openDirectChat, forwardItem = forwardItem, updateContactStats = updateContactStats, updateMemberStats = updateMemberStats, syncContactConnection = syncContactConnection, syncMemberConnection = syncMemberConnection, findModelChat = findModelChat, findModelMember = findModelMember, scrollToItem = scrollToItem, scrollToQuotedItemFromItem = scrollToQuotedItemFromItem, setReaction = setReaction, showItemDetails = showItemDetails, reveal = reveal, showMemberInfo = showMemberInfo, developerTools = developerTools, showViaProxy = showViaProxy, itemSeparation = itemSeparation, showTimestamp = itemSeparation.timestamp)
} }
} }
@@ -24,12 +24,12 @@ import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.* import androidx.compose.ui.unit.*
import chat.simplex.common.model.* import chat.simplex.common.model.*
import chat.simplex.common.model.ChatModel.controller import chat.simplex.common.model.ChatModel.controller
import chat.simplex.common.model.ChatModel.currentUser
import chat.simplex.common.platform.* import chat.simplex.common.platform.*
import chat.simplex.common.ui.theme.* import chat.simplex.common.ui.theme.*
import chat.simplex.common.views.chat.* import chat.simplex.common.views.chat.*
import chat.simplex.common.views.helpers.* import chat.simplex.common.views.helpers.*
import chat.simplex.res.MR import chat.simplex.res.MR
import kotlinx.coroutines.launch
import kotlinx.datetime.Clock import kotlinx.datetime.Clock
import kotlin.math.* import kotlin.math.*
@@ -51,12 +51,6 @@ fun chatEventText(eventText: String, ts: String): AnnotatedString =
withStyle(chatEventStyle) { append("$eventText $ts") } withStyle(chatEventStyle) { append("$eventText $ts") }
} }
data class ChatItemReactionMenuItem (
val name: String,
val image: String?,
val onClick: (() -> Unit)?
)
@Composable @Composable
fun ChatItemView( fun ChatItemView(
rhId: Long?, rhId: Long?,
@@ -93,7 +87,6 @@ fun ChatItemView(
showItemDetails: (ChatInfo, ChatItem) -> Unit, showItemDetails: (ChatInfo, ChatItem) -> Unit,
reveal: (Boolean) -> Unit, reveal: (Boolean) -> Unit,
showMemberInfo: (GroupInfo, GroupMember) -> Unit, showMemberInfo: (GroupInfo, GroupMember) -> Unit,
showChatInfo: () -> Unit,
developerTools: Boolean, developerTools: Boolean,
showViaProxy: Boolean, showViaProxy: Boolean,
showTimestamp: Boolean, showTimestamp: Boolean,
@@ -127,7 +120,7 @@ fun ChatItemView(
Row(verticalAlignment = Alignment.CenterVertically, modifier = Modifier.chatItemOffset(cItem, itemSeparation.largeGap, inverted = true, revealed = true)) { Row(verticalAlignment = Alignment.CenterVertically, modifier = Modifier.chatItemOffset(cItem, itemSeparation.largeGap, inverted = true, revealed = true)) {
cItem.reactions.forEach { r -> cItem.reactions.forEach { r ->
val showReactionMenu = remember { mutableStateOf(false) } val showReactionMenu = remember { mutableStateOf(false) }
val reactionMenuItems = remember { mutableStateOf(emptyList<ChatItemReactionMenuItem>()) } val reactionMembers = remember { mutableStateOf(emptyList<MemberReaction>()) }
val interactionSource = remember { MutableInteractionSource() } val interactionSource = remember { MutableInteractionSource() }
val enterInteraction = remember { HoverInteraction.Enter() } val enterInteraction = remember { HoverInteraction.Enter() }
KeyChangeEffect(highlighted.value) { KeyChangeEffect(highlighted.value) {
@@ -141,39 +134,18 @@ fun ChatItemView(
var modifier = Modifier.padding(horizontal = 5.dp, vertical = 2.dp).clip(RoundedCornerShape(8.dp)) var modifier = Modifier.padding(horizontal = 5.dp, vertical = 2.dp).clip(RoundedCornerShape(8.dp))
if (cInfo.featureEnabled(ChatFeature.Reactions)) { if (cInfo.featureEnabled(ChatFeature.Reactions)) {
fun showReactionsMenu() { fun showReactionsMenu() {
when (cInfo) { if (cInfo is ChatInfo.Group) {
is ChatInfo.Group -> { withBGApi {
withBGApi { try {
try { val members = controller.apiGetReactionMembers(rhId, cInfo.groupInfo.groupId, cItem.id, r.reaction)
val members = controller.apiGetReactionMembers(rhId, cInfo.groupInfo.groupId, cItem.id, r.reaction) if (members != null) {
if (members != null) { showReactionMenu.value = true
showReactionMenu.value = true reactionMembers.value = members
reactionMenuItems.value = members.map {
val enabled = cInfo.groupInfo.membership.groupMemberId != it.groupMember.groupMemberId
val click = if (enabled) ({ showMemberInfo(cInfo.groupInfo, it.groupMember) }) else null
ChatItemReactionMenuItem(it.groupMember.displayName, it.groupMember.image, click)
}
}
} catch (e: Exception) {
Log.d(TAG, "chatItemView ChatItemReactions onLongClick: unexpected exception: ${e.stackTraceToString()}")
} }
} catch (e: Exception) {
Log.d(TAG, "hatItemView ChatItemReactions onLongClick: unexpected exception: ${e.stackTraceToString()}")
} }
} }
is ChatInfo.Direct -> {
showReactionMenu.value = true
val reactions = mutableListOf<ChatItemReactionMenuItem>()
if (!r.userReacted || r.totalReacted > 1) {
val contact = cInfo.contact
reactions.add(ChatItemReactionMenuItem(contact.displayName, contact.image, showChatInfo))
}
if (r.userReacted) {
reactions.add(ChatItemReactionMenuItem(generalGetString(MR.strings.sender_you_pronoun), currentUser.value?.image, null))
}
reactionMenuItems.value = reactions
}
else -> {}
} }
} }
modifier = modifier modifier = modifier
@@ -194,19 +166,19 @@ fun ChatItemView(
Row(modifier.padding(2.dp), verticalAlignment = Alignment.CenterVertically) { Row(modifier.padding(2.dp), verticalAlignment = Alignment.CenterVertically) {
ReactionIcon(r.reaction.text, fontSize = 12.sp) ReactionIcon(r.reaction.text, fontSize = 12.sp)
DefaultDropdownMenu(showMenu = showReactionMenu) { DefaultDropdownMenu(showMenu = showReactionMenu) {
reactionMenuItems.value.forEach { m -> reactionMembers.value.forEach { m ->
ItemAction( ItemAction(
text = m.name, text = m.groupMember.displayName,
composable = { ProfileImage(44.dp, m.image) }, composable = { ProfileImage(44.dp, m.groupMember.image) },
onClick = { onClick = {
val click = m.onClick if (cInfo is ChatInfo.Group && cInfo.groupInfo.membership.groupMemberId != m.groupMember.groupMemberId) {
if (click != null) { showMemberInfo(cInfo.groupInfo, m.groupMember)
click() showReactionMenu.value = false
} else {
showReactionMenu.value = false showReactionMenu.value = false
} }
}, },
lineLimit = 1, lineLimit = 1
color = if (m.onClick == null) MaterialTheme.colors.secondary else MenuTextColor
) )
} }
} }
@@ -1216,7 +1188,6 @@ fun PreviewChatItemView(
showItemDetails = { _, _ -> }, showItemDetails = { _, _ -> },
reveal = {}, reveal = {},
showMemberInfo = { _, _ ->}, showMemberInfo = { _, _ ->},
showChatInfo = {},
developerTools = false, developerTools = false,
showViaProxy = false, showViaProxy = false,
showTimestamp = true, showTimestamp = true,
@@ -1262,7 +1233,6 @@ fun PreviewChatItemViewDeletedContent() {
showItemDetails = { _, _ -> }, showItemDetails = { _, _ -> },
reveal = {}, reveal = {},
showMemberInfo = { _, _ ->}, showMemberInfo = { _, _ ->},
showChatInfo = {},
developerTools = false, developerTools = false,
showViaProxy = false, showViaProxy = false,
preview = true, preview = true,
@@ -30,7 +30,6 @@ import kotlinx.datetime.*
import java.io.* import java.io.*
import java.net.URI import java.net.URI
import java.nio.file.Files import java.nio.file.Files
import java.nio.file.StandardCopyOption
import java.text.SimpleDateFormat import java.text.SimpleDateFormat
import java.util.* import java.util.*
import kotlin.collections.ArrayList import kotlin.collections.ArrayList
@@ -45,14 +44,11 @@ fun DatabaseView() {
val chatArchiveFile = remember { mutableStateOf<String?>(null) } val chatArchiveFile = remember { mutableStateOf<String?>(null) }
val stopped = remember { m.chatRunning }.value == false val stopped = remember { m.chatRunning }.value == false
val saveArchiveLauncher = rememberFileChooserLauncher(false) { to: URI? -> val saveArchiveLauncher = rememberFileChooserLauncher(false) { to: URI? ->
val archive = chatArchiveFile.value val file = chatArchiveFile.value
if (archive != null && to != null) { if (file != null && to != null) {
copyFileToFile(File(archive), to) {} copyFileToFile(File(file), to) {
} chatArchiveFile.value = null
// delete no matter the database was exported or canceled the export process }
if (archive != null) {
File(archive).delete()
chatArchiveFile.value = null
} }
} }
val appFilesCountAndSize = remember { mutableStateOf(directoryFileCountAndSize(appFilesDir.absolutePath)) } val appFilesCountAndSize = remember { mutableStateOf(directoryFileCountAndSize(appFilesDir.absolutePath)) }
@@ -684,8 +680,6 @@ suspend fun importArchive(
} finally { } finally {
File(archivePath).delete() File(archivePath).delete()
} }
} else {
progressIndicator.value = false
} }
return false return false
} }
@@ -697,15 +691,14 @@ private fun saveArchiveFromURI(importedArchiveURI: URI): String? {
if (inputStream != null && archiveName != null) { if (inputStream != null && archiveName != null) {
val archivePath = "$databaseExportDir${File.separator}$archiveName" val archivePath = "$databaseExportDir${File.separator}$archiveName"
val destFile = File(archivePath) val destFile = File(archivePath)
Files.copy(inputStream, destFile.toPath(), StandardCopyOption.REPLACE_EXISTING) Files.copy(inputStream, destFile.toPath())
archivePath archivePath
} else { } else {
Log.e(TAG, "saveArchiveFromURI null inputStream") Log.e(TAG, "saveArchiveFromURI null inputStream")
null null
} }
} catch (e: Exception) { } catch (e: Exception) {
AlertManager.shared.showAlertMsg(generalGetString(MR.strings.error_saving_database), e.stackTraceToString()) Log.e(TAG, "saveArchiveFromURI error: ${e.message}")
Log.e(TAG, "saveArchiveFromURI error: ${e.stackTraceToString()}")
null null
} }
} }
@@ -316,8 +316,9 @@ fun removeWallpaperFile(fileName: String? = null) {
WallpaperType.cachedImages.remove(fileName) WallpaperType.cachedImages.remove(fileName)
} }
fun <T> createTmpFileAndDelete(onCreated: (File) -> T): T { fun <T> createTmpFileAndDelete(dir: File = tmpDir, onCreated: (File) -> T): T {
val tmpFile = File(tmpDir, UUID.randomUUID().toString()) val tmpFile = File(dir, UUID.randomUUID().toString())
tmpFile.parentFile.mkdirs()
tmpFile.deleteOnExit() tmpFile.deleteOnExit()
ChatModel.filesToDelete.add(tmpFile) ChatModel.filesToDelete.add(tmpFile)
try { try {
@@ -16,6 +16,8 @@ import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.desktop.ui.tooling.preview.Preview import androidx.compose.desktop.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp import androidx.compose.ui.unit.sp
import chat.simplex.common.model.ChatController.appPrefs
import chat.simplex.common.model.ChatModel
import chat.simplex.common.model.* import chat.simplex.common.model.*
import chat.simplex.common.model.ChatController.setConditionsNotified import chat.simplex.common.model.ChatController.setConditionsNotified
import chat.simplex.common.model.ServerOperator.Companion.dummyOperatorInfo import chat.simplex.common.model.ServerOperator.Companion.dummyOperatorInfo
@@ -766,7 +768,9 @@ private val versionDescriptions: List<VersionDescription> = listOf(
private val lastVersion = versionDescriptions.last().version private val lastVersion = versionDescriptions.last().version
fun setLastVersionDefault(m: ChatModel) { fun setLastVersionDefault(m: ChatModel) {
m.controller.appPrefs.whatsNewVersion.set(lastVersion) if (appPrefs.whatsNewVersion.get() != lastVersion) {
appPrefs.whatsNewVersion.set(lastVersion)
}
} }
fun shouldShowWhatsNew(m: ChatModel): Boolean { fun shouldShowWhatsNew(m: ChatModel): Boolean {
@@ -14,7 +14,6 @@ import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalUriHandler import androidx.compose.ui.platform.LocalUriHandler
import chat.simplex.common.model.* import chat.simplex.common.model.*
import chat.simplex.common.model.ChatController.appPrefs
import chat.simplex.common.platform.* import chat.simplex.common.platform.*
import dev.icerock.moko.resources.compose.painterResource import dev.icerock.moko.resources.compose.painterResource
import dev.icerock.moko.resources.compose.stringResource import dev.icerock.moko.resources.compose.stringResource
@@ -45,12 +44,6 @@ fun DeveloperView(withAuth: (title: String, desc: String, block: () -> Unit) ->
if (devTools.value) { if (devTools.value) {
SectionDividerSpaced(maxTopPadding = true) SectionDividerSpaced(maxTopPadding = true)
SectionView(stringResource(MR.strings.developer_options_section).uppercase()) { SectionView(stringResource(MR.strings.developer_options_section).uppercase()) {
SettingsActionItemWithContent(painterResource(MR.images.ic_breaking_news), stringResource(MR.strings.debug_logs)) {
DefaultSwitch(
checked = remember { appPrefs.logLevel.state }.value <= LogLevel.DEBUG,
onCheckedChange = { appPrefs.logLevel.set(if (it) LogLevel.DEBUG else LogLevel.WARNING) }
)
}
SettingsPreferenceItem(painterResource(MR.images.ic_drive_folder_upload), stringResource(MR.strings.confirm_database_upgrades), m.controller.appPrefs.confirmDBUpgrades) SettingsPreferenceItem(painterResource(MR.images.ic_drive_folder_upload), stringResource(MR.strings.confirm_database_upgrades), m.controller.appPrefs.confirmDBUpgrades)
if (appPlatform.isDesktop) { if (appPlatform.isDesktop) {
TerminalAlwaysVisibleItem(m.controller.appPrefs.terminalAlwaysVisible) { checked -> TerminalAlwaysVisibleItem(m.controller.appPrefs.terminalAlwaysVisible) { checked ->
@@ -907,13 +907,13 @@
<string name="show_dev_options">Show:</string> <string name="show_dev_options">Show:</string>
<string name="hide_dev_options">Hide:</string> <string name="hide_dev_options">Hide:</string>
<string name="show_developer_options">Show developer options</string> <string name="show_developer_options">Show developer options</string>
<string name="debug_logs">Enable logs</string>
<string name="developer_options">Database IDs and Transport isolation option.</string> <string name="developer_options">Database IDs and Transport isolation option.</string>
<string name="developer_options_section">Developer options</string> <string name="developer_options_section">Developer options</string>
<string name="show_internal_errors">Show internal errors</string> <string name="show_internal_errors">Show internal errors</string>
<string name="show_slow_api_calls">Show slow API calls</string> <string name="show_slow_api_calls">Show slow API calls</string>
<string name="shutdown_alert_question">Shutdown?</string> <string name="shutdown_alert_question">Shutdown?</string>
<string name="shutdown_alert_desc">Notifications will stop working until you re-launch the app</string> <string name="shutdown_alert_desc">Notifications will stop working until you re-launch the app</string>
<string name="prefs_error_saving_settings">Error saving settings</string>
<!-- Address Items - UserAddressView.kt --> <!-- Address Items - UserAddressView.kt -->
<string name="create_address">Create address</string> <string name="create_address">Create address</string>
@@ -1339,7 +1339,6 @@
<string name="chat_database_exported_migrate">You may migrate the exported database.</string> <string name="chat_database_exported_migrate">You may migrate the exported database.</string>
<string name="chat_database_exported_not_all_files">Some file(s) were not exported</string> <string name="chat_database_exported_not_all_files">Some file(s) were not exported</string>
<string name="chat_database_exported_continue">Continue</string> <string name="chat_database_exported_continue">Continue</string>
<string name="error_saving_database">Error saving database</string>
<!-- DatabaseEncryptionView.kt --> <!-- DatabaseEncryptionView.kt -->
<string name="save_passphrase_in_keychain">Save passphrase in Keystore</string> <string name="save_passphrase_in_keychain">Save passphrase in Keystore</string>
@@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" height="22px" viewBox="0 -960 960 960" width="22px" fill="#5f6368"><path d="M262.5-285q11.5 0 20.25-8.5t8.75-20q0-11.5-8.75-20.25t-20.25-8.75q-11.5 0-20 8.75T234-313.5q0 11.5 8.5 20t20 8.5Zm-29-168.5H291v-227h-57.5v227Zm217.5 174h275.5V-337H451v57.5Zm0-174h275.5V-511H451v57.5Zm0-169.5h275.5v-57.5H451v57.5ZM134.5-124.5q-22.97 0-40.23-17.27Q77-159.03 77-182v-596q0-22.97 17.27-40.23 17.26-17.27 40.23-17.27h691q22.97 0 40.23 17.27Q883-800.97 883-778v596q0 22.97-17.27 40.23-17.26 17.27-40.23 17.27h-691Zm0-57.5h691v-596h-691v596Zm0 0v-596 596Z"/></svg>

Before

Width:  |  Height:  |  Size: 592 B

@@ -67,7 +67,7 @@ object NtfManager {
ntf.second.close() ntf.second.close()
} catch (e: Exception) { } catch (e: Exception) {
// Can be java.lang.UnsupportedOperationException, for example. May do nothing // Can be java.lang.UnsupportedOperationException, for example. May do nothing
Log.e(TAG, "Failed to close notification: ${e.stackTraceToString()}") println("Failed to close notification: ${e.stackTraceToString()}")
}*/ }*/
} }
} }
@@ -85,8 +85,7 @@ object NtfManager {
} }
fun cancelAllNotifications() { fun cancelAllNotifications() {
// prevNtfs.forEach { try { it.second.close() } catch (e: Exception) { Log.e(TAG, "Failed to close notification: ${e // prevNtfs.forEach { try { it.second.close() } catch (e: Exception) { println("Failed to close notification: ${e.stackTraceToString()}") } }
// .stackTraceToString()}") } }
withBGApi { withBGApi {
prevNtfsMutex.withLock { prevNtfsMutex.withLock {
prevNtfs.clear() prevNtfs.clear()
@@ -154,7 +153,7 @@ object NtfManager {
ImageIO.write(icon.toAwtImage(), "PNG", newFile.outputStream()) ImageIO.write(icon.toAwtImage(), "PNG", newFile.outputStream())
newFile.absolutePath newFile.absolutePath
} catch (e: Exception) { } catch (e: Exception) {
Log.e(TAG, "Failed to write an icon to tmpDir: ${e.stackTraceToString()}") println("Failed to write an icon to tmpDir: ${e.stackTraceToString()}")
null null
} }
} else null } else null
@@ -17,6 +17,8 @@ actual val wallpapersDir: File = File(dataDir.absolutePath + File.separator + "s
actual val coreTmpDir: File = File(dataDir.absolutePath + File.separator + "tmp") actual val coreTmpDir: File = File(dataDir.absolutePath + File.separator + "tmp")
actual val dbAbsolutePrefixPath: String = dataDir.absolutePath + File.separator + "simplex_v1" actual val dbAbsolutePrefixPath: String = dataDir.absolutePath + File.separator + "simplex_v1"
actual val preferencesDir = File(desktopPlatform.configPath).also { it.parentFile.mkdirs() } actual val preferencesDir = File(desktopPlatform.configPath).also { it.parentFile.mkdirs() }
actual val preferencesTmpDir = File(desktopPlatform.configPath, "tmp")
.also { it.deleteRecursively() }
actual val chatDatabaseFileName: String = "simplex_v1_chat.db" actual val chatDatabaseFileName: String = "simplex_v1_chat.db"
actual val agentDatabaseFileName: String = "simplex_v1_agent.db" actual val agentDatabaseFileName: String = "simplex_v1_agent.db"
@@ -1,10 +1,8 @@
package chat.simplex.common.platform package chat.simplex.common.platform
import chat.simplex.common.model.ChatController.appPrefs
actual object Log { actual object Log {
actual fun d(tag: String, text: String) { if (appPrefs.logLevel.get() <= LogLevel.DEBUG && appPrefs.developerTools.get()) println("D: $text") } actual fun d(tag: String, text: String) = println("D: $text")
actual fun e(tag: String, text: String) { if (appPrefs.logLevel.get() <= LogLevel.ERROR || !appPrefs.developerTools.get()) println("E: $text") } actual fun e(tag: String, text: String) = println("E: $text")
actual fun i(tag: String, text: String) { if (appPrefs.logLevel.get() <= LogLevel.INFO && appPrefs.developerTools.get()) println("I: $text") } actual fun i(tag: String, text: String) = println("I: $text")
actual fun w(tag: String, text: String) { if (appPrefs.logLevel.get() <= LogLevel.WARNING || !appPrefs.developerTools.get()) println("W: $text") } actual fun w(tag: String, text: String) = println("W: $text")
} }
@@ -9,15 +9,16 @@ import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.* import androidx.compose.ui.unit.*
import chat.simplex.common.simplexWindowState import chat.simplex.common.simplexWindowState
import chat.simplex.common.views.helpers.* import chat.simplex.common.views.helpers.*
import chat.simplex.res.MR
import com.jthemedetecor.OsThemeDetector import com.jthemedetecor.OsThemeDetector
import com.russhwolf.settings.* import com.russhwolf.settings.*
import dev.icerock.moko.resources.ImageResource import dev.icerock.moko.resources.ImageResource
import dev.icerock.moko.resources.StringResource import dev.icerock.moko.resources.StringResource
import dev.icerock.moko.resources.desc.desc import dev.icerock.moko.resources.desc.desc
import kotlinx.coroutines.*
import java.io.File import java.io.File
import java.nio.file.Files
import java.nio.file.StandardCopyOption
import java.util.* import java.util.*
import java.util.concurrent.Executors
@Composable @Composable
actual fun font(name: String, res: String, weight: FontWeight, style: FontStyle): Font = actual fun font(name: String, res: String, weight: FontWeight, style: FontStyle): Font =
@@ -37,10 +38,8 @@ catch (e: Exception) {
private val settingsFile = private val settingsFile =
File(desktopPlatform.configPath + File.separator + "settings.properties") File(desktopPlatform.configPath + File.separator + "settings.properties")
.also { it.parentFile.mkdirs() }
private val settingsThemesFile = private val settingsThemesFile =
File(desktopPlatform.configPath + File.separator + "themes.properties") File(desktopPlatform.configPath + File.separator + "themes.properties")
.also { it.parentFile.mkdirs() }
private val settingsProps = private val settingsProps =
Properties() Properties()
@@ -61,11 +60,35 @@ private val settingsThemesProps =
Properties() Properties()
.also { props -> try { settingsThemesFile.reader().use { props.load(it) } } catch (e: Exception) { /**/ } } .also { props -> try { settingsThemesFile.reader().use { props.load(it) } } catch (e: Exception) { /**/ } }
private const val lock = "settingsSaver"
private val settingsWriterThread = Executors.newSingleThreadExecutor().asCoroutineDispatcher() actual val settings: Settings = PropertiesSettings(settingsProps) {
synchronized(lock) {
actual val settings: Settings = PropertiesSettings(settingsProps) { CoroutineScope(settingsWriterThread).launch { settingsFile.writer().use { settingsProps.store(it, "") } } } try {
actual val settingsThemes: Settings = PropertiesSettings(settingsThemesProps) { CoroutineScope(settingsWriterThread).launch { settingsThemesFile.writer().use { settingsThemesProps.store(it, "") } } } createTmpFileAndDelete(preferencesTmpDir) { tmpFile ->
tmpFile.writer().use { settingsProps.store(it, "") }
settingsFile.parentFile.mkdirs()
Files.move(tmpFile.toPath(), settingsFile.toPath(), StandardCopyOption.REPLACE_EXISTING)
}
} catch (e: Exception) {
AlertManager.shared.showAlertMsg(generalGetString(MR.strings.prefs_error_saving_settings), e.stackTraceToString())
throw e
}
}
}
actual val settingsThemes: Settings = PropertiesSettings(settingsThemesProps) {
synchronized(lock) {
try {
createTmpFileAndDelete(preferencesTmpDir) { tmpFile ->
tmpFile.writer().use { settingsThemesProps.store(it, "") }
settingsThemesFile.parentFile.mkdirs()
Files.move(tmpFile.toPath(), settingsThemesFile.toPath(), StandardCopyOption.REPLACE_EXISTING)
}
} catch (e: Exception) {
AlertManager.shared.showAlertMsg(generalGetString(MR.strings.prefs_error_saving_settings), e.stackTraceToString())
throw e
}
}
}
actual fun windowOrientation(): WindowOrientation = actual fun windowOrientation(): WindowOrientation =
if (simplexWindowState.windowState.size.width > simplexWindowState.windowState.size.height) { if (simplexWindowState.windowState.size.width > simplexWindowState.windowState.size.height) {
+4 -4
View File
@@ -24,11 +24,11 @@ android.nonTransitiveRClass=true
kotlin.mpp.androidSourceSetLayoutVersion=2 kotlin.mpp.androidSourceSetLayoutVersion=2
kotlin.jvm.target=11 kotlin.jvm.target=11
android.version_name=6.2.1 android.version_name=6.2
android.version_code=261 android.version_code=259
desktop.version_name=6.2.1 desktop.version_name=6.2
desktop.version_code=83 desktop.version_code=82
kotlin.version=1.9.23 kotlin.version=1.9.23
gradle.plugin.version=8.2.0 gradle.plugin.version=8.2.0
+1 -1
View File
@@ -12,7 +12,7 @@ constraints: zip +disable-bzip2 +disable-zstd
source-repository-package source-repository-package
type: git type: git
location: https://github.com/simplex-chat/simplexmq.git location: https://github.com/simplex-chat/simplexmq.git
tag: 0a827307d666de819d5cef17f70a78be5cd0e410 tag: 79e9447b73cc315ce35042b0a5f210c07ea39b07
source-repository-package source-repository-package
type: git type: git
@@ -38,25 +38,6 @@
</description> </description>
<releases> <releases>
<release version="6.2.1" date="2024-12-12">
<url type="details">https://simplex.chat/blog/20241210-simplex-network-v6-2-servers-by-flux-business-chats.html</url>
<description>
<p>New in v6.2.1:</p>
<ul>
<li>fixes</li>
<li>offer to "fix" encryption when calling or making direct connection with member.</li>
<li>broken layout.</li>
<li>option to enable debug logs (disabled by default).</li>
<li>show who reacted in direct chats.</li>
</ul>
<p>New in v6.2:</p>
<ul>
<li>SimpleX Chat and Flux made an agreement to include servers operated by Flux into the app to improve metadata privacy.</li>
<li>Business chats your customers privacy.</li>
<li>Improved user experience in chats: open on the first unread, jump to quoted messages, see who reacted.</li>
</ul>
</description>
</release>
<release version="6.2.0" date="2024-12-08"> <release version="6.2.0" date="2024-12-08">
<url type="details">https://simplex.chat/blog/20241210-simplex-network-v6-2-servers-by-flux-business-chats.html</url> <url type="details">https://simplex.chat/blog/20241210-simplex-network-v6-2-servers-by-flux-business-chats.html</url>
<description> <description>
+1 -1
View File
@@ -1,5 +1,5 @@
{ {
"https://github.com/simplex-chat/simplexmq.git"."0a827307d666de819d5cef17f70a78be5cd0e410" = "09z0y6w1c51szd9r90fl259inn76yahfbb164mawd34sr41ccp7g"; "https://github.com/simplex-chat/simplexmq.git"."79e9447b73cc315ce35042b0a5f210c07ea39b07" = "16z7z5a3f7gw0h188manykp008d1bqpydlrj7h497mgyjmp4cy9m";
"https://github.com/simplex-chat/hs-socks.git"."a30cc7a79a08d8108316094f8f2f82a0c5e1ac51" = "0yasvnr7g91k76mjkamvzab2kvlb1g5pspjyjn2fr6v83swjhj38"; "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/direct-sqlcipher.git"."f814ee68b16a9447fbb467ccc8f29bdd3546bfd9" = "1ql13f4kfwkbaq7nygkxgw84213i0zm7c1a8hwvramayxl38dq5d";
"https://github.com/simplex-chat/sqlcipher-simple.git"."a46bd361a19376c5211f1058908fc0ae6bf42446" = "1z0r78d8f0812kxbgsm735qf6xx8lvaz27k1a0b4a2m0sshpd5gl"; "https://github.com/simplex-chat/sqlcipher-simple.git"."a46bd361a19376c5211f1058908fc0ae6bf42446" = "1z0r78d8f0812kxbgsm735qf6xx8lvaz27k1a0b4a2m0sshpd5gl";
+2 -1
View File
@@ -92,8 +92,9 @@ import Simplex.Chat.Types.Preferences
import Simplex.Chat.Types.Shared import Simplex.Chat.Types.Shared
import Simplex.Chat.Util (encryptFile, liftIOEither, shuffle) import Simplex.Chat.Util (encryptFile, liftIOEither, shuffle)
import qualified Simplex.Chat.Util as U import qualified Simplex.Chat.Util as U
import Simplex.FileTransfer.Client.Main (maxFileSize, maxFileSizeHard)
import Simplex.FileTransfer.Client.Presets (defaultXFTPServers) import Simplex.FileTransfer.Client.Presets (defaultXFTPServers)
import Simplex.FileTransfer.Description (FileDescriptionURI (..), ValidFileDescription, maxFileSize, maxFileSizeHard) import Simplex.FileTransfer.Description (FileDescriptionURI (..), ValidFileDescription)
import qualified Simplex.FileTransfer.Description as FD import qualified Simplex.FileTransfer.Description as FD
import Simplex.FileTransfer.Protocol (FileParty (..), FilePartyI) import Simplex.FileTransfer.Protocol (FileParty (..), FilePartyI)
import qualified Simplex.FileTransfer.Transport as XFTP import qualified Simplex.FileTransfer.Transport as XFTP