Merge branch 'master-ghc8107' into master-android
This commit is contained in:
commit
30db24265e
1
.github/workflows/web.yml
vendored
1
.github/workflows/web.yml
vendored
@ -9,6 +9,7 @@ on:
|
||||
- website/**
|
||||
- images/**
|
||||
- blog/**
|
||||
- docs/**
|
||||
- .github/workflows/web.yml
|
||||
|
||||
jobs:
|
||||
|
@ -48,11 +48,6 @@
|
||||
5C55A921283CCCB700C4E99E /* IncomingCallView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C55A920283CCCB700C4E99E /* IncomingCallView.swift */; };
|
||||
5C55A923283CEDE600C4E99E /* SoundPlayer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C55A922283CEDE600C4E99E /* SoundPlayer.swift */; };
|
||||
5C55A92E283D0FDE00C4E99E /* sounds in Resources */ = {isa = PBXBuildFile; fileRef = 5C55A92D283D0FDE00C4E99E /* sounds */; };
|
||||
5C56251A2AC1DE5900A21210 /* libHSsimplex-chat-5.3.1.0-625aldG8rLm27VEosiv5y7-ghc8.10.7.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5C5625152AC1DE5900A21210 /* libHSsimplex-chat-5.3.1.0-625aldG8rLm27VEosiv5y7-ghc8.10.7.a */; };
|
||||
5C56251B2AC1DE5900A21210 /* libHSsimplex-chat-5.3.1.0-625aldG8rLm27VEosiv5y7.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5C5625162AC1DE5900A21210 /* libHSsimplex-chat-5.3.1.0-625aldG8rLm27VEosiv5y7.a */; };
|
||||
5C56251C2AC1DE5900A21210 /* libgmpxx.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5C5625172AC1DE5900A21210 /* libgmpxx.a */; };
|
||||
5C56251D2AC1DE5900A21210 /* libgmp.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5C5625182AC1DE5900A21210 /* libgmp.a */; };
|
||||
5C56251E2AC1DE5900A21210 /* libffi.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5C5625192AC1DE5900A21210 /* libffi.a */; };
|
||||
5C577F7D27C83AA10006112D /* MarkdownHelp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C577F7C27C83AA10006112D /* MarkdownHelp.swift */; };
|
||||
5C58BCD6292BEBE600AF9E4F /* CIChatFeatureView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C58BCD5292BEBE600AF9E4F /* CIChatFeatureView.swift */; };
|
||||
5C5DB70E289ABDD200730FFF /* AppearanceSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C5DB70D289ABDD200730FFF /* AppearanceSettings.swift */; };
|
||||
@ -119,6 +114,11 @@
|
||||
5CC1C99527A6CF7F000D9FF6 /* ShareSheet.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CC1C99427A6CF7F000D9FF6 /* ShareSheet.swift */; };
|
||||
5CC2C0FC2809BF11000C35E3 /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = 5CC2C0FA2809BF11000C35E3 /* Localizable.strings */; };
|
||||
5CC2C0FF2809BF11000C35E3 /* SimpleX--iOS--InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 5CC2C0FD2809BF11000C35E3 /* SimpleX--iOS--InfoPlist.strings */; };
|
||||
5CC7398D2AC9D168009470A9 /* libHSsimplex-chat-5.4.0.0-EcKpK3v0NXRK7pgDye4kqp-ghc8.10.7.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5CC739882AC9D168009470A9 /* libHSsimplex-chat-5.4.0.0-EcKpK3v0NXRK7pgDye4kqp-ghc8.10.7.a */; };
|
||||
5CC7398E2AC9D168009470A9 /* libgmp.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5CC739892AC9D168009470A9 /* libgmp.a */; };
|
||||
5CC7398F2AC9D168009470A9 /* libffi.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5CC7398A2AC9D168009470A9 /* libffi.a */; };
|
||||
5CC739902AC9D168009470A9 /* libgmpxx.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5CC7398B2AC9D168009470A9 /* libgmpxx.a */; };
|
||||
5CC739912AC9D168009470A9 /* libHSsimplex-chat-5.4.0.0-EcKpK3v0NXRK7pgDye4kqp.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5CC7398C2AC9D168009470A9 /* libHSsimplex-chat-5.4.0.0-EcKpK3v0NXRK7pgDye4kqp.a */; };
|
||||
5CC868F329EB540C0017BBFD /* CIRcvDecryptionError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CC868F229EB540C0017BBFD /* CIRcvDecryptionError.swift */; };
|
||||
5CCB939C297EFCB100399E78 /* NavStackCompat.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CCB939B297EFCB100399E78 /* NavStackCompat.swift */; };
|
||||
5CCD403427A5F6DF00368C90 /* AddContactView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CCD403327A5F6DF00368C90 /* AddContactView.swift */; };
|
||||
@ -293,11 +293,6 @@
|
||||
5C55A920283CCCB700C4E99E /* IncomingCallView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IncomingCallView.swift; sourceTree = "<group>"; };
|
||||
5C55A922283CEDE600C4E99E /* SoundPlayer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SoundPlayer.swift; sourceTree = "<group>"; };
|
||||
5C55A92D283D0FDE00C4E99E /* sounds */ = {isa = PBXFileReference; lastKnownFileType = folder; path = sounds; sourceTree = "<group>"; };
|
||||
5C5625152AC1DE5900A21210 /* libHSsimplex-chat-5.3.1.0-625aldG8rLm27VEosiv5y7-ghc8.10.7.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-5.3.1.0-625aldG8rLm27VEosiv5y7-ghc8.10.7.a"; sourceTree = "<group>"; };
|
||||
5C5625162AC1DE5900A21210 /* libHSsimplex-chat-5.3.1.0-625aldG8rLm27VEosiv5y7.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-5.3.1.0-625aldG8rLm27VEosiv5y7.a"; sourceTree = "<group>"; };
|
||||
5C5625172AC1DE5900A21210 /* libgmpxx.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmpxx.a; sourceTree = "<group>"; };
|
||||
5C5625182AC1DE5900A21210 /* libgmp.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmp.a; sourceTree = "<group>"; };
|
||||
5C5625192AC1DE5900A21210 /* libffi.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libffi.a; sourceTree = "<group>"; };
|
||||
5C577F7C27C83AA10006112D /* MarkdownHelp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MarkdownHelp.swift; sourceTree = "<group>"; };
|
||||
5C58BCD5292BEBE600AF9E4F /* CIChatFeatureView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CIChatFeatureView.swift; sourceTree = "<group>"; };
|
||||
5C5B67912ABAF4B500DA9412 /* bg */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = bg; path = bg.lproj/Localizable.strings; sourceTree = "<group>"; };
|
||||
@ -400,6 +395,11 @@
|
||||
5CC1C99427A6CF7F000D9FF6 /* ShareSheet.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShareSheet.swift; sourceTree = "<group>"; };
|
||||
5CC2C0FB2809BF11000C35E3 /* ru */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ru; path = ru.lproj/Localizable.strings; sourceTree = "<group>"; };
|
||||
5CC2C0FE2809BF11000C35E3 /* ru */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ru; path = "ru.lproj/SimpleX--iOS--InfoPlist.strings"; sourceTree = "<group>"; };
|
||||
5CC739882AC9D168009470A9 /* libHSsimplex-chat-5.4.0.0-EcKpK3v0NXRK7pgDye4kqp-ghc8.10.7.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-5.4.0.0-EcKpK3v0NXRK7pgDye4kqp-ghc8.10.7.a"; sourceTree = "<group>"; };
|
||||
5CC739892AC9D168009470A9 /* libgmp.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmp.a; sourceTree = "<group>"; };
|
||||
5CC7398A2AC9D168009470A9 /* libffi.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libffi.a; sourceTree = "<group>"; };
|
||||
5CC7398B2AC9D168009470A9 /* libgmpxx.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmpxx.a; sourceTree = "<group>"; };
|
||||
5CC7398C2AC9D168009470A9 /* libHSsimplex-chat-5.4.0.0-EcKpK3v0NXRK7pgDye4kqp.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-5.4.0.0-EcKpK3v0NXRK7pgDye4kqp.a"; sourceTree = "<group>"; };
|
||||
5CC868F229EB540C0017BBFD /* CIRcvDecryptionError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CIRcvDecryptionError.swift; sourceTree = "<group>"; };
|
||||
5CCB939B297EFCB100399E78 /* NavStackCompat.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NavStackCompat.swift; sourceTree = "<group>"; };
|
||||
5CCD403327A5F6DF00368C90 /* AddContactView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddContactView.swift; sourceTree = "<group>"; };
|
||||
@ -507,12 +507,12 @@
|
||||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
5CC739902AC9D168009470A9 /* libgmpxx.a in Frameworks */,
|
||||
5CE2BA93284534B000EC33A6 /* libiconv.tbd in Frameworks */,
|
||||
5C56251C2AC1DE5900A21210 /* libgmpxx.a in Frameworks */,
|
||||
5C56251B2AC1DE5900A21210 /* libHSsimplex-chat-5.3.1.0-625aldG8rLm27VEosiv5y7.a in Frameworks */,
|
||||
5C56251A2AC1DE5900A21210 /* libHSsimplex-chat-5.3.1.0-625aldG8rLm27VEosiv5y7-ghc8.10.7.a in Frameworks */,
|
||||
5C56251E2AC1DE5900A21210 /* libffi.a in Frameworks */,
|
||||
5C56251D2AC1DE5900A21210 /* libgmp.a in Frameworks */,
|
||||
5CC7398D2AC9D168009470A9 /* libHSsimplex-chat-5.4.0.0-EcKpK3v0NXRK7pgDye4kqp-ghc8.10.7.a in Frameworks */,
|
||||
5CC7398E2AC9D168009470A9 /* libgmp.a in Frameworks */,
|
||||
5CC739912AC9D168009470A9 /* libHSsimplex-chat-5.4.0.0-EcKpK3v0NXRK7pgDye4kqp.a in Frameworks */,
|
||||
5CC7398F2AC9D168009470A9 /* libffi.a in Frameworks */,
|
||||
5CE2BA94284534BB00EC33A6 /* libz.tbd in Frameworks */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
@ -574,11 +574,11 @@
|
||||
5C764E5C279C70B7000C6508 /* Libraries */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
5C5625192AC1DE5900A21210 /* libffi.a */,
|
||||
5C5625182AC1DE5900A21210 /* libgmp.a */,
|
||||
5C5625172AC1DE5900A21210 /* libgmpxx.a */,
|
||||
5C5625152AC1DE5900A21210 /* libHSsimplex-chat-5.3.1.0-625aldG8rLm27VEosiv5y7-ghc8.10.7.a */,
|
||||
5C5625162AC1DE5900A21210 /* libHSsimplex-chat-5.3.1.0-625aldG8rLm27VEosiv5y7.a */,
|
||||
5CC7398A2AC9D168009470A9 /* libffi.a */,
|
||||
5CC739892AC9D168009470A9 /* libgmp.a */,
|
||||
5CC7398B2AC9D168009470A9 /* libgmpxx.a */,
|
||||
5CC739882AC9D168009470A9 /* libHSsimplex-chat-5.4.0.0-EcKpK3v0NXRK7pgDye4kqp-ghc8.10.7.a */,
|
||||
5CC7398C2AC9D168009470A9 /* libHSsimplex-chat-5.4.0.0-EcKpK3v0NXRK7pgDye4kqp.a */,
|
||||
);
|
||||
path = Libraries;
|
||||
sourceTree = "<group>";
|
||||
@ -1486,7 +1486,7 @@
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CODE_SIGN_ENTITLEMENTS = "SimpleX (iOS).entitlements";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 174;
|
||||
CURRENT_PROJECT_VERSION = 176;
|
||||
DEVELOPMENT_TEAM = 5NN7GUYB6T;
|
||||
ENABLE_BITCODE = NO;
|
||||
ENABLE_PREVIEWS = YES;
|
||||
@ -1507,7 +1507,7 @@
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 5.3.1;
|
||||
MARKETING_VERSION = 5.4;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = chat.simplex.app;
|
||||
PRODUCT_NAME = SimpleX;
|
||||
SDKROOT = iphoneos;
|
||||
@ -1528,7 +1528,7 @@
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CODE_SIGN_ENTITLEMENTS = "SimpleX (iOS).entitlements";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 174;
|
||||
CURRENT_PROJECT_VERSION = 176;
|
||||
DEVELOPMENT_TEAM = 5NN7GUYB6T;
|
||||
ENABLE_BITCODE = NO;
|
||||
ENABLE_PREVIEWS = YES;
|
||||
@ -1549,7 +1549,7 @@
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 5.3.1;
|
||||
MARKETING_VERSION = 5.4;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = chat.simplex.app;
|
||||
PRODUCT_NAME = SimpleX;
|
||||
SDKROOT = iphoneos;
|
||||
@ -1608,7 +1608,7 @@
|
||||
CODE_SIGN_ENTITLEMENTS = "SimpleX NSE/SimpleX NSE.entitlements";
|
||||
CODE_SIGN_IDENTITY = "Apple Development";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 174;
|
||||
CURRENT_PROJECT_VERSION = 176;
|
||||
DEVELOPMENT_TEAM = 5NN7GUYB6T;
|
||||
ENABLE_BITCODE = NO;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
@ -1621,7 +1621,7 @@
|
||||
"@executable_path/Frameworks",
|
||||
"@executable_path/../../Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 5.3.1;
|
||||
MARKETING_VERSION = 5.4;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = "chat.simplex.app.SimpleX-NSE";
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||
@ -1640,7 +1640,7 @@
|
||||
CODE_SIGN_ENTITLEMENTS = "SimpleX NSE/SimpleX NSE.entitlements";
|
||||
CODE_SIGN_IDENTITY = "Apple Development";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 174;
|
||||
CURRENT_PROJECT_VERSION = 176;
|
||||
DEVELOPMENT_TEAM = 5NN7GUYB6T;
|
||||
ENABLE_BITCODE = NO;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
@ -1653,7 +1653,7 @@
|
||||
"@executable_path/Frameworks",
|
||||
"@executable_path/../../Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 5.3.1;
|
||||
MARKETING_VERSION = 5.4;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = "chat.simplex.app.SimpleX-NSE";
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||
@ -1672,7 +1672,7 @@
|
||||
APPLICATION_EXTENSION_API_ONLY = YES;
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 174;
|
||||
CURRENT_PROJECT_VERSION = 176;
|
||||
DEFINES_MODULE = YES;
|
||||
DEVELOPMENT_TEAM = 5NN7GUYB6T;
|
||||
DYLIB_COMPATIBILITY_VERSION = 1;
|
||||
@ -1696,7 +1696,7 @@
|
||||
"$(inherited)",
|
||||
"$(PROJECT_DIR)/Libraries/sim",
|
||||
);
|
||||
MARKETING_VERSION = 5.3.1;
|
||||
MARKETING_VERSION = 5.4;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = chat.simplex.SimpleXChat;
|
||||
PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)";
|
||||
SDKROOT = iphoneos;
|
||||
@ -1718,7 +1718,7 @@
|
||||
APPLICATION_EXTENSION_API_ONLY = YES;
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 174;
|
||||
CURRENT_PROJECT_VERSION = 176;
|
||||
DEFINES_MODULE = YES;
|
||||
DEVELOPMENT_TEAM = 5NN7GUYB6T;
|
||||
DYLIB_COMPATIBILITY_VERSION = 1;
|
||||
@ -1742,7 +1742,7 @@
|
||||
"$(inherited)",
|
||||
"$(PROJECT_DIR)/Libraries/sim",
|
||||
);
|
||||
MARKETING_VERSION = 5.3.1;
|
||||
MARKETING_VERSION = 5.4;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = chat.simplex.SimpleXChat;
|
||||
PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)";
|
||||
SDKROOT = iphoneos;
|
||||
|
@ -0,0 +1,39 @@
|
||||
package chat.simplex.common.views.chatlist
|
||||
|
||||
import androidx.compose.foundation.*
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.material.Divider
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.unit.dp
|
||||
import chat.simplex.common.views.helpers.*
|
||||
|
||||
@Composable
|
||||
actual fun ChatListNavLinkLayout(
|
||||
chatLinkPreview: @Composable () -> Unit,
|
||||
click: () -> Unit,
|
||||
dropdownMenuItems: (@Composable () -> Unit)?,
|
||||
showMenu: MutableState<Boolean>,
|
||||
stopped: Boolean,
|
||||
selectedChat: State<Boolean>
|
||||
) {
|
||||
var modifier = Modifier.fillMaxWidth()
|
||||
if (!stopped) modifier = modifier
|
||||
.combinedClickable(onClick = click, onLongClick = { showMenu.value = true })
|
||||
.onRightClick { showMenu.value = true }
|
||||
Box(modifier) {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(start = 8.dp, top = 8.dp, end = 12.dp, bottom = 8.dp),
|
||||
verticalAlignment = Alignment.Top
|
||||
) {
|
||||
chatLinkPreview()
|
||||
}
|
||||
if (dropdownMenuItems != null) {
|
||||
DefaultDropdownMenu(showMenu, dropdownMenuItems = dropdownMenuItems)
|
||||
}
|
||||
}
|
||||
Divider(Modifier.padding(horizontal = 8.dp))
|
||||
}
|
@ -31,9 +31,9 @@ else()
|
||||
set(CMAKE_BUILD_RPATH "@loader_path")
|
||||
endif()
|
||||
|
||||
if(${CMAKE_SYSTEM_PROCESSOR} MATCHES "amd64")
|
||||
if(${CMAKE_SYSTEM_PROCESSOR} MATCHES "amd64" OR ${CMAKE_SYSTEM_PROCESSOR} MATCHES "AMD64")
|
||||
set(OS_LIB_ARCH "x86_64")
|
||||
elseif(${CMAKE_SYSTEM_PROCESSOR} MATCHES "arm64")
|
||||
elseif(${CMAKE_SYSTEM_PROCESSOR} MATCHES "arm64" OR ${CMAKE_SYSTEM_PROCESSOR} MATCHES "ARM64")
|
||||
set(OS_LIB_ARCH "aarch64")
|
||||
else()
|
||||
set(OS_LIB_ARCH "${CMAKE_SYSTEM_PROCESSOR}")
|
||||
@ -55,8 +55,13 @@ add_library( # Sets the name of the library.
|
||||
|
||||
add_library( simplex SHARED IMPORTED )
|
||||
# Lib has different name because of version, find it
|
||||
FILE(GLOB SIMPLEXLIB ${CMAKE_SOURCE_DIR}/libs/${OS_LIB_PATH}-${OS_LIB_ARCH}/libHSsimplex-chat-*.${OS_LIB_EXT})
|
||||
set_target_properties( simplex PROPERTIES IMPORTED_LOCATION ${SIMPLEXLIB})
|
||||
FILE(GLOB SIMPLEXLIB ${CMAKE_SOURCE_DIR}/libs/${OS_LIB_PATH}-${OS_LIB_ARCH}/lib*simplex*.${OS_LIB_EXT})
|
||||
|
||||
if(WIN32)
|
||||
set_target_properties( simplex PROPERTIES IMPORTED_IMPLIB ${SIMPLEXLIB})
|
||||
else()
|
||||
set_target_properties( simplex PROPERTIES IMPORTED_LOCATION ${SIMPLEXLIB})
|
||||
endif()
|
||||
|
||||
|
||||
# Specifies libraries CMake should link to your target library. You
|
||||
|
@ -224,6 +224,7 @@ object ChatModel {
|
||||
}
|
||||
// add to current chat
|
||||
if (chatId.value == cInfo.id) {
|
||||
Log.d(TAG, "TODOCHAT: addChatItem: adding to chat ${chatId.value} from ${cInfo.id} ${cItem.id}, size ${chatItems.size}")
|
||||
withContext(Dispatchers.Main) {
|
||||
// Prevent situation when chat item already in the list received from backend
|
||||
if (chatItems.none { it.id == cItem.id }) {
|
||||
@ -231,6 +232,7 @@ object ChatModel {
|
||||
chatItems.add(kotlin.math.max(0, chatItems.lastIndex), cItem)
|
||||
} else {
|
||||
chatItems.add(cItem)
|
||||
Log.d(TAG, "TODOCHAT: addChatItem: added to chat ${chatId.value} from ${cInfo.id} ${cItem.id}, size ${chatItems.size}")
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -259,13 +261,16 @@ object ChatModel {
|
||||
}
|
||||
// update current chat
|
||||
return if (chatId.value == cInfo.id) {
|
||||
Log.d(TAG, "TODOCHAT: upsertChatItem: upserting to chat ${chatId.value} from ${cInfo.id} ${cItem.id}, size ${chatItems.size}")
|
||||
withContext(Dispatchers.Main) {
|
||||
val itemIndex = chatItems.indexOfFirst { it.id == cItem.id }
|
||||
if (itemIndex >= 0) {
|
||||
chatItems[itemIndex] = cItem
|
||||
Log.d(TAG, "TODOCHAT: upsertChatItem: updated in chat $chatId from ${cInfo.id} ${cItem.id}, size ${chatItems.size}")
|
||||
false
|
||||
} else {
|
||||
chatItems.add(cItem)
|
||||
Log.d(TAG, "TODOCHAT: upsertChatItem: added to chat $chatId from ${cInfo.id} ${cItem.id}, size ${chatItems.size}")
|
||||
true
|
||||
}
|
||||
}
|
||||
@ -374,6 +379,7 @@ object ChatModel {
|
||||
var markedRead = 0
|
||||
if (chatId.value == cInfo.id) {
|
||||
var i = 0
|
||||
Log.d(TAG, "TODOCHAT: markItemsReadInCurrentChat: marking read ${cInfo.id}, current chatId ${chatId.value}, size was ${chatItems.size}")
|
||||
while (i < chatItems.count()) {
|
||||
val item = chatItems[i]
|
||||
if (item.meta.itemStatus is CIStatus.RcvNew && (range == null || (range.from <= item.id && item.id <= range.to))) {
|
||||
@ -388,6 +394,7 @@ object ChatModel {
|
||||
}
|
||||
i += 1
|
||||
}
|
||||
Log.d(TAG, "TODOCHAT: markItemsReadInCurrentChat: marked read ${cInfo.id}, current chatId ${chatId.value}, size now ${chatItems.size}")
|
||||
}
|
||||
return markedRead
|
||||
}
|
||||
|
@ -66,11 +66,13 @@ fun ChatView(chatId: String, chatModel: ChatModel, onComposed: suspend (chatId:
|
||||
launch {
|
||||
snapshotFlow { chatModel.chatId.value }
|
||||
.distinctUntilChanged()
|
||||
.onEach { Log.d(TAG, "TODOCHAT: chatId: activeChatId ${activeChat.value?.id} == new chatId $it ${activeChat.value?.id == it} ") }
|
||||
.filter { it != null && activeChat.value?.id != it }
|
||||
.collect { chatId ->
|
||||
// Redisplay the whole hierarchy if the chat is different to make going from groups to direct chat working correctly
|
||||
// Also for situation when chatId changes after clicking in notification, etc
|
||||
activeChat.value = chatModel.getChat(chatId!!)
|
||||
Log.d(TAG, "TODOCHAT: chatId: activeChatId became ${activeChat.value?.id}")
|
||||
markUnreadChatAsRead(activeChat, chatModel)
|
||||
}
|
||||
}
|
||||
@ -89,9 +91,13 @@ fun ChatView(chatId: String, chatModel: ChatModel, onComposed: suspend (chatId:
|
||||
}
|
||||
}
|
||||
.distinctUntilChanged()
|
||||
.onEach { Log.d(TAG, "TODOCHAT: chats: activeChatId ${activeChat.value?.id} == new chatId ${it?.id} ${activeChat.value?.id == it?.id} ") }
|
||||
// Only changed chatInfo is important thing. Other properties can be skipped for reducing recompositions
|
||||
.filter { it != null && it?.chatInfo != activeChat.value?.chatInfo }
|
||||
.collect { activeChat.value = it }
|
||||
.collect {
|
||||
activeChat.value = it
|
||||
Log.d(TAG, "TODOCHAT: chats: activeChatId became ${activeChat.value?.id}")
|
||||
}
|
||||
}
|
||||
}
|
||||
val view = LocalMultiplatformView()
|
||||
@ -218,7 +224,9 @@ fun ChatView(chatId: String, chatModel: ChatModel, onComposed: suspend (chatId:
|
||||
val firstId = chatModel.chatItems.firstOrNull()?.id
|
||||
if (c != null && firstId != null) {
|
||||
withApi {
|
||||
Log.d(TAG, "TODOCHAT: loadPrevMessages: loading for ${c.id}, current chatId ${ChatModel.chatId.value}, size was ${ChatModel.chatItems.size}")
|
||||
apiLoadPrevMessages(c.chatInfo, chatModel, firstId, searchText.value)
|
||||
Log.d(TAG, "TODOCHAT: loadPrevMessages: loaded for ${c.id}, current chatId ${ChatModel.chatId.value}, size now ${ChatModel.chatItems.size}")
|
||||
}
|
||||
}
|
||||
},
|
||||
|
@ -1,7 +1,6 @@
|
||||
package chat.simplex.common.views.chatlist
|
||||
|
||||
import SectionItemView
|
||||
import androidx.compose.foundation.combinedClickable
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.material.*
|
||||
import androidx.compose.runtime.*
|
||||
@ -14,6 +13,10 @@ import dev.icerock.moko.resources.compose.stringResource
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.desktop.ui.tooling.preview.Preview
|
||||
import androidx.compose.foundation.*
|
||||
import androidx.compose.foundation.interaction.InteractionSource
|
||||
import androidx.compose.ui.graphics.drawscope.ContentDrawScope
|
||||
import androidx.compose.ui.platform.LocalViewConfiguration
|
||||
import androidx.compose.ui.text.AnnotatedString
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.unit.dp
|
||||
@ -44,6 +47,7 @@ fun ChatListNavLinkView(chat: Chat, chatModel: ChatModel) {
|
||||
showMenu.value = false
|
||||
delay(500L)
|
||||
}
|
||||
val selectedChat = remember(chat.id) { derivedStateOf { chat.id == ChatModel.chatId.value } }
|
||||
val showChatPreviews = chatModel.showChatPreviews.value
|
||||
when (chat.chatInfo) {
|
||||
is ChatInfo.Direct -> {
|
||||
@ -53,7 +57,8 @@ fun ChatListNavLinkView(chat: Chat, chatModel: ChatModel) {
|
||||
click = { directChatAction(chat.chatInfo, chatModel) },
|
||||
dropdownMenuItems = { ContactMenuItems(chat, chatModel, showMenu, showMarkRead) },
|
||||
showMenu,
|
||||
stopped
|
||||
stopped,
|
||||
selectedChat
|
||||
)
|
||||
}
|
||||
is ChatInfo.Group ->
|
||||
@ -62,7 +67,8 @@ fun ChatListNavLinkView(chat: Chat, chatModel: ChatModel) {
|
||||
click = { groupChatAction(chat.chatInfo.groupInfo, chatModel) },
|
||||
dropdownMenuItems = { GroupMenuItems(chat, chat.chatInfo.groupInfo, chatModel, showMenu, showMarkRead) },
|
||||
showMenu,
|
||||
stopped
|
||||
stopped,
|
||||
selectedChat
|
||||
)
|
||||
is ChatInfo.ContactRequest ->
|
||||
ChatListNavLinkLayout(
|
||||
@ -70,7 +76,8 @@ fun ChatListNavLinkView(chat: Chat, chatModel: ChatModel) {
|
||||
click = { contactRequestAlertDialog(chat.chatInfo, chatModel) },
|
||||
dropdownMenuItems = { ContactRequestMenuItems(chat.chatInfo, chatModel, showMenu) },
|
||||
showMenu,
|
||||
stopped
|
||||
stopped,
|
||||
selectedChat
|
||||
)
|
||||
is ChatInfo.ContactConnection ->
|
||||
ChatListNavLinkLayout(
|
||||
@ -84,7 +91,8 @@ fun ChatListNavLinkView(chat: Chat, chatModel: ChatModel) {
|
||||
},
|
||||
dropdownMenuItems = { ContactConnectionMenuItems(chat.chatInfo, chatModel, showMenu) },
|
||||
showMenu,
|
||||
stopped
|
||||
stopped,
|
||||
selectedChat
|
||||
)
|
||||
is ChatInfo.InvalidJSON ->
|
||||
ChatListNavLinkLayout(
|
||||
@ -97,7 +105,8 @@ fun ChatListNavLinkView(chat: Chat, chatModel: ChatModel) {
|
||||
},
|
||||
dropdownMenuItems = null,
|
||||
showMenu,
|
||||
stopped
|
||||
stopped,
|
||||
selectedChat
|
||||
)
|
||||
}
|
||||
}
|
||||
@ -122,9 +131,11 @@ suspend fun openDirectChat(contactId: Long, chatModel: ChatModel) {
|
||||
}
|
||||
|
||||
suspend fun openChat(chatInfo: ChatInfo, chatModel: ChatModel) {
|
||||
Log.d(TAG, "TODOCHAT: openChat: opening ${chatInfo.id}, current chatId ${ChatModel.chatId.value}, size ${ChatModel.chatItems.size}")
|
||||
val chat = chatModel.controller.apiGetChat(chatInfo.chatType, chatInfo.apiId)
|
||||
if (chat != null) {
|
||||
openChat(chat, chatModel)
|
||||
Log.d(TAG, "TODOCHAT: openChat: opened ${chatInfo.id}, current chatId ${ChatModel.chatId.value}, size ${ChatModel.chatItems.size}")
|
||||
}
|
||||
}
|
||||
|
||||
@ -628,32 +639,14 @@ fun updateChatSettings(chat: Chat, chatSettings: ChatSettings, chatModel: ChatMo
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun ChatListNavLinkLayout(
|
||||
expect fun ChatListNavLinkLayout(
|
||||
chatLinkPreview: @Composable () -> Unit,
|
||||
click: () -> Unit,
|
||||
dropdownMenuItems: (@Composable () -> Unit)?,
|
||||
showMenu: MutableState<Boolean>,
|
||||
stopped: Boolean
|
||||
) {
|
||||
var modifier = Modifier.fillMaxWidth()
|
||||
if (!stopped) modifier = modifier
|
||||
.combinedClickable(onClick = click, onLongClick = { showMenu.value = true })
|
||||
.onRightClick { showMenu.value = true }
|
||||
Box(modifier) {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(start = 8.dp, top = 8.dp, end = 12.dp, bottom = 8.dp),
|
||||
verticalAlignment = Alignment.Top
|
||||
) {
|
||||
chatLinkPreview()
|
||||
}
|
||||
if (dropdownMenuItems != null) {
|
||||
DefaultDropdownMenu(showMenu, dropdownMenuItems = dropdownMenuItems)
|
||||
}
|
||||
}
|
||||
Divider(Modifier.padding(horizontal = 8.dp))
|
||||
}
|
||||
stopped: Boolean,
|
||||
selectedChat: State<Boolean>
|
||||
)
|
||||
|
||||
@Preview/*(
|
||||
uiMode = Configuration.UI_MODE_NIGHT_YES,
|
||||
@ -690,7 +683,8 @@ fun PreviewChatListNavLinkDirect() {
|
||||
click = {},
|
||||
dropdownMenuItems = null,
|
||||
showMenu = remember { mutableStateOf(false) },
|
||||
stopped = false
|
||||
stopped = false,
|
||||
selectedChat = remember { mutableStateOf(false) }
|
||||
)
|
||||
}
|
||||
}
|
||||
@ -730,7 +724,8 @@ fun PreviewChatListNavLinkGroup() {
|
||||
click = {},
|
||||
dropdownMenuItems = null,
|
||||
showMenu = remember { mutableStateOf(false) },
|
||||
stopped = false
|
||||
stopped = false,
|
||||
selectedChat = remember { mutableStateOf(false) }
|
||||
)
|
||||
}
|
||||
}
|
||||
@ -750,7 +745,8 @@ fun PreviewChatListNavLinkContactRequest() {
|
||||
click = {},
|
||||
dropdownMenuItems = null,
|
||||
showMenu = remember { mutableStateOf(false) },
|
||||
stopped = false
|
||||
stopped = false,
|
||||
selectedChat = remember { mutableStateOf(false) }
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -16,6 +16,7 @@ enum class DesktopPlatform(val libPath: String, val libExtension: String, val co
|
||||
MAC_AARCH64("/libs/mac-aarch64", "dylib", unixConfigPath, unixDataPath);
|
||||
|
||||
fun isLinux() = this == LINUX_X86_64 || this == LINUX_AARCH64
|
||||
fun isWindows() = this == WINDOWS_X86_64
|
||||
fun isMac() = this == MAC_X86_64 || this == MAC_AARCH64
|
||||
}
|
||||
|
||||
|
@ -54,9 +54,9 @@ actual object AudioPlayer: AudioPlayerInterface {
|
||||
if (fileSource.cryptoArgs != null) {
|
||||
val tmpFile = fileSource.createTmpFileIfNeeded()
|
||||
decryptCryptoFile(absoluteFilePath, fileSource.cryptoArgs, tmpFile.absolutePath)
|
||||
player.media().prepare("file://${tmpFile.absolutePath}")
|
||||
player.media().prepare(tmpFile.toURI().toString().replaceFirst("file:", "file://"))
|
||||
} else {
|
||||
player.media().prepare("file://$absoluteFilePath")
|
||||
player.media().prepare(File(absoluteFilePath).toURI().toString().replaceFirst("file:", "file://"))
|
||||
}
|
||||
}.onFailure {
|
||||
Log.e(TAG, it.stackTraceToString())
|
||||
@ -171,7 +171,7 @@ actual object AudioPlayer: AudioPlayerInterface {
|
||||
var res: Int? = null
|
||||
try {
|
||||
val helperPlayer = AudioPlayerComponent().mediaPlayer()
|
||||
helperPlayer.media().startPaused("file://$unencryptedFilePath")
|
||||
helperPlayer.media().startPaused(File(unencryptedFilePath).toURI().toString().replaceFirst("file:", "file://"))
|
||||
res = helperPlayer.duration
|
||||
helperPlayer.stop()
|
||||
helperPlayer.release()
|
||||
|
@ -0,0 +1,60 @@
|
||||
package chat.simplex.common.views.chatlist
|
||||
|
||||
import androidx.compose.foundation.*
|
||||
import androidx.compose.foundation.interaction.InteractionSource
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.material.Divider
|
||||
import androidx.compose.material.MaterialTheme
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.drawscope.ContentDrawScope
|
||||
import androidx.compose.ui.unit.dp
|
||||
import chat.simplex.common.views.helpers.*
|
||||
|
||||
private object NoIndication : Indication {
|
||||
private object NoIndicationInstance : IndicationInstance {
|
||||
override fun ContentDrawScope.drawIndication() {
|
||||
drawContent()
|
||||
}
|
||||
}
|
||||
@Composable
|
||||
override fun rememberUpdatedInstance(interactionSource: InteractionSource): IndicationInstance {
|
||||
return NoIndicationInstance
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
actual fun ChatListNavLinkLayout(
|
||||
chatLinkPreview: @Composable () -> Unit,
|
||||
click: () -> Unit,
|
||||
dropdownMenuItems: (@Composable () -> Unit)?,
|
||||
showMenu: MutableState<Boolean>,
|
||||
stopped: Boolean,
|
||||
selectedChat: State<Boolean>
|
||||
) {
|
||||
var modifier = Modifier.fillMaxWidth()
|
||||
if (!stopped) modifier = modifier
|
||||
.background(color = if (selectedChat.value) MaterialTheme.colors.background.mixWith(MaterialTheme.colors.onBackground, 0.95f) else Color.Unspecified)
|
||||
.combinedClickable(onClick = click, onLongClick = { showMenu.value = true })
|
||||
.onRightClick { showMenu.value = true }
|
||||
CompositionLocalProvider(
|
||||
LocalIndication provides if (selectedChat.value && !stopped) NoIndication else LocalIndication.current
|
||||
) {
|
||||
Box(modifier) {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(start = 8.dp, top = 8.dp, end = 12.dp, bottom = 8.dp),
|
||||
verticalAlignment = Alignment.Top
|
||||
) {
|
||||
chatLinkPreview()
|
||||
}
|
||||
if (dropdownMenuItems != null) {
|
||||
DefaultDropdownMenu(showMenu, dropdownMenuItems = dropdownMenuItems)
|
||||
}
|
||||
}
|
||||
}
|
||||
Divider()
|
||||
}
|
@ -10,12 +10,12 @@ import androidx.compose.ui.input.key.*
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.window.*
|
||||
import chat.simplex.common.DialogParams
|
||||
import chat.simplex.common.platform.desktopPlatform
|
||||
import chat.simplex.res.MR
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import java.awt.FileDialog
|
||||
import java.io.File
|
||||
import java.util.*
|
||||
import javax.swing.JFileChooser
|
||||
import javax.swing.filechooser.FileFilter
|
||||
import javax.swing.filechooser.FileNameExtensionFilter
|
||||
@ -53,7 +53,7 @@ fun FrameWindowScope.FileDialogChooser(
|
||||
params: DialogParams,
|
||||
onResult: (result: List<File>) -> Unit
|
||||
) {
|
||||
if (isLinux()) {
|
||||
if (desktopPlatform.isLinux() || desktopPlatform.isWindows()) {
|
||||
FileDialogChooserMultiple(title, isLoad, params.filename, params.allowMultiple, params.fileFilter, params.fileFilterDescription, onResult)
|
||||
} else {
|
||||
FileDialogAwt(title, isLoad, params.filename, params.allowMultiple, params.fileFilter, onResult)
|
||||
@ -121,7 +121,7 @@ fun FrameWindowScope.FileDialogChooserMultiple(
|
||||
}
|
||||
|
||||
/*
|
||||
* Has graphic glitches on many Linux distributions, so use only on non-Linux systems
|
||||
* Has graphic glitches on many Linux distributions, so use only on non-Linux systems. Also file filter doesn't work on Windows
|
||||
* */
|
||||
@Composable
|
||||
private fun FrameWindowScope.FileDialogAwt(
|
||||
@ -159,5 +159,3 @@ private fun FrameWindowScope.FileDialogAwt(
|
||||
},
|
||||
dispose = FileDialog::dispose
|
||||
)
|
||||
|
||||
fun isLinux(): Boolean = System.getProperty("os.name", "generic").lowercase(Locale.ENGLISH) == "linux"
|
||||
|
@ -88,7 +88,7 @@ actual fun escapedHtmlToAnnotatedString(text: String, density: Density): Annotat
|
||||
}
|
||||
|
||||
actual fun getAppFileUri(fileName: String): URI =
|
||||
URI("file:" + appFilesDir.absolutePath + File.separator + fileName)
|
||||
URI(appFilesDir.toURI().toString() + "/" + fileName)
|
||||
|
||||
actual fun getLoadedImage(file: CIFile?): Pair<ImageBitmap, ByteArray>? {
|
||||
val filePath = getLoadedFilePath(file)
|
||||
|
@ -63,9 +63,10 @@ compose {
|
||||
windows {
|
||||
packageName = "SimpleX"
|
||||
iconFile.set(project.file("src/jvmMain/resources/distribute/simplex.ico"))
|
||||
console = true
|
||||
perUserInstall = true
|
||||
console = false
|
||||
perUserInstall = false
|
||||
dirChooser = true
|
||||
shortcut = true
|
||||
}
|
||||
macOS {
|
||||
packageName = "SimpleX"
|
||||
@ -119,9 +120,9 @@ cmake {
|
||||
/*machines.customMachines.register("linux-aarch64") {
|
||||
toolchainFile.set(project.file("$cppPath/toolchains/aarch64-linux-gnu-gcc.cmake"))
|
||||
}*/
|
||||
machines.customMachines.register("win-amd64") {
|
||||
/*machines.customMachines.register("win-amd64") {
|
||||
toolchainFile.set(project.file("$cppPath/toolchains/x86_64-windows-mingw32-gcc.cmake"))
|
||||
}
|
||||
}*/
|
||||
if (machines.host.name == "mac-amd64") {
|
||||
machines.customMachines.register("mac-amd64") {
|
||||
toolchainFile.set(project.file("$cppPath/toolchains/x86_64-mac-apple-darwin-gcc.cmake"))
|
||||
@ -139,6 +140,9 @@ cmake {
|
||||
val main by creating {
|
||||
cmakeLists.set(file("$cppPath/desktop/CMakeLists.txt"))
|
||||
targetMachines.addAll(compileMachineTargets.toSet())
|
||||
if (machines.host.name.contains("win")) {
|
||||
cmakeArgs.add("-G MinGW Makefiles")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -191,7 +195,7 @@ afterEvaluate {
|
||||
copyIfNeeded(destinationDir, copyDetails)
|
||||
}
|
||||
copy {
|
||||
from("${project(":desktop").buildDir}/cmake/main/win-amd64", "$cppPath/desktop/libs/windows-x86_64", "$cppPath/desktop/libs/windows-x86_64/deps")
|
||||
from("${project(":desktop").buildDir}/cmake/main/windows-amd64", "$cppPath/desktop/libs/windows-x86_64", "$cppPath/desktop/libs/windows-x86_64/deps")
|
||||
into("src/jvmMain/resources/libs/windows-x86_64")
|
||||
include("*.dll")
|
||||
eachFile {
|
||||
|
@ -18,13 +18,15 @@ fun main() {
|
||||
|
||||
@Suppress("UnsafeDynamicallyLoadedCode")
|
||||
private fun initHaskell() {
|
||||
val libApp = "libapp-lib.${desktopPlatform.libExtension}"
|
||||
val libsTmpDir = File(tmpDir.absolutePath + File.separator + "libs")
|
||||
copyResources(desktopPlatform.libPath, libsTmpDir.toPath())
|
||||
System.load(File(libsTmpDir, libApp).absolutePath)
|
||||
|
||||
vlcDir.deleteRecursively()
|
||||
Files.move(File(libsTmpDir, "vlc").toPath(), vlcDir.toPath(), StandardCopyOption.REPLACE_EXISTING)
|
||||
if (desktopPlatform == DesktopPlatform.WINDOWS_X86_64) {
|
||||
windowsLoadRequiredLibs(libsTmpDir)
|
||||
} else {
|
||||
System.load(File(libsTmpDir, "libapp-lib.${desktopPlatform.libExtension}").absolutePath)
|
||||
}
|
||||
// No picture without preloading it, only sound. However, with libs from AppImage it works without preloading
|
||||
//val libXcb = "libvlc_xcb_events.so.0.0.0"
|
||||
//System.load(File(File(vlcDir, "vlc"), libXcb).absolutePath)
|
||||
@ -55,3 +57,25 @@ private fun copyResources(from: String, to: Path) {
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
private fun windowsLoadRequiredLibs(libsTmpDir: File) {
|
||||
val mainLibs = arrayOf(
|
||||
"libcrypto-3-x64.dll",
|
||||
"libffi-8.dll",
|
||||
"libgmp-10.dll",
|
||||
"libsimplex.dll",
|
||||
"libapp-lib.dll"
|
||||
)
|
||||
mainLibs.forEach {
|
||||
System.load(File(libsTmpDir, it).absolutePath)
|
||||
}
|
||||
val vlcLibs = arrayOf(
|
||||
"libvlccore.dll",
|
||||
"libvlc.dll",
|
||||
"axvlc.dll",
|
||||
"npvlc.dll"
|
||||
)
|
||||
vlcLibs.forEach {
|
||||
System.load(File(vlcDir, it).absolutePath)
|
||||
}
|
||||
}
|
||||
|
@ -25,11 +25,11 @@ android.nonTransitiveRClass=true
|
||||
android.enableJetifier=true
|
||||
kotlin.mpp.androidSourceSetLayoutVersion=2
|
||||
|
||||
android.version_name=5.3.1
|
||||
android.version_code=154
|
||||
android.version_name=5.4-beta.0
|
||||
android.version_code=156
|
||||
|
||||
desktop.version_name=5.3.1
|
||||
desktop.version_code=11
|
||||
desktop.version_name=5.4-beta.0
|
||||
desktop.version_code=12
|
||||
|
||||
kotlin.version=1.8.20
|
||||
gradle.plugin.version=7.4.2
|
||||
|
@ -96,7 +96,7 @@ Incognito mode was [added a year ago](./20220901-simplex-chat-v3.2-incognito-mod
|
||||
|
||||
It is now simpler to use - you can decide whether to connect to a contact or join a group using your main profile at a point when you create an invitation link or connect via a link or QR code.
|
||||
|
||||
When you are connecting to people your know you usually want to share your main profile, and when connecting to public groups or strangers, you may prefer to use a random profile.
|
||||
When you are connecting to people you know you usually want to share your main profile, and when connecting to public groups or strangers, you may prefer to use a random profile.
|
||||
|
||||
## SimpleX platform
|
||||
|
||||
|
@ -1,13 +1,15 @@
|
||||
---
|
||||
title: Download SimpleX apps
|
||||
permalink: /downloads/index.html
|
||||
revision: 20.09.2023
|
||||
revision: 01.10.2023
|
||||
---
|
||||
|
||||
| Updated 20.09.2023 | Languages: EN |
|
||||
| Updated 01.10.2023 | Languages: EN |
|
||||
# Download SimpleX apps
|
||||
|
||||
The latest version is v5.3.1.
|
||||
The latest stable version is v5.3.1.
|
||||
|
||||
You can get the latest beta releases from [GitHub](https://github.com/simplex-chat/simplex-chat/releases).
|
||||
|
||||
- [desktop](#desktop-app)
|
||||
- [mobile](#mobile-apps)
|
||||
@ -23,7 +25,7 @@ Using the same profile as on mobile device is not yet supported – you need to
|
||||
|
||||
**Mac**: [x86_64](https://github.com/simplex-chat/simplex-chat/releases/download/v5.3.1/simplex-desktop-macos-x86_64.dmg) (Intel), [aarch64](https://github.com/simplex-chat/simplex-chat/releases/download/v5.3.1/simplex-desktop-macos-aarch64.dmg) (Apple Silicon).
|
||||
|
||||
**Windows**: coming soon.
|
||||
**Windows**: [x86_64](https://github.com/simplex-chat/simplex-chat/releases/download/v5.4.0-beta.0/simplex-desktop-windows-x86-64.msi) (BETA).
|
||||
|
||||
## Mobile apps
|
||||
|
||||
|
@ -7,7 +7,7 @@ function readlink() {
|
||||
}
|
||||
|
||||
if [ -z "${1}" ]; then
|
||||
echo "Job repo is unset. Provide it via first argument like: $(readlink "$0")/download_libs.sh https://something.com/job/something/{master,stable}"
|
||||
echo "Job repo is unset. Provide it via first argument like: $(readlink "$0")/download-libs.sh https://something.com/job/something/{master,stable}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
|
22
scripts/desktop/build-lib-windows.sh
Executable file
22
scripts/desktop/build-lib-windows.sh
Executable file
@ -0,0 +1,22 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -e
|
||||
|
||||
function readlink() {
|
||||
echo "$(cd "$(dirname "$1")"; pwd -P)"
|
||||
}
|
||||
root_dir="$(dirname "$(dirname "$(readlink "$0")")")"
|
||||
|
||||
OS=windows
|
||||
ARCH=`uname -a | rev | cut -d' ' -f2 | rev`
|
||||
JOB_REPO=${1:-$SIMPLEX_CI_REPO_URL}
|
||||
|
||||
cd $root_dir
|
||||
|
||||
rm -rf apps/multiplatform/common/src/commonMain/cpp/desktop/libs/$OS-$ARCH/
|
||||
rm -rf apps/multiplatform/desktop/src/jvmMain/resources/libs/$OS-$ARCH/
|
||||
rm -rf apps/multiplatform/desktop/build/cmake
|
||||
|
||||
mkdir -p apps/multiplatform/common/src/commonMain/cpp/desktop/libs/$OS-$ARCH/
|
||||
scripts/desktop/download-lib-windows.sh $JOB_REPO
|
||||
scripts/desktop/prepare-vlc-windows.sh
|
27
scripts/desktop/download-lib-windows.sh
Normal file
27
scripts/desktop/download-lib-windows.sh
Normal file
@ -0,0 +1,27 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -e
|
||||
|
||||
function readlink() {
|
||||
echo "$(cd "$(dirname "$1")"; pwd -P)"
|
||||
}
|
||||
|
||||
if [ -z "${1}" ]; then
|
||||
echo "Job repo is unset. Provide it via first argument like: $(readlink "$0")/download-lib-windows.sh https://something.com/job/something/{windows,windows-8107}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
job_repo=$1
|
||||
arch=x86_64
|
||||
root_dir="$(dirname "$(dirname "$(readlink "$0")")")"
|
||||
output_dir="$root_dir/apps/multiplatform/common/src/commonMain/cpp/desktop/libs/windows-$arch/"
|
||||
|
||||
mkdir -p "$output_dir"/deps 2> /dev/null
|
||||
|
||||
curl --location -o libsimplex.zip $job_repo/$arch-linux.$arch-windows:lib:simplex-chat/latest/download/1 && \
|
||||
$WINDIR\\System32\\tar.exe -xf libsimplex.zip && \
|
||||
mv libsimplex.dll "$output_dir" && \
|
||||
mv libcrypto*.dll "$output_dir/deps" && \
|
||||
mv libffi*.dll "$output_dir/deps" && \
|
||||
mv libgmp*.dll "$output_dir/deps" && \
|
||||
rm libsimplex.zip
|
25
scripts/desktop/prepare-vlc-windows.sh
Normal file
25
scripts/desktop/prepare-vlc-windows.sh
Normal file
@ -0,0 +1,25 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -e
|
||||
|
||||
function readlink() {
|
||||
echo "$(cd "$(dirname "$1")"; pwd -P)"
|
||||
}
|
||||
root_dir="$(dirname "$(dirname "$(readlink "$0")")")"
|
||||
vlc_dir=$root_dir/apps/multiplatform/common/src/commonMain/cpp/desktop/libs/windows-x86_64/deps/vlc
|
||||
rm -rf $vlc_dir
|
||||
mkdir -p $vlc_dir/vlc || exit 0
|
||||
|
||||
cd /tmp
|
||||
mkdir tmp 2>/dev/null || true
|
||||
cd tmp
|
||||
curl https://irltoolkit.mm.fcix.net/videolan-ftp/vlc/3.0.18/win64/vlc-3.0.18-win64.zip -L -o vlc
|
||||
$WINDIR\\System32\\tar.exe -xf vlc
|
||||
cd vlc-*
|
||||
# Setting the same date as the date that will be on the file after extraction from JAR to make VLC cache checker happy
|
||||
find plugins | grep ".dll" | xargs touch -m -d "1970-01-01T00:00:00Z"
|
||||
./vlc-cache-gen plugins
|
||||
cp *.dll $vlc_dir/
|
||||
cp -r -p plugins/ $vlc_dir/vlc/plugins
|
||||
cd ../../
|
||||
rm -rf tmp
|
@ -456,6 +456,7 @@ test-suite simplex-chat-test
|
||||
MobileTests
|
||||
ProtocolTests
|
||||
SchemaDump
|
||||
ValidNames
|
||||
ViewTests
|
||||
WebRTCTests
|
||||
Broadcast.Bot
|
||||
|
@ -28,7 +28,7 @@ import Data.Bifunctor (bimap, first)
|
||||
import qualified Data.ByteString.Base64 as B64
|
||||
import Data.ByteString.Char8 (ByteString)
|
||||
import qualified Data.ByteString.Char8 as B
|
||||
import Data.Char (isSpace, toLower)
|
||||
import Data.Char
|
||||
import Data.Constraint (Dict (..))
|
||||
import Data.Either (fromRight, rights)
|
||||
import Data.Fixed (div')
|
||||
@ -355,6 +355,7 @@ processChatCommand :: forall m. ChatMonad m => ChatCommand -> m ChatResponse
|
||||
processChatCommand = \case
|
||||
ShowActiveUser -> withUser' $ pure . CRActiveUser
|
||||
CreateActiveUser NewUser {profile, sameServers, pastTimestamp} -> do
|
||||
forM_ profile $ \Profile {displayName} -> checkValidName displayName
|
||||
p@Profile {displayName} <- liftIO $ maybe generateRandomProfile pure profile
|
||||
u <- asks currentUser
|
||||
(smp, smpServers) <- chooseServers SPSMP
|
||||
@ -891,7 +892,8 @@ processChatCommand = \case
|
||||
filesInfo <- withStore' $ \db -> getContactFileInfo db user ct
|
||||
withChatLock "deleteChat direct" . procCmd $ do
|
||||
deleteFilesAndConns user filesInfo
|
||||
when (contactActive ct && notify) . void $ sendDirectContactMessage ct XDirectDel
|
||||
when (isReady ct && contactActive ct && notify) $
|
||||
void (sendDirectContactMessage ct XDirectDel) `catchChatError` const (pure ())
|
||||
contactConnIds <- map aConnId <$> withStore (\db -> getContactConnections db userId ct)
|
||||
deleteAgentConnectionsAsync user contactConnIds
|
||||
-- functions below are called in separate transactions to prevent crashes on android
|
||||
@ -1448,7 +1450,8 @@ processChatCommand = \case
|
||||
chatRef <- getChatRef user chatName
|
||||
chatItemId <- getChatItemIdByText user chatRef msg
|
||||
processChatCommand $ APIChatItemReaction chatRef chatItemId add reaction
|
||||
APINewGroup userId gProfile -> withUserId userId $ \user -> do
|
||||
APINewGroup userId gProfile@GroupProfile {displayName} -> withUserId userId $ \user -> do
|
||||
checkValidName displayName
|
||||
gVar <- asks idsDrg
|
||||
groupInfo <- withStore $ \db -> createNewGroup db gVar user gProfile
|
||||
pure $ CRGroupCreated user groupInfo
|
||||
@ -1953,9 +1956,10 @@ processChatCommand = \case
|
||||
updateProfile :: User -> Profile -> m ChatResponse
|
||||
updateProfile user p' = updateProfile_ user p' $ withStore $ \db -> updateUserProfile db user p'
|
||||
updateProfile_ :: User -> Profile -> m User -> m ChatResponse
|
||||
updateProfile_ user@User {profile = p} p' updateUser
|
||||
updateProfile_ user@User {profile = p@LocalProfile {displayName = n}} p'@Profile {displayName = n'} updateUser
|
||||
| p' == fromLocalProfile p = pure $ CRUserProfileNoChange user
|
||||
| otherwise = do
|
||||
when (n /= n') $ checkValidName n'
|
||||
-- read contacts before user update to correctly merge preferences
|
||||
-- [incognito] filter out contacts with whom user has incognito connections
|
||||
contacts <-
|
||||
@ -1997,8 +2001,9 @@ processChatCommand = \case
|
||||
when (directOrUsed ct') $ createSndFeatureItems user ct ct'
|
||||
pure $ CRContactPrefsUpdated user ct ct'
|
||||
runUpdateGroupProfile :: User -> Group -> GroupProfile -> m ChatResponse
|
||||
runUpdateGroupProfile user (Group g@GroupInfo {groupProfile = p} ms) p' = do
|
||||
runUpdateGroupProfile user (Group g@GroupInfo {groupProfile = p@GroupProfile {displayName = n}} ms) p'@GroupProfile {displayName = n'} = do
|
||||
assertUserGroupRole g GROwner
|
||||
when (n /= n') $ checkValidName n'
|
||||
g' <- withStore $ \db -> updateGroupProfile db user g p'
|
||||
(msg, _) <- sendGroupMessage user g' ms (XGrpInfo p')
|
||||
let cd = CDGroupSnd g'
|
||||
@ -2007,6 +2012,10 @@ processChatCommand = \case
|
||||
toView $ CRNewChatItem user (AChatItem SCTGroup SMDSnd (GroupChat g') ci)
|
||||
createGroupFeatureChangedItems user cd CISndGroupFeature g g'
|
||||
pure $ CRGroupUpdated user g g' Nothing
|
||||
checkValidName :: GroupName -> m ()
|
||||
checkValidName displayName = do
|
||||
let validName = T.pack $ mkValidName $ T.unpack displayName
|
||||
when (displayName /= validName) $ throwChatError CEInvalidDisplayName {displayName, validName}
|
||||
assertUserGroupRole :: GroupInfo -> GroupMemberRole -> m ()
|
||||
assertUserGroupRole g@GroupInfo {membership} requiredRole = do
|
||||
when (memberRole (membership :: GroupMember) < requiredRole) $ throwChatError $ CEGroupUserRole g requiredRole
|
||||
@ -5235,8 +5244,7 @@ getCreateActiveUser st testView = do
|
||||
where
|
||||
loop = do
|
||||
displayName <- getContactName
|
||||
fullName <- T.pack <$> getWithPrompt "full name (optional)"
|
||||
withTransaction st (\db -> runExceptT $ createUserRecord db (AgentUserId 1) Profile {displayName, fullName, image = Nothing, contactLink = Nothing, preferences = Nothing} True) >>= \case
|
||||
withTransaction st (\db -> runExceptT $ createUserRecord db (AgentUserId 1) Profile {displayName, fullName = "", image = Nothing, contactLink = Nothing, preferences = Nothing} True) >>= \case
|
||||
Left SEDuplicateName -> do
|
||||
putStrLn "chosen display name is already used by another profile on this device, choose another one"
|
||||
loop
|
||||
@ -5266,10 +5274,13 @@ getCreateActiveUser st testView = do
|
||||
T.unpack $ localDisplayName <> if T.null fullName || localDisplayName == fullName then "" else " (" <> fullName <> ")"
|
||||
getContactName :: IO ContactName
|
||||
getContactName = do
|
||||
displayName <- getWithPrompt "display name (no spaces)"
|
||||
if null displayName || isJust (find (== ' ') displayName)
|
||||
then putStrLn "display name has space(s), choose another one" >> getContactName
|
||||
else pure $ T.pack displayName
|
||||
displayName <- getWithPrompt "display name"
|
||||
let validName = mkValidName displayName
|
||||
if
|
||||
| null displayName -> putStrLn "display name can't be empty" >> getContactName
|
||||
| null validName -> putStrLn "display name is invalid, please choose another" >> getContactName
|
||||
| displayName /= validName -> putStrLn ("display name is invalid, you could use this one: " <> validName) >> getContactName
|
||||
| otherwise -> pure $ T.pack displayName
|
||||
getWithPrompt :: String -> IO String
|
||||
getWithPrompt s = putStr (s <> ": ") >> hFlush stdout >> getLine
|
||||
|
||||
@ -5600,7 +5611,13 @@ chatCommandP =
|
||||
mcTextP = MCText . safeDecodeUtf8 <$> A.takeByteString
|
||||
msgContentP = "text " *> mcTextP <|> "json " *> jsonP
|
||||
ciDeleteMode = "broadcast" $> CIDMBroadcast <|> "internal" $> CIDMInternal
|
||||
displayName = safeDecodeUtf8 <$> (B.cons <$> A.satisfy refChar <*> A.takeTill (== ' '))
|
||||
displayName = safeDecodeUtf8 <$> (quoted "'\"" <|> takeNameTill isSpace)
|
||||
where
|
||||
takeNameTill p =
|
||||
A.peekChar' >>= \c ->
|
||||
if refChar c then A.takeTill p else fail "invalid first character in display name"
|
||||
quoted cs = A.choice [A.char c *> takeNameTill (== c) <* A.char c | c <- cs]
|
||||
refChar c = c > ' ' && c /= '#' && c /= '@'
|
||||
sendMsgQuote msgDir = SendMessageQuote <$> displayName <* A.space <*> pure msgDir <*> quotedMsg <*> msgTextP
|
||||
quotedMsg = safeDecodeUtf8 <$> (A.char '(' *> A.takeTill (== ')') <* A.char ')') <* optional A.space
|
||||
reactionP = MREmoji <$> (mrEmojiChar <$?> (toEmoji <$> A.anyChar))
|
||||
@ -5613,7 +5630,6 @@ chatCommandP =
|
||||
'*' -> head "❤️"
|
||||
'^' -> '🚀'
|
||||
c -> c
|
||||
refChar c = c > ' ' && c /= '#' && c /= '@'
|
||||
liveMessageP = " live=" *> onOffP <|> pure False
|
||||
sendMessageTTLP = " ttl=" *> ((Just <$> A.decimal) <|> ("default" $> Nothing)) <|> pure Nothing
|
||||
receiptSettings = do
|
||||
@ -5708,3 +5724,16 @@ timeItToView s action = do
|
||||
let diff = diffToMilliseconds $ diffUTCTime t2 t1
|
||||
toView $ CRTimedAction s diff
|
||||
pure a
|
||||
|
||||
mkValidName :: String -> String
|
||||
mkValidName = reverse . dropWhile isSpace . fst . foldl' addChar ("", '\NUL')
|
||||
where
|
||||
addChar (r, prev) c = if notProhibited && validChar then (c' : r, c') else (r, prev)
|
||||
where
|
||||
c' = if isSpace c then ' ' else c
|
||||
validChar
|
||||
| prev == '\NUL' || isSpace prev = validFirstChar
|
||||
| isPunctuation prev = validFirstChar || isSpace c
|
||||
| otherwise = validFirstChar || isSpace c || isMark c || isPunctuation c
|
||||
validFirstChar = isLetter c || isNumber c || isSymbol c
|
||||
notProhibited = c `notElem` ("@#'\"`" :: String)
|
||||
|
@ -882,6 +882,7 @@ data ChatErrorType
|
||||
| CEEmptyUserPassword {userId :: UserId}
|
||||
| CEUserAlreadyHidden {userId :: UserId}
|
||||
| CEUserNotHidden {userId :: UserId}
|
||||
| CEInvalidDisplayName {displayName :: Text, validName :: Text}
|
||||
| CEChatNotStarted
|
||||
| CEChatNotStopped
|
||||
| CEChatStoreChanged
|
||||
|
@ -65,6 +65,8 @@ foreign export ccall "chat_parse_server" cChatParseServer :: CString -> IO CJSON
|
||||
|
||||
foreign export ccall "chat_password_hash" cChatPasswordHash :: CString -> CString -> IO CString
|
||||
|
||||
foreign export ccall "chat_valid_name" cChatValidName :: CString -> IO CString
|
||||
|
||||
foreign export ccall "chat_encrypt_media" cChatEncryptMedia :: CString -> Ptr Word8 -> CInt -> IO CString
|
||||
|
||||
foreign export ccall "chat_decrypt_media" cChatDecryptMedia :: CString -> Ptr Word8 -> CInt -> IO CString
|
||||
@ -124,6 +126,10 @@ cChatPasswordHash cPwd cSalt = do
|
||||
salt <- B.packCString cSalt
|
||||
newCStringFromBS $ chatPasswordHash pwd salt
|
||||
|
||||
-- This function supports utf8 strings
|
||||
cChatValidName :: CString -> IO CString
|
||||
cChatValidName cName = newCString . mkValidName =<< peekCString cName
|
||||
|
||||
mobileChatOpts :: String -> String -> ChatOpts
|
||||
mobileChatOpts dbFilePrefix dbKey =
|
||||
ChatOpts
|
||||
|
@ -14,7 +14,7 @@ import Data.Aeson (ToJSON)
|
||||
import qualified Data.Aeson as J
|
||||
import qualified Data.ByteString.Char8 as B
|
||||
import qualified Data.ByteString.Lazy.Char8 as LB
|
||||
import Data.Char (toUpper)
|
||||
import Data.Char (isSpace, toUpper)
|
||||
import Data.Function (on)
|
||||
import Data.Int (Int64)
|
||||
import Data.List (groupBy, intercalate, intersperse, partition, sortOn)
|
||||
@ -223,7 +223,7 @@ responseToView user_ ChatConfig {logLevel, showReactions, showReceipts, testView
|
||||
CRLeftMember u g m -> ttyUser u [ttyGroup' g <> ": " <> ttyMember m <> " left the group"]
|
||||
CRGroupEmpty u g -> ttyUser u [ttyFullGroup g <> ": group is empty"]
|
||||
CRGroupRemoved u g -> ttyUser u [ttyFullGroup g <> ": you are no longer a member or group deleted"]
|
||||
CRGroupDeleted u g m -> ttyUser u [ttyGroup' g <> ": " <> ttyMember m <> " deleted the group", "use " <> highlight ("/d #" <> groupName' g) <> " to delete the local copy of the group"]
|
||||
CRGroupDeleted u g m -> ttyUser u [ttyGroup' g <> ": " <> ttyMember m <> " deleted the group", "use " <> highlight ("/d #" <> viewGroupName g) <> " to delete the local copy of the group"]
|
||||
CRGroupUpdated u g g' m -> ttyUser u $ viewGroupUpdated g g' m
|
||||
CRGroupProfile u g -> ttyUser u $ viewGroupProfile g
|
||||
CRGroupDescription u g -> ttyUser u $ viewGroupDescription g
|
||||
@ -673,10 +673,7 @@ viewContactNotFound cName suspectedMember =
|
||||
["no contact " <> ttyContact cName <> useMessageMember]
|
||||
where
|
||||
useMessageMember = case suspectedMember of
|
||||
Just (g, m) -> do
|
||||
let GroupInfo {localDisplayName = gName} = g
|
||||
GroupMember {localDisplayName = mName} = m
|
||||
", use " <> highlight' ("@#" <> T.unpack gName <> " " <> T.unpack mName <> " <your message>")
|
||||
Just (g, m) -> ", use " <> highlight ("@#" <> viewGroupName g <> " " <> viewMemberName m <> " <your message>")
|
||||
_ -> ""
|
||||
|
||||
viewChatCleared :: AChatInfo -> [StyledString]
|
||||
@ -729,14 +726,14 @@ groupLink_ intro g cReq mRole =
|
||||
(plain . strEncode) cReq,
|
||||
"",
|
||||
"Anybody can connect to you and join group as " <> showRole mRole <> " with: " <> highlight' "/c <group_link_above>",
|
||||
"to show it again: " <> highlight ("/show link #" <> groupName' g),
|
||||
"to delete it: " <> highlight ("/delete link #" <> groupName' g) <> " (joined members will remain connected to you)"
|
||||
"to show it again: " <> highlight ("/show link #" <> viewGroupName g),
|
||||
"to delete it: " <> highlight ("/delete link #" <> viewGroupName g) <> " (joined members will remain connected to you)"
|
||||
]
|
||||
|
||||
viewGroupLinkDeleted :: GroupInfo -> [StyledString]
|
||||
viewGroupLinkDeleted g =
|
||||
[ "Group link is deleted - joined members will remain connected.",
|
||||
"To create a new group link use " <> highlight ("/create link #" <> groupName' g)
|
||||
"To create a new group link use " <> highlight ("/create link #" <> viewGroupName g)
|
||||
]
|
||||
|
||||
viewSentInvitation :: Maybe Profile -> Bool -> [StyledString]
|
||||
@ -753,20 +750,20 @@ viewSentInvitation incognitoProfile testView =
|
||||
viewReceivedContactRequest :: ContactName -> Profile -> [StyledString]
|
||||
viewReceivedContactRequest c Profile {fullName} =
|
||||
[ ttyFullName c fullName <> " wants to connect to you!",
|
||||
"to accept: " <> highlight ("/ac " <> c),
|
||||
"to reject: " <> highlight ("/rc " <> c) <> " (the sender will NOT be notified)"
|
||||
"to accept: " <> highlight ("/ac " <> viewName c),
|
||||
"to reject: " <> highlight ("/rc " <> viewName c) <> " (the sender will NOT be notified)"
|
||||
]
|
||||
|
||||
viewGroupCreated :: GroupInfo -> [StyledString]
|
||||
viewGroupCreated g@GroupInfo {localDisplayName = n} =
|
||||
viewGroupCreated g =
|
||||
[ "group " <> ttyFullGroup g <> " is created",
|
||||
"to add members use " <> highlight ("/a " <> n <> " <name>") <> " or " <> highlight ("/create link #" <> n)
|
||||
"to add members use " <> highlight ("/a " <> viewGroupName g <> " <name>") <> " or " <> highlight ("/create link #" <> viewGroupName g)
|
||||
]
|
||||
|
||||
viewCannotResendInvitation :: GroupInfo -> ContactName -> [StyledString]
|
||||
viewCannotResendInvitation GroupInfo {localDisplayName = gn} c =
|
||||
[ ttyContact c <> " is already invited to group " <> ttyGroup gn,
|
||||
"to re-send invitation: " <> highlight ("/rm " <> gn <> " " <> c) <> ", " <> highlight ("/a " <> gn <> " " <> c)
|
||||
viewCannotResendInvitation g c =
|
||||
[ ttyContact c <> " is already invited to group " <> ttyGroup' g,
|
||||
"to re-send invitation: " <> highlight ("/rm " <> viewGroupName g <> " " <> c) <> ", " <> highlight ("/a " <> viewGroupName g <> " " <> viewName c)
|
||||
]
|
||||
|
||||
viewDirectMessagesProhibited :: MsgDirection -> Contact -> [StyledString]
|
||||
@ -787,11 +784,11 @@ viewReceivedGroupInvitation :: GroupInfo -> Contact -> GroupMemberRole -> [Style
|
||||
viewReceivedGroupInvitation g c role =
|
||||
ttyFullGroup g <> ": " <> ttyContact' c <> " invites you to join the group as " <> plain (strEncode role) :
|
||||
case incognitoMembershipProfile g of
|
||||
Just mp -> ["use " <> highlight ("/j " <> groupName' g) <> " to join incognito as " <> incognitoProfile' (fromLocalProfile mp)]
|
||||
Nothing -> ["use " <> highlight ("/j " <> groupName' g) <> " to accept"]
|
||||
Just mp -> ["use " <> highlight ("/j " <> viewGroupName g) <> " to join incognito as " <> incognitoProfile' (fromLocalProfile mp)]
|
||||
Nothing -> ["use " <> highlight ("/j " <> viewGroupName g) <> " to accept"]
|
||||
|
||||
groupPreserved :: GroupInfo -> [StyledString]
|
||||
groupPreserved g = ["use " <> highlight ("/d #" <> groupName' g) <> " to delete the group"]
|
||||
groupPreserved g = ["use " <> highlight ("/d #" <> viewGroupName g) <> " to delete the group"]
|
||||
|
||||
connectedMember :: GroupMember -> StyledString
|
||||
connectedMember m = case memberCategory m of
|
||||
@ -841,7 +838,7 @@ viewGroupMembers (Group GroupInfo {membership} members) = map groupMember . filt
|
||||
_ -> ""
|
||||
|
||||
viewContactConnected :: Contact -> Maybe Profile -> Bool -> [StyledString]
|
||||
viewContactConnected ct@Contact {localDisplayName} userIncognitoProfile testView =
|
||||
viewContactConnected ct userIncognitoProfile testView =
|
||||
case userIncognitoProfile of
|
||||
Just profile ->
|
||||
if testView
|
||||
@ -850,7 +847,7 @@ viewContactConnected ct@Contact {localDisplayName} userIncognitoProfile testView
|
||||
where
|
||||
message =
|
||||
[ ttyFullContact ct <> ": contact is connected, your incognito profile for this contact is " <> incognitoProfile' profile,
|
||||
"use " <> highlight ("/i " <> localDisplayName) <> " to print out this incognito profile again"
|
||||
"use " <> highlight ("/i " <> viewContactName ct) <> " to print out this incognito profile again"
|
||||
]
|
||||
Nothing ->
|
||||
[ttyFullContact ct <> ": contact is connected"]
|
||||
@ -860,10 +857,10 @@ viewGroupsList [] = ["you have no groups!", "to create: " <> highlight' "/g <nam
|
||||
viewGroupsList gs = map groupSS $ sortOn ldn_ gs
|
||||
where
|
||||
ldn_ = T.toLower . (localDisplayName :: GroupInfo -> GroupName) . fst
|
||||
groupSS (g@GroupInfo {localDisplayName = ldn, groupProfile = GroupProfile {fullName}, membership, chatSettings}, GroupSummary {currentMembers}) =
|
||||
groupSS (g@GroupInfo {membership, chatSettings}, GroupSummary {currentMembers}) =
|
||||
case memberStatus membership of
|
||||
GSMemInvited -> groupInvitation' g
|
||||
s -> membershipIncognito g <> ttyGroup ldn <> optFullName ldn fullName <> viewMemberStatus s
|
||||
s -> membershipIncognito g <> ttyFullGroup g <> viewMemberStatus s
|
||||
where
|
||||
viewMemberStatus = \case
|
||||
GSMemRemoved -> delete "you are removed"
|
||||
@ -871,18 +868,18 @@ viewGroupsList gs = map groupSS $ sortOn ldn_ gs
|
||||
GSMemGroupDeleted -> delete "group deleted"
|
||||
_
|
||||
| enableNtfs chatSettings -> " (" <> memberCount <> ")"
|
||||
| otherwise -> " (" <> memberCount <> ", muted, you can " <> highlight ("/unmute #" <> ldn) <> ")"
|
||||
delete reason = " (" <> reason <> ", delete local copy: " <> highlight ("/d #" <> ldn) <> ")"
|
||||
| otherwise -> " (" <> memberCount <> ", muted, you can " <> highlight ("/unmute #" <> viewGroupName g) <> ")"
|
||||
delete reason = " (" <> reason <> ", delete local copy: " <> highlight ("/d #" <> viewGroupName g) <> ")"
|
||||
memberCount = sShow currentMembers <> " member" <> if currentMembers == 1 then "" else "s"
|
||||
|
||||
groupInvitation' :: GroupInfo -> StyledString
|
||||
groupInvitation' g@GroupInfo {localDisplayName = ldn, groupProfile = GroupProfile {fullName}} =
|
||||
highlight ("#" <> ldn)
|
||||
highlight ("#" <> viewName ldn)
|
||||
<> optFullName ldn fullName
|
||||
<> " - you are invited ("
|
||||
<> highlight ("/j " <> ldn)
|
||||
<> highlight ("/j " <> viewName ldn)
|
||||
<> joinText
|
||||
<> highlight ("/d #" <> ldn)
|
||||
<> highlight ("/d #" <> viewName ldn)
|
||||
<> " to delete invitation)"
|
||||
where
|
||||
joinText = case incognitoMembershipProfile g of
|
||||
@ -890,21 +887,21 @@ groupInvitation' g@GroupInfo {localDisplayName = ldn, groupProfile = GroupProfil
|
||||
Nothing -> " to join, "
|
||||
|
||||
viewContactsMerged :: Contact -> Contact -> [StyledString]
|
||||
viewContactsMerged _into@Contact {localDisplayName = c1} _merged@Contact {localDisplayName = c2} =
|
||||
[ "contact " <> ttyContact c2 <> " is merged into " <> ttyContact c1,
|
||||
"use " <> ttyToContact c1 <> highlight' "<message>" <> " to send messages"
|
||||
viewContactsMerged c1 c2 =
|
||||
[ "contact " <> ttyContact' c2 <> " is merged into " <> ttyContact' c1,
|
||||
"use " <> ttyToContact' c1 <> highlight' "<message>" <> " to send messages"
|
||||
]
|
||||
|
||||
viewUserProfile :: Profile -> [StyledString]
|
||||
viewUserProfile Profile {displayName, fullName} =
|
||||
[ "user profile: " <> ttyFullName displayName fullName,
|
||||
"use " <> highlight' "/p <display name> [<full name>]" <> " to change it",
|
||||
"use " <> highlight' "/p <display name>" <> " to change it",
|
||||
"(the updated profile will be sent to all your contacts)"
|
||||
]
|
||||
|
||||
viewUserPrivacy :: User -> User -> [StyledString]
|
||||
viewUserPrivacy User {userId} User {userId = userId', localDisplayName = n', showNtfs, viewPwdHash} =
|
||||
[ (if userId == userId' then "current " else "") <> "user " <> plain n' <> ":",
|
||||
[ plain $ (if userId == userId' then "current " else "") <> "user " <> viewName n' <> ":",
|
||||
"messages are " <> if showNtfs then "shown" else "hidden (use /tail to view)",
|
||||
"profile is " <> if isJust viewPwdHash then "hidden" else "visible"
|
||||
]
|
||||
@ -1050,18 +1047,18 @@ viewGroupMemberSwitch g m (SwitchProgress qd phase _) = case qd of
|
||||
QDSnd -> [ttyGroup' g <> ": " <> ttyMember m <> " " <> viewSwitchPhase phase <> " for you"]
|
||||
|
||||
viewContactRatchetSync :: Contact -> RatchetSyncProgress -> [StyledString]
|
||||
viewContactRatchetSync ct@Contact {localDisplayName = c} RatchetSyncProgress {ratchetSyncStatus = rss} =
|
||||
viewContactRatchetSync ct RatchetSyncProgress {ratchetSyncStatus = rss} =
|
||||
[ttyContact' ct <> ": " <> (plain . ratchetSyncStatusToText) rss]
|
||||
<> help
|
||||
where
|
||||
help = ["use " <> highlight ("/sync " <> c) <> " to synchronize" | rss `elem` [RSAllowed, RSRequired]]
|
||||
help = ["use " <> highlight ("/sync " <> viewContactName ct) <> " to synchronize" | rss `elem` [RSAllowed, RSRequired]]
|
||||
|
||||
viewGroupMemberRatchetSync :: GroupInfo -> GroupMember -> RatchetSyncProgress -> [StyledString]
|
||||
viewGroupMemberRatchetSync g m@GroupMember {localDisplayName = n} RatchetSyncProgress {ratchetSyncStatus = rss} =
|
||||
viewGroupMemberRatchetSync g m RatchetSyncProgress {ratchetSyncStatus = rss} =
|
||||
[ttyGroup' g <> " " <> ttyMember m <> ": " <> (plain . ratchetSyncStatusToText) rss]
|
||||
<> help
|
||||
where
|
||||
help = ["use " <> highlight ("/sync #" <> groupName' g <> " " <> n) <> " to synchronize" | rss `elem` [RSAllowed, RSRequired]]
|
||||
help = ["use " <> highlight ("/sync #" <> viewGroupName g <> " " <> viewMemberName m) <> " to synchronize" | rss `elem` [RSAllowed, RSRequired]]
|
||||
|
||||
viewContactVerificationReset :: Contact -> [StyledString]
|
||||
viewContactVerificationReset ct =
|
||||
@ -1072,10 +1069,10 @@ viewGroupMemberVerificationReset g m =
|
||||
[ttyGroup' g <> " " <> ttyMember m <> ": security code changed"]
|
||||
|
||||
viewContactCode :: Contact -> Text -> Bool -> [StyledString]
|
||||
viewContactCode ct@Contact {localDisplayName = c} = viewSecurityCode (ttyContact' ct) ("/verify " <> c <> " <code from your contact>")
|
||||
viewContactCode ct = viewSecurityCode (ttyContact' ct) ("/verify " <> viewContactName ct <> " <code from your contact>")
|
||||
|
||||
viewGroupMemberCode :: GroupInfo -> GroupMember -> Text -> Bool -> [StyledString]
|
||||
viewGroupMemberCode g m@GroupMember {localDisplayName = n} = viewSecurityCode (ttyGroup' g <> " " <> ttyMember m) ("/verify #" <> groupName' g <> " " <> n <> " <code from your contact>")
|
||||
viewGroupMemberCode g m = viewSecurityCode (ttyGroup' g <> " " <> ttyMember m) ("/verify #" <> viewGroupName g <> " " <> viewMemberName m <> " <code from your contact>")
|
||||
|
||||
viewSecurityCode :: StyledString -> Text -> Text -> Bool -> [StyledString]
|
||||
viewSecurityCode name cmd code testView
|
||||
@ -1201,9 +1198,9 @@ bold' :: String -> StyledString
|
||||
bold' = styled Bold
|
||||
|
||||
viewContactAliasUpdated :: Contact -> [StyledString]
|
||||
viewContactAliasUpdated Contact {localDisplayName = n, profile = LocalProfile {localAlias}}
|
||||
| localAlias == "" = ["contact " <> ttyContact n <> " alias removed"]
|
||||
| otherwise = ["contact " <> ttyContact n <> " alias updated: " <> plain localAlias]
|
||||
viewContactAliasUpdated ct@Contact {profile = LocalProfile {localAlias}}
|
||||
| localAlias == "" = ["contact " <> ttyContact' ct <> " alias removed"]
|
||||
| otherwise = ["contact " <> ttyContact' ct <> " alias updated: " <> plain localAlias]
|
||||
|
||||
viewConnectionAliasUpdated :: PendingContactConnection -> [StyledString]
|
||||
viewConnectionAliasUpdated PendingContactConnection {pccConnId, localAlias}
|
||||
@ -1380,10 +1377,10 @@ savingFile' testView (AChatItem _ _ chat ChatItem {file = Just CIFile {fileId, f
|
||||
savingFile' _ _ = ["saving file"] -- shouldn't happen
|
||||
|
||||
receivingFile_' :: StyledString -> AChatItem -> [StyledString]
|
||||
receivingFile_' status (AChatItem _ _ (DirectChat Contact {localDisplayName = c}) ChatItem {file = Just CIFile {fileId, fileName}, chatDir = CIDirectRcv}) =
|
||||
[status <> " receiving " <> fileTransferStr fileId fileName <> " from " <> ttyContact c]
|
||||
receivingFile_' status (AChatItem _ _ _ ChatItem {file = Just CIFile {fileId, fileName}, chatDir = CIGroupRcv GroupMember {localDisplayName = m}}) =
|
||||
[status <> " receiving " <> fileTransferStr fileId fileName <> " from " <> ttyContact m]
|
||||
receivingFile_' status (AChatItem _ _ (DirectChat c) ChatItem {file = Just CIFile {fileId, fileName}, chatDir = CIDirectRcv}) =
|
||||
[status <> " receiving " <> fileTransferStr fileId fileName <> " from " <> ttyContact' c]
|
||||
receivingFile_' status (AChatItem _ _ _ ChatItem {file = Just CIFile {fileId, fileName}, chatDir = CIGroupRcv m}) =
|
||||
[status <> " receiving " <> fileTransferStr fileId fileName <> " from " <> ttyMember m]
|
||||
receivingFile_' status _ = [status <> " receiving file"] -- shouldn't happen
|
||||
|
||||
receivingFile_ :: StyledString -> RcvFileTransfer -> [StyledString]
|
||||
@ -1550,6 +1547,9 @@ viewChatError logLevel = \case
|
||||
CEEmptyUserPassword _ -> ["user password is required"]
|
||||
CEUserAlreadyHidden _ -> ["user is already hidden"]
|
||||
CEUserNotHidden _ -> ["user is not hidden"]
|
||||
CEInvalidDisplayName {displayName, validName} -> map plain $
|
||||
["invalid display name: " <> viewName displayName]
|
||||
<> ["you could use this one: " <> viewName validName | not (T.null validName)]
|
||||
CEChatNotStarted -> ["error: chat not started"]
|
||||
CEChatNotStopped -> ["error: chat not stopped"]
|
||||
CEChatStoreChanged -> ["error: chat store changed, please restart chat"]
|
||||
@ -1562,8 +1562,8 @@ viewChatError logLevel = \case
|
||||
]
|
||||
CEContactNotFound cName m_ -> viewContactNotFound cName m_
|
||||
CEContactNotReady c -> [ttyContact' c <> ": not ready"]
|
||||
CEContactDisabled ct -> [ttyContact' ct <> ": disabled, to enable: " <> highlight ("/enable " <> viewContactName ct) <> ", to delete: " <> highlight ("/d " <> viewContactName ct)]
|
||||
CEContactNotActive c -> [ttyContact' c <> ": not active"]
|
||||
CEContactDisabled Contact {localDisplayName = c} -> [ttyContact c <> ": disabled, to enable: " <> highlight ("/enable " <> c) <> ", to delete: " <> highlight ("/d " <> c)]
|
||||
CEConnectionDisabled Connection {connId, connType} -> [plain $ "connection " <> textEncode connType <> " (" <> tshow connId <> ") is disabled" | logLevel <= CLLWarning]
|
||||
CEGroupDuplicateMember c -> ["contact " <> ttyContact c <> " is already in the group"]
|
||||
CEGroupDuplicateMemberId -> ["cannot add member - duplicate member ID"]
|
||||
@ -1575,7 +1575,7 @@ viewChatError logLevel = \case
|
||||
CEContactIncognitoCantInvite -> ["you're using your main profile for this group - prohibited to invite contacts to whom you are connected incognito"]
|
||||
CEGroupIncognitoCantInvite -> ["you've connected to this group using an incognito profile - prohibited to invite contacts"]
|
||||
CEGroupContactRole c -> ["contact " <> ttyContact c <> " has insufficient permissions for this group action"]
|
||||
CEGroupNotJoined g -> ["you did not join this group, use " <> highlight ("/join #" <> groupName' g)]
|
||||
CEGroupNotJoined g -> ["you did not join this group, use " <> highlight ("/join #" <> viewGroupName g)]
|
||||
CEGroupMemberNotActive -> ["your group connection is not active yet, try later"]
|
||||
CEGroupMemberUserRemoved -> ["you are no longer a member of the group"]
|
||||
CEGroupMemberNotFound -> ["group doesn't have this member"]
|
||||
@ -1635,8 +1635,8 @@ viewChatError logLevel = \case
|
||||
SEFileIdNotFoundBySharedMsgId _ -> [] -- recipient tried to accept cancelled file
|
||||
SEConnectionNotFound agentConnId -> ["event connection not found, agent ID: " <> sShow agentConnId | logLevel <= CLLWarning] -- mutes delete group error
|
||||
SEChatItemNotFoundByText text -> ["message not found by text: " <> plain text]
|
||||
SEDuplicateGroupLink g -> ["you already have link for this group, to show: " <> highlight ("/show link #" <> groupName' g)]
|
||||
SEGroupLinkNotFound g -> ["no group link, to create: " <> highlight ("/create link #" <> groupName' g)]
|
||||
SEDuplicateGroupLink g -> ["you already have link for this group, to show: " <> highlight ("/show link #" <> viewGroupName g)]
|
||||
SEGroupLinkNotFound g -> ["no group link, to create: " <> highlight ("/create link #" <> viewGroupName g)]
|
||||
e -> ["chat db error: " <> sShow e]
|
||||
ChatErrorDatabase err -> case err of
|
||||
DBErrorEncrypted -> ["error: chat database is already encrypted"]
|
||||
@ -1680,8 +1680,8 @@ viewChatError logLevel = \case
|
||||
|
||||
viewConnectionEntityDisabled :: ConnectionEntity -> [StyledString]
|
||||
viewConnectionEntityDisabled entity = case entity of
|
||||
RcvDirectMsgConnection _ (Just Contact {localDisplayName = c}) -> ["[" <> entityLabel <> "] connection is disabled, to enable: " <> highlight ("/enable " <> c) <> ", to delete: " <> highlight ("/d " <> c)]
|
||||
RcvGroupMsgConnection _ GroupInfo {localDisplayName = g} GroupMember {localDisplayName = m} -> ["[" <> entityLabel <> "] connection is disabled, to enable: " <> highlight ("/enable #" <> g <> " " <> m)]
|
||||
RcvDirectMsgConnection _ (Just c) -> ["[" <> entityLabel <> "] connection is disabled, to enable: " <> highlight ("/enable " <> viewContactName c) <> ", to delete: " <> highlight ("/d " <> viewContactName c)]
|
||||
RcvGroupMsgConnection _ g m -> ["[" <> entityLabel <> "] connection is disabled, to enable: " <> highlight ("/enable #" <> viewGroupName g <> " " <> viewMemberName m)]
|
||||
_ -> ["[" <> entityLabel <> "] connection is disabled"]
|
||||
where
|
||||
entityLabel = connEntityLabel entity
|
||||
@ -1696,7 +1696,7 @@ connEntityLabel = \case
|
||||
UserContactConnection _ UserContact {} -> "contact address"
|
||||
|
||||
ttyContact :: ContactName -> StyledString
|
||||
ttyContact = styled $ colored Green
|
||||
ttyContact = styled (colored Green) . viewName
|
||||
|
||||
ttyContact' :: Contact -> StyledString
|
||||
ttyContact' Contact {localDisplayName = c} = ttyContact c
|
||||
@ -1716,37 +1716,46 @@ ttyFullName :: ContactName -> Text -> StyledString
|
||||
ttyFullName c fullName = ttyContact c <> optFullName c fullName
|
||||
|
||||
ttyToContact :: ContactName -> StyledString
|
||||
ttyToContact c = ttyTo $ "@" <> c <> " "
|
||||
ttyToContact c = ttyTo $ "@" <> viewName c <> " "
|
||||
|
||||
ttyToContact' :: Contact -> StyledString
|
||||
ttyToContact' ct@Contact {localDisplayName = c} = ctIncognito ct <> ttyToContact c
|
||||
|
||||
ttyToContactEdited' :: Contact -> StyledString
|
||||
ttyToContactEdited' ct@Contact {localDisplayName = c} = ctIncognito ct <> ttyTo ("@" <> c <> " [edited] ")
|
||||
ttyToContactEdited' ct@Contact {localDisplayName = c} = ctIncognito ct <> ttyTo ("@" <> viewName c <> " [edited] ")
|
||||
|
||||
ttyQuotedContact :: Contact -> StyledString
|
||||
ttyQuotedContact Contact {localDisplayName = c} = ttyFrom $ c <> ">"
|
||||
ttyQuotedContact Contact {localDisplayName = c} = ttyFrom $ viewName c <> ">"
|
||||
|
||||
ttyQuotedMember :: Maybe GroupMember -> StyledString
|
||||
ttyQuotedMember (Just GroupMember {localDisplayName = c}) = "> " <> ttyFrom c
|
||||
ttyQuotedMember (Just GroupMember {localDisplayName = c}) = "> " <> ttyFrom (viewName c)
|
||||
ttyQuotedMember _ = "> " <> ttyFrom "?"
|
||||
|
||||
ttyFromContact :: Contact -> StyledString
|
||||
ttyFromContact ct@Contact {localDisplayName = c} = ctIncognito ct <> ttyFrom (c <> "> ")
|
||||
ttyFromContact ct@Contact {localDisplayName = c} = ctIncognito ct <> ttyFrom (viewName c <> "> ")
|
||||
|
||||
ttyFromContactEdited :: Contact -> StyledString
|
||||
ttyFromContactEdited ct@Contact {localDisplayName = c} = ctIncognito ct <> ttyFrom (c <> "> [edited] ")
|
||||
ttyFromContactEdited ct@Contact {localDisplayName = c} = ctIncognito ct <> ttyFrom (viewName c <> "> [edited] ")
|
||||
|
||||
ttyFromContactDeleted :: Contact -> Maybe Text -> StyledString
|
||||
ttyFromContactDeleted ct@Contact {localDisplayName = c} deletedText_ =
|
||||
ctIncognito ct <> ttyFrom (c <> "> " <> maybe "" (\t -> "[" <> t <> "] ") deletedText_)
|
||||
ctIncognito ct <> ttyFrom (viewName c <> "> " <> maybe "" (\t -> "[" <> t <> "] ") deletedText_)
|
||||
|
||||
ttyGroup :: GroupName -> StyledString
|
||||
ttyGroup g = styled (colored Blue) $ "#" <> g
|
||||
ttyGroup g = styled (colored Blue) $ "#" <> viewName g
|
||||
|
||||
ttyGroup' :: GroupInfo -> StyledString
|
||||
ttyGroup' = ttyGroup . groupName'
|
||||
|
||||
viewContactName :: Contact -> Text
|
||||
viewContactName = viewName . localDisplayName'
|
||||
|
||||
viewGroupName :: GroupInfo -> Text
|
||||
viewGroupName = viewName . groupName'
|
||||
|
||||
viewMemberName :: GroupMember -> Text
|
||||
viewMemberName GroupMember {localDisplayName = n} = viewName n
|
||||
|
||||
ttyGroups :: [GroupName] -> StyledString
|
||||
ttyGroups [] = ""
|
||||
ttyGroups [g] = ttyGroup g
|
||||
@ -1767,8 +1776,7 @@ ttyFromGroupDeleted g m deletedText_ =
|
||||
membershipIncognito g <> ttyFrom (fromGroup_ g m <> maybe "" (\t -> "[" <> t <> "] ") deletedText_)
|
||||
|
||||
fromGroup_ :: GroupInfo -> GroupMember -> Text
|
||||
fromGroup_ GroupInfo {localDisplayName = g} GroupMember {localDisplayName = m} =
|
||||
"#" <> g <> " " <> m <> "> "
|
||||
fromGroup_ g m = "#" <> viewGroupName g <> " " <> viewMemberName m <> "> "
|
||||
|
||||
ttyFrom :: Text -> StyledString
|
||||
ttyFrom = styled $ colored Yellow
|
||||
@ -1777,12 +1785,13 @@ ttyTo :: Text -> StyledString
|
||||
ttyTo = styled $ colored Cyan
|
||||
|
||||
ttyToGroup :: GroupInfo -> StyledString
|
||||
ttyToGroup g@GroupInfo {localDisplayName = n} =
|
||||
membershipIncognito g <> ttyTo ("#" <> n <> " ")
|
||||
ttyToGroup g = membershipIncognito g <> ttyTo ("#" <> viewGroupName g <> " ")
|
||||
|
||||
ttyToGroupEdited :: GroupInfo -> StyledString
|
||||
ttyToGroupEdited g@GroupInfo {localDisplayName = n} =
|
||||
membershipIncognito g <> ttyTo ("#" <> n <> " [edited] ")
|
||||
ttyToGroupEdited g = membershipIncognito g <> ttyTo ("#" <> viewGroupName g <> " [edited] ")
|
||||
|
||||
viewName :: Text -> Text
|
||||
viewName s = if T.any isSpace s then "'" <> s <> "'" else s
|
||||
|
||||
ttyFilePath :: FilePath -> StyledString
|
||||
ttyFilePath = plain
|
||||
|
@ -8,7 +8,7 @@ import ChatTests.Utils
|
||||
import Control.Concurrent (threadDelay)
|
||||
import Control.Concurrent.Async (concurrently_)
|
||||
import qualified Data.Text as T
|
||||
import Simplex.Chat.Types (ConnStatus (..), GroupMemberRole (..))
|
||||
import Simplex.Chat.Types (ConnStatus (..), GroupMemberRole (..), Profile (..))
|
||||
import System.Directory (copyFile, createDirectoryIfMissing)
|
||||
import Test.Hspec
|
||||
|
||||
@ -17,6 +17,7 @@ chatProfileTests = do
|
||||
describe "user profiles" $ do
|
||||
it "update user profile and notify contacts" testUpdateProfile
|
||||
it "update user profile with image" testUpdateProfileImage
|
||||
it "use multiword profile names" testMultiWordProfileNames
|
||||
describe "user contact link" $ do
|
||||
it "create and connect via contact link" testUserContactLink
|
||||
it "add contact link to profile" testProfileLink
|
||||
@ -62,7 +63,7 @@ testUpdateProfile =
|
||||
createGroup3 "team" alice bob cath
|
||||
alice ##> "/p"
|
||||
alice <## "user profile: alice (Alice)"
|
||||
alice <## "use /p <display name> [<full name>] to change it"
|
||||
alice <## "use /p <display name> to change it"
|
||||
alice <## "(the updated profile will be sent to all your contacts)"
|
||||
alice ##> "/p alice"
|
||||
concurrentlyN_
|
||||
@ -117,6 +118,76 @@ testUpdateProfileImage =
|
||||
bob <## "use @alice2 <message> to send messages"
|
||||
(bob </)
|
||||
|
||||
testMultiWordProfileNames :: HasCallStack => FilePath -> IO ()
|
||||
testMultiWordProfileNames =
|
||||
testChat3 aliceProfile' bobProfile' cathProfile' $
|
||||
\alice bob cath -> do
|
||||
alice ##> "/c"
|
||||
inv <- getInvitation alice
|
||||
bob ##> ("/c " <> inv)
|
||||
bob <## "confirmation sent!"
|
||||
concurrently_
|
||||
(bob <## "'Alice Jones': contact is connected")
|
||||
(alice <## "'Bob James': contact is connected")
|
||||
alice #> "@'Bob James' hi"
|
||||
bob <# "'Alice Jones'> hi"
|
||||
alice ##> "/g 'Our Team'"
|
||||
alice <## "group #'Our Team' is created"
|
||||
alice <## "to add members use /a 'Our Team' <name> or /create link #'Our Team'"
|
||||
alice ##> "/a 'Our Team' 'Bob James' admin"
|
||||
alice <## "invitation to join the group #'Our Team' sent to 'Bob James'"
|
||||
bob <## "#'Our Team': 'Alice Jones' invites you to join the group as admin"
|
||||
bob <## "use /j 'Our Team' to accept"
|
||||
bob ##> "/j 'Our Team'"
|
||||
bob <## "#'Our Team': you joined the group"
|
||||
alice <## "#'Our Team': 'Bob James' joined the group"
|
||||
bob ##> "/c"
|
||||
inv' <- getInvitation bob
|
||||
cath ##> ("/c " <> inv')
|
||||
cath <## "confirmation sent!"
|
||||
concurrently_
|
||||
(cath <## "'Bob James': contact is connected")
|
||||
(bob <## "'Cath Johnson': contact is connected")
|
||||
bob ##> "/a 'Our Team' 'Cath Johnson'"
|
||||
bob <## "invitation to join the group #'Our Team' sent to 'Cath Johnson'"
|
||||
cath <## "#'Our Team': 'Bob James' invites you to join the group as member"
|
||||
cath <## "use /j 'Our Team' to accept"
|
||||
cath ##> "/j 'Our Team'"
|
||||
concurrentlyN_
|
||||
[ bob <## "#'Our Team': 'Cath Johnson' joined the group",
|
||||
do
|
||||
cath <## "#'Our Team': you joined the group"
|
||||
cath <## "#'Our Team': member 'Alice Jones' is connected",
|
||||
do
|
||||
alice <## "#'Our Team': 'Bob James' added 'Cath Johnson' to the group (connecting...)"
|
||||
alice <## "#'Our Team': new member 'Cath Johnson' is connected"
|
||||
]
|
||||
bob #> "#'Our Team' hi"
|
||||
alice <# "#'Our Team' 'Bob James'> hi"
|
||||
cath <# "#'Our Team' 'Bob James'> hi"
|
||||
alice `send` "@'Cath Johnson' hello"
|
||||
alice <## "member #'Our Team' 'Cath Johnson' does not have direct connection, creating"
|
||||
alice <## "contact for member #'Our Team' 'Cath Johnson' is created"
|
||||
alice <## "sent invitation to connect directly to member #'Our Team' 'Cath Johnson'"
|
||||
alice <# "@'Cath Johnson' hello"
|
||||
cath <## "#'Our Team' 'Alice Jones' is creating direct contact 'Alice Jones' with you"
|
||||
cath <# "'Alice Jones'> hello"
|
||||
cath <## "'Alice Jones': contact is connected"
|
||||
alice <## "'Cath Johnson': contact is connected"
|
||||
cath ##> "/p 'Cath J'"
|
||||
cath <## "user profile is changed to 'Cath J' (your 2 contacts are notified)"
|
||||
alice <## "contact 'Cath Johnson' changed to 'Cath J'"
|
||||
alice <## "use @'Cath J' <message> to send messages"
|
||||
bob <## "contact 'Cath Johnson' changed to 'Cath J'"
|
||||
bob <## "use @'Cath J' <message> to send messages"
|
||||
alice #> "@'Cath J' hi"
|
||||
cath <# "'Alice Jones'> hi"
|
||||
where
|
||||
aliceProfile' = baseProfile {displayName = "Alice Jones"}
|
||||
bobProfile' = baseProfile {displayName = "Bob James"}
|
||||
cathProfile' = baseProfile {displayName = "Cath Johnson"}
|
||||
baseProfile = Profile {displayName = "", fullName = "", image = Nothing, contactLink = Nothing, preferences = defaultPrefs}
|
||||
|
||||
testUserContactLink :: HasCallStack => FilePath -> IO ()
|
||||
testUserContactLink =
|
||||
testChat3 aliceProfile bobProfile cathProfile $
|
||||
|
@ -435,7 +435,7 @@ lastItemId cc = do
|
||||
showActiveUser :: HasCallStack => TestCC -> String -> Expectation
|
||||
showActiveUser cc name = do
|
||||
cc <## ("user profile: " <> name)
|
||||
cc <## "use /p <display name> [<full name>] to change it"
|
||||
cc <## "use /p <display name> to change it"
|
||||
cc <## "(the updated profile will be sent to all your contacts)"
|
||||
|
||||
connectUsers :: HasCallStack => TestCC -> TestCC -> IO ()
|
||||
|
@ -61,6 +61,8 @@ mobileTests = do
|
||||
it "utf8 name 1" $ testFileEncryptionCApi "тест"
|
||||
it "utf8 name 2" $ testFileEncryptionCApi "👍"
|
||||
it "no exception on missing file" testMissingFileEncryptionCApi
|
||||
describe "validate name" $ do
|
||||
it "should convert invalid name to a valid name" testValidNameCApi
|
||||
|
||||
noActiveUser :: LB.ByteString
|
||||
#if defined(darwin_HOST_OS) && defined(swiftJSON)
|
||||
@ -266,6 +268,14 @@ testMissingFileEncryptionCApi tmp = do
|
||||
err' <- peekCAString =<< cChatDecryptFile cToPath cKey cNonce cToPath'
|
||||
err' `shouldContain` toPath
|
||||
|
||||
testValidNameCApi :: FilePath -> IO ()
|
||||
testValidNameCApi _ = do
|
||||
let goodName = "Джон Доу 👍"
|
||||
cName1 <- cChatValidName =<< newCString goodName
|
||||
peekCString cName1 `shouldReturn` goodName
|
||||
cName2 <- cChatValidName =<< newCString " @'Джон' Доу 👍 "
|
||||
peekCString cName2 `shouldReturn` goodName
|
||||
|
||||
jDecode :: FromJSON a => String -> IO (Maybe a)
|
||||
jDecode = pure . J.decode . LB.pack
|
||||
|
||||
|
@ -12,6 +12,7 @@ import SchemaDump
|
||||
import Test.Hspec
|
||||
import UnliftIO.Temporary (withTempDirectory)
|
||||
import ViewTests
|
||||
import ValidNames
|
||||
import WebRTCTests
|
||||
|
||||
main :: IO ()
|
||||
@ -23,6 +24,7 @@ main = do
|
||||
describe "SimpleX chat view" viewTests
|
||||
describe "SimpleX chat protocol" protocolTests
|
||||
describe "WebRTC encryption" webRTCTests
|
||||
describe "Valid names" validNameTests
|
||||
around testBracket $ do
|
||||
describe "Mobile API Tests" mobileTests
|
||||
describe "SimpleX chat client" chatTests
|
||||
|
27
tests/ValidNames.hs
Normal file
27
tests/ValidNames.hs
Normal file
@ -0,0 +1,27 @@
|
||||
module ValidNames where
|
||||
|
||||
import Simplex.Chat
|
||||
import Test.Hspec
|
||||
|
||||
validNameTests :: Spec
|
||||
validNameTests = describe "valid chat names" $ do
|
||||
it "should keep valid and fix invalid names" testMkValidName
|
||||
|
||||
testMkValidName :: IO ()
|
||||
testMkValidName = do
|
||||
mkValidName "alice" `shouldBe` "alice"
|
||||
mkValidName "алиса" `shouldBe` "алиса"
|
||||
mkValidName "John Doe" `shouldBe` "John Doe"
|
||||
mkValidName "J.Doe" `shouldBe` "J.Doe"
|
||||
mkValidName "J. Doe" `shouldBe` "J. Doe"
|
||||
mkValidName "J..Doe" `shouldBe` "J.Doe"
|
||||
mkValidName "J ..Doe" `shouldBe` "J Doe"
|
||||
mkValidName "J . . Doe" `shouldBe` "J Doe"
|
||||
mkValidName "@alice" `shouldBe` "alice"
|
||||
mkValidName "#alice" `shouldBe` "alice"
|
||||
mkValidName " alice" `shouldBe` "alice"
|
||||
mkValidName "alice " `shouldBe` "alice"
|
||||
mkValidName "John Doe" `shouldBe` "John Doe"
|
||||
mkValidName "'John Doe'" `shouldBe` "John Doe"
|
||||
mkValidName "\"John Doe\"" `shouldBe` "John Doe"
|
||||
mkValidName "`John Doe`" `shouldBe` "John Doe"
|
Loading…
Reference in New Issue
Block a user