Merge branch 'master' into master-android

This commit is contained in:
Evgeny Poberezkin 2023-09-21 12:06:10 +01:00
commit d802ae0058
56 changed files with 825 additions and 218 deletions

View File

@ -125,7 +125,9 @@ jobs:
shell: bash
run: |
cabal build --enable-tests
echo "::set-output name=bin_path::$(cabal list-bin simplex-chat)"
path=$(cabal list-bin simplex-chat)
echo "bin_path=$path" >> $GITHUB_OUTPUT
echo "bin_hash=$(echo SHA2-512\(${{ matrix.asset_name }}\)= $(openssl sha512 $path | cut -d' ' -f 2))" >> $GITHUB_OUTPUT
- name: Unix upload CLI binary to release
if: startsWith(github.ref, 'refs/tags/v') && matrix.os != 'windows-latest'
@ -136,6 +138,16 @@ jobs:
asset_name: ${{ matrix.asset_name }}
tag: ${{ github.ref }}
- name: Unix update CLI binary hash
if: startsWith(github.ref, 'refs/tags/v') && matrix.os != 'windows-latest'
uses: softprops/action-gh-release@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
append_body: true
body: |
${{ steps.unix_cli_build.outputs.bin_hash }}
- name: Setup Java
if: startsWith(github.ref, 'refs/tags/v')
uses: actions/setup-java@v3
@ -152,7 +164,9 @@ jobs:
scripts/desktop/build-lib-linux.sh
cd apps/multiplatform
./gradlew packageDeb
echo "::set-output name=package_path::$(echo $PWD/release/main/deb/simplex_*_amd64.deb)"
path=$(echo $PWD/release/main/deb/simplex_*_amd64.deb)
echo "package_path=$path" >> $GITHUB_OUTPUT
echo "package_hash=$(echo SHA2-512\(${{ matrix.desktop_asset_name }}\)= $(openssl sha512 $path | cut -d' ' -f 2))" >> $GITHUB_OUTPUT
- name: Linux make AppImage
id: linux_appimage_build
@ -160,7 +174,9 @@ jobs:
shell: bash
run: |
scripts/desktop/make-appimage-linux.sh
echo "::set-output name=appimage_path::$(echo $PWD/apps/multiplatform/release/main/*imple*.AppImage)"
path=$(echo $PWD/apps/multiplatform/release/main/*imple*.AppImage)
echo "appimage_path=$path" >> $GITHUB_OUTPUT
echo "appimage_hash=$(echo SHA2-512\(simplex-desktop-x86_64.AppImage\)= $(openssl sha512 $path | cut -d' ' -f 2))" >> $GITHUB_OUTPUT
- name: Mac build desktop
id: mac_desktop_build
@ -171,8 +187,10 @@ jobs:
APPLE_SIMPLEX_NOTARIZATION_APPLE_ID: ${{ secrets.APPLE_SIMPLEX_NOTARIZATION_APPLE_ID }}
APPLE_SIMPLEX_NOTARIZATION_PASSWORD: ${{ secrets.APPLE_SIMPLEX_NOTARIZATION_PASSWORD }}
run: |
scripts/desktop/build-desktop-mac-ci.sh
echo "::set-output name=package_path::$(echo $PWD/release/main/dmg/SimpleX-*.dmg)"
scripts/build-desktop-mac.sh
path=$(echo $PWD/apps/multiplatform/release/main/dmg/SimpleX-*.dmg)
echo "package_path=$path" >> $GITHUB_OUTPUT
echo "package_hash=$(echo SHA2-512\(${{ matrix.desktop_asset_name }}\)= $(openssl sha512 $path | cut -d' ' -f 2))" >> $GITHUB_OUTPUT
- name: Linux upload desktop package to release
if: startsWith(github.ref, 'refs/tags/v') && (matrix.os == 'ubuntu-20.04' || matrix.os == 'ubuntu-22.04')
@ -183,6 +201,16 @@ jobs:
asset_name: ${{ matrix.desktop_asset_name }}
tag: ${{ github.ref }}
- name: Linux update desktop package hash
if: startsWith(github.ref, 'refs/tags/v') && (matrix.os == 'ubuntu-20.04' || matrix.os == 'ubuntu-22.04')
uses: softprops/action-gh-release@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
append_body: true
body: |
${{ steps.linux_desktop_build.outputs.package_hash }}
- name: Linux upload AppImage to release
if: startsWith(github.ref, 'refs/tags/v') && matrix.os == 'ubuntu-20.04'
uses: svenstaro/upload-release-action@v2
@ -192,6 +220,16 @@ jobs:
asset_name: simplex-desktop-x86_64.AppImage
tag: ${{ github.ref }}
- name: Linux update AppImage hash
if: startsWith(github.ref, 'refs/tags/v') && matrix.os == 'ubuntu-20.04'
uses: softprops/action-gh-release@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
append_body: true
body: |
${{ steps.linux_appimage_build.outputs.appimage_hash }}
- name: Mac upload desktop package to release
if: startsWith(github.ref, 'refs/tags/v') && matrix.os == 'macos-latest'
uses: svenstaro/upload-release-action@v2
@ -201,6 +239,16 @@ jobs:
asset_name: ${{ matrix.desktop_asset_name }}
tag: ${{ github.ref }}
- name: Mac update desktop package hash
if: startsWith(github.ref, 'refs/tags/v') && matrix.os == 'macos-latest'
uses: softprops/action-gh-release@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
append_body: true
body: |
${{ steps.mac_desktop_build.outputs.package_hash }}
- name: Unix test
if: matrix.os != 'windows-latest'
timeout-minutes: 30
@ -220,11 +268,12 @@ jobs:
shell: cmd
run: |
cabal build --enable-tests
cabal list-bin simplex-chat > tmp_bin_path
set /p bin_path= < tmp_bin_path
echo ::set-output name=bin_path::%bin_path%
rm -rf dist-newstyle/src/direct-sq*
path=$(cabal list-bin simplex-chat | tail -n 1)
echo "bin_path=$path" >> $GITHUB_OUTPUT
echo "bin_hash=$(echo SHA2-512\(${{ matrix.asset_name }}\)= $(openssl sha512 $path | cut -d' ' -f 2))" >> $GITHUB_OUTPUT
- name: Windows upload binary to release
- name: Windows upload CLI binary to release
if: startsWith(github.ref, 'refs/tags/v') && matrix.os == 'windows-latest'
uses: svenstaro/upload-release-action@v2
with:
@ -233,4 +282,14 @@ jobs:
asset_name: ${{ matrix.asset_name }}
tag: ${{ github.ref }}
- name: Windows update CLI binary hash
if: startsWith(github.ref, 'refs/tags/v') && matrix.os == 'windows-latest'
uses: softprops/action-gh-release@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
append_body: true
body: |
${{ steps.windows_build.outputs.bin_hash }}
# Windows /

View File

@ -48,6 +48,11 @@
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 */; };
5C5624FC2ABB39B900A21210 /* libgmpxx.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5C5624F72ABB39B900A21210 /* libgmpxx.a */; };
5C5624FD2ABB39B900A21210 /* libgmp.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5C5624F82ABB39B900A21210 /* libgmp.a */; };
5C5624FE2ABB39B900A21210 /* libHSsimplex-chat-5.3.0.8-D1oMkI9pySuA3Aa2cfRrBY-ghc8.10.7.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5C5624F92ABB39B900A21210 /* libHSsimplex-chat-5.3.0.8-D1oMkI9pySuA3Aa2cfRrBY-ghc8.10.7.a */; };
5C5624FF2ABB39B900A21210 /* libHSsimplex-chat-5.3.0.8-D1oMkI9pySuA3Aa2cfRrBY.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5C5624FA2ABB39B900A21210 /* libHSsimplex-chat-5.3.0.8-D1oMkI9pySuA3Aa2cfRrBY.a */; };
5C5625002ABB39B900A21210 /* libffi.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5C5624FB2ABB39B900A21210 /* 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 */; };
@ -78,11 +83,6 @@
5C9CC7AD28C55D7800BEF955 /* DatabaseEncryptionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C9CC7AC28C55D7800BEF955 /* DatabaseEncryptionView.swift */; };
5C9D13A3282187BB00AB8B43 /* WebRTC.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C9D13A2282187BB00AB8B43 /* WebRTC.swift */; };
5C9D811A2AA8727A001D49FD /* CryptoFile.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C9D81182AA7A4F1001D49FD /* CryptoFile.swift */; };
5C9E127E2AAE62A500C9D8FF /* libHSsimplex-chat-5.3.0.7-6JlIR0UqFTrEzd5R0Y6B8t-ghc8.10.7.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5C9E12792AAE62A500C9D8FF /* libHSsimplex-chat-5.3.0.7-6JlIR0UqFTrEzd5R0Y6B8t-ghc8.10.7.a */; };
5C9E127F2AAE62A500C9D8FF /* libgmpxx.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5C9E127A2AAE62A500C9D8FF /* libgmpxx.a */; };
5C9E12802AAE62A500C9D8FF /* libgmp.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5C9E127B2AAE62A500C9D8FF /* libgmp.a */; };
5C9E12812AAE62A500C9D8FF /* libffi.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5C9E127C2AAE62A500C9D8FF /* libffi.a */; };
5C9E12822AAE62A500C9D8FF /* libHSsimplex-chat-5.3.0.7-6JlIR0UqFTrEzd5R0Y6B8t.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5C9E127D2AAE62A500C9D8FF /* libHSsimplex-chat-5.3.0.7-6JlIR0UqFTrEzd5R0Y6B8t.a */; };
5C9FD96E27A5D6ED0075386C /* SendMessageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C9FD96D27A5D6ED0075386C /* SendMessageView.swift */; };
5CA059DC279559F40002BEB4 /* Tests_iOS.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CA059DB279559F40002BEB4 /* Tests_iOS.swift */; };
5CA059DE279559F40002BEB4 /* Tests_iOSLaunchTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CA059DD279559F40002BEB4 /* Tests_iOSLaunchTests.swift */; };
@ -293,6 +293,11 @@
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>"; };
5C5624F72ABB39B900A21210 /* libgmpxx.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmpxx.a; sourceTree = "<group>"; };
5C5624F82ABB39B900A21210 /* libgmp.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmp.a; sourceTree = "<group>"; };
5C5624F92ABB39B900A21210 /* libHSsimplex-chat-5.3.0.8-D1oMkI9pySuA3Aa2cfRrBY-ghc8.10.7.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-5.3.0.8-D1oMkI9pySuA3Aa2cfRrBY-ghc8.10.7.a"; sourceTree = "<group>"; };
5C5624FA2ABB39B900A21210 /* libHSsimplex-chat-5.3.0.8-D1oMkI9pySuA3Aa2cfRrBY.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-5.3.0.8-D1oMkI9pySuA3Aa2cfRrBY.a"; sourceTree = "<group>"; };
5C5624FB2ABB39B900A21210 /* 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>"; };
@ -342,11 +347,6 @@
5C9CC7AC28C55D7800BEF955 /* DatabaseEncryptionView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DatabaseEncryptionView.swift; sourceTree = "<group>"; };
5C9D13A2282187BB00AB8B43 /* WebRTC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WebRTC.swift; sourceTree = "<group>"; };
5C9D81182AA7A4F1001D49FD /* CryptoFile.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CryptoFile.swift; sourceTree = "<group>"; };
5C9E12792AAE62A500C9D8FF /* libHSsimplex-chat-5.3.0.7-6JlIR0UqFTrEzd5R0Y6B8t-ghc8.10.7.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-5.3.0.7-6JlIR0UqFTrEzd5R0Y6B8t-ghc8.10.7.a"; sourceTree = "<group>"; };
5C9E127A2AAE62A500C9D8FF /* libgmpxx.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmpxx.a; sourceTree = "<group>"; };
5C9E127B2AAE62A500C9D8FF /* libgmp.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmp.a; sourceTree = "<group>"; };
5C9E127C2AAE62A500C9D8FF /* libffi.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libffi.a; sourceTree = "<group>"; };
5C9E127D2AAE62A500C9D8FF /* libHSsimplex-chat-5.3.0.7-6JlIR0UqFTrEzd5R0Y6B8t.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-5.3.0.7-6JlIR0UqFTrEzd5R0Y6B8t.a"; sourceTree = "<group>"; };
5C9FD96A27A56D4D0075386C /* JSON.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JSON.swift; sourceTree = "<group>"; };
5C9FD96D27A5D6ED0075386C /* SendMessageView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SendMessageView.swift; sourceTree = "<group>"; };
5CA059C3279559F40002BEB4 /* SimpleXApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SimpleXApp.swift; sourceTree = "<group>"; };
@ -507,13 +507,13 @@
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
5C9E127F2AAE62A500C9D8FF /* libgmpxx.a in Frameworks */,
5C9E12812AAE62A500C9D8FF /* libffi.a in Frameworks */,
5C9E12802AAE62A500C9D8FF /* libgmp.a in Frameworks */,
5C5624FE2ABB39B900A21210 /* libHSsimplex-chat-5.3.0.8-D1oMkI9pySuA3Aa2cfRrBY-ghc8.10.7.a in Frameworks */,
5C5624FF2ABB39B900A21210 /* libHSsimplex-chat-5.3.0.8-D1oMkI9pySuA3Aa2cfRrBY.a in Frameworks */,
5C5624FC2ABB39B900A21210 /* libgmpxx.a in Frameworks */,
5CE2BA93284534B000EC33A6 /* libiconv.tbd in Frameworks */,
5C5625002ABB39B900A21210 /* libffi.a in Frameworks */,
5C5624FD2ABB39B900A21210 /* libgmp.a in Frameworks */,
5CE2BA94284534BB00EC33A6 /* libz.tbd in Frameworks */,
5C9E127E2AAE62A500C9D8FF /* libHSsimplex-chat-5.3.0.7-6JlIR0UqFTrEzd5R0Y6B8t-ghc8.10.7.a in Frameworks */,
5C9E12822AAE62A500C9D8FF /* libHSsimplex-chat-5.3.0.7-6JlIR0UqFTrEzd5R0Y6B8t.a in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@ -574,11 +574,11 @@
5C764E5C279C70B7000C6508 /* Libraries */ = {
isa = PBXGroup;
children = (
5C9E127C2AAE62A500C9D8FF /* libffi.a */,
5C9E127B2AAE62A500C9D8FF /* libgmp.a */,
5C9E127A2AAE62A500C9D8FF /* libgmpxx.a */,
5C9E12792AAE62A500C9D8FF /* libHSsimplex-chat-5.3.0.7-6JlIR0UqFTrEzd5R0Y6B8t-ghc8.10.7.a */,
5C9E127D2AAE62A500C9D8FF /* libHSsimplex-chat-5.3.0.7-6JlIR0UqFTrEzd5R0Y6B8t.a */,
5C5624FB2ABB39B900A21210 /* libffi.a */,
5C5624F82ABB39B900A21210 /* libgmp.a */,
5C5624F72ABB39B900A21210 /* libgmpxx.a */,
5C5624F92ABB39B900A21210 /* libHSsimplex-chat-5.3.0.8-D1oMkI9pySuA3Aa2cfRrBY-ghc8.10.7.a */,
5C5624FA2ABB39B900A21210 /* libHSsimplex-chat-5.3.0.8-D1oMkI9pySuA3Aa2cfRrBY.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 = 170;
CURRENT_PROJECT_VERSION = 171;
DEVELOPMENT_TEAM = 5NN7GUYB6T;
ENABLE_BITCODE = NO;
ENABLE_PREVIEWS = YES;
@ -1528,7 +1528,7 @@
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_ENTITLEMENTS = "SimpleX (iOS).entitlements";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 170;
CURRENT_PROJECT_VERSION = 171;
DEVELOPMENT_TEAM = 5NN7GUYB6T;
ENABLE_BITCODE = NO;
ENABLE_PREVIEWS = YES;
@ -1608,7 +1608,7 @@
CODE_SIGN_ENTITLEMENTS = "SimpleX NSE/SimpleX NSE.entitlements";
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 170;
CURRENT_PROJECT_VERSION = 171;
DEVELOPMENT_TEAM = 5NN7GUYB6T;
ENABLE_BITCODE = NO;
GENERATE_INFOPLIST_FILE = YES;
@ -1640,7 +1640,7 @@
CODE_SIGN_ENTITLEMENTS = "SimpleX NSE/SimpleX NSE.entitlements";
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 170;
CURRENT_PROJECT_VERSION = 171;
DEVELOPMENT_TEAM = 5NN7GUYB6T;
ENABLE_BITCODE = NO;
GENERATE_INFOPLIST_FILE = YES;
@ -1672,7 +1672,7 @@
APPLICATION_EXTENSION_API_ONLY = YES;
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 170;
CURRENT_PROJECT_VERSION = 171;
DEFINES_MODULE = YES;
DEVELOPMENT_TEAM = 5NN7GUYB6T;
DYLIB_COMPATIBILITY_VERSION = 1;
@ -1718,7 +1718,7 @@
APPLICATION_EXTENSION_API_ONLY = YES;
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 170;
CURRENT_PROJECT_VERSION = 171;
DEFINES_MODULE = YES;
DEVELOPMENT_TEAM = 5NN7GUYB6T;
DYLIB_COMPATIBILITY_VERSION = 1;

View File

@ -44,6 +44,7 @@ import java.net.URI
@Composable
actual fun PlatformTextField(
composeState: MutableState<ComposeState>,
sendMsgEnabled: Boolean,
textStyle: MutableState<TextStyle>,
showDeleteTextButton: MutableState<Boolean>,
userIsObserver: Boolean,
@ -60,6 +61,7 @@ actual fun PlatformTextField(
val paddingEnd = with(LocalDensity.current) { 45.dp.roundToPx() }
val paddingBottom = with(LocalDensity.current) { 7.dp.roundToPx() }
var showKeyboard by remember { mutableStateOf(false) }
var freeFocus by remember { mutableStateOf(false) }
LaunchedEffect(cs.contextItem) {
if (cs.contextItem is ComposeContextItem.QuotedItem) {
delay(100)
@ -70,6 +72,11 @@ actual fun PlatformTextField(
showKeyboard = true
}
}
LaunchedEffect(sendMsgEnabled) {
if (!sendMsgEnabled) {
freeFocus = true
}
}
AndroidView(modifier = Modifier, factory = {
val editText = @SuppressLint("AppCompatCustomView") object: EditText(it) {
@ -142,6 +149,11 @@ actual fun PlatformTextField(
imm.showSoftInput(it, InputMethodManager.SHOW_IMPLICIT)
showKeyboard = false
}
if (freeFocus) {
it.clearFocus()
hideKeyboard(it)
freeFocus = false
}
showDeleteTextButton.value = it.lineCount >= 4 && !cs.inProgress
}
if (composeState.value.preview is ComposePreview.VoicePreview) {

View File

@ -606,10 +606,13 @@ data class Chat (
val userCanSend: Boolean
get() = when (chatInfo) {
is ChatInfo.Direct -> true
is ChatInfo.Group -> {
val m = chatInfo.groupInfo.membership
m.memberActive && m.memberRole >= GroupMemberRole.Member
is ChatInfo.Group -> chatInfo.groupInfo.membership.memberRole >= GroupMemberRole.Member
else -> false
}
val nextSendGrpInv: Boolean
get() = when (chatInfo) {
is ChatInfo.Direct -> chatInfo.contact.nextSendGrpInv
else -> false
}
@ -799,13 +802,18 @@ data class Contact(
val userPreferences: ChatPreferences,
val mergedPreferences: ContactUserPreferences,
override val createdAt: Instant,
override val updatedAt: Instant
override val updatedAt: Instant,
val contactGroupMemberId: Long? = null,
val contactGrpInvSent: Boolean
): SomeChat, NamedChat {
override val chatType get() = ChatType.Direct
override val id get() = "@$contactId"
override val apiId get() = contactId
override val ready get() = activeConn.connStatus == ConnStatus.Ready
override val sendMsgEnabled get() = !(activeConn.connectionStats?.ratchetSyncSendProhibited ?: false)
override val sendMsgEnabled get() =
(ready && !(activeConn.connectionStats?.ratchetSyncSendProhibited ?: false))
|| nextSendGrpInv
val nextSendGrpInv get() = contactGroupMemberId != null && !contactGrpInvSent
override val ntfsEnabled get() = chatSettings.enableNtfs
override val incognito get() = contactConnIncognito
override fun featureEnabled(feature: ChatFeature) = when (feature) {
@ -856,7 +864,8 @@ data class Contact(
userPreferences = ChatPreferences.sampleData,
mergedPreferences = ContactUserPreferences.sampleData,
createdAt = Clock.System.now(),
updatedAt = Clock.System.now()
updatedAt = Clock.System.now(),
contactGrpInvSent = false
)
}
}
@ -881,6 +890,7 @@ class ContactSubStatus(
data class Connection(
val connId: Long,
val agentConnId: String,
val peerChatVRange: VersionRange,
val connStatus: ConnStatus,
val connLevel: Int,
val viaGroupLink: Boolean,
@ -890,10 +900,17 @@ data class Connection(
) {
val id: ChatId get() = ":$connId"
companion object {
val sampleData = Connection(connId = 1, agentConnId = "abc", connStatus = ConnStatus.Ready, connLevel = 0, viaGroupLink = false, customUserProfileId = null)
val sampleData = Connection(connId = 1, agentConnId = "abc", connStatus = ConnStatus.Ready, connLevel = 0, viaGroupLink = false, peerChatVRange = VersionRange(1, 1), customUserProfileId = null)
}
}
@Serializable
data class VersionRange(val minVersion: Int, val maxVersion: Int) {
fun isCompatibleRange(vRange: VersionRange): Boolean =
this.minVersion <= vRange.maxVersion && vRange.minVersion <= this.maxVersion
}
@Serializable
data class SecurityCode(val securityCode: String, val verifiedAt: Instant)
@ -1224,6 +1241,7 @@ class MemberSubError (
@Serializable
class UserContactRequest (
val contactRequestId: Long,
val cReqChatVRange: VersionRange,
override val localDisplayName: String,
val profile: Profile,
override val createdAt: Instant,
@ -1246,6 +1264,7 @@ class UserContactRequest (
companion object {
val sampleData = UserContactRequest(
contactRequestId = 1,
cReqChatVRange = VersionRange(1, 1),
localDisplayName = "alice",
profile = Profile.sampleData,
createdAt = Clock.System.now(),
@ -1465,6 +1484,7 @@ data class ChatItem (
is RcvGroupEvent.GroupDeleted -> showNtfDir
is RcvGroupEvent.GroupUpdated -> false
is RcvGroupEvent.InvitedViaGroupLink -> false
is RcvGroupEvent.MemberCreatedContact -> false
}
is CIContent.SndGroupEventContent -> showNtfDir
is CIContent.RcvConnEventContent -> false
@ -2464,6 +2484,7 @@ sealed class RcvGroupEvent() {
@Serializable @SerialName("groupDeleted") class GroupDeleted(): RcvGroupEvent()
@Serializable @SerialName("groupUpdated") class GroupUpdated(val groupProfile: GroupProfile): RcvGroupEvent()
@Serializable @SerialName("invitedViaGroupLink") class InvitedViaGroupLink(): RcvGroupEvent()
@Serializable @SerialName("memberCreatedContact") class MemberCreatedContact(): RcvGroupEvent()
val text: String get() = when (this) {
is MemberAdded -> String.format(generalGetString(MR.strings.rcv_group_event_member_added), profile.profileViewName)
@ -2476,6 +2497,7 @@ sealed class RcvGroupEvent() {
is GroupDeleted -> generalGetString(MR.strings.rcv_group_event_group_deleted)
is GroupUpdated -> generalGetString(MR.strings.rcv_group_event_updated_group_profile)
is InvitedViaGroupLink -> generalGetString(MR.strings.rcv_group_event_invited_via_your_group_link)
is MemberCreatedContact -> generalGetString(MR.strings.rcv_group_event_member_created_contact)
}
}

View File

@ -26,6 +26,12 @@ import java.util.Date
typealias ChatCtrl = Long
// currentChatVersion in core
const val CURRENT_CHAT_VERSION: Int = 2
// version range that supports establishing direct connection with a group member (xGrpDirectInvVRange in core)
val CREATE_MEMBER_CONTACT_VRANGE = VersionRange(minVersion = 2, maxVersion = CURRENT_CHAT_VERSION)
enum class CallOnLockScreen {
DISABLE,
SHOW,
@ -784,16 +790,18 @@ object ChatController {
return null
}
suspend fun apiGetContactCode(contactId: Long): Pair<Contact, String> {
suspend fun apiGetContactCode(contactId: Long): Pair<Contact, String>? {
val r = sendCmd(CC.APIGetContactCode(contactId))
if (r is CR.ContactCode) return r.contact to r.connectionCode
throw Exception("failed to get contact code: ${r.responseType} ${r.details}")
Log.e(TAG,"failed to get contact code: ${r.responseType} ${r.details}")
return null
}
suspend fun apiGetGroupMemberCode(groupId: Long, groupMemberId: Long): Pair<GroupMember, String> {
suspend fun apiGetGroupMemberCode(groupId: Long, groupMemberId: Long): Pair<GroupMember, String>? {
val r = sendCmd(CC.APIGetGroupMemberCode(groupId, groupMemberId))
if (r is CR.GroupMemberCode) return r.member to r.connectionCode
throw Exception("failed to get group member code: ${r.responseType} ${r.details}")
Log.e(TAG,"failed to get group member code: ${r.responseType} ${r.details}")
return null
}
suspend fun apiVerifyContact(contactId: Long, connectionCode: String?): Pair<Boolean, String>? {
@ -1272,6 +1280,30 @@ object ChatController {
}
}
suspend fun apiCreateMemberContact(groupId: Long, groupMemberId: Long): Contact? {
return when (val r = sendCmd(CC.APICreateMemberContact(groupId, groupMemberId))) {
is CR.NewMemberContact -> r.contact
else -> {
if (!(networkErrorAlert(r))) {
apiErrorAlert("apiCreateMemberContact", generalGetString(MR.strings.error_creating_member_contact), r)
}
null
}
}
}
suspend fun apiSendMemberContactInvitation(contactId: Long, mc: MsgContent): Contact? {
return when (val r = sendCmd(CC.APISendMemberContactInvitation(contactId, mc))) {
is CR.NewMemberContactSentInv -> r.contact
else -> {
if (!(networkErrorAlert(r))) {
apiErrorAlert("apiSendMemberContactInvitation", generalGetString(MR.strings.error_sending_message_contact_invitation), r)
}
null
}
}
}
suspend fun allowFeatureToContact(contact: Contact, feature: ChatFeature, param: Int? = null) {
val prefs = contact.mergedPreferences.toPreferences().setAllowed(feature, param = param)
val toContact = apiSetContactPrefs(contact.contactId, prefs)
@ -1527,6 +1559,10 @@ object ChatController {
if (active(r.user)) {
chatModel.updateGroup(r.toGroup)
}
is CR.NewMemberContactReceivedInv ->
if (active(r.user)) {
chatModel.updateContact(r.contact)
}
is CR.RcvFileStart ->
chatItemSimpleUpdate(r.user, r.chatItem)
is CR.RcvFileComplete ->
@ -1822,6 +1858,8 @@ sealed class CC {
class APIGroupLinkMemberRole(val groupId: Long, val memberRole: GroupMemberRole): CC()
class APIDeleteGroupLink(val groupId: Long): CC()
class APIGetGroupLink(val groupId: Long): CC()
class APICreateMemberContact(val groupId: Long, val groupMemberId: Long): CC()
class APISendMemberContactInvitation(val contactId: Long, val mc: MsgContent): CC()
class APIGetUserProtoServers(val userId: Long, val serverProtocol: ServerProtocol): CC()
class APISetUserProtoServers(val userId: Long, val serverProtocol: ServerProtocol, val servers: List<ServerCfg>): CC()
class APITestProtoServer(val userId: Long, val server: String): CC()
@ -1927,6 +1965,8 @@ sealed class CC {
is APIGroupLinkMemberRole -> "/_set link role #$groupId ${memberRole.name.lowercase()}"
is APIDeleteGroupLink -> "/_delete link #$groupId"
is APIGetGroupLink -> "/_get link #$groupId"
is APICreateMemberContact -> "/_create member contact #$groupId $groupMemberId"
is APISendMemberContactInvitation -> "/_invite member contact @$contactId ${mc.cmdString}"
is APIGetUserProtoServers -> "/_servers $userId ${serverProtocol.name.lowercase()}"
is APISetUserProtoServers -> "/_servers $userId ${serverProtocol.name.lowercase()} ${protoServersStr(servers)}"
is APITestProtoServer -> "/_server test $userId $server"
@ -2021,6 +2061,8 @@ sealed class CC {
is APIGroupLinkMemberRole -> "apiGroupLinkMemberRole"
is APIDeleteGroupLink -> "apiDeleteGroupLink"
is APIGetGroupLink -> "apiGetGroupLink"
is APICreateMemberContact -> "apiCreateMemberContact"
is APISendMemberContactInvitation -> "apiSendMemberContactInvitation"
is APIGetUserProtoServers -> "apiGetUserProtoServers"
is APISetUserProtoServers -> "apiSetUserProtoServers"
is APITestProtoServer -> "testProtoServer"
@ -3311,6 +3353,9 @@ sealed class CR {
@Serializable @SerialName("groupLinkCreated") class GroupLinkCreated(val user: UserRef, val groupInfo: GroupInfo, val connReqContact: String, val memberRole: GroupMemberRole): CR()
@Serializable @SerialName("groupLink") class GroupLink(val user: UserRef, val groupInfo: GroupInfo, val connReqContact: String, val memberRole: GroupMemberRole): CR()
@Serializable @SerialName("groupLinkDeleted") class GroupLinkDeleted(val user: UserRef, val groupInfo: GroupInfo): CR()
@Serializable @SerialName("newMemberContact") class NewMemberContact(val user: UserRef, val contact: Contact, val groupInfo: GroupInfo, val member: GroupMember): CR()
@Serializable @SerialName("newMemberContactSentInv") class NewMemberContactSentInv(val user: UserRef, val contact: Contact, val groupInfo: GroupInfo, val member: GroupMember): CR()
@Serializable @SerialName("newMemberContactReceivedInv") class NewMemberContactReceivedInv(val user: UserRef, val contact: Contact, val groupInfo: GroupInfo, val member: GroupMember): CR()
// receiving file events
@Serializable @SerialName("rcvFileAccepted") class RcvFileAccepted(val user: UserRef, val chatItem: AChatItem): CR()
@Serializable @SerialName("rcvFileAcceptedSndCancelled") class RcvFileAcceptedSndCancelled(val user: UserRef, val rcvFileTransfer: RcvFileTransfer): CR()
@ -3438,6 +3483,9 @@ sealed class CR {
is GroupLinkCreated -> "groupLinkCreated"
is GroupLink -> "groupLink"
is GroupLinkDeleted -> "groupLinkDeleted"
is NewMemberContact -> "newMemberContact"
is NewMemberContactSentInv -> "newMemberContactSentInv"
is NewMemberContactReceivedInv -> "newMemberContactReceivedInv"
is RcvFileAcceptedSndCancelled -> "rcvFileAcceptedSndCancelled"
is RcvFileAccepted -> "rcvFileAccepted"
is RcvFileStart -> "rcvFileStart"
@ -3563,6 +3611,9 @@ sealed class CR {
is GroupLinkCreated -> withUser(user, "groupInfo: $groupInfo\nconnReqContact: $connReqContact\nmemberRole: $memberRole")
is GroupLink -> withUser(user, "groupInfo: $groupInfo\nconnReqContact: $connReqContact\nmemberRole: $memberRole")
is GroupLinkDeleted -> withUser(user, json.encodeToString(groupInfo))
is NewMemberContact -> withUser(user, "contact: $contact\ngroupInfo: $groupInfo\nmember: $member")
is NewMemberContactSentInv -> withUser(user, "contact: $contact\ngroupInfo: $groupInfo\nmember: $member")
is NewMemberContactReceivedInv -> withUser(user, "contact: $contact\ngroupInfo: $groupInfo\nmember: $member")
is RcvFileAcceptedSndCancelled -> withUser(user, noDetails())
is RcvFileAccepted -> withUser(user, json.encodeToString(chatItem))
is RcvFileStart -> withUser(user, json.encodeToString(chatItem))
@ -3820,6 +3871,7 @@ sealed class ChatErrorType {
is AgentCommandError -> "agentCommandError"
is InvalidFileDescription -> "invalidFileDescription"
is ConnectionIncognitoChangeProhibited -> "connectionIncognitoChangeProhibited"
is PeerChatVRangeIncompatible -> "peerChatVRangeIncompatible"
is InternalError -> "internalError"
is CEException -> "exception $message"
}
@ -3894,6 +3946,7 @@ sealed class ChatErrorType {
@Serializable @SerialName("agentCommandError") class AgentCommandError(val message: String): ChatErrorType()
@Serializable @SerialName("invalidFileDescription") class InvalidFileDescription(val message: String): ChatErrorType()
@Serializable @SerialName("connectionIncognitoChangeProhibited") object ConnectionIncognitoChangeProhibited: ChatErrorType()
@Serializable @SerialName("peerChatVRangeIncompatible") object PeerChatVRangeIncompatible: ChatErrorType()
@Serializable @SerialName("internalError") class InternalError(val message: String): ChatErrorType()
@Serializable @SerialName("exception") class CEException(val message: String): ChatErrorType()
}
@ -3922,6 +3975,7 @@ sealed class StoreError {
is GroupMemberNameNotFound -> "groupMemberNameNotFound"
is GroupMemberNotFound -> "groupMemberNotFound"
is GroupMemberNotFoundByMemberId -> "groupMemberNotFoundByMemberId"
is MemberContactGroupMemberNotFound -> "memberContactGroupMemberNotFound"
is GroupWithoutUser -> "groupWithoutUser"
is DuplicateGroupMember -> "duplicateGroupMember"
is GroupAlreadyJoined -> "groupAlreadyJoined"
@ -3979,6 +4033,7 @@ sealed class StoreError {
@Serializable @SerialName("groupMemberNameNotFound") class GroupMemberNameNotFound(val groupId: Long, val groupMemberName: String): StoreError()
@Serializable @SerialName("groupMemberNotFound") class GroupMemberNotFound(val groupMemberId: Long): StoreError()
@Serializable @SerialName("groupMemberNotFoundByMemberId") class GroupMemberNotFoundByMemberId(val memberId: String): StoreError()
@Serializable @SerialName("memberContactGroupMemberNotFound") class MemberContactGroupMemberNotFound(val contactId: Long): StoreError()
@Serializable @SerialName("groupWithoutUser") object GroupWithoutUser: StoreError()
@Serializable @SerialName("duplicateGroupMember") object DuplicateGroupMember: StoreError()
@Serializable @SerialName("groupAlreadyJoined") object GroupAlreadyJoined: StoreError()

View File

@ -8,6 +8,7 @@ import chat.simplex.common.views.chat.ComposeState
@Composable
expect fun PlatformTextField(
composeState: MutableState<ComposeState>,
sendMsgEnabled: Boolean,
textStyle: MutableState<TextStyle>,
showDeleteTextButton: MutableState<Boolean>,
userIsObserver: Boolean,

View File

@ -85,6 +85,8 @@ fun TerminalLayout(
recState = remember { mutableStateOf(RecordingState.NotStarted) },
isDirectChat = false,
liveMessageAlertShown = SharedPreference(get = { false }, set = {}),
sendMsgEnabled = true,
nextSendGrpInv = false,
needToAllowVoiceToContact = false,
allowedVoiceByPrefs = false,
userIsObserver = false,

View File

@ -291,6 +291,7 @@ fun ChatInfoLayout(
SectionDividerSpaced()
}
if (contact.ready) {
SectionView {
if (connectionCode != null) {
VerifyCodeButton(contact.verified, verifyClicked)
@ -300,12 +301,13 @@ fun ChatInfoLayout(
if (cStats != null && cStats.ratchetSyncAllowed) {
SynchronizeConnectionButton(syncContactConnection)
}
// } else if (developerTools) {
// SynchronizeConnectionButtonForce(syncContactConnectionForce)
// }
// } else if (developerTools) {
// SynchronizeConnectionButtonForce(syncContactConnectionForce)
// }
}
SectionDividerSpaced()
}
SectionDividerSpaced()
if (contact.contactLink != null) {
SectionView(stringResource(MR.strings.address_section_title).uppercase()) {
QRCode(contact.contactLink, Modifier.padding(horizontal = DEFAULT_PADDING, vertical = DEFAULT_PADDING_HALF).aspectRatio(1f))
@ -316,12 +318,14 @@ fun ChatInfoLayout(
SectionDividerSpaced()
}
if (contact.ready) {
SectionView(title = stringResource(MR.strings.conn_stats_section_title_servers)) {
SectionItemView({
AlertManager.shared.showAlertMsg(
generalGetString(MR.strings.network_status),
contactNetworkStatus.statusExplanation
)}) {
)
}) {
NetworkStatusRow(contactNetworkStatus)
}
if (cStats != null) {
@ -346,6 +350,8 @@ fun ChatInfoLayout(
}
}
SectionDividerSpaced()
}
SectionView {
ClearChatButton(clearChat)
DeleteContactButton(deleteContact)

View File

@ -114,7 +114,18 @@ fun ChatView(chatId: String, chatModel: ChatModel, onComposed: suspend (chatId:
unreadCount,
composeState,
composeView = {
if (chat.chatInfo.sendMsgEnabled) {
Column(
Modifier.fillMaxWidth(),
horizontalAlignment = Alignment.CenterHorizontally
) {
if (chat.chatInfo is ChatInfo.Direct && !chat.chatInfo.contact.ready && !chat.chatInfo.contact.nextSendGrpInv) {
Text(
generalGetString(MR.strings.contact_connection_pending),
Modifier.padding(top = 4.dp),
fontSize = 14.sp,
color = MaterialTheme.colors.secondary
)
}
ComposeView(
chatModel, chat, composeState, attachmentOption,
showChooseAttachment = { scope.launch { attachmentBottomSheetState.show() } }
@ -145,7 +156,7 @@ fun ChatView(chatId: String, chatModel: ChatModel, onComposed: suspend (chatId:
var preloadedLink: Pair<String, GroupMemberRole>? = null
if (chat.chatInfo is ChatInfo.Direct) {
preloadedContactInfo = chatModel.controller.apiContactInfo(chat.chatInfo.apiId)
preloadedCode = chatModel.controller.apiGetContactCode(chat.chatInfo.apiId).second
preloadedCode = chatModel.controller.apiGetContactCode(chat.chatInfo.apiId)?.second
} else if (chat.chatInfo is ChatInfo.Group) {
setGroupMembers(chat.chatInfo.groupInfo, chatModel)
preloadedLink = chatModel.controller.apiGetGroupLink(chat.chatInfo.groupInfo.groupId)
@ -158,7 +169,7 @@ fun ChatView(chatId: String, chatModel: ChatModel, onComposed: suspend (chatId:
KeyChangeEffect(chat.id, ChatModel.networkStatuses.toMap()) {
contactInfo = chatModel.controller.apiContactInfo(chat.chatInfo.apiId)
preloadedContactInfo = contactInfo
code = chatModel.controller.apiGetContactCode(chat.chatInfo.apiId).second
code = chatModel.controller.apiGetContactCode(chat.chatInfo.apiId)?.second
preloadedCode = code
}
ChatInfoView(chatModel, (chat.chatInfo as ChatInfo.Direct).contact, contactInfo?.first, contactInfo?.second, chat.chatInfo.localAlias, code, close)
@ -183,12 +194,8 @@ fun ChatView(chatId: String, chatModel: ChatModel, onComposed: suspend (chatId:
val r = chatModel.controller.apiGroupMemberInfo(groupInfo.groupId, member.groupMemberId)
val stats = r?.second
val (_, code) = if (member.memberActive) {
try {
chatModel.controller.apiGetGroupMemberCode(groupInfo.apiId, member.groupMemberId)
} catch (e: Exception) {
Log.e(TAG, e.stackTraceToString())
member to null
}
val memCode = chatModel.controller.apiGetGroupMemberCode(groupInfo.apiId, member.groupMemberId)
member to memCode?.second
} else {
member to null
}
@ -280,6 +287,11 @@ fun ChatView(chatId: String, chatModel: ChatModel, onComposed: suspend (chatId:
chatModel.controller.allowFeatureToContact(contact, feature, param)
}
},
openDirectChat = { contactId ->
withApi {
openDirectChat(contactId, chatModel)
}
},
updateContactStats = { contact ->
withApi {
val r = chatModel.controller.apiContactInfo(chat.chatInfo.apiId)
@ -409,6 +421,7 @@ fun ChatLayout(
startCall: (CallMediaType) -> Unit,
acceptCall: (Contact) -> Unit,
acceptFeature: (Contact, ChatFeature, Int?) -> Unit,
openDirectChat: (Long) -> Unit,
updateContactStats: (Contact) -> Unit,
updateMemberStats: (GroupInfo, GroupMember) -> Unit,
syncContactConnection: (Contact) -> Unit,
@ -485,7 +498,7 @@ fun ChatLayout(
ChatItemsList(
chat, unreadCount, composeState, chatItems, searchValue,
useLinkPreviews, linkMode, showMemberInfo, loadPrevMessages, deleteMessage,
receiveFile, cancelFile, joinGroup, acceptCall, acceptFeature,
receiveFile, cancelFile, joinGroup, acceptCall, acceptFeature, openDirectChat,
updateContactStats, updateMemberStats, syncContactConnection, syncMemberConnection, findModelChat, findModelMember,
setReaction, showItemDetails, markRead, setFloatingButton, onComposed,
)
@ -534,16 +547,23 @@ fun ChatInfoToolbar(
IconButton({
showMenu.value = false
startCall(CallMediaType.Audio)
}) {
Icon(painterResource(MR.images.ic_call_500), stringResource(MR.strings.icon_descr_more_button), tint = MaterialTheme.colors.primary)
},
enabled = chat.chatInfo.contact.ready) {
Icon(
painterResource(MR.images.ic_call_500),
stringResource(MR.strings.icon_descr_more_button),
tint = if (chat.chatInfo.contact.ready) MaterialTheme.colors.primary else MaterialTheme.colors.secondary
)
}
}
if (chat.chatInfo.contact.ready) {
menuItems.add {
ItemAction(stringResource(MR.strings.icon_descr_video_call).capitalize(Locale.current), painterResource(MR.images.ic_videocam), onClick = {
showMenu.value = false
startCall(CallMediaType.Video)
})
}
}
} else if (chat.chatInfo is ChatInfo.Group && chat.chatInfo.groupInfo.canAddMembers && !chat.chatInfo.incognito) {
barButtons.add {
IconButton({
@ -554,6 +574,7 @@ fun ChatInfoToolbar(
}
}
}
if ((chat.chatInfo is ChatInfo.Direct && chat.chatInfo.contact.ready) || chat.chatInfo is ChatInfo.Group) {
val ntfsEnabled = remember { mutableStateOf(chat.chatInfo.ntfsEnabled) }
menuItems.add {
ItemAction(
@ -569,6 +590,7 @@ fun ChatInfoToolbar(
}
)
}
}
barButtons.add {
IconButton({ showMenu.value = true }) {
@ -661,6 +683,7 @@ fun BoxWithConstraintsScope.ChatItemsList(
joinGroup: (Long) -> Unit,
acceptCall: (Contact) -> Unit,
acceptFeature: (Contact, ChatFeature, Int?) -> Unit,
openDirectChat: (Long) -> Unit,
updateContactStats: (Contact) -> Unit,
updateMemberStats: (GroupInfo, GroupMember) -> Unit,
syncContactConnection: (Contact) -> Unit,
@ -808,7 +831,7 @@ fun BoxWithConstraintsScope.ChatItemsList(
) {
MemberImage(member)
}
ChatItemView(chat.chatInfo, cItem, composeState, provider, useLinkPreviews = useLinkPreviews, linkMode = linkMode, deleteMessage = deleteMessage, receiveFile = receiveFile, cancelFile = cancelFile, joinGroup = {}, acceptCall = acceptCall, acceptFeature = acceptFeature, updateContactStats = updateContactStats, updateMemberStats = updateMemberStats, syncContactConnection = syncContactConnection, syncMemberConnection = syncMemberConnection, findModelChat = findModelChat, findModelMember = findModelMember, scrollToItem = scrollToItem, setReaction = setReaction, showItemDetails = showItemDetails, getConnectedMemberNames = ::getConnectedMemberNames)
ChatItemView(chat.chatInfo, cItem, composeState, provider, useLinkPreviews = useLinkPreviews, linkMode = linkMode, deleteMessage = deleteMessage, receiveFile = receiveFile, cancelFile = cancelFile, joinGroup = {}, acceptCall = acceptCall, acceptFeature = acceptFeature, openDirectChat = openDirectChat, updateContactStats = updateContactStats, updateMemberStats = updateMemberStats, syncContactConnection = syncContactConnection, syncMemberConnection = syncMemberConnection, findModelChat = findModelChat, findModelMember = findModelMember, scrollToItem = scrollToItem, setReaction = setReaction, showItemDetails = showItemDetails, getConnectedMemberNames = ::getConnectedMemberNames)
}
}
} else {
@ -817,7 +840,7 @@ fun BoxWithConstraintsScope.ChatItemsList(
.padding(start = 8.dp + MEMBER_IMAGE_SIZE + 4.dp, end = if (voiceWithTransparentBack) 12.dp else 66.dp)
.then(swipeableModifier)
) {
ChatItemView(chat.chatInfo, cItem, composeState, provider, useLinkPreviews = useLinkPreviews, linkMode = linkMode, deleteMessage = deleteMessage, receiveFile = receiveFile, cancelFile = cancelFile, joinGroup = {}, acceptCall = acceptCall, acceptFeature = acceptFeature, updateContactStats = updateContactStats, updateMemberStats = updateMemberStats, syncContactConnection = syncContactConnection, syncMemberConnection = syncMemberConnection, findModelChat = findModelChat, findModelMember = findModelMember, scrollToItem = scrollToItem, setReaction = setReaction, showItemDetails = showItemDetails, getConnectedMemberNames = ::getConnectedMemberNames)
ChatItemView(chat.chatInfo, cItem, composeState, provider, useLinkPreviews = useLinkPreviews, linkMode = linkMode, deleteMessage = deleteMessage, receiveFile = receiveFile, cancelFile = cancelFile, joinGroup = {}, acceptCall = acceptCall, acceptFeature = acceptFeature, openDirectChat = openDirectChat, updateContactStats = updateContactStats, updateMemberStats = updateMemberStats, syncContactConnection = syncContactConnection, syncMemberConnection = syncMemberConnection, findModelChat = findModelChat, findModelMember = findModelMember, scrollToItem = scrollToItem, setReaction = setReaction, showItemDetails = showItemDetails, getConnectedMemberNames = ::getConnectedMemberNames)
}
}
}
@ -827,7 +850,7 @@ fun BoxWithConstraintsScope.ChatItemsList(
.padding(start = if (voiceWithTransparentBack) 12.dp else 104.dp, end = 12.dp)
.then(swipeableModifier)
) {
ChatItemView(chat.chatInfo, cItem, composeState, provider, useLinkPreviews = useLinkPreviews, linkMode = linkMode, deleteMessage = deleteMessage, receiveFile = receiveFile, cancelFile = cancelFile, joinGroup = {}, acceptCall = acceptCall, acceptFeature = acceptFeature, updateContactStats = updateContactStats, updateMemberStats = updateMemberStats, syncContactConnection = syncContactConnection, syncMemberConnection = syncMemberConnection, findModelChat = findModelChat, findModelMember = findModelMember, scrollToItem = scrollToItem, setReaction = setReaction, showItemDetails = showItemDetails)
ChatItemView(chat.chatInfo, cItem, composeState, provider, useLinkPreviews = useLinkPreviews, linkMode = linkMode, deleteMessage = deleteMessage, receiveFile = receiveFile, cancelFile = cancelFile, joinGroup = {}, acceptCall = acceptCall, acceptFeature = acceptFeature, openDirectChat = openDirectChat, updateContactStats = updateContactStats, updateMemberStats = updateMemberStats, syncContactConnection = syncContactConnection, syncMemberConnection = syncMemberConnection, findModelChat = findModelChat, findModelMember = findModelMember, scrollToItem = scrollToItem, setReaction = setReaction, showItemDetails = showItemDetails)
}
}
} else { // direct message
@ -838,7 +861,7 @@ fun BoxWithConstraintsScope.ChatItemsList(
end = if (sent || voiceWithTransparentBack) 12.dp else 76.dp,
).then(swipeableModifier)
) {
ChatItemView(chat.chatInfo, cItem, composeState, provider, useLinkPreviews = useLinkPreviews, linkMode = linkMode, deleteMessage = deleteMessage, receiveFile = receiveFile, cancelFile = cancelFile, joinGroup = joinGroup, acceptCall = acceptCall, acceptFeature = acceptFeature, updateContactStats = updateContactStats, updateMemberStats = updateMemberStats, syncContactConnection = syncContactConnection, syncMemberConnection = syncMemberConnection, findModelChat = findModelChat, findModelMember = findModelMember, scrollToItem = scrollToItem, setReaction = setReaction, showItemDetails = showItemDetails)
ChatItemView(chat.chatInfo, cItem, composeState, provider, useLinkPreviews = useLinkPreviews, linkMode = linkMode, deleteMessage = deleteMessage, receiveFile = receiveFile, cancelFile = cancelFile, joinGroup = joinGroup, acceptCall = acceptCall, acceptFeature = acceptFeature, openDirectChat = openDirectChat, updateContactStats = updateContactStats, updateMemberStats = updateMemberStats, syncContactConnection = syncContactConnection, syncMemberConnection = syncMemberConnection, findModelChat = findModelChat, findModelMember = findModelMember, scrollToItem = scrollToItem, setReaction = setReaction, showItemDetails = showItemDetails)
}
}
@ -1263,6 +1286,7 @@ fun PreviewChatLayout() {
startCall = {},
acceptCall = { _ -> },
acceptFeature = { _, _, _ -> },
openDirectChat = { _ -> },
updateContactStats = { },
updateMemberStats = { _, _ -> },
syncContactConnection = { },
@ -1330,6 +1354,7 @@ fun PreviewGroupChatLayout() {
startCall = {},
acceptCall = { _ -> },
acceptFeature = { _, _, _ -> },
openDirectChat = { _ -> },
updateContactStats = { },
updateMemberStats = { _, _ -> },
syncContactConnection = { },

View File

@ -0,0 +1,39 @@
package chat.simplex.common.views.chat
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.*
import androidx.compose.material.*
import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import chat.simplex.common.ui.theme.*
import chat.simplex.common.views.helpers.generalGetString
import chat.simplex.res.MR
import dev.icerock.moko.resources.compose.painterResource
import dev.icerock.moko.resources.compose.stringResource
@Composable
fun ComposeContextInvitingContactMemberView() {
val sentColor = CurrentColors.collectAsState().value.appColors.sentMessage
Row(
Modifier
.height(60.dp)
.fillMaxWidth()
.padding(top = 8.dp)
.background(sentColor),
verticalAlignment = Alignment.CenterVertically
) {
Icon(
painterResource(MR.images.ic_chat),
stringResource(MR.strings.button_send_direct_message),
modifier = Modifier
.padding(start = 12.dp, end = 8.dp)
.height(20.dp)
.width(20.dp),
tint = MaterialTheme.colors.secondary
)
Text(generalGetString(MR.strings.compose_send_direct_message_to_connect))
}
}

View File

@ -335,8 +335,6 @@ fun ComposeView(
return null
}
suspend fun sendMessageAsync(text: String?, live: Boolean, ttl: Int?): ChatItem? {
val cInfo = chat.chatInfo
val cs = composeState.value
@ -358,6 +356,7 @@ fun ComposeView(
MsgContent.MCText(msgText)
}
}
else -> MsgContent.MCText(msgText)
}
}
@ -374,6 +373,14 @@ fun ComposeView(
}
}
suspend fun sendMemberContactInvitation() {
val mc = checkLinkPreview()
val contact = chatModel.controller.apiSendMemberContactInvitation(chat.chatInfo.apiId, mc)
if (contact != null) {
chatModel.updateContact(contact)
}
}
suspend fun updateMessage(ei: ChatItem, cInfo: ChatInfo, live: Boolean): ChatItem? {
val oldMsgContent = ei.content.msgContent
if (oldMsgContent != null) {
@ -397,7 +404,10 @@ fun ComposeView(
}
clearCurrentDraft()
if (cs.contextItem is ComposeContextItem.EditingItem) {
if (chat.nextSendGrpInv) {
sendMemberContactInvitation()
sent = null
} else if (cs.contextItem is ComposeContextItem.EditingItem) {
val ei = cs.contextItem.chatItem
sent = updateMessage(ei, cInfo, live)
} else if (liveMessage != null && liveMessage.sent) {
@ -655,9 +665,14 @@ fun ComposeView(
}
val userCanSend = rememberUpdatedState(chat.userCanSend)
val sendMsgEnabled = rememberUpdatedState(chat.chatInfo.sendMsgEnabled)
val userIsObserver = rememberUpdatedState(chat.userIsObserver)
val nextSendGrpInv = rememberUpdatedState(chat.nextSendGrpInv)
Column {
if (nextSendGrpInv.value) {
ComposeContextInvitingContactMemberView()
}
if (composeState.value.preview !is ComposePreview.VoicePreview || composeState.value.editing) {
contextItemView()
when {
@ -690,15 +705,21 @@ fun ComposeView(
} else {
showChooseAttachment
}
val attachmentEnabled =
!composeState.value.attachmentDisabled
&& sendMsgEnabled.value
&& userCanSend.value
&& !isGroupAndProhibitedFiles
&& !nextSendGrpInv.value
IconButton(
attachmentClicked,
Modifier.padding(bottom = if (appPlatform.isAndroid) 0.dp else 7.dp),
enabled = !composeState.value.attachmentDisabled && rememberUpdatedState(chat.userCanSend).value
enabled = attachmentEnabled
) {
Icon(
painterResource(MR.images.ic_attach_file_filled_500),
contentDescription = stringResource(MR.strings.attach),
tint = if (!composeState.value.attachmentDisabled && userCanSend.value && !isGroupAndProhibitedFiles) MaterialTheme.colors.primary else MaterialTheme.colors.secondary,
tint = if (attachmentEnabled) MaterialTheme.colors.primary else MaterialTheme.colors.secondary,
modifier = Modifier
.size(28.dp)
.clip(CircleShape)
@ -774,6 +795,8 @@ fun ComposeView(
recState,
chat.chatInfo is ChatInfo.Direct,
liveMessageAlertShown = chatModel.controller.appPrefs.liveMessageAlertShown,
sendMsgEnabled = sendMsgEnabled.value,
nextSendGrpInv = nextSendGrpInv.value,
needToAllowVoiceToContact,
allowedVoiceByPrefs,
allowVoiceToContact = ::allowVoiceToContact,

View File

@ -37,6 +37,8 @@ fun SendMsgView(
recState: MutableState<RecordingState>,
isDirectChat: Boolean,
liveMessageAlertShown: SharedPreference<Boolean>,
sendMsgEnabled: Boolean,
nextSendGrpInv: Boolean,
needToAllowVoiceToContact: Boolean,
allowedVoiceByPrefs: Boolean,
userIsObserver: Boolean,
@ -74,16 +76,16 @@ fun SendMsgView(
false
}
}
val showVoiceButton = cs.message.isEmpty() && showVoiceRecordIcon && !composeState.value.editing &&
val showVoiceButton = !nextSendGrpInv && cs.message.isEmpty() && showVoiceRecordIcon && !composeState.value.editing &&
cs.liveMessage == null && (cs.preview is ComposePreview.NoPreview || recState.value is RecordingState.Started)
val showDeleteTextButton = rememberSaveable { mutableStateOf(false) }
PlatformTextField(composeState, textStyle, showDeleteTextButton, userIsObserver, onMessageChange, editPrevMessage) {
PlatformTextField(composeState, sendMsgEnabled, textStyle, showDeleteTextButton, userIsObserver, onMessageChange, editPrevMessage) {
if (!cs.inProgress) {
sendMessage(null)
}
}
// Disable clicks on text field
if (cs.preview is ComposePreview.VoicePreview || !userCanSend || cs.inProgress) {
if (!sendMsgEnabled || cs.preview is ComposePreview.VoicePreview || !userCanSend || cs.inProgress) {
Box(
Modifier
.matchParentSize()
@ -110,7 +112,7 @@ fun SendMsgView(
}
when {
progressByTimeout -> ProgressIndicator()
showVoiceButton -> {
showVoiceButton && sendMsgEnabled -> {
Row(verticalAlignment = Alignment.CenterVertically) {
val stopRecOnNextClick = remember { mutableStateOf(false) }
when {
@ -150,7 +152,7 @@ fun SendMsgView(
else -> {
val cs = composeState.value
val icon = if (cs.editing || cs.liveMessage != null) painterResource(MR.images.ic_check_filled) else painterResource(MR.images.ic_arrow_upward)
val disabled = !cs.sendEnabled() ||
val disabled = !sendMsgEnabled || !cs.sendEnabled() ||
(!allowedVoiceByPrefs && cs.preview is ComposePreview.VoicePreview) ||
cs.endLiveDisabled
val showDropdown = rememberSaveable { mutableStateOf(false) }
@ -159,7 +161,7 @@ fun SendMsgView(
fun MenuItems(): List<@Composable () -> Unit> {
val menuItems = mutableListOf<@Composable () -> Unit>()
if (cs.liveMessage == null && !cs.editing) {
if (cs.liveMessage == null && !cs.editing && !nextSendGrpInv || sendMsgEnabled) {
if (
cs.preview !is ComposePreview.VoicePreview &&
cs.contextItem is ComposeContextItem.NoContextItem &&
@ -599,6 +601,8 @@ fun PreviewSendMsgView() {
recState = remember { mutableStateOf(RecordingState.NotStarted) },
isDirectChat = true,
liveMessageAlertShown = SharedPreference(get = { true }, set = { }),
sendMsgEnabled = true,
nextSendGrpInv = false,
needToAllowVoiceToContact = false,
allowedVoiceByPrefs = true,
userIsObserver = false,
@ -630,6 +634,8 @@ fun PreviewSendMsgViewEditing() {
recState = remember { mutableStateOf(RecordingState.NotStarted) },
isDirectChat = true,
liveMessageAlertShown = SharedPreference(get = { true }, set = { }),
sendMsgEnabled = true,
nextSendGrpInv = false,
needToAllowVoiceToContact = false,
allowedVoiceByPrefs = true,
userIsObserver = false,
@ -661,6 +667,8 @@ fun PreviewSendMsgViewInProgress() {
recState = remember { mutableStateOf(RecordingState.NotStarted) },
isDirectChat = true,
liveMessageAlertShown = SharedPreference(get = { true }, set = { }),
sendMsgEnabled = true,
nextSendGrpInv = false,
needToAllowVoiceToContact = false,
allowedVoiceByPrefs = true,
userIsObserver = false,

View File

@ -76,12 +76,8 @@ fun GroupChatInfoView(chatModel: ChatModel, groupLink: String?, groupLinkMemberR
val r = chatModel.controller.apiGroupMemberInfo(groupInfo.groupId, member.groupMemberId)
val stats = r?.second
val (_, code) = if (member.memberActive) {
try {
chatModel.controller.apiGetGroupMemberCode(groupInfo.apiId, member.groupMemberId)
} catch (e: Exception) {
Log.e(TAG, e.stackTraceToString())
member to null
}
val memCode = chatModel.controller.apiGetGroupMemberCode(groupInfo.apiId, member.groupMemberId)
member to memCode?.second
} else {
member to null
}

View File

@ -35,6 +35,7 @@ import chat.simplex.common.views.newchat.*
import chat.simplex.common.views.usersettings.SettingsActionItem
import chat.simplex.common.model.GroupInfo
import chat.simplex.common.platform.*
import chat.simplex.common.views.chatlist.openChat
import chat.simplex.res.MR
import kotlinx.datetime.Clock
@ -52,6 +53,8 @@ fun GroupMemberInfoView(
val chat = chatModel.chats.firstOrNull { it.id == chatModel.chatId.value }
val connStats = remember { mutableStateOf(connectionStats) }
val developerTools = chatModel.controller.appPrefs.developerTools.get()
var progressIndicator by remember { mutableStateOf(false) }
if (chat != null) {
val newRole = remember { mutableStateOf(member.memberRole) }
GroupMemberInfoLayout(
@ -76,6 +79,20 @@ fun GroupMemberInfoView(
}
}
},
createMemberContact = {
withApi {
progressIndicator = true
val memberContact = chatModel.controller.apiCreateMemberContact(groupInfo.apiId, member.groupMemberId)
if (memberContact != null) {
val memberChat = Chat(ChatInfo.Direct(memberContact), chatItems = arrayListOf())
chatModel.addChat(memberChat)
openChat(memberChat, chatModel)
closeAll()
chatModel.setContactNetworkStatus(memberContact, NetworkStatus.Connected())
}
progressIndicator = false
}
},
connectViaAddress = { connReqUri ->
connectViaMemberAddressAlert(connReqUri)
},
@ -170,6 +187,10 @@ fun GroupMemberInfoView(
}
}
)
if (progressIndicator) {
ProgressIndicator()
}
}
}
@ -201,6 +222,7 @@ fun GroupMemberInfoLayout(
connectionCode: String?,
getContactChat: (Long) -> Chat?,
openDirectChat: (Long) -> Unit,
createMemberContact: () -> Unit,
connectViaAddress: (String) -> Unit,
removeMember: () -> Unit,
onRoleSelected: (GroupMemberRole) -> Unit,
@ -237,9 +259,13 @@ fun GroupMemberInfoLayout(
if (member.memberActive) {
SectionView {
if (contactId != null) {
if (knownDirectChat(contactId) != null || groupInfo.fullGroupPreferences.directMessages.on) {
if (contactId != null && knownDirectChat(contactId) != null) {
OpenChatButton(onClick = { openDirectChat(contactId) })
} else if (groupInfo.fullGroupPreferences.directMessages.on) {
if (contactId != null) {
OpenChatButton(onClick = { openDirectChat(contactId) })
} else if (member.activeConn?.peerChatVRange?.isCompatibleRange(CREATE_MEMBER_CONTACT_VRANGE) == true) {
OpenChatButton(onClick = { createMemberContact() })
}
}
if (connectionCode != null) {
@ -498,6 +524,7 @@ fun PreviewGroupMemberInfoLayout() {
connectionCode = "123",
getContactChat = { Chat.sampleData },
openDirectChat = {},
createMemberContact = {},
connectViaAddress = {},
removeMember = {},
onRoleSelected = {},

View File

@ -0,0 +1,70 @@
package chat.simplex.common.views.chat.item
import androidx.compose.foundation.layout.*
import androidx.compose.material.*
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.text.*
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import chat.simplex.common.views.helpers.generalGetString
import chat.simplex.common.model.*
import chat.simplex.res.MR
@Composable
fun CIMemberCreatedContactView(
chatItem: ChatItem,
openDirectChat: (Long) -> Unit
) {
fun eventText(): AnnotatedString {
val memberDisplayName = chatItem.memberDisplayName
return if (memberDisplayName != null) {
buildAnnotatedString {
withStyle(chatEventStyle) { append(memberDisplayName) }
append(" ")
withStyle(chatEventStyle) { append(chatItem.content.text) }
}
} else {
buildAnnotatedString {
withStyle(chatEventStyle) { append(chatItem.content.text) }
}
}
}
Row(
Modifier.padding(horizontal = 6.dp, vertical = 6.dp),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(4.dp)
) {
if (chatItem.chatDir is CIDirection.GroupRcv && chatItem.chatDir.groupMember.memberContactId != null) {
val openChatStyle = SpanStyle(color = MaterialTheme.colors.primary, fontSize = 12.sp)
val annotatedText = buildAnnotatedString {
append(eventText())
append(" ")
withAnnotation(tag = "Open", annotation = "Open") {
withStyle(openChatStyle) { append(generalGetString(MR.strings.rcv_group_event_open_chat) + " ") }
}
withStyle(chatEventStyle) { append(chatItem.timestampText) }
}
fun open(offset: Int): Boolean = annotatedText.getStringAnnotations(tag = "Open", start = offset, end = offset).isNotEmpty()
ClickableText(
annotatedText,
onClick = {
if (open(it)) {
openDirectChat(chatItem.chatDir.groupMember.memberContactId)
}
},
shouldConsumeEvent = ::open
)
} else {
val annotatedText = buildAnnotatedString {
append(eventText())
append(" ")
withStyle(chatEventStyle) { append(chatItem.timestampText) }
}
Text(annotatedText)
}
}
}

View File

@ -54,6 +54,7 @@ fun ChatItemView(
acceptCall: (Contact) -> Unit,
scrollToItem: (Long) -> Unit,
acceptFeature: (Contact, ChatFeature, Int?) -> Unit,
openDirectChat: (Long) -> Unit,
updateContactStats: (Contact) -> Unit,
updateMemberStats: (GroupInfo, GroupMember) -> Unit,
syncContactConnection: (Contact) -> Unit,
@ -348,6 +349,7 @@ fun ChatItemView(
is CIContent.SndGroupInvitation -> CIGroupInvitationView(cItem, c.groupInvitation, c.memberRole, joinGroup = joinGroup, chatIncognito = cInfo.incognito)
is CIContent.RcvGroupEventContent -> when (c.rcvGroupEvent) {
is RcvGroupEvent.MemberConnected -> CIEventView(membersConnectedItemText())
is RcvGroupEvent.MemberCreatedContact -> CIMemberCreatedContactView(cItem, openDirectChat)
else -> EventItemView()
}
is CIContent.SndGroupEventContent -> EventItemView()
@ -572,6 +574,7 @@ fun PreviewChatItemView() {
acceptCall = { _ -> },
scrollToItem = {},
acceptFeature = { _, _, _ -> },
openDirectChat = { _ -> },
updateContactStats = { },
updateMemberStats = { _, _ -> },
syncContactConnection = { },
@ -601,6 +604,7 @@ fun PreviewChatItemViewDeletedContent() {
acceptCall = { _ -> },
scrollToItem = {},
acceptFeature = { _, _, _ -> },
openDirectChat = { _ -> },
updateContactStats = { },
updateMemberStats = { _, _ -> },
syncContactConnection = { },

View File

@ -103,11 +103,7 @@ fun ChatListNavLinkView(chat: Chat, chatModel: ChatModel) {
}
fun directChatAction(chatInfo: ChatInfo, chatModel: ChatModel) {
if (chatInfo.ready) {
withBGApi { openChat(chatInfo, chatModel) }
} else {
pendingContactAlertDialog(chatInfo, chatModel)
}
}
fun groupChatAction(groupInfo: GroupInfo, chatModel: ChatModel) {
@ -118,15 +114,28 @@ fun groupChatAction(groupInfo: GroupInfo, chatModel: ChatModel) {
}
}
suspend fun openChat(chatInfo: ChatInfo, chatModel: ChatModel) {
val chat = chatModel.controller.apiGetChat(chatInfo.chatType, chatInfo.apiId)
suspend fun openDirectChat(contactId: Long, chatModel: ChatModel) {
val chat = chatModel.controller.apiGetChat(ChatType.Direct, contactId)
if (chat != null) {
chatModel.chatItems.clear()
chatModel.chatItems.addAll(chat.chatItems)
chatModel.chatId.value = chatInfo.id
chatModel.chatId.value = "@$contactId"
}
}
suspend fun openChat(chatInfo: ChatInfo, chatModel: ChatModel) {
val chat = chatModel.controller.apiGetChat(chatInfo.chatType, chatInfo.apiId)
if (chat != null) {
openChat(chat, chatModel)
}
}
suspend fun openChat(chat: Chat, chatModel: ChatModel) {
chatModel.chatItems.clear()
chatModel.chatItems.addAll(chat.chatItems)
chatModel.chatId.value = chat.chatInfo.id
}
suspend fun apiLoadPrevMessages(chatInfo: ChatInfo, chatModel: ChatModel, beforeChatItemId: Long, search: String) {
val pagination = ChatPagination.Before(beforeChatItemId, ChatPagination.PRELOAD_COUNT)
val chat = chatModel.controller.apiGetChat(chatInfo.chatType, chatInfo.apiId, pagination, search) ?: return

View File

@ -172,7 +172,9 @@ fun ChatPreviewView(
} else {
when (cInfo) {
is ChatInfo.Direct ->
if (!cInfo.ready) {
if (cInfo.contact.nextSendGrpInv) {
Text(stringResource(MR.strings.member_contact_send_direct_message), color = MaterialTheme.colors.secondary)
} else if (!cInfo.ready) {
Text(stringResource(MR.strings.contact_connection_pending), color = MaterialTheme.colors.secondary)
}
is ChatInfo.Group ->

View File

@ -272,6 +272,7 @@
<string name="this_text_is_available_in_settings">This text is available in settings</string>
<string name="your_chats">Chats</string>
<string name="contact_connection_pending">connecting…</string>
<string name="member_contact_send_direct_message">send direct message</string>
<string name="group_preview_you_are_invited">you are invited to group</string>
<string name="group_preview_join_as">join as %s</string>
<string name="group_connection_pending">connecting…</string>
@ -304,6 +305,7 @@
<string name="observer_cant_send_message_desc">Please contact group admin.</string>
<string name="files_and_media_prohibited">Files and media prohibited!</string>
<string name="only_owners_can_enable_files_and_media">Only group owners can enable files and media.</string>
<string name="compose_send_direct_message_to_connect">Send direct message to connect</string>
<!-- Images - chat.simplex.app.views.chat.item.CIImageView.kt -->
<string name="image_descr">Image</string>
@ -1114,6 +1116,7 @@
<string name="rcv_group_event_group_deleted">deleted group</string>
<string name="rcv_group_event_updated_group_profile">updated group profile</string>
<string name="rcv_group_event_invited_via_your_group_link">invited via your group link</string>
<string name="rcv_group_event_member_created_contact">connected directly</string>
<string name="snd_group_event_changed_member_role">you changed role of %s to %s</string>
<string name="snd_group_event_changed_role_for_yourself">you changed role for yourself to %s</string>
<string name="snd_group_event_member_deleted">you removed %1$s</string>
@ -1124,6 +1127,8 @@
<string name="rcv_group_event_3_members_connected">%s, %s and %s connected</string>
<string name="rcv_group_event_n_members_connected">%s, %s and %d other members connected</string>
<string name="rcv_group_event_open_chat">Open</string>
<!-- Conn event chat items -->
<string name="rcv_conn_event_switch_queue_phase_completed">changed address for you</string>
<string name="rcv_conn_event_switch_queue_phase_changing">changing address…</string>
@ -1201,6 +1206,8 @@
<string name="error_creating_link_for_group">Error creating group link</string>
<string name="error_updating_link_for_group">Error updating group link</string>
<string name="error_deleting_link_for_group">Error deleting group link</string>
<string name="error_creating_member_contact">Error creating member contact</string>
<string name="error_sending_message_contact_invitation">Sending message contact invitation</string>
<string name="only_group_owners_can_change_prefs">Only group owners can change group preferences.</string>
<string name="address_section_title">Address</string>
<string name="share_address">Share address</string>

View File

@ -33,6 +33,7 @@ import kotlin.text.substring
@Composable
actual fun PlatformTextField(
composeState: MutableState<ComposeState>,
sendMsgEnabled: Boolean,
textStyle: MutableState<TextStyle>,
showDeleteTextButton: MutableState<Boolean>,
userIsObserver: Boolean,
@ -42,6 +43,7 @@ actual fun PlatformTextField(
) {
val cs = composeState.value
val focusRequester = remember { FocusRequester() }
val focusManager = LocalFocusManager.current
val keyboard = LocalSoftwareKeyboardController.current
val padding = PaddingValues(12.dp, 12.dp, 45.dp, 0.dp)
LaunchedEffect(cs.contextItem) {
@ -51,6 +53,13 @@ actual fun PlatformTextField(
delay(50)
keyboard?.show()
}
LaunchedEffect(sendMsgEnabled) {
if (!sendMsgEnabled) {
focusManager.clearFocus()
delay(50)
keyboard?.hide()
}
}
val isRtl = remember(cs.message) { isRtl(cs.message.subSequence(0, min(50, cs.message.length))) }
var textFieldValueState by remember { mutableStateOf(TextFieldValue(text = cs.message)) }
val textFieldValue = textFieldValueState.copy(text = cs.message)
@ -113,7 +122,8 @@ actual fun PlatformTextField(
}
}
}
}
},
)
showDeleteTextButton.value = cs.message.split("\n").size >= 4 && !cs.inProgress
if (composeState.value.preview is ComposePreview.VoicePreview) {

View File

@ -25,11 +25,11 @@ android.nonTransitiveRClass=true
android.enableJetifier=true
kotlin.mpp.androidSourceSetLayoutVersion=2
android.version_name=5.3-beta.7
android.version_code=149
android.version_name=5.3-beta.8
android.version_code=150
desktop.version_name=1.5.0
desktop.version_code=7
desktop.version_name=1.6.0
desktop.version_code=8
kotlin.version=1.8.20
gradle.plugin.version=7.4.2

View File

@ -9,7 +9,7 @@ constraints: zip +disable-bzip2 +disable-zstd
source-repository-package
type: git
location: https://github.com/simplex-chat/simplexmq.git
tag: 343865553295da5edeef30c1d8589b47c66bd9b0
tag: 53c793d5590d3c781aa3fbf72993eee262c7aa83
source-repository-package
type: git

View File

@ -16,7 +16,7 @@ revision: 31.01.2023
- [Windows](#windows)
- [Build from source](#build-from-source)
- [Using Docker](#using-docker)
- [Using Haskell stack](#using-haskell-stack)
- [Using Haskell in any OS](#in-any-os)
- [Usage](#usage)
- [Running the chat client](#running-the-chat-client)
- [Access messaging servers via Tor](#access-messaging-servers-via-tor-beta)
@ -102,27 +102,49 @@ DOCKER_BUILDKIT=1 docker build --output ~/.local/bin .
#### In any OS
1. Install [Haskell GHCup](https://www.haskell.org/ghcup/), GHC 8.10.7 and cabal:
1. Install [Haskell GHCup](https://www.haskell.org/ghcup/), GHC 9.6.2 and cabal 3.10.1.0:
```shell
curl --proto '=https' --tlsv1.2 -sSf https://get-ghcup.haskell.org | sh
```
2. Build the project:
You can use `ghcup tui` to check or add GHC and cabal versions.
2. Clone the source code:
```shell
git clone git@github.com:simplex-chat/simplex-chat.git
cd simplex-chat
git checkout stable
# on Linux
# or to build a specific version:
# git checkout v5.3.0-beta.8
```
`master` is a development branch, it may containt unstable code.
3. Prepare the system:
On Linux:
```shell
apt-get update && apt-get install -y build-essential libgmp3-dev zlib1g-dev
cp scripts/cabal.project.local.linux cabal.project.local
# or on MacOS:
# brew install openssl@1.1
# cp scripts/cabal.project.local.mac cabal.project.local
# you may need to amend cabal.project.local to point to the actual openssl location
```
On Mac:
```
brew install openssl@1.1
cp scripts/cabal.project.local.mac cabal.project.local
```
You may need to amend cabal.project.local to point to the actual openssl location.
4. Build the app:
```shell
cabal update
cabal install
cabal install simplex-chat
```
## Usage

42
docs/DOWNLOADS.md Normal file
View File

@ -0,0 +1,42 @@
---
title: Download SimpleX apps
permalink: /downloads/index.html
revision: 20.09.2023
---
| Updated 20.09.2023 | Languages: EN |
# Download SimpleX apps
- [desktop](#desktop-app)
- [mobile](#mobile-apps)
- [terminal](#terminal-console-app) (console)
## Desktop app
<img src="/docs/images/simplex-desktop-light.png" alt="desktop app" width=500>
The latest version of desktop app is v5.3-beta.8 (1.6.0 in the app).
Using the same profile as on mobile device is not yet supported you need to create a separate profile to use desktop apps.
**Linux**: [AppImage](https://github.com/simplex-chat/simplex-chat/releases/download/v5.3.0-beta.8/simplex-desktop-x86_64.AppImage) (most Linux distros), [Ubuntu 20.04](https://github.com/simplex-chat/simplex-chat/releases/download/v5.3.0-beta.8/simplex-desktop-ubuntu-20_04-x86_64.deb) (and Debian-based distros), [Ubuntu 22.04](https://github.com/simplex-chat/simplex-chat/releases/download/v5.3.0-beta.8/simplex-desktop-ubuntu-22_04-x86_64.deb).
**Mac**: [x86_64](https://github.com/simplex-chat/simplex-chat/releases/download/v5.3.0-beta.8/simplex-desktop-macos-x86_64.dmg) (Intel), [aarch64](https://github.com/simplex-chat/simplex-chat/releases/download/v5.3.0-beta.8/simplex-desktop-macos-aarch64.dmg) (Apple Silicon).
**Windows**: coming soon.
## Mobile apps
**iOS**: [App store](https://apps.apple.com/us/app/simplex-chat/id1605771084) (v5.2.3), [TestFlight](https://testflight.apple.com/join/DWuT2LQu) (v5.3-beta.8).
**Android**: [Play store](https://play.google.com/store/apps/details?id=chat.simplex.app), [F-Droid](https://simplex.chat/fdroid/), [APK aarch64](https://github.com/simplex-chat/simplex-chat/releases/download/v5.3.0-beta.8/simplex.apk), [APK armv7](https://github.com/simplex-chat/simplex-chat/releases/download/v5.3.0-beta.8/simplex-armv7a.apk).
## Terminal (console) app
See [Using terminal app](/docs/CLI.md).
**Linux**: [Ubuntu 20.04](https://github.com/simplex-chat/simplex-chat/releases/download/v5.3.0-beta.8/simplex-chat-ubuntu-20_04-x86-64), [Ubuntu 22.04](https://github.com/simplex-chat/simplex-chat/releases/download/v5.3.0-beta.8/simplex-chat-ubuntu-22_04-x86-64).
**Mac** [x86_64](https://github.com/simplex-chat/simplex-chat/releases/download/v5.3.0-beta.8/simplex-chat-macos-x86-64), aarch64 - [compile from source](./CLI.md#).
**Windows**: [x86_64](https://github.com/simplex-chat/simplex-chat/releases/download/v5.3.0-beta.8/simplex-chat-windows-x86-64).

View File

@ -1,3 +1,9 @@
---
title: Join SimpleX Chat team
permalink: /jobs/index.html
layout: layouts/jobs.html
---
# Join SimpleX Chat team
SimpleX Chat Ltd is a seed stage startup with a lot of user growth in 2022-2023, and a lot of exciting technical and product problems to solve to grow faster.
@ -7,35 +13,6 @@ We currently have 4 full-time people in the team - all engineers, including the
We want to add up to 3 people to the team.
**You**:
- **Passionate about joining SimpleX Chat team**:
- already use SimpleX Chat to communicate with friends/family or participate in public SimpleX Chat groups.
- passionate about privacy, security and communications.
- interested to make contributions to SimpleX Chat open-source project in your free time before we hire you, as an extended test.
- **Exceptionally pragmatic, very fast and customer-focussed**:
- care about the customers (aka users) and about the product we build much more than about the code quality, technology stack, etc.
- believe that the simplest solution is the best.
- 2-3x faster than the most competent people you worked with.
- focus on solving only today's problems and resist engineering for the future (aka over-engineering) see [The Duct Tape Programmer](https://www.joelonsoftware.com/2009/09/23/the-duct-tape-programmer/) and [Why I Hate Frameworks](https://medium.com/@johnfliu/why-i-hate-frameworks-6af8cbadba42).
- do not suffer from "not invented here" syndrome, at the same time interested to design and implement protocols and systems from the ground up when appropriate.
- **Love software engineering**:
- have 5y+ of software engineering experience in complex projects,
- great understanding of the common principles:
- data structures, bits and byte manipulation
- text encoding and manipulation
- software design and algorithms
- concurrency
- networking
- **Want to join a very early stage startup**:
- high pace and intensity, longer hours.
- a substantial part of the compensation is stock options.
- full transparency we believe that too much [autonomy](https://twitter.com/KentBeck/status/851459129830850561) hurts learning and slows down progress.
## Who we are looking for
### Systems Haskell engineer
@ -63,6 +40,35 @@ You are a product UX expert who designs great user experiences directly in iOS c
Knowledge of Android and Kotlin Multiplatform would be a bonus - we use Kotlin Jetpack Compose for our Android and desktop apps.
## About you
- **Passionate about joining SimpleX Chat team**:
- already use SimpleX Chat to communicate with friends/family or participate in public SimpleX Chat groups.
- passionate about privacy, security and communications.
- interested to make contributions to SimpleX Chat open-source project in your free time before we hire you, as an extended test.
- **Exceptionally pragmatic, very fast and customer-focussed**:
- care about the customers (aka users) and about the product we build much more than about the code quality, technology stack, etc.
- believe that the simplest solution is the best.
- 2-3x faster than the most competent people you worked with.
- focus on solving only today's problems and resist engineering for the future (aka over-engineering) see [The Duct Tape Programmer](https://www.joelonsoftware.com/2009/09/23/the-duct-tape-programmer/) and [Why I Hate Frameworks](https://medium.com/@johnfliu/why-i-hate-frameworks-6af8cbadba42).
- do not suffer from "not invented here" syndrome, at the same time interested to design and implement protocols and systems from the ground up when appropriate.
- **Love software engineering**:
- have 5y+ of software engineering experience in complex projects,
- great understanding of the common principles:
- data structures, bits and byte manipulation
- text encoding and manipulation
- software design and algorithms
- concurrency
- networking
- **Want to join a very early stage startup**:
- high pace and intensity, longer hours.
- a substantial part of the compensation is stock options.
- full transparency we believe that too much [autonomy](https://twitter.com/KentBeck/status/851459129830850561) hurts learning and slows down progress.
## How to join the team
1. [Install the app](../README.md#install-the-app), try using it with the friends and [join some user groups](https://github.com/simplex-chat/simplex-chat#join-user-groups) you will discover a lot of things that need improvements.

View File

Before

Width:  |  Height:  |  Size: 556 KiB

After

Width:  |  Height:  |  Size: 556 KiB

View File

Before

Width:  |  Height:  |  Size: 550 KiB

After

Width:  |  Height:  |  Size: 550 KiB

View File

Before

Width:  |  Height:  |  Size: 545 KiB

After

Width:  |  Height:  |  Size: 545 KiB

View File

@ -1,5 +1,5 @@
{
"https://github.com/simplex-chat/simplexmq.git"."343865553295da5edeef30c1d8589b47c66bd9b0" = "01md63iq78swfr6rn0b3a10168ivcha4ql37hk0k4knfqc09fv45";
"https://github.com/simplex-chat/simplexmq.git"."53c793d5590d3c781aa3fbf72993eee262c7aa83" = "0f0ldlgqwrapgfw5gnaj00xvb14c8nykyjr9fhy79h4r16g614x8";
"https://github.com/simplex-chat/hs-socks.git"."a30cc7a79a08d8108316094f8f2f82a0c5e1ac51" = "0yasvnr7g91k76mjkamvzab2kvlb1g5pspjyjn2fr6v83swjhj38";
"https://github.com/kazu-yamamoto/http2.git"."b5a1b7200cf5bc7044af34ba325284271f6dff25" = "0dqb50j57an64nf4qcf5vcz4xkd1vzvghvf8bk529c1k30r9nfzb";
"https://github.com/simplex-chat/direct-sqlcipher.git"."34309410eb2069b029b8fc1872deb1e0db123294" = "0kwkmhyfsn2lixdlgl15smgr1h5gjk7fky6abzh8rng2h5ymnffd";

View File

@ -49,7 +49,7 @@ extra-deps:
# - simplexmq-1.0.0@sha256:34b2004728ae396e3ae449cd090ba7410781e2b3cefc59259915f4ca5daa9ea8,8561
# - ../simplexmq
- github: simplex-chat/simplexmq
commit: 343865553295da5edeef30c1d8589b47c66bd9b0
commit: 53c793d5590d3c781aa3fbf72993eee262c7aa83
- github: kazu-yamamoto/http2
commit: b5a1b7200cf5bc7044af34ba325284271f6dff25
# - ../direct-sqlcipher

View File

@ -188,6 +188,50 @@ module.exports = function (ty) {
return dom.serialize()
})
ty.addFilter('wrapH3s', function (content, page) {
if (!page.url.includes("/jobs/")) {
return content
}
const dom = new JSDOM(content)
const document = dom.window.document
const makeBlock = (block) => {
const jobTab = document.createElement('div')
jobTab.className = "job-tab"
const flexDiv = document.createElement('div')
flexDiv.className = "flex items-center justify-between job-tab-btn cursor-pointer"
flexDiv.innerHTML = `
<${block.tagName}>${block.innerHTML}</${block.tagName}>
<svg class="fill-grey-black dark:fill-white" width="10" height="5" viewBox="0 0 10 5" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M8.40813 4.79332C8.69689 5.06889 9.16507 5.06889 9.45384 4.79332C9.7426 4.51775 9.7426 4.07097 9.45384 3.7954L5.69327 0.206676C5.65717 0.17223 5.61827 0.142089 5.57727 0.116255C5.29026 -0.064587 4.90023 -0.0344467 4.64756 0.206676L0.886983 3.7954C0.598219 4.07097 0.598219 4.51775 0.886983 4.79332C1.17575 5.06889 1.64393 5.06889 1.93269 4.79332L5.17041 1.70356L8.40813 4.79332Z"></path>
</svg>
`
jobTab.appendChild(flexDiv)
const jobContent = document.createElement('div')
jobContent.className = "job-tab-content"
jobTab.appendChild(jobContent)
block.parentNode.insertBefore(jobTab, block)
block.remove()
let sibling = jobTab.nextElementSibling
const siblingsToMove = []
while (sibling && !['H3', 'H2'].includes(sibling.tagName)) {
siblingsToMove.push(sibling)
sibling = sibling.nextElementSibling
}
siblingsToMove.forEach(el => jobContent.appendChild(el))
}
Array.from(document.querySelectorAll("h3")).forEach(makeBlock)
return dom.serialize()
})
ty.addShortcode("completeRoute", (obj) => {
const urlParts = obj.url.split("/")
@ -271,7 +315,8 @@ module.exports = function (ty) {
referenceMenu.data.forEach(referenceSubmenu => {
docs.forEach(doc => {
const url = doc.url.replace("/docs/", "")
const urlParts = url.split("/")
let urlParts = url.split("/")
urlParts = urlParts.filter((ele) => ele !== "")
if (doc.inputPath.split('/').includes(referenceSubmenu)) {
if (urlParts.length === 1 && urlParts[0] !== "") {

View File

@ -54,13 +54,17 @@ Object.entries(fileLanguageMapping).forEach(([fileName, languages]) => {
// Calculate the permalink based on the file's location
const linkPath = path.relative(directoryPath, fullPath).replace(/\.md$/, '.html');
const permalink = `/docs/${linkPath}`.toLowerCase();
parsedMatter.data.permalink = permalink;
if (fileName === 'JOIN_TEAM') {
parsedMatter.data.active_jobs = true;
}
if (!parsedMatter.data.permalink) parsedMatter.data.permalink = permalink;
// Update the frontmatter with the new languages list
parsedMatter.data.supportedLangsForDoc = languages;
// Add the layout value
parsedMatter.data.layout = 'layouts/doc.html';
if (!parsedMatter.data.layout) parsedMatter.data.layout = 'layouts/doc.html';
if (fullPath.startsWith(path.join(directoryPath, langFolder))) {
// Non-English files

View File

@ -132,7 +132,7 @@
"donate-here-to-help-us": "تبرّع هنا لمساعدتنا",
"sign-up-to-receive-our-updates": "اشترك للحصول على آخر مستجداتنا",
"enter-your-email-address": "أدخل عنوان بريدك الإلكتروني",
"get-simplex": "احصل على SimpleX",
"get-simplex": "احصل على SimpleX <a href=\"/downloads\">desktop app</a>",
"why-simplex-is": "لماذا SimpleX",
"unique": "فريد من نوعه",
"learn-more": "اقرأ أكثر",

View File

@ -114,7 +114,7 @@
"donate-here-to-help-us": "Přispějte zde a pomozte nám",
"sign-up-to-receive-our-updates": "Přihlaste se k odběru novinek",
"enter-your-email-address": "vložte svou e-mailovou adresu",
"get-simplex": "Získat SimpleX",
"get-simplex": "Získat SimpleX <a href=\"/downloads\">desktop app</a>",
"why-simplex-is": "Proč je SimpleX",
"unique": "jedinečný",
"learn-more": "Další informace",

View File

@ -127,7 +127,7 @@
"donate-here-to-help-us": "Spenden Sie, um uns zu unterstützen",
"sign-up-to-receive-our-updates": "Melden Sie sich an, um Updates von uns zu erhalten",
"enter-your-email-address": "Geben Sie Ihre Mail-Adresse ein",
"get-simplex": "Laden Sie sich SimpleX herunter",
"get-simplex": "Laden Sie sich SimpleX herunter <a href=\"/downloads\">desktop app</a>",
"learn-more": "Erfahren Sie mehr darüber",
"more-info": "Weitere Informationen",
"hide-info": "Informationen verbergen",

View File

@ -30,10 +30,12 @@
"hero-p-1": "Other apps have user IDs: Signal, Matrix, Session, Briar, Jami, Cwtch, etc.<br> SimpleX does not, <strong>not even random numbers</strong>.<br> This radically improves your privacy.",
"hero-overlay-1-textlink": "Why user IDs are bad for privacy?",
"hero-overlay-2-textlink": "How does SimpleX work?",
"hero-overlay-3-textlink": "Security assessment",
"hero-2-header": "Make a private connection",
"hero-2-header-desc": "The video shows how you connect to your friend via their 1-time QR-code, in person or via a video link. You can also connect by sharing an invitation link.",
"hero-overlay-1-title": "How does SimpleX work?",
"hero-overlay-2-title": "Why user IDs are bad for privacy?",
"hero-overlay-3-title": "Security assessment",
"feature-1-title": "E2E-encrypted messages with markdown and editing",
"feature-2-title": "E2E-encrypted<br>images and files",
"feature-3-title": "Decentralized secret groups &mdash;<br>only users know they exist",
@ -99,6 +101,9 @@
"hero-overlay-card-2-p-2": "They could then correlate this information with the existing public social networks, and determine some real identities.",
"hero-overlay-card-2-p-3": "Even with the most private apps that use Tor v3 services, if you talk to two different contacts via the same profile they can prove that they are connected to the same person.",
"hero-overlay-card-2-p-4": "SimpleX protects against these attacks by not having any user IDs in its design. And, if you use Incognito mode, you will have a different display name for each contact, avoiding any shared data between them.",
"hero-overlay-card-3-p-1": "<a href=\"https://www.trailofbits.com/about/\">Trail of Bits</a> is a leading security and technology consultancy whose clients include big tech, governmental agencies and major blockchain projects.",
"hero-overlay-card-3-p-2": "Trail of Bits reviewed SimpleX platform cryptography and networking components in November 2022.",
"hero-overlay-card-3-p-3": "Read more in <a href=\"/blog/20221108-simplex-chat-v4.2-security-audit-new-website.html\">the announcement</a>.",
"simplex-network-overlay-card-1-p-1": "<a href='https://en.wikipedia.org/wiki/Peer-to-peer'>P2P</a> messaging protocols and apps have various problems that make them less reliable than SimpleX, more complex to analyse, and vulnerable to several types of attack.",
"simplex-network-overlay-card-1-li-1": "P2P networks rely on some variant of <a href='https://en.wikipedia.org/wiki/Distributed_hash_table'>DHT</a> to route messages. DHT designs have to balance delivery guarantee and latency. SimpleX has both better delivery guarantee and lower latency than P2P, because the message can be redundantly passed via several servers in parallel, using the servers chosen by the recipient. In P2P networks the message is passed through <em>O(log N)</em> nodes sequentially, using nodes chosen by the algorithm.",
"simplex-network-overlay-card-1-li-2": "SimpleX design, unlike most P2P networks, has no global user identifiers of any kind, even temporary, and only uses temporary pairwise identifiers, providing better anonymity and metadata protection.",
@ -143,7 +148,7 @@
"donate-here-to-help-us": "Donate here to help us",
"sign-up-to-receive-our-updates": "Sign up to receive our updates",
"enter-your-email-address": "Enter your email address",
"get-simplex": "Get SimpleX",
"get-simplex": "Get SimpleX <a href=\"/downloads\">desktop app</a>",
"why-simplex-is": "Why SimpleX is",
"unique": "unique",
"learn-more": "Learn more",
@ -229,6 +234,7 @@
"docs-dropdown-6": "WebRTC servers",
"docs-dropdown-7": "Translate SimpleX Chat",
"docs-dropdown-8": "SimpleX Directory Service",
"docs-dropdown-9": "Downloads",
"newer-version-of-eng-msg": "There is a newer version of this page in English.",
"click-to-see": "Click to see",
"menu": "Menu",
@ -243,5 +249,6 @@
"f-droid-org-repo": "F-Droid.org repo",
"stable-versions-built-by-f-droid-org": "Stable versions built by F-Droid.org",
"releases-to-this-repo-are-done-1-2-days-later": "The releases to this repo are done 1-2 days later",
"f-droid-page-f-droid-org-repo-section-text": "SimpleX Chat and F-Droid.org repositories sign builds with the different keys. To switch, please <a href='/docs/guide/chat-profiles.html#move-your-chat-profiles-to-another-device'>export</a> the chat database and re-install the app."
"f-droid-page-f-droid-org-repo-section-text": "SimpleX Chat and F-Droid.org repositories sign builds with the different keys. To switch, please <a href='/docs/guide/chat-profiles.html#move-your-chat-profiles-to-another-device'>export</a> the chat database and re-install the app.",
"jobs": "Join team"
}

View File

@ -137,7 +137,7 @@
"sign-up-to-receive-our-updates": "Suscríbase para recibir nuestras actualizaciones",
"donate-here-to-help-us": "Para ayudarnos haga una donación aquí",
"enter-your-email-address": "Escriba su dirección de correo electrónico",
"get-simplex": "Obtenga SimpleX",
"get-simplex": "Obtenga SimpleX <a href=\"/downloads\">desktop app</a>",
"why-simplex-is": "Por qué SimpleX es",
"unique": "único",
"learn-more": "Descubra más",

View File

@ -186,7 +186,7 @@
"donate-here-to-help-us": "Tue meitä täällä lahjoituksilla",
"sign-up-to-receive-our-updates": "Tilaa päivityksemme",
"enter-your-email-address": "Syötä sähköpostiosoitteesi",
"get-simplex": "Hanki SimpleX",
"get-simplex": "Hanki SimpleX <a href=\"/downloads\">desktop app</a>",
"why-simplex-is": "Miksi SimpleX on",
"unique": "ainutlaatuinen",
"learn-more": "Lue lisää",

View File

@ -143,7 +143,7 @@
"donate-here-to-help-us": "Faites un don ici pour nous aider",
"sign-up-to-receive-our-updates": "Inscrivez-vous pour recevoir nos mises à jour",
"enter-your-email-address": "Entrez votre adresse e-mail",
"get-simplex": "Obtenir SimpleX",
"get-simplex": "Obtenir SimpleX <a href=\"/downloads\">desktop app</a>",
"why-simplex-is": "Pourquoi SimpleX est",
"unique": "unique",
"learn-more": "En savoir plus",

View File

@ -81,7 +81,7 @@
"join": "Unisciti a",
"we-invite-you-to-join-the-conversation": "Ti invitiamo a unirti alla conversazione",
"enter-your-email-address": "Inserisci il tuo indirizzo email",
"get-simplex": "Ottieni SimpleX",
"get-simplex": "Ottieni SimpleX <a href=\"/downloads\">desktop app</a>",
"why-simplex-is": "Perché SimpleX è",
"unique": "unico",
"learn-more": "Maggiori informazioni",

View File

@ -172,7 +172,7 @@
"simplex-unique-overlay-card-1-p-2": "メッセージを配信するために、SimpleX は一方向メッセージ キューの<a href='https://csrc.nist.gov/glossary/term/Pairwise_Pseudonymous_Identifier'>ペアワイズ匿名アドレス</a>を使用し、受信メッセージと送信メッセージに分けて、通常は異なるサーバーを経由します。 SimpleX を使用することは、<strong>別の「バーナー」 を使用するようなものです。 連絡先ごとにメールまたは電話</strong>を使用できるため、管理に手間がかかりません。",
"simplex-unique-overlay-card-3-p-4": "送受信されるサーバー トラフィックの間に共通の識別子や暗号文はありません。 &mdash; 誰かがそれを観察している場合、たとえ TLS が侵害されたとしても、誰が誰と通信しているのかを簡単に判断することはできません。",
"docs-dropdown-2": "Android ファイルへのアクセス",
"get-simplex": "SimpleXを入手する",
"get-simplex": "SimpleXを入手する <a href=\"/downloads\">desktop app</a>",
"privacy-matters-overlay-card-3-p-1": "誰もが通信のプライバシーとセキュリティに気を配る必要があります。 たとえ何も隠すものがなかったとしても、無害な会話はあなたを危険にさらす可能性があります。",
"simplex-unique-2-title": "スパムや悪用から<br>保護されています",
"comparison-section-list-point-2": "DNSベースのアドレス",

View File

@ -194,7 +194,7 @@
"simplex-unique-overlay-card-4-p-3": "Als u overweegt om voor het SimpleX platform te ontwikkelen, bijvoorbeeld de chatbot voor gebruikers van de SimpleX app, of de integratie van de SimpleX Chat bibliotheek in uw mobiele apps, <a href='https://simplex.chat/contact# /?v=1&smp=smp%3A%2F%2FPQUV2eL0t7OStZOoAsPEV2QYWt4-xilbakvGUGOItUo%3D%40smp6.simplex.im%2FK1rslx-m5bpXVIdMZg9NLUZ_8JBm8xTt%23MCowBQYDK2VuAyEALDeVe-sG8mRY22LsXlPgiwTNs9dbiLrNuA7f3ZMAJ2w%3D' target='_blank'>get in touch</a> for any advice en ondersteuning.",
"simplex-unique-card-1-p-1": "SimpleX beschermt de privacy van uw profiel, contacten en metadata en verbergt deze voor SimpleX platformservers en eventuele waarnemers.",
"enter-your-email-address": "Voer uw e-mail adres in",
"get-simplex": "Verkrijg SimpleX",
"get-simplex": "Verkrijg SimpleX <a href=\"/downloads\">desktop app</a>",
"tap-to-close": "Tik om te sluiten",
"contact-hero-header": "Je hebt een adres ontvangen om verbinding te maken met SimpleX Chat",
"invitation-hero-header": "Je hebt een eenmalige link ontvangen om verbinding te maken met SimpleX Chat",

View File

@ -159,7 +159,7 @@
"simplex-unique-card-4-p-2": "Możesz <strong>używać SimpleX z własnymi serwerami</strong> lub z serwerami dostarczonymi przez nas &mdash; i nadal łączyć się z dowolnym użytkownikiem.",
"we-invite-you-to-join-the-conversation": "Zapraszamy do udziału w rozmowie",
"enter-your-email-address": "Wpisz swój adres e-mail",
"get-simplex": "Pobierz SimpleX",
"get-simplex": "Pobierz SimpleX <a href=\"/downloads\">desktop app</a>",
"why-simplex-is": "Dlaczego SimpleX jest",
"join": "Dołącz do",
"join-us-on-GitHub": "Dołącz do nas na GitHubie",

View File

@ -146,7 +146,7 @@
"donate-here-to-help-us": "Doe aqui para nos ajudar",
"sign-up-to-receive-our-updates": "Inscreva-se para receber nossas atualizações",
"enter-your-email-address": "Digite seu endereço de e-mail",
"get-simplex": "Obtenha o SimpleX",
"get-simplex": "Obtenha o SimpleX <a href=\"/downloads\">desktop app</a>",
"why-simplex-is": "Por que o SimpleX é",
"unique": "único",
"learn-more": "Saiba mais",

View File

@ -173,7 +173,7 @@
"donate-here-to-help-us": "Пожертвуйте тут, щоб допомогти нам",
"sign-up-to-receive-our-updates": "Підпишіться на наші оновлення",
"enter-your-email-address": "Введіть адресу вашої електронної пошти",
"get-simplex": "Отримати SimpleX",
"get-simplex": "Отримати SimpleX <a href=\"/downloads\">desktop app</a>",
"why-simplex-is": "Чому SimpleX це",
"unique": "унікальний",
"learn-more": "Дізнайтеся більше",

View File

@ -26,7 +26,7 @@
"simplex-unique-overlay-card-3-p-4": "发送和接收的服务器流量之间没有共同的标识符或密文—— 如果有人在观察它,他们也无法轻易确定谁与谁通信,即使 TLS 受到威胁。",
"simplex-unique-card-4-p-1": "SimpleX 网络是完全去中心化的,并且独立于任何加密货币或除互联网以外的任何其他平台。",
"join": "加入",
"get-simplex": "获取 SimpleX",
"get-simplex": "获取 SimpleX <a href=\"/downloads\">desktop app</a>",
"hide-info": "隐藏信息",
"contact-hero-header": "您收到了一个用于连接 SimpleX Chat 的地址",
"contact-hero-p-2": "还没有下载 SimpleX Chat 吗?",

View File

@ -31,6 +31,10 @@
{
"title": "docs-dropdown-7",
"url": "/docs/translations.html"
},
{
"title": "docs-dropdown-9",
"url": "/downloads/"
}
]
}

View File

@ -26,7 +26,8 @@
"SERVER.md",
"TRANSLATIONS.md",
"WEBRTC.md",
"XFTP-SERVER.md"
"XFTP-SERVER.md",
"DOWNLOADS.md"
]
},
{

View File

@ -23,6 +23,18 @@
"showImage": true,
"contentBody": "overlay_content/hero/card_2.html"
}
},
{
"id": 3,
"imgLight": "/img/trail-of-bits-light.png",
"imgDark": "/img/trail-of-bits-dark.png",
"overlayContent": {
"overlayId": "security-assessment",
"overlayScrollTo": "",
"title": "hero-overlay-3-title",
"showImage": true,
"contentBody": "overlay_content/hero/card_3.html"
}
}
]
}

View File

@ -45,6 +45,12 @@
"flag": "/img/flags/it.svg",
"enabled": true
},
{
"label": "ja",
"name": "日本語",
"flag": "/img/flags/jp.svg",
"enabled": true
},
{
"label": "nl",
"name": "Nederlands",

View File

@ -17,6 +17,8 @@
{{ overlay(hero_overlays.sections[1], lang) }}
<a href="javascript:void(0)" data-show-overlay="{{ hero_overlays.sections[0].overlayContent.overlayId }}" class="open-overlay-btn underline text-primary-light dark:text-primary-dark block text-center xl:text-left xl:rtl:text-right text-[14px] xl:text-[16px] leading-[34px] underline-offset-2">{{ "hero-overlay-2-textlink" | i18n({}, lang ) | safe }}</a>
{{ overlay(hero_overlays.sections[0], lang) }}
<a href="javascript:void(0)" data-show-overlay="{{ hero_overlays.sections[2].overlayContent.overlayId }}" class="open-overlay-btn underline text-primary-light dark:text-primary-dark block text-center xl:text-left xl:rtl:text-right text-[14px] xl:text-[16px] leading-[34px] underline-offset-2">{{ "hero-overlay-3-textlink" | i18n({}, lang ) | safe }}</a>
{{ overlay(hero_overlays.sections[2], lang) }}
</article>
<article class="w-full xl:max-w-[600px]">

View File

@ -0,0 +1,45 @@
<!DOCTYPE html>
<html lang="{{ page.url | getlang }}"
{% for language in languages.languages %}
{% if language.label == page.url | getlang %}
dir="{{ "rtl" if language.rtl else "ltr" }}"
{% endif %}
{% endfor %}>
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{{ title }}</title>
<meta name="Content-Type" content="text/html;charset=utf-8" />
<link rel="icon" type="image/png" sizes="96x96" href="/img/favicon.ico" />
<link href="/css/tailwind.css" rel="stylesheet" />
<link id="prism-theme" rel="stylesheet" href="/css/prism-light.min.css" />
<link href="/css/style.css" rel="stylesheet" />
<link rel="stylesheet" href="/css/doc.css" />
<script async defer src="https://buttons.github.io/buttons.js"></script>
</head>
<body class="bg-[#F3F6F7] dark:bg-[#0C0B13]">
<section class="w-full bg-transparent fixed top-0 z-50">
{% include "navbar.html" %}
</section>
<section id="doc" class="bg-white dark:bg-[#17203D] mt-[66px]">
<main class="container px-4 lg:px-7 py-10 md:py-16">
<div>{{ content | wrapH3s(page) | safe }}</div>
</main>
</section>
{% include "footer.html" %}
</body>
<script>
window.addEventListener('click', (e) => {
if (e.target.closest('.job-tab-btn')) {
e.target.closest('.job-tab').classList.toggle('active')
}
})
</script>
</html>

View File

@ -100,6 +100,14 @@
<hr class="dark:opacity-[0.1]" >
<li class="nav-link relative {% if active_jobs %}active{% endif %}">
<a href="/jobs" class="flex items-center justify-between gap-2 lg:py-5 whitespace-nowrap">
<span class="text-[16px] leading-[26px] tracking-[0.01em] nav-link-text text-black dark:text-white before:bg-black dark:before:bg-white">{{ "jobs" | i18n({}, lang ) | safe }}</span>
</a>
</li>
<hr class="dark:opacity-[0.1]" >
<li class="nav-link relative {% if active_blog %}active{% endif %}">
<a href="/blog" class="flex items-center justify-between gap-2 lg:py-5">
<span class="text-[16px] leading-[26px] tracking-[0.01em] nav-link-text text-black dark:text-white before:bg-black dark:before:bg-white">{{ "blog" | i18n({}, lang ) | safe }}</span>
@ -125,7 +133,7 @@
</div>
</nav>
{% if 'blog' not in page.url %}
{% if ('blog' not in page.url) and ('jobs' not in page.url) %}
<div class="nav-link relative flag-container">
<a href="javascript:void(0);" class="flex items-center justify-end ltr:ml-8 ltr:lg:ml-5 ltr:xl:ml-10 rtl:mr-8 rtl:lg:mr-5 rtl:xl:mr-10 h-6 w-8 whitespace-nowrap">
{% for language in languages.languages %}
@ -142,7 +150,7 @@
</a>
<ul class="flex flex-col items-start gap-2 h-fit absolute top-11 -left-10 bg-white dark:bg-black mt-[10px] py-4 min-w-[170px] rounded-md shadow-[0_0_3px_rgb(60_72_88_/_15%)] sub-menu overflow-auto">
{% if "docs" in page.url %}
{% if ("docs" in page.url) or ('downloads' in page.url) %}
{% for supportedLang in supportedLangsForDoc %}
{% for language in languages.languages %}
{% if language.label == supportedLang %}

View File

@ -0,0 +1,9 @@
<p>
{{ "hero-overlay-card-3-p-1" | i18n({}, lang ) | safe }}
</p>
<p>
{{ "hero-overlay-card-3-p-2" | i18n({}, lang ) | safe }}
</p>
<p>
{{ "hero-overlay-card-3-p-3" | i18n({}, lang ) | safe }}
</p>

View File

@ -675,7 +675,8 @@ p a{
.content_copy_with_tooltip .content {
font-size: 15px;
}
.contact-tab > .contact-tab-content{
.contact-tab > .contact-tab-content,
.job-tab > .job-tab-content{
opacity: 0;
max-height: 0;
transition: all 0.5s ease;
@ -683,21 +684,26 @@ p a{
transform: translateY(10px);
overflow: hidden;
}
.contact-tab svg{
.contact-tab svg,
.job-tab svg{
transform: rotate(-180deg);
transition: all .5s ease;
}
.contact-tab.active > .contact-tab-content{
.contact-tab.active > .contact-tab-content,
.job-tab.active > .job-tab-content{
opacity: 1;
max-height: 300px;
visibility: visible;
transform: translateY(0px);
}
.for-tablet .contact-tab.active > .contact-tab-content{
.for-tablet .contact-tab.active > .contact-tab-content,
.for-tablet .job-tab.active > .job-tab-content{
min-height: 450px;
}
.contact-tab.active svg,
.contact-tab:hover svg{
.contact-tab:hover svg,
.job-tab.active svg,
.job-tab:hover svg{
transform: rotate(0deg);
}

View File

@ -0,0 +1,11 @@
<svg xmlns="http://www.w3.org/2000/svg" id="flag-icons-jp" viewBox="0 0 640 480">
<defs>
<clipPath id="jp-a">
<path fill-opacity=".7" d="M-88 32h640v480H-88z"/>
</clipPath>
</defs>
<g fill-rule="evenodd" stroke-width="1pt" clip-path="url(#jp-a)" transform="translate(88 -32)">
<path fill="#fff" d="M-128 32h720v480h-720z"/>
<circle cx="523.1" cy="344.1" r="194.9" fill="#bc002d" transform="translate(-168.4 8.6) scale(.76554)"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 471 B