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,
composeState: $composeState,
selectedMember: $selectedMember,
showChatInfoSheet: $showChatInfoSheet,
revealedChatItem: $revealedChatItem,
selectedChatItems: $selectedChatItems,
forwardedChatItems: $forwardedChatItems
@@ -894,14 +893,12 @@ struct ChatView: View {
private struct ChatItemWithMenu: View {
@EnvironmentObject var m: ChatModel
@EnvironmentObject var theme: AppTheme
@AppStorage(DEFAULT_PROFILE_IMAGE_CORNER_RADIUS) private var profileRadius = defaultProfileImageCorner
@Binding @ObservedObject var chat: Chat
@ObservedObject var dummyModel: ChatItemDummyModel = .shared
let chatItem: ChatItem
let maxWidth: CGFloat
@Binding var composeState: ComposeState
@Binding var selectedMember: GMember?
@Binding var showChatInfoSheet: Bool
@Binding var revealedChatItem: ChatItem?
@State private var deletingItem: ChatItem? = nil
@@ -1258,22 +1255,16 @@ struct ChatView: View {
setReaction(ci, add: !r.userReacted, reaction: r.reaction)
}
}
switch chat.chatInfo {
case let .group(groupInfo):
if case let .group(groupInfo) = chat.chatInfo {
v.contextMenu {
ReactionContextMenu(
groupInfo: groupInfo,
itemId: ci.id,
reactionCount: r,
selectedMember: $selectedMember,
profileRadius: profileRadius
selectedMember: $selectedMember
)
}
case let .direct(contact):
v.contextMenu {
contactReactionMenu(contact, r)
}
default:
} else {
v
}
}
@@ -1776,20 +1767,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 {
@EnvironmentObject var theme: AppTheme
@@ -1882,12 +1859,13 @@ struct ReactionContextMenu: View {
var itemId: Int64
var reactionCount: CIReactionCount
@Binding var selectedMember: GMember?
var profileRadius: CGFloat
@State private var memberReactions: [MemberReaction] = []
@AppStorage(DEFAULT_PROFILE_IMAGE_CORNER_RADIUS) private var radius = defaultProfileImageCorner
var body: some View {
groupMemberReactionList()
.task {
logger.debug("ReactionContextMenu task \(radius)")
await loadChatItemReaction()
}
}
@@ -1911,12 +1889,27 @@ struct ReactionContextMenu: View {
selectedMember = member
}
} 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)
}
}
}
private func cropImage(_ img: String?) -> UIImage? {
return if let originalImage = imageFromBase64(img) {
maskToCustomShape(originalImage, size: 30, radius: radius)
} else {
nil
}
}
private func loadChatItemReaction() async {
do {
@@ -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 {
let path = Path { path in
if radius >= 50 {
+20 -20
View File
@@ -1931,7 +1931,7 @@
CLANG_TIDY_MISC_REDUNDANT_EXPRESSION = YES;
CODE_SIGN_ENTITLEMENTS = "SimpleX (iOS).entitlements";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 255;
CURRENT_PROJECT_VERSION = 254;
DEAD_CODE_STRIPPING = YES;
DEVELOPMENT_TEAM = 5NN7GUYB6T;
ENABLE_BITCODE = NO;
@@ -1956,7 +1956,7 @@
"@executable_path/Frameworks",
);
LLVM_LTO = YES_THIN;
MARKETING_VERSION = 6.2.1;
MARKETING_VERSION = 6.2;
PRODUCT_BUNDLE_IDENTIFIER = chat.simplex.app;
PRODUCT_NAME = SimpleX;
SDKROOT = iphoneos;
@@ -1980,7 +1980,7 @@
CLANG_TIDY_MISC_REDUNDANT_EXPRESSION = YES;
CODE_SIGN_ENTITLEMENTS = "SimpleX (iOS).entitlements";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 255;
CURRENT_PROJECT_VERSION = 254;
DEAD_CODE_STRIPPING = YES;
DEVELOPMENT_TEAM = 5NN7GUYB6T;
ENABLE_BITCODE = NO;
@@ -2005,7 +2005,7 @@
"@executable_path/Frameworks",
);
LLVM_LTO = YES;
MARKETING_VERSION = 6.2.1;
MARKETING_VERSION = 6.2;
PRODUCT_BUNDLE_IDENTIFIER = chat.simplex.app;
PRODUCT_NAME = SimpleX;
SDKROOT = iphoneos;
@@ -2021,11 +2021,11 @@
buildSettings = {
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 255;
CURRENT_PROJECT_VERSION = 254;
DEVELOPMENT_TEAM = 5NN7GUYB6T;
GENERATE_INFOPLIST_FILE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 15.0;
MARKETING_VERSION = 6.2.1;
MARKETING_VERSION = 6.2;
PRODUCT_BUNDLE_IDENTIFIER = "chat.simplex.Tests-iOS";
PRODUCT_NAME = "$(TARGET_NAME)";
SDKROOT = iphoneos;
@@ -2041,11 +2041,11 @@
buildSettings = {
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 255;
CURRENT_PROJECT_VERSION = 254;
DEVELOPMENT_TEAM = 5NN7GUYB6T;
GENERATE_INFOPLIST_FILE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 15.0;
MARKETING_VERSION = 6.2.1;
MARKETING_VERSION = 6.2;
PRODUCT_BUNDLE_IDENTIFIER = "chat.simplex.Tests-iOS";
PRODUCT_NAME = "$(TARGET_NAME)";
SDKROOT = iphoneos;
@@ -2066,7 +2066,7 @@
CODE_SIGN_ENTITLEMENTS = "SimpleX NSE/SimpleX NSE.entitlements";
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 255;
CURRENT_PROJECT_VERSION = 254;
DEVELOPMENT_TEAM = 5NN7GUYB6T;
ENABLE_BITCODE = NO;
GCC_OPTIMIZATION_LEVEL = s;
@@ -2081,7 +2081,7 @@
"@executable_path/../../Frameworks",
);
LLVM_LTO = YES;
MARKETING_VERSION = 6.2.1;
MARKETING_VERSION = 6.2;
PRODUCT_BUNDLE_IDENTIFIER = "chat.simplex.app.SimpleX-NSE";
PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = "";
@@ -2103,7 +2103,7 @@
CODE_SIGN_ENTITLEMENTS = "SimpleX NSE/SimpleX NSE.entitlements";
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 255;
CURRENT_PROJECT_VERSION = 254;
DEVELOPMENT_TEAM = 5NN7GUYB6T;
ENABLE_BITCODE = NO;
ENABLE_CODE_COVERAGE = NO;
@@ -2118,7 +2118,7 @@
"@executable_path/../../Frameworks",
);
LLVM_LTO = YES;
MARKETING_VERSION = 6.2.1;
MARKETING_VERSION = 6.2;
PRODUCT_BUNDLE_IDENTIFIER = "chat.simplex.app.SimpleX-NSE";
PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = "";
@@ -2140,7 +2140,7 @@
CLANG_TIDY_BUGPRONE_REDUNDANT_BRANCH_CONDITION = YES;
CLANG_TIDY_MISC_REDUNDANT_EXPRESSION = YES;
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 255;
CURRENT_PROJECT_VERSION = 254;
DEFINES_MODULE = YES;
DEVELOPMENT_TEAM = 5NN7GUYB6T;
DYLIB_COMPATIBILITY_VERSION = 1;
@@ -2166,7 +2166,7 @@
"$(PROJECT_DIR)/Libraries/sim",
);
LLVM_LTO = YES;
MARKETING_VERSION = 6.2.1;
MARKETING_VERSION = 6.2;
PRODUCT_BUNDLE_IDENTIFIER = chat.simplex.SimpleXChat;
PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)";
SDKROOT = iphoneos;
@@ -2191,7 +2191,7 @@
CLANG_TIDY_BUGPRONE_REDUNDANT_BRANCH_CONDITION = YES;
CLANG_TIDY_MISC_REDUNDANT_EXPRESSION = YES;
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 255;
CURRENT_PROJECT_VERSION = 254;
DEFINES_MODULE = YES;
DEVELOPMENT_TEAM = 5NN7GUYB6T;
DYLIB_COMPATIBILITY_VERSION = 1;
@@ -2217,7 +2217,7 @@
"$(PROJECT_DIR)/Libraries/sim",
);
LLVM_LTO = YES;
MARKETING_VERSION = 6.2.1;
MARKETING_VERSION = 6.2;
PRODUCT_BUNDLE_IDENTIFIER = chat.simplex.SimpleXChat;
PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)";
SDKROOT = iphoneos;
@@ -2242,7 +2242,7 @@
CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
CODE_SIGN_ENTITLEMENTS = "SimpleX SE/SimpleX SE.entitlements";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 255;
CURRENT_PROJECT_VERSION = 254;
DEVELOPMENT_TEAM = 5NN7GUYB6T;
ENABLE_USER_SCRIPT_SANDBOXING = YES;
GCC_C_LANGUAGE_STANDARD = gnu17;
@@ -2257,7 +2257,7 @@
"@executable_path/../../Frameworks",
);
LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
MARKETING_VERSION = 6.2.1;
MARKETING_VERSION = 6.2;
PRODUCT_BUNDLE_IDENTIFIER = "chat.simplex.app.SimpleX-SE";
PRODUCT_NAME = "$(TARGET_NAME)";
SDKROOT = iphoneos;
@@ -2276,7 +2276,7 @@
CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
CODE_SIGN_ENTITLEMENTS = "SimpleX SE/SimpleX SE.entitlements";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 255;
CURRENT_PROJECT_VERSION = 254;
DEVELOPMENT_TEAM = 5NN7GUYB6T;
ENABLE_USER_SCRIPT_SANDBOXING = YES;
GCC_C_LANGUAGE_STANDARD = gnu17;
@@ -2291,7 +2291,7 @@
"@executable_path/../../Frameworks",
);
LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
MARKETING_VERSION = 6.2.1;
MARKETING_VERSION = 6.2;
PRODUCT_BUNDLE_IDENTIFIER = "chat.simplex.app.SimpleX-SE";
PRODUCT_NAME = "$(TARGET_NAME)";
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 dbAbsolutePrefixPath: String = dataDir.absolutePath + File.separator + "files"
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 agentDatabaseFileName: String = "files_agent.db"
@@ -1,11 +1,10 @@
package chat.simplex.common.platform
import android.util.Log
import chat.simplex.common.model.ChatController.appPrefs
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 e(tag: String, text: String) { if (appPrefs.logLevel.get() <= LogLevel.ERROR || !appPrefs.developerTools.get()) Log.e(tag, text) }
actual fun i(tag: String, text: String) { if (appPrefs.logLevel.get() <= LogLevel.INFO && appPrefs.developerTools.get()) Log.i(tag, text) }
actual fun w(tag: String, text: String) { if (appPrefs.logLevel.get() <= LogLevel.WARNING || !appPrefs.developerTools.get()) Log.w(tag, text) }
actual fun d(tag: String, text: String) = Log.d(tag, text).run{}
actual fun e(tag: String, text: String) = Log.e(tag, text).run{}
actual fun i(tag: String, text: String) = Log.i(tag, text).run{}
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 chatStopped = mkBoolPreference(SHARED_PREFS_CHAT_STOPPED, 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 showSlowApiCalls = mkBoolPreference(SHARED_PREFS_SHOW_SLOW_API_CALLS, 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_STOPPED = "ChatStopped"
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_SLOW_API_CALLS = "ShowSlowApiCalls"
private const val SHARED_PREFS_TERMINAL_ALWAYS_VISIBLE = "TerminalAlwaysVisible"
@@ -3117,8 +3115,12 @@ class SharedPreference<T>(val get: () -> T, set: (T) -> Unit) {
init {
this.set = { value ->
set(value)
_state.value = value
try {
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 chat.simplex.common.model.*
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 com.charleskorn.kaml.*
import kotlinx.serialization.encodeToString
@@ -11,6 +11,8 @@ import java.io.*
import java.net.URI
import java.net.URLDecoder
import java.net.URLEncoder
import java.nio.file.Files
import java.nio.file.StandardCopyOption
expect val dataDir: File
expect val tmpDir: File
@@ -20,6 +22,7 @@ expect val wallpapersDir: File
expect val coreTmpDir: File
expect val dbAbsolutePrefixPath: String
expect val preferencesDir: File
expect val preferencesTmpDir: File
expect val chatDatabaseFileName: String
expect val agentDatabaseFileName: String
@@ -142,16 +145,23 @@ fun readThemeOverrides(): List<ThemeOverrides> {
}
}
private const val lock = "themesWriter"
fun writeThemeOverrides(overrides: List<ThemeOverrides>): Boolean =
try {
File(getPreferenceFilePath("themes.yaml")).outputStream().use {
val string = yaml.encodeToString(ThemesFile(themes = overrides))
it.bufferedWriter().use { it.write(string) }
synchronized(lock) {
try {
val themesFile = File(getPreferenceFilePath("themes.yaml"))
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) =
@@ -2,10 +2,6 @@ package chat.simplex.common.platform
const val TAG = "SIMPLEX"
enum class LogLevel {
DEBUG, INFO, WARNING, ERROR
}
expect object Log {
fun d(tag: String, text: String)
fun e(tag: String, text: String)
@@ -102,7 +102,9 @@ object ThemeManager {
}
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())
platform.androidSetNightModeIfSupported()
val c = CurrentColors.value.colors
@@ -296,7 +296,6 @@ fun ChatItemInfoView(chatRh: Long?, ci: ChatItem, ciInfo: ChatItemInfo, devTools
}
}
SectionBottomSpacer()
SectionBottomSpacer()
}
}
@@ -310,7 +309,6 @@ fun ChatItemInfoView(chatRh: Long?, ci: ChatItem, ciInfo: ChatItemInfo, devTools
QuotedMsgView(qi)
}
SectionBottomSpacer()
SectionBottomSpacer()
}
}
@@ -326,7 +324,6 @@ fun ChatItemInfoView(chatRh: Long?, ci: ChatItem, ciInfo: ChatItemInfo, devTools
ForwardedFromView(forwardedFromItem)
}
SectionBottomSpacer()
SectionBottomSpacer()
}
}
@@ -398,7 +395,6 @@ fun ChatItemInfoView(chatRh: Long?, ci: ChatItem, ciInfo: ChatItemInfo, devTools
}
}
SectionBottomSpacer()
SectionBottomSpacer()
}
}
@@ -437,11 +433,12 @@ fun ChatItemInfoView(chatRh: Long?, ci: ChatItem, ciInfo: ChatItemInfo, devTools
Column {
if (numTabs() > 1) {
Box(
Column(
Modifier
.fillMaxHeight()
.fillMaxHeight(),
verticalArrangement = Arrangement.SpaceBetween
) {
Column {
Column(Modifier.weight(1f)) {
when (val sel = selection.value) {
is CIInfoTab.Delivery -> {
DeliveryTab(sel.memberDeliveryStatuses)
@@ -482,7 +479,7 @@ fun ChatItemInfoView(chatRh: Long?, ci: ChatItem, ciInfo: ChatItemInfo, devTools
}
}
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(
selectedTabIndex = availableTabs.indexOfFirst { it::class == selection.value::class },
Modifier.height(AppBarHeight * fontSizeSqrtMultiplier),
@@ -665,18 +665,13 @@ fun ChatLayout(
AdaptingBottomPaddingLayout(Modifier, CHAT_COMPOSE_LAYOUT_ID, composeViewHeight) {
if (chatInfo != null) {
Box(Modifier.fillMaxSize()) {
// disables scrolling to top of chat item on click inside the bubble
CompositionLocalProvider(LocalBringIntoViewSpec provides object : BringIntoViewSpec {
override fun calculateScrollDistance(offset: Float, size: Float, containerSize: Float): Float = 0f
}) {
ChatItemsList(
remoteHostId, chatInfo, unreadCount, composeState, composeViewHeight, searchValue,
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,
)
}
ChatItemsList(
remoteHostId, chatInfo, unreadCount, composeState, composeViewHeight, searchValue,
useLinkPreviews, linkMode, selectedChatItems, showMemberInfo, 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(
@@ -944,7 +939,6 @@ fun BoxScope.ChatItemsList(
linkMode: SimplexLinkMode,
selectedChatItems: MutableState<Set<Long>?>,
showMemberInfo: (GroupInfo, GroupMember) -> Unit,
showChatInfo: () -> Unit,
loadMessages: suspend (ChatId, ChatPagination, ActiveChatState, visibleItemIndexesNonReversed: () -> IntRange) -> Unit,
deleteMessage: (Long, CIDeleteMode) -> Unit,
deleteMessages: (List<Long>) -> Unit,
@@ -1072,7 +1066,7 @@ fun BoxScope.ChatItemsList(
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 chat.simplex.common.model.*
import chat.simplex.common.model.ChatModel.controller
import chat.simplex.common.model.ChatModel.currentUser
import chat.simplex.common.platform.*
import chat.simplex.common.ui.theme.*
import chat.simplex.common.views.chat.*
import chat.simplex.common.views.helpers.*
import chat.simplex.res.MR
import kotlinx.coroutines.launch
import kotlinx.datetime.Clock
import kotlin.math.*
@@ -51,12 +51,6 @@ fun chatEventText(eventText: String, ts: String): AnnotatedString =
withStyle(chatEventStyle) { append("$eventText $ts") }
}
data class ChatItemReactionMenuItem (
val name: String,
val image: String?,
val onClick: (() -> Unit)?
)
@Composable
fun ChatItemView(
rhId: Long?,
@@ -93,7 +87,6 @@ fun ChatItemView(
showItemDetails: (ChatInfo, ChatItem) -> Unit,
reveal: (Boolean) -> Unit,
showMemberInfo: (GroupInfo, GroupMember) -> Unit,
showChatInfo: () -> Unit,
developerTools: Boolean,
showViaProxy: Boolean,
showTimestamp: Boolean,
@@ -127,7 +120,7 @@ fun ChatItemView(
Row(verticalAlignment = Alignment.CenterVertically, modifier = Modifier.chatItemOffset(cItem, itemSeparation.largeGap, inverted = true, revealed = true)) {
cItem.reactions.forEach { r ->
val showReactionMenu = remember { mutableStateOf(false) }
val reactionMenuItems = remember { mutableStateOf(emptyList<ChatItemReactionMenuItem>()) }
val reactionMembers = remember { mutableStateOf(emptyList<MemberReaction>()) }
val interactionSource = remember { MutableInteractionSource() }
val enterInteraction = remember { HoverInteraction.Enter() }
KeyChangeEffect(highlighted.value) {
@@ -141,39 +134,18 @@ fun ChatItemView(
var modifier = Modifier.padding(horizontal = 5.dp, vertical = 2.dp).clip(RoundedCornerShape(8.dp))
if (cInfo.featureEnabled(ChatFeature.Reactions)) {
fun showReactionsMenu() {
when (cInfo) {
is ChatInfo.Group -> {
withBGApi {
try {
val members = controller.apiGetReactionMembers(rhId, cInfo.groupInfo.groupId, cItem.id, r.reaction)
if (members != null) {
showReactionMenu.value = true
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()}")
if (cInfo is ChatInfo.Group) {
withBGApi {
try {
val members = controller.apiGetReactionMembers(rhId, cInfo.groupInfo.groupId, cItem.id, r.reaction)
if (members != null) {
showReactionMenu.value = true
reactionMembers.value = members
}
} 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
@@ -194,19 +166,19 @@ fun ChatItemView(
Row(modifier.padding(2.dp), verticalAlignment = Alignment.CenterVertically) {
ReactionIcon(r.reaction.text, fontSize = 12.sp)
DefaultDropdownMenu(showMenu = showReactionMenu) {
reactionMenuItems.value.forEach { m ->
reactionMembers.value.forEach { m ->
ItemAction(
text = m.name,
composable = { ProfileImage(44.dp, m.image) },
text = m.groupMember.displayName,
composable = { ProfileImage(44.dp, m.groupMember.image) },
onClick = {
val click = m.onClick
if (click != null) {
click()
if (cInfo is ChatInfo.Group && cInfo.groupInfo.membership.groupMemberId != m.groupMember.groupMemberId) {
showMemberInfo(cInfo.groupInfo, m.groupMember)
showReactionMenu.value = false
} else {
showReactionMenu.value = false
}
},
lineLimit = 1,
color = if (m.onClick == null) MaterialTheme.colors.secondary else MenuTextColor
lineLimit = 1
)
}
}
@@ -1216,7 +1188,6 @@ fun PreviewChatItemView(
showItemDetails = { _, _ -> },
reveal = {},
showMemberInfo = { _, _ ->},
showChatInfo = {},
developerTools = false,
showViaProxy = false,
showTimestamp = true,
@@ -1262,7 +1233,6 @@ fun PreviewChatItemViewDeletedContent() {
showItemDetails = { _, _ -> },
reveal = {},
showMemberInfo = { _, _ ->},
showChatInfo = {},
developerTools = false,
showViaProxy = false,
preview = true,
@@ -30,7 +30,6 @@ import kotlinx.datetime.*
import java.io.*
import java.net.URI
import java.nio.file.Files
import java.nio.file.StandardCopyOption
import java.text.SimpleDateFormat
import java.util.*
import kotlin.collections.ArrayList
@@ -45,14 +44,11 @@ fun DatabaseView() {
val chatArchiveFile = remember { mutableStateOf<String?>(null) }
val stopped = remember { m.chatRunning }.value == false
val saveArchiveLauncher = rememberFileChooserLauncher(false) { to: URI? ->
val archive = chatArchiveFile.value
if (archive != null && to != null) {
copyFileToFile(File(archive), to) {}
}
// delete no matter the database was exported or canceled the export process
if (archive != null) {
File(archive).delete()
chatArchiveFile.value = null
val file = chatArchiveFile.value
if (file != null && to != null) {
copyFileToFile(File(file), to) {
chatArchiveFile.value = null
}
}
}
val appFilesCountAndSize = remember { mutableStateOf(directoryFileCountAndSize(appFilesDir.absolutePath)) }
@@ -684,8 +680,6 @@ suspend fun importArchive(
} finally {
File(archivePath).delete()
}
} else {
progressIndicator.value = false
}
return false
}
@@ -697,15 +691,14 @@ private fun saveArchiveFromURI(importedArchiveURI: URI): String? {
if (inputStream != null && archiveName != null) {
val archivePath = "$databaseExportDir${File.separator}$archiveName"
val destFile = File(archivePath)
Files.copy(inputStream, destFile.toPath(), StandardCopyOption.REPLACE_EXISTING)
Files.copy(inputStream, destFile.toPath())
archivePath
} else {
Log.e(TAG, "saveArchiveFromURI null inputStream")
null
}
} catch (e: Exception) {
AlertManager.shared.showAlertMsg(generalGetString(MR.strings.error_saving_database), e.stackTraceToString())
Log.e(TAG, "saveArchiveFromURI error: ${e.stackTraceToString()}")
Log.e(TAG, "saveArchiveFromURI error: ${e.message}")
null
}
}
@@ -316,8 +316,9 @@ fun removeWallpaperFile(fileName: String? = null) {
WallpaperType.cachedImages.remove(fileName)
}
fun <T> createTmpFileAndDelete(onCreated: (File) -> T): T {
val tmpFile = File(tmpDir, UUID.randomUUID().toString())
fun <T> createTmpFileAndDelete(dir: File = tmpDir, onCreated: (File) -> T): T {
val tmpFile = File(dir, UUID.randomUUID().toString())
tmpFile.parentFile.mkdirs()
tmpFile.deleteOnExit()
ChatModel.filesToDelete.add(tmpFile)
try {
@@ -16,6 +16,8 @@ import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.desktop.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
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.ChatController.setConditionsNotified
import chat.simplex.common.model.ServerOperator.Companion.dummyOperatorInfo
@@ -766,7 +768,9 @@ private val versionDescriptions: List<VersionDescription> = listOf(
private val lastVersion = versionDescriptions.last().version
fun setLastVersionDefault(m: ChatModel) {
m.controller.appPrefs.whatsNewVersion.set(lastVersion)
if (appPrefs.whatsNewVersion.get() != lastVersion) {
appPrefs.whatsNewVersion.set(lastVersion)
}
}
fun shouldShowWhatsNew(m: ChatModel): Boolean {
@@ -14,7 +14,6 @@ import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalUriHandler
import chat.simplex.common.model.*
import chat.simplex.common.model.ChatController.appPrefs
import chat.simplex.common.platform.*
import dev.icerock.moko.resources.compose.painterResource
import dev.icerock.moko.resources.compose.stringResource
@@ -45,12 +44,6 @@ fun DeveloperView(withAuth: (title: String, desc: String, block: () -> Unit) ->
if (devTools.value) {
SectionDividerSpaced(maxTopPadding = true)
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)
if (appPlatform.isDesktop) {
TerminalAlwaysVisibleItem(m.controller.appPrefs.terminalAlwaysVisible) { checked ->
@@ -907,13 +907,13 @@
<string name="show_dev_options">Show:</string>
<string name="hide_dev_options">Hide:</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_section">Developer options</string>
<string name="show_internal_errors">Show internal errors</string>
<string name="show_slow_api_calls">Show slow API calls</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="prefs_error_saving_settings">Error saving settings</string>
<!-- Address Items - UserAddressView.kt -->
<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_not_all_files">Some file(s) were not exported</string>
<string name="chat_database_exported_continue">Continue</string>
<string name="error_saving_database">Error saving database</string>
<!-- DatabaseEncryptionView.kt -->
<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()
} catch (e: Exception) {
// 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() {
// prevNtfs.forEach { try { it.second.close() } catch (e: Exception) { Log.e(TAG, "Failed to close notification: ${e
// .stackTraceToString()}") } }
// prevNtfs.forEach { try { it.second.close() } catch (e: Exception) { println("Failed to close notification: ${e.stackTraceToString()}") } }
withBGApi {
prevNtfsMutex.withLock {
prevNtfs.clear()
@@ -154,7 +153,7 @@ object NtfManager {
ImageIO.write(icon.toAwtImage(), "PNG", newFile.outputStream())
newFile.absolutePath
} 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
}
} 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 dbAbsolutePrefixPath: String = dataDir.absolutePath + File.separator + "simplex_v1"
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 agentDatabaseFileName: String = "simplex_v1_agent.db"
@@ -1,10 +1,8 @@
package chat.simplex.common.platform
import chat.simplex.common.model.ChatController.appPrefs
actual object Log {
actual fun d(tag: String, text: String) { if (appPrefs.logLevel.get() <= LogLevel.DEBUG && appPrefs.developerTools.get()) println("D: $text") }
actual fun e(tag: String, text: String) { if (appPrefs.logLevel.get() <= LogLevel.ERROR || !appPrefs.developerTools.get()) println("E: $text") }
actual fun i(tag: String, text: String) { if (appPrefs.logLevel.get() <= LogLevel.INFO && appPrefs.developerTools.get()) println("I: $text") }
actual fun w(tag: String, text: String) { if (appPrefs.logLevel.get() <= LogLevel.WARNING || !appPrefs.developerTools.get()) println("W: $text") }
actual fun d(tag: String, text: String) = println("D: $text")
actual fun e(tag: String, text: String) = println("E: $text")
actual fun i(tag: String, text: String) = println("I: $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 chat.simplex.common.simplexWindowState
import chat.simplex.common.views.helpers.*
import chat.simplex.res.MR
import com.jthemedetecor.OsThemeDetector
import com.russhwolf.settings.*
import dev.icerock.moko.resources.ImageResource
import dev.icerock.moko.resources.StringResource
import dev.icerock.moko.resources.desc.desc
import kotlinx.coroutines.*
import java.io.File
import java.nio.file.Files
import java.nio.file.StandardCopyOption
import java.util.*
import java.util.concurrent.Executors
@Composable
actual fun font(name: String, res: String, weight: FontWeight, style: FontStyle): Font =
@@ -37,10 +38,8 @@ catch (e: Exception) {
private val settingsFile =
File(desktopPlatform.configPath + File.separator + "settings.properties")
.also { it.parentFile.mkdirs() }
private val settingsThemesFile =
File(desktopPlatform.configPath + File.separator + "themes.properties")
.also { it.parentFile.mkdirs() }
private val settingsProps =
Properties()
@@ -61,11 +60,35 @@ private val settingsThemesProps =
Properties()
.also { props -> try { settingsThemesFile.reader().use { props.load(it) } } catch (e: Exception) { /**/ } }
private val settingsWriterThread = Executors.newSingleThreadExecutor().asCoroutineDispatcher()
actual val settings: Settings = PropertiesSettings(settingsProps) { CoroutineScope(settingsWriterThread).launch { settingsFile.writer().use { settingsProps.store(it, "") } } }
actual val settingsThemes: Settings = PropertiesSettings(settingsThemesProps) { CoroutineScope(settingsWriterThread).launch { settingsThemesFile.writer().use { settingsThemesProps.store(it, "") } } }
private const val lock = "settingsSaver"
actual val settings: Settings = PropertiesSettings(settingsProps) {
synchronized(lock) {
try {
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 =
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.jvm.target=11
android.version_name=6.2.1
android.version_code=261
android.version_name=6.2
android.version_code=259
desktop.version_name=6.2.1
desktop.version_code=83
desktop.version_name=6.2
desktop.version_code=82
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: 0a827307d666de819d5cef17f70a78be5cd0e410
tag: 79e9447b73cc315ce35042b0a5f210c07ea39b07
source-repository-package
type: git
@@ -38,25 +38,6 @@
</description>
<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">
<url type="details">https://simplex.chat/blog/20241210-simplex-network-v6-2-servers-by-flux-business-chats.html</url>
<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/direct-sqlcipher.git"."f814ee68b16a9447fbb467ccc8f29bdd3546bfd9" = "1ql13f4kfwkbaq7nygkxgw84213i0zm7c1a8hwvramayxl38dq5d";
"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.Util (encryptFile, liftIOEither, shuffle)
import qualified Simplex.Chat.Util as U
import Simplex.FileTransfer.Client.Main (maxFileSize, maxFileSizeHard)
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 Simplex.FileTransfer.Protocol (FileParty (..), FilePartyI)
import qualified Simplex.FileTransfer.Transport as XFTP