mirror of
https://github.com/simplex-chat/simplex-chat.git
synced 2026-05-14 19:05:27 +00:00
Merge branch 'master' into ae/oklch-color-space-plan
This commit is contained in:
+136
-13
@@ -10,17 +10,25 @@ on:
|
||||
- "!*-fdroid"
|
||||
- "!*-armv7a"
|
||||
pull_request:
|
||||
paths-ignore:
|
||||
- "apps/ios"
|
||||
- "apps/multiplatform"
|
||||
- "blog"
|
||||
- "docs"
|
||||
- "fastlane"
|
||||
- "images"
|
||||
- "packages"
|
||||
- "website"
|
||||
- "README.md"
|
||||
- "PRIVACY.md"
|
||||
paths:
|
||||
- "src/**"
|
||||
- "apps/simplex-chat/**"
|
||||
- "apps/simplex-bot/**"
|
||||
- "apps/simplex-bot-advanced/**"
|
||||
- "apps/simplex-broadcast-bot/**"
|
||||
- "apps/simplex-directory-service/**"
|
||||
- "tests/**"
|
||||
- "bots/src/**"
|
||||
- "simplex-chat.cabal"
|
||||
- "cabal.project"
|
||||
- "Dockerfile*"
|
||||
- "scripts/ci/**"
|
||||
- "scripts/desktop/**"
|
||||
- ".github/**"
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: ${{ !startsWith(github.ref, 'refs/tags/v') }}
|
||||
|
||||
# This workflow uses custom actions (prepare-build and prepare-release) defined in:
|
||||
#
|
||||
@@ -294,6 +302,7 @@ jobs:
|
||||
if: startsWith(github.ref, 'refs/tags/v') && matrix.should_run == true
|
||||
shell: docker exec -t builder sh -eu {0}
|
||||
run: |
|
||||
export ASSETS_DIR='../../assets'
|
||||
scripts/desktop/make-deb-linux.sh
|
||||
|
||||
- name: Prepare Desktop
|
||||
@@ -319,6 +328,7 @@ jobs:
|
||||
if: startsWith(github.ref, 'refs/tags/v') && matrix.os == '22.04' && matrix.should_run == true
|
||||
shell: docker exec -t builder sh -eu {0}
|
||||
run: |
|
||||
export ASSETS_DIR='../../assets'
|
||||
scripts/desktop/make-appimage-linux.sh
|
||||
|
||||
- name: Prepare AppImage
|
||||
@@ -369,6 +379,100 @@ jobs:
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# =================================
|
||||
# Linux PostgreSQL Library Build
|
||||
# =================================
|
||||
|
||||
build-linux-postgres:
|
||||
name: "ubuntu-22.04-x86_64 (Postgres lib), GHC: ${{ needs.variables.outputs.GHC_VER }}"
|
||||
needs: [maybe-release, variables]
|
||||
runs-on: ubuntu-22.04
|
||||
if: startsWith(github.ref, 'refs/tags/v')
|
||||
steps:
|
||||
- name: Checkout Code
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Get UID and GID
|
||||
id: ids
|
||||
run: |
|
||||
echo "uid=$(id -u)" >> $GITHUB_OUTPUT
|
||||
echo "gid=$(id -g)" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Free disk space
|
||||
shell: bash
|
||||
run: ./scripts/ci/linux_util_free_space.sh
|
||||
|
||||
- name: Restore cached build
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: |
|
||||
~/.cabal/store
|
||||
dist-newstyle
|
||||
key: ubuntu-22.04-x86_64-postgres-ghc${{ needs.variables.outputs.GHC_VER }}-${{ hashFiles('cabal.project', 'simplex-chat.cabal') }}
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: simplex-chat/docker-setup-buildx-action@v3
|
||||
|
||||
- name: Build and cache Docker image
|
||||
uses: simplex-chat/docker-build-push-action@v6
|
||||
with:
|
||||
context: .
|
||||
load: true
|
||||
file: Dockerfile.build
|
||||
tags: build/22.04:latest
|
||||
build-args: |
|
||||
TAG=22.04
|
||||
HASH=sha256:5c8b2c0a6c745bc177669abfaa716b4bc57d58e2ea3882fb5da67f4d59e3dda5
|
||||
GHC=${{ needs.variables.outputs.GHC_VER }}
|
||||
USER_UID=${{ steps.ids.outputs.uid }}
|
||||
USER_GID=${{ steps.ids.outputs.gid }}
|
||||
|
||||
- name: Start container
|
||||
shell: bash
|
||||
run: |
|
||||
docker run -t -d \
|
||||
--name builder \
|
||||
-v ~/.cabal:/root/.cabal \
|
||||
-v /home/runner/work/_temp:/home/runner/work/_temp \
|
||||
-v ${{ github.workspace }}:/project \
|
||||
build/22.04:latest
|
||||
|
||||
- name: Prepare cabal.project.local
|
||||
shell: bash
|
||||
run: |
|
||||
echo "ignore-project: False" >> cabal.project.local
|
||||
echo "package direct-sqlcipher" >> cabal.project.local
|
||||
echo " flags: +openssl" >> cabal.project.local
|
||||
|
||||
- name: Build postgres library
|
||||
shell: docker exec -t builder sh -eu {0}
|
||||
run: |
|
||||
cabal clean
|
||||
cabal update
|
||||
scripts/desktop/build-lib-linux.sh postgres
|
||||
|
||||
- name: Copy libs from container
|
||||
shell: bash
|
||||
run: |
|
||||
ARCH=x86_64
|
||||
GHC_VER=${{ needs.variables.outputs.GHC_VER }}
|
||||
BUILD_DIR=$(echo dist-newstyle/build/${ARCH}-linux/ghc-${GHC_VER}/simplex-chat-*)
|
||||
mkdir -p postgres-libs
|
||||
cp ${BUILD_DIR}/build/libsimplex.so postgres-libs/
|
||||
cp ${BUILD_DIR}/build/deps/* postgres-libs/
|
||||
|
||||
- name: Upload postgres libs artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: simplex-libs-linux-postgres-x86_64
|
||||
path: postgres-libs/
|
||||
|
||||
- name: Fix permissions for cache
|
||||
shell: bash
|
||||
run: |
|
||||
sudo chmod -R 777 dist-newstyle ~/.cabal
|
||||
sudo chown -R $(id -u):$(id -g) dist-newstyle ~/.cabal
|
||||
|
||||
# =========================
|
||||
# MacOS Build
|
||||
# =========================
|
||||
@@ -447,6 +551,7 @@ jobs:
|
||||
APPLE_SIMPLEX_NOTARIZATION_APPLE_ID: ${{ secrets.APPLE_SIMPLEX_NOTARIZATION_APPLE_ID }}
|
||||
APPLE_SIMPLEX_NOTARIZATION_PASSWORD: ${{ secrets.APPLE_SIMPLEX_NOTARIZATION_PASSWORD }}
|
||||
run: |
|
||||
export ASSETS_DIR='../../assets'
|
||||
scripts/ci/build-desktop-mac.sh
|
||||
path=$(echo $PWD/apps/multiplatform/release/main/dmg/SimpleX-*.dmg)
|
||||
echo "package_path=$path" >> $GITHUB_OUTPUT
|
||||
@@ -573,7 +678,7 @@ jobs:
|
||||
export PATH=$PATH:/c/ghcup/bin:$(echo /c/tools/ghc-*/bin || echo)
|
||||
scripts/desktop/build-lib-windows.sh
|
||||
cd apps/multiplatform
|
||||
./gradlew packageMsi
|
||||
./gradlew -Psimplex.assets.dir=../../assets packageMsi
|
||||
rm -rf dist-newstyle/src/direct-sq*
|
||||
path=$(echo $PWD/release/main/msi/*imple*.msi | sed 's#/\([a-z]\)#\1:#' | sed 's#/#\\#g')
|
||||
echo "package_path=$path" >> $GITHUB_OUTPUT
|
||||
@@ -605,7 +710,7 @@ jobs:
|
||||
|
||||
release-nodejs-libs:
|
||||
runs-on: ubuntu-latest
|
||||
needs: [build-linux, build-macos]
|
||||
needs: [build-linux, build-linux-postgres, build-macos]
|
||||
if: startsWith(github.ref, 'refs/tags/v') && (!cancelled())
|
||||
steps:
|
||||
- name: Checkout current repository
|
||||
@@ -614,6 +719,13 @@ jobs:
|
||||
- name: Install packages for archiving
|
||||
run: sudo apt install -y msitools gcc-mingw-w64
|
||||
|
||||
- name: Download postgres libs artifact
|
||||
if: needs.build-linux-postgres.result == 'success'
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: simplex-libs-linux-postgres-x86_64
|
||||
path: ${{ runner.temp }}/postgres-libs
|
||||
|
||||
- name: Build archives
|
||||
run: |
|
||||
INIT_DIR='${{ runner.temp }}/artifacts'
|
||||
@@ -670,6 +782,17 @@ jobs:
|
||||
zip -r "${PREFIX}-windows-x86_64.zip" libs
|
||||
mv "${PREFIX}-windows-x86_64.zip" "$RELEASE_DIR" && cd "$INIT_DIR"
|
||||
|
||||
# Linux PostgreSQL (only if postgres build succeeded)
|
||||
# -------------------------------------------------
|
||||
POSTGRES_LIBS='${{ runner.temp }}/postgres-libs'
|
||||
if [ -d "$POSTGRES_LIBS" ]; then
|
||||
mkdir -p linux-postgres/libs
|
||||
cp "${POSTGRES_LIBS}"/*.so linux-postgres/libs/
|
||||
cd linux-postgres
|
||||
zip -r "${PREFIX}-linux-x86_64-postgres.zip" libs
|
||||
mv "${PREFIX}-linux-x86_64-postgres.zip" "$RELEASE_DIR" && cd "$INIT_DIR"
|
||||
fi
|
||||
|
||||
- name: Create release in libs repo and upload artifacts
|
||||
uses: softprops/action-gh-release@v2
|
||||
with:
|
||||
|
||||
@@ -425,9 +425,9 @@ Please do NOT report security vulnerabilities via GitHub issues.
|
||||
|
||||
## License
|
||||
|
||||
This software is licensed under the GNU Affero General Public License version 3 (AGPLv3). See the [LICENSE](./LICENSE) file for details. The SimpleX and SimpleX Chat name, logo, and associated branding materials are not covered by this license and are subject to the terms outlined in the [TRADEMARK](./docs/TRADEMARK.md) file.
|
||||
This software is licensed under the GNU Affero General Public License version 3 (AGPLv3). See the [LICENSE](./LICENSE) file for details. The SimpleX and SimpleX Chat name, logo, associated branding materials, and application and website graphic assets (illustrations, images, visual designs, etc.) are not covered by this license and are subject to the terms outlined in the [TRADEMARK](./docs/TRADEMARK.md) and [ASSETS_LICENSE](./assets/ASSETS_LICENSE.md) files respectively.
|
||||
|
||||
Graphic designs, artworks and layouts are not licensed for re-use. If you want to use them in your publications, please ask for permission. Texts can be used as direct quotes, referencing the source.
|
||||
If you want to use any graphic assets in your publications, please ask for permission. Texts can be used as direct quotes, referencing the source.
|
||||
|
||||
[<img src="https://raw.githubusercontent.com/simplex-chat/.github/refs/heads/master/profile/images/apple_store.svg" alt="iOS app" height="42">](https://apps.apple.com/us/app/simplex-chat/id1605771084)
|
||||
|
||||
|
||||
@@ -2183,7 +2183,7 @@ func startChat(refreshInvitations: Bool = true, onboarding: Bool = false) throws
|
||||
withAnimation {
|
||||
let savedOnboardingStage = onboardingStageDefault.get()
|
||||
m.onboardingStage = [.step1_SimpleXInfo, .step2_CreateProfile].contains(savedOnboardingStage) && m.users.count == 1
|
||||
? .step3_ChooseServerOperators
|
||||
? .step4_NetworkCommitments
|
||||
: savedOnboardingStage
|
||||
if m.onboardingStage == .onboardingComplete && !privacyDeliveryReceiptsSet.get() {
|
||||
m.setDeliveryReceipts = true
|
||||
|
||||
@@ -172,8 +172,8 @@ struct ChatItemContentView<Content: View>: View {
|
||||
case .rcvBlocked: deletedItemView()
|
||||
case let .sndDirectE2EEInfo(e2eeInfo): CIEventView(eventText: directE2EEInfoText(e2eeInfo))
|
||||
case let .rcvDirectE2EEInfo(e2eeInfo): CIEventView(eventText: directE2EEInfoText(e2eeInfo))
|
||||
case .sndGroupE2EEInfo: CIEventView(eventText: e2eeInfoNoPQText())
|
||||
case .rcvGroupE2EEInfo: CIEventView(eventText: e2eeInfoNoPQText())
|
||||
case let .sndGroupE2EEInfo(e2eeInfo): CIEventView(eventText: groupE2EEInfoText(e2eeInfo))
|
||||
case let .rcvGroupE2EEInfo(e2eeInfo): CIEventView(eventText: groupE2EEInfoText(e2eeInfo))
|
||||
case .chatBanner: EmptyView()
|
||||
case let .invalidJSON(json): CIInvalidJSONView(json: json)
|
||||
}
|
||||
@@ -257,6 +257,12 @@ struct ChatItemContentView<Content: View>: View {
|
||||
e2eeInfoText("Messages, files and calls are protected by **end-to-end encryption** with perfect forward secrecy, repudiation and break-in recovery.")
|
||||
}
|
||||
|
||||
private func groupE2EEInfoText(_ info: E2EEInfo) -> Text {
|
||||
info.public == true
|
||||
? e2eeInfoText("Messages in this channel are **not end-to-end encrypted**. Chat relays can see these messages.")
|
||||
: e2eeInfoNoPQText()
|
||||
}
|
||||
|
||||
private func e2eeInfoText(_ s: LocalizedStringKey) -> Text {
|
||||
Text(s)
|
||||
.font(.caption)
|
||||
|
||||
@@ -515,7 +515,7 @@ struct ComposeView: View {
|
||||
sendMessageView(
|
||||
disableSendButton,
|
||||
placeholder: chat.chatInfo.groupInfo.map { gi in
|
||||
gi.useRelays && gi.membership.memberRole >= .owner
|
||||
gi.useRelays && gi.membership.memberRole >= .owner && chat.chatInfo.groupChatScope() == nil
|
||||
? NSLocalizedString("Broadcast", comment: "compose placeholder for channel owner")
|
||||
: nil
|
||||
} ?? nil
|
||||
@@ -1659,7 +1659,7 @@ struct ComposeView: View {
|
||||
type: chat.chatInfo.chatType,
|
||||
id: chat.chatInfo.apiId,
|
||||
scope: chat.chatInfo.groupChatScope(),
|
||||
sendAsGroup: chat.chatInfo.groupInfo.map { $0.useRelays && $0.membership.memberRole >= .owner } ?? false,
|
||||
sendAsGroup: chat.chatInfo.sendAsGroup,
|
||||
live: live,
|
||||
ttl: ttl,
|
||||
composedMessages: msgs
|
||||
|
||||
@@ -103,6 +103,10 @@ struct GroupChatInfoView: View {
|
||||
}
|
||||
}
|
||||
|
||||
let showUserSupportChat = groupInfo.membership.memberActive
|
||||
&& ((groupInfo.fullGroupPreferences.support.on && groupInfo.membership.memberRole < .moderator)
|
||||
|| groupInfo.membership.supportChat != nil)
|
||||
|
||||
if groupInfo.useRelays {
|
||||
Section {
|
||||
// TODO [relays] allow other owners to manage channel link (requires protocol changes to share link ownership)
|
||||
@@ -124,6 +128,12 @@ struct GroupChatInfoView: View {
|
||||
if groupInfo.isOwner || members.contains(where: { $0.wrapped.memberRole >= .owner }) {
|
||||
channelMembersButton()
|
||||
}
|
||||
if groupInfo.membership.memberRole >= .moderator {
|
||||
memberSupportButton()
|
||||
}
|
||||
if showUserSupportChat {
|
||||
UserSupportChatNavLink(chat: chat, groupInfo: groupInfo, scrollToItemId: $scrollToItemId)
|
||||
}
|
||||
} footer: {
|
||||
if !groupInfo.isOwner && groupInfo.groupProfile.publicGroup?.groupLink != nil {
|
||||
Text("You can share a link or a QR code - anybody will be able to join the channel.")
|
||||
@@ -141,8 +151,7 @@ struct GroupChatInfoView: View {
|
||||
if groupInfo.canModerate {
|
||||
GroupReportsChatNavLink(chat: chat, groupInfo: groupInfo, scrollToItemId: $scrollToItemId)
|
||||
}
|
||||
if groupInfo.membership.memberActive
|
||||
&& (groupInfo.membership.memberRole < .moderator || groupInfo.membership.supportChat != nil) {
|
||||
if showUserSupportChat {
|
||||
UserSupportChatNavLink(chat: chat, groupInfo: groupInfo, scrollToItemId: $scrollToItemId)
|
||||
}
|
||||
} header: {
|
||||
|
||||
@@ -121,13 +121,15 @@ struct GroupMemberInfoView: View {
|
||||
}
|
||||
|
||||
if connectionLoaded {
|
||||
let showMemberSupportChat = !openedFromSupportChat
|
||||
&& groupInfo.membership.memberRole >= .moderator
|
||||
&& member.memberRole != .relay
|
||||
&& ((groupInfo.fullGroupPreferences.support.on && member.memberRole < .moderator)
|
||||
|| member.supportChat != nil)
|
||||
|
||||
if member.memberActive {
|
||||
Section {
|
||||
if !openedFromSupportChat
|
||||
&& groupInfo.membership.memberRole >= .moderator
|
||||
&& member.memberRole != .relay
|
||||
&& (member.memberRole < .moderator || member.supportChat != nil) {
|
||||
if showMemberSupportChat {
|
||||
MemberInfoSupportChatNavLink(groupInfo: groupInfo, member: groupMember, scrollToItemId: $scrollToItemId)
|
||||
}
|
||||
if let code = connectionCode,
|
||||
@@ -142,6 +144,10 @@ struct GroupMemberInfoView: View {
|
||||
// synchronizeConnectionButtonForce()
|
||||
// }
|
||||
}
|
||||
} else if groupInfo.useRelays && member.memberCurrent && showMemberSupportChat {
|
||||
Section {
|
||||
MemberInfoSupportChatNavLink(groupInfo: groupInfo, member: groupMember, scrollToItemId: $scrollToItemId)
|
||||
}
|
||||
}
|
||||
|
||||
if let contactLink = member.contactLink {
|
||||
|
||||
@@ -46,13 +46,30 @@ struct GroupPreferencesView: View {
|
||||
featureSection(.voice, $preferences.voice.enable, $preferences.voice.role)
|
||||
featureSection(.files, $preferences.files.enable, $preferences.files.role)
|
||||
featureSection(.simplexLinks, $preferences.simplexLinks.enable, $preferences.simplexLinks.role)
|
||||
featureSection(.reports, $preferences.reports.enable)
|
||||
featureSection(.reports, $preferences.reports.enable, disabled: true) // enable reports in 7.0 once directory support added
|
||||
featureSection(.history, $preferences.history.enable)
|
||||
featureSection(.support, $preferences.support.enable, disabled: true)
|
||||
} else {
|
||||
featureSection(.timedMessages, $preferences.timedMessages.enable)
|
||||
featureSection(.fullDelete, $preferences.fullDelete.enable)
|
||||
featureSection(.reactions, $preferences.reactions.enable)
|
||||
featureSection(.history, $preferences.history.enable)
|
||||
let supportNotice = NSLocalizedString("Chats with admins in public channels have no E2E encryption - use only with trusted chat relays.", comment: "alert message")
|
||||
featureSection(.support, $preferences.support.enable, notice: supportNotice)
|
||||
.onChange(of: preferences.support.enable) { enable in
|
||||
if enable == .on {
|
||||
showAlert(
|
||||
NSLocalizedString("Enable chats with admins?", comment: "alert title"),
|
||||
message: supportNotice,
|
||||
actions: {[
|
||||
UIAlertAction(title: NSLocalizedString("Enable", comment: "alert button"), style: .destructive) { _ in },
|
||||
UIAlertAction(title: NSLocalizedString("Cancel", comment: "alert button"), style: .cancel) { _ in
|
||||
preferences.support.enable = .off
|
||||
}
|
||||
]}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if groupInfo.isOwner {
|
||||
@@ -92,7 +109,7 @@ struct GroupPreferencesView: View {
|
||||
}
|
||||
}
|
||||
|
||||
private func featureSection(_ feature: GroupFeature, _ enableFeature: Binding<GroupFeatureEnabled>, _ enableForRole: Binding<GroupMemberRole?>? = nil) -> some View {
|
||||
private func featureSection(_ feature: GroupFeature, _ enableFeature: Binding<GroupFeatureEnabled>, _ enableForRole: Binding<GroupMemberRole?>? = nil, disabled: Bool = false, notice: String? = nil) -> some View {
|
||||
Section {
|
||||
let color: Color = enableFeature.wrappedValue == .on ? .green : theme.colors.secondary
|
||||
let icon = enableFeature.wrappedValue == .on ? feature.iconFilled : feature.icon
|
||||
@@ -103,9 +120,9 @@ struct GroupPreferencesView: View {
|
||||
set: { on, _ in enableFeature.wrappedValue = on ? .on : .off }
|
||||
)
|
||||
settingsRow(icon, color: color) {
|
||||
Toggle(feature.text, isOn: enable)
|
||||
Toggle(feature.text(isChannel: groupInfo.isChannel), isOn: enable)
|
||||
}
|
||||
.disabled(feature == .reports) // remove in 6.4
|
||||
.disabled(disabled)
|
||||
if timedOn {
|
||||
DropdownCustomTimePicker(
|
||||
selection: $preferences.timedMessages.ttl,
|
||||
@@ -126,7 +143,7 @@ struct GroupPreferencesView: View {
|
||||
}
|
||||
} else {
|
||||
settingsRow(icon, color: color) {
|
||||
infoRow(Text(feature.text), enableFeature.wrappedValue.text)
|
||||
infoRow(Text(feature.text(isChannel: groupInfo.isChannel)), enableFeature.wrappedValue.text)
|
||||
}
|
||||
if timedOn {
|
||||
infoRow("Delete after", timeText(preferences.timedMessages.ttl))
|
||||
@@ -144,8 +161,11 @@ struct GroupPreferencesView: View {
|
||||
}
|
||||
}
|
||||
} footer: {
|
||||
Text(feature.enableDescription(enableFeature.wrappedValue, groupInfo.isOwner))
|
||||
.foregroundColor(theme.colors.secondary)
|
||||
VStack(alignment: .leading) {
|
||||
Text(feature.enableDescription(enableFeature.wrappedValue, groupInfo.isOwner, isChannel: groupInfo.isChannel))
|
||||
if let notice { Text(notice) }
|
||||
}
|
||||
.foregroundColor(theme.colors.secondary)
|
||||
}
|
||||
.onChange(of: enableFeature.wrappedValue) { enabled in
|
||||
if case .off = enabled {
|
||||
|
||||
@@ -45,7 +45,7 @@ struct MemberSupportView: View {
|
||||
: membersWithChats.filter { $0.wrapped.localAliasAndFullName.localizedLowercase.contains(s) }
|
||||
|
||||
if membersWithChats.isEmpty {
|
||||
Text("No chats with members")
|
||||
Text(groupInfo.fullGroupPreferences.support.on ? "No chats with members" : "Chats with members are disabled")
|
||||
.foregroundColor(.secondary)
|
||||
} else {
|
||||
List {
|
||||
|
||||
@@ -401,6 +401,13 @@ struct ChatListView: View {
|
||||
.padding(.top, oneHandUI ? 8 : 0)
|
||||
.id("searchBar")
|
||||
}
|
||||
if !oneHandUICardShown {
|
||||
OneHandUICard()
|
||||
.padding(.vertical, 6)
|
||||
.scaleEffect(x: 1, y: oneHandUI ? -1 : 1, anchor: .center)
|
||||
.listRowSeparator(.hidden)
|
||||
.listRowBackground(Color.clear)
|
||||
}
|
||||
if #available(iOS 16.0, *) {
|
||||
ForEach(cs, id: \.viewId) { chat in
|
||||
ChatListNavLink(chat: chat, parentSheet: $sheet)
|
||||
@@ -420,13 +427,6 @@ struct ChatListView: View {
|
||||
.disabled(chatModel.chatRunning != true || chatModel.deletedChats.contains(chat.chatInfo.id))
|
||||
}
|
||||
}
|
||||
if !oneHandUICardShown {
|
||||
OneHandUICard()
|
||||
.padding(.vertical, 6)
|
||||
.scaleEffect(x: 1, y: oneHandUI ? -1 : 1, anchor: .center)
|
||||
.listRowSeparator(.hidden)
|
||||
.listRowBackground(Color.clear)
|
||||
}
|
||||
if !addressCreationCardShown && hasConversations {
|
||||
ConnectBannerCard()
|
||||
.padding(.vertical, 6)
|
||||
@@ -839,11 +839,11 @@ struct TagsView: View {
|
||||
nil
|
||||
}
|
||||
let active = tag == selectedPresetTag
|
||||
let (icon, text) = presetTagLabel(tag: tag, active: active)
|
||||
let (icon, menuIcon, text) = presetTagLabel(tag: tag, active: active)
|
||||
let color: Color = active ? .accentColor : .secondary
|
||||
|
||||
HStack(spacing: 4) {
|
||||
Image(systemName: icon)
|
||||
Image(systemName: menuIcon ?? icon)
|
||||
.foregroundColor(color)
|
||||
ZStack {
|
||||
Text(text).fontWeight(.semibold).foregroundColor(.clear)
|
||||
@@ -886,9 +886,9 @@ struct TagsView: View {
|
||||
Button {
|
||||
setActiveFilter(filter: .presetTag(tag))
|
||||
} label: {
|
||||
let (systemName, text) = presetTagLabel(tag: tag, active: tag == selectedPresetTag)
|
||||
let (icon, _, text) = presetTagLabel(tag: tag, active: tag == selectedPresetTag)
|
||||
HStack {
|
||||
Image(systemName: systemName)
|
||||
Image(systemName: icon)
|
||||
Text(text)
|
||||
}
|
||||
}
|
||||
@@ -896,8 +896,8 @@ struct TagsView: View {
|
||||
}
|
||||
} label: {
|
||||
if let tag = selectedPresetTag, tag.сollapse {
|
||||
let (systemName, _) = presetTagLabel(tag: tag, active: true)
|
||||
Image(systemName: systemName)
|
||||
let (icon, menuIcon, _) = presetTagLabel(tag: tag, active: true)
|
||||
Image(systemName: menuIcon ?? icon)
|
||||
.foregroundColor(.accentColor)
|
||||
} else {
|
||||
Image(systemName: "list.bullet")
|
||||
@@ -907,15 +907,15 @@ struct TagsView: View {
|
||||
.frame(minWidth: 28)
|
||||
}
|
||||
|
||||
private func presetTagLabel(tag: PresetTag, active: Bool) -> (String, LocalizedStringKey) {
|
||||
private func presetTagLabel(tag: PresetTag, active: Bool) -> (item: String, menu: String?, label: LocalizedStringKey) {
|
||||
switch tag {
|
||||
case .groupReports: (active ? "flag.fill" : "flag", "Reports")
|
||||
case .favorites: (active ? "star.fill" : "star", "Favorites")
|
||||
case .contacts: (active ? "person.fill" : "person", "Contacts")
|
||||
case .groups: (active ? "person.2.fill" : "person.2", "Groups")
|
||||
case .channels: (active ? "antenna.radiowaves.left.and.right.circle.fill" : "antenna.radiowaves.left.and.right.circle", "Channels")
|
||||
case .business: (active ? "briefcase.fill" : "briefcase", "Businesses")
|
||||
case .notes: (active ? "folder.fill" : "folder", "Notes")
|
||||
case .groupReports: (item: active ? "flag.fill" : "flag", menu: nil, label: "Reports")
|
||||
case .favorites: (item: active ? "star.fill" : "star", menu: nil, label: "Favorites")
|
||||
case .contacts: (item: active ? "person.fill" : "person", menu: nil, label: "Contacts")
|
||||
case .groups: (item: active ? "person.2.fill" : "person.2", menu: nil, label: "Groups")
|
||||
case .channels: (item: active ? "antenna.radiowaves.left.and.right.circle.fill" : "antenna.radiowaves.left.and.right", menu: "antenna.radiowaves.left.and.right", label: "Channels")
|
||||
case .business: (item: active ? "briefcase.fill" : "briefcase", menu: nil, label: "Businesses")
|
||||
case .notes: (item: active ? "folder.fill" : "folder", menu: nil, label: "Notes")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -11,27 +11,46 @@ import SimpleXChat
|
||||
|
||||
struct OneHandUICard: View {
|
||||
@EnvironmentObject var theme: AppTheme
|
||||
@Environment(\.dynamicTypeSize) private var userFont: DynamicTypeSize
|
||||
@AppStorage(GROUP_DEFAULT_ONE_HAND_UI, store: groupDefaults) private var oneHandUI = true
|
||||
@AppStorage(DEFAULT_ONE_HAND_UI_CARD_SHOWN) private var oneHandUICardShown = false
|
||||
@AppStorage(DEFAULT_TOOLBAR_MATERIAL) private var toolbarMaterial = ToolbarMaterial.defaultMaterial
|
||||
@State private var showOneHandUIAlert = false
|
||||
|
||||
var body: some View {
|
||||
ZStack(alignment: .topTrailing) {
|
||||
VStack(alignment: .leading, spacing: 8) {
|
||||
Text("Toggle chat list:").font(.title3)
|
||||
Toggle("Reachable chat toolbar", isOn: $oneHandUI)
|
||||
HStack(spacing: 2) {
|
||||
segment(
|
||||
icon: "platter.filled.bottom.and.arrow.down.iphone",
|
||||
text: "Bottom bar",
|
||||
isSelected: oneHandUI
|
||||
) {
|
||||
withAnimation { oneHandUI = true }
|
||||
}
|
||||
Image(systemName: "multiply")
|
||||
.foregroundColor(theme.colors.secondary)
|
||||
.onTapGesture {
|
||||
showOneHandUIAlert = true
|
||||
.background { if oneHandUI { Color(uiColor: .systemGray5) } }
|
||||
.background(ToolbarMaterial.material(toolbarMaterial))
|
||||
ZStack(alignment: .trailing) {
|
||||
segment(
|
||||
icon: "platter.filled.top.and.arrow.up.iphone",
|
||||
text: "Top bar",
|
||||
isSelected: !oneHandUI
|
||||
) {
|
||||
withAnimation { oneHandUI = false }
|
||||
}
|
||||
Image(systemName: "multiply")
|
||||
.foregroundColor(theme.colors.secondary)
|
||||
.frame(width: 12, height: 12)
|
||||
.padding(.vertical, 4)
|
||||
.padding(.trailing, 16)
|
||||
.padding(.leading, 4)
|
||||
.contentShape(Rectangle())
|
||||
.onTapGesture {
|
||||
showOneHandUIAlert = true
|
||||
}
|
||||
}
|
||||
.frame(maxWidth: .infinity, maxHeight: .infinity)
|
||||
.background { if !oneHandUI { Color(uiColor: .systemGray5) } }
|
||||
.background(ToolbarMaterial.material(toolbarMaterial))
|
||||
}
|
||||
.padding()
|
||||
.background(theme.appColors.sentMessage)
|
||||
.cornerRadius(12)
|
||||
.frame(height: dynamicSize(userFont).rowHeight)
|
||||
.clipShape(Capsule())
|
||||
.alert(isPresented: $showOneHandUIAlert) {
|
||||
Alert(
|
||||
title: Text("Reachable chat toolbar"),
|
||||
@@ -44,6 +63,22 @@ struct OneHandUICard: View {
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private func segment(icon: String, text: LocalizedStringKey, isSelected: Bool, action: @escaping () -> Void) -> some View {
|
||||
HStack(spacing: 8) {
|
||||
Image(systemName: icon)
|
||||
.font(.body)
|
||||
.foregroundColor(isSelected ? theme.colors.secondary : theme.colors.primary)
|
||||
Text(text)
|
||||
.font(.subheadline)
|
||||
.foregroundColor(isSelected ? theme.colors.secondary : theme.colors.onBackground)
|
||||
}
|
||||
.padding(.leading, 16)
|
||||
.padding(.vertical, 4)
|
||||
.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .leading)
|
||||
.contentShape(Rectangle())
|
||||
.onTapGesture { action() }
|
||||
}
|
||||
}
|
||||
|
||||
#Preview {
|
||||
|
||||
@@ -110,8 +110,8 @@ struct MigrateToAppGroupView: View {
|
||||
do {
|
||||
resetChatCtrl()
|
||||
try initializeChat(start: true)
|
||||
onboardingStageDefault.set(.step4_SetNotificationsMode)
|
||||
chatModel.onboardingStage = .step4_SetNotificationsMode
|
||||
onboardingStageDefault.set(.step4_NetworkCommitments)
|
||||
chatModel.onboardingStage = .step4_NetworkCommitments
|
||||
setV3DBMigration(.ready)
|
||||
} catch let error {
|
||||
dbContainerGroupDefault.set(.documents)
|
||||
|
||||
@@ -21,6 +21,19 @@ struct DetermineWidth: View {
|
||||
}
|
||||
}
|
||||
|
||||
struct DetermineHeight: View {
|
||||
typealias Key = MaximumHeightPreferenceKey
|
||||
var body: some View {
|
||||
GeometryReader { proxy in
|
||||
Color.clear
|
||||
.preference(
|
||||
key: MaximumHeightPreferenceKey.self,
|
||||
value: proxy.size.height
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct DetermineWidthImageVideoItem: View {
|
||||
typealias Key = MaximumWidthImageVideoPreferenceKey
|
||||
var body: some View {
|
||||
@@ -41,6 +54,13 @@ struct MaximumWidthPreferenceKey: PreferenceKey {
|
||||
}
|
||||
}
|
||||
|
||||
struct MaximumHeightPreferenceKey: PreferenceKey {
|
||||
static var defaultValue: CGFloat = 0
|
||||
static func reduce(value: inout CGFloat, nextValue: () -> CGFloat) {
|
||||
value = max(value, nextValue())
|
||||
}
|
||||
}
|
||||
|
||||
struct MaximumWidthImageVideoPreferenceKey: PreferenceKey {
|
||||
static var defaultValue: CGFloat = 0
|
||||
static func reduce(value: inout CGFloat, nextValue: () -> CGFloat) {
|
||||
|
||||
@@ -86,6 +86,40 @@ func showSheet(
|
||||
}
|
||||
}
|
||||
|
||||
func openExternalLink(_ url: URL) {
|
||||
let s = url.absoluteString
|
||||
if s.starts(with: "https://simplex.chat/contact#") || (s.starts(with: "https://smp") && s.contains(".simplex.im/a#")) {
|
||||
ChatModel.shared.appOpenUrl = url
|
||||
} else {
|
||||
showAlert(
|
||||
title: NSLocalizedString("Open external link?", comment: "alert title"),
|
||||
message: s,
|
||||
buttonTitle: NSLocalizedString("Open", comment: "alert button"),
|
||||
buttonAction: { UIApplication.shared.open(url) },
|
||||
cancelButton: true
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
struct ExternalLink<Label: View>: View {
|
||||
let destination: URL
|
||||
let label: Label
|
||||
|
||||
init(destination: URL, @ViewBuilder label: () -> Label) {
|
||||
self.destination = destination
|
||||
self.label = label()
|
||||
}
|
||||
|
||||
init(_ titleKey: LocalizedStringKey, destination: URL) where Label == Text {
|
||||
self.destination = destination
|
||||
self.label = Text(titleKey)
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
Button { openExternalLink(destination) } label: { label }
|
||||
}
|
||||
}
|
||||
|
||||
let okAlertAction = UIAlertAction(title: NSLocalizedString("Ok", comment: "alert button"), style: .default)
|
||||
|
||||
let cancelAlertAction = UIAlertAction(title: NSLocalizedString("Cancel", comment: "alert button"), style: .cancel)
|
||||
|
||||
@@ -10,6 +10,7 @@ import SwiftUI
|
||||
import SimpleXChat
|
||||
|
||||
struct AddChannelView: View {
|
||||
@Environment(\.colorScheme) var colorScheme
|
||||
@EnvironmentObject var m: ChatModel
|
||||
@EnvironmentObject var theme: AppTheme
|
||||
@StateObject private var channelRelaysModel = ChannelRelaysModel.shared
|
||||
@@ -45,28 +46,39 @@ struct AddChannelView: View {
|
||||
private func profileStepView() -> some View {
|
||||
List {
|
||||
Group {
|
||||
ZStack(alignment: .center) {
|
||||
ZStack(alignment: .topTrailing) {
|
||||
ProfileImage(imageStr: profile.image, iconName: "antenna.radiowaves.left.and.right.circle.fill", size: 128)
|
||||
if profile.image != nil {
|
||||
Button {
|
||||
profile.image = nil
|
||||
} label: {
|
||||
Image(systemName: "multiply")
|
||||
.resizable()
|
||||
.aspectRatio(contentMode: .fit)
|
||||
.frame(width: 12)
|
||||
HStack(spacing: 0) {
|
||||
Spacer(minLength: 0)
|
||||
ZStack(alignment: .center) {
|
||||
ZStack(alignment: .topTrailing) {
|
||||
ProfileImage(imageStr: profile.image, iconName: "antenna.radiowaves.left.and.right.circle.fill", size: 128)
|
||||
if profile.image != nil {
|
||||
Button {
|
||||
profile.image = nil
|
||||
} label: {
|
||||
Image(systemName: "multiply")
|
||||
.resizable()
|
||||
.aspectRatio(contentMode: .fit)
|
||||
.frame(width: 12)
|
||||
}
|
||||
}
|
||||
}
|
||||
editImageButton { showChooseSource = true }
|
||||
.buttonStyle(BorderlessButtonStyle())
|
||||
}
|
||||
editImageButton { showChooseSource = true }
|
||||
.buttonStyle(BorderlessButtonStyle())
|
||||
.padding(.horizontal, 10) // Offsets transparent space built into 3D asset
|
||||
#if SIMPLEX_ASSETS
|
||||
Spacer(minLength: 0)
|
||||
Image(colorScheme == .light ? "create-channel" : "create-channel-light")
|
||||
.resizable()
|
||||
.scaledToFit()
|
||||
.frame(height: 140)
|
||||
#endif
|
||||
Spacer(minLength: 0)
|
||||
}
|
||||
.frame(maxWidth: .infinity, alignment: .center)
|
||||
}
|
||||
.listRowBackground(Color.clear)
|
||||
.listRowSeparator(.hidden)
|
||||
.listRowInsets(EdgeInsets(top: 8, leading: 0, bottom: 8, trailing: 0))
|
||||
.listRowInsets(EdgeInsets(top: 8, leading: 0, bottom: 0, trailing: 0))
|
||||
|
||||
Section {
|
||||
channelNameTextField()
|
||||
@@ -161,7 +173,10 @@ struct AddChannelView: View {
|
||||
private func createChannel() {
|
||||
focusDisplayName = false
|
||||
profile.displayName = profile.displayName.trimmingCharacters(in: .whitespaces)
|
||||
profile.groupPreferences = GroupPreferences(history: GroupPreference(enable: .on))
|
||||
profile.groupPreferences = GroupPreferences(
|
||||
history: GroupPreference(enable: .on),
|
||||
support: GroupPreference(enable: .off)
|
||||
)
|
||||
creationInProgress = true
|
||||
Task {
|
||||
do {
|
||||
@@ -323,24 +338,24 @@ struct AddChannelView: View {
|
||||
.compactSectionSpacing()
|
||||
|
||||
Section {
|
||||
Button("Channel link") {
|
||||
Button("Continue") {
|
||||
if activeCount >= total {
|
||||
showLinkStep = true
|
||||
} else if activeCount > 0 {
|
||||
let actions: [UIAlertAction] = if activeCount + failedCount < total {
|
||||
[
|
||||
UIAlertAction(title: NSLocalizedString("Proceed", comment: "alert action"), style: .default) { _ in showLinkStep = true },
|
||||
UIAlertAction(title: NSLocalizedString("Continue", comment: "alert action"), style: .default) { _ in showLinkStep = true },
|
||||
UIAlertAction(title: NSLocalizedString("Wait", comment: "alert action"), style: .cancel) { _ in }
|
||||
]
|
||||
} else {
|
||||
[
|
||||
UIAlertAction(title: NSLocalizedString("Proceed", comment: "alert action"), style: .default) { _ in showLinkStep = true },
|
||||
UIAlertAction(title: NSLocalizedString("Continue", comment: "alert action"), style: .default) { _ in showLinkStep = true },
|
||||
cancelAlertAction
|
||||
]
|
||||
}
|
||||
showAlert(
|
||||
NSLocalizedString("Not all relays connected", comment: "alert title"),
|
||||
message: String.localizedStringWithFormat(NSLocalizedString("Channel will start working with %d of %d relays. Proceed?", comment: "alert message"), activeCount, total),
|
||||
message: String.localizedStringWithFormat(NSLocalizedString("Channel will start working with %d of %d relays. Continue?", comment: "alert message"), activeCount, total),
|
||||
actions: { actions }
|
||||
)
|
||||
}
|
||||
@@ -352,7 +367,12 @@ struct AddChannelView: View {
|
||||
.navigationBarBackButtonHidden(true)
|
||||
.toolbar {
|
||||
ToolbarItem(placement: .navigationBarTrailing) {
|
||||
Button("Cancel") { cancelChannelCreation(gInfo) }
|
||||
Button("Delete channel") { showCancelChannelAlert(gInfo) }
|
||||
}
|
||||
}
|
||||
.onDisappear {
|
||||
if !showLinkStep && m.creatingChannelId == gInfo.id {
|
||||
showCancelChannelAlert(gInfo)
|
||||
}
|
||||
}
|
||||
.onChange(of: channelRelaysModel.groupRelays) { relays in
|
||||
@@ -414,6 +434,24 @@ struct AddChannelView: View {
|
||||
}
|
||||
}
|
||||
|
||||
private func showCancelChannelAlert(_ gInfo: GroupInfo) {
|
||||
let activeCount = groupRelays.filter { $0.relayStatus == .rsActive && relayMemberConnFailed($0) == nil }.count
|
||||
let total = groupRelays.count
|
||||
showAlert(
|
||||
NSLocalizedString("Cancel creating channel?", comment: "alert title"),
|
||||
message: String.localizedStringWithFormat(
|
||||
NSLocalizedString("Your new channel %@ is connected to %d of %d relays.\nIf you cancel, the channel will be deleted - you can create it again.", comment: "alert message"),
|
||||
gInfo.groupProfile.displayName, activeCount, total
|
||||
),
|
||||
actions: {[
|
||||
UIAlertAction(title: NSLocalizedString("Wait", comment: "alert action"), style: .cancel) { _ in },
|
||||
UIAlertAction(title: NSLocalizedString("Cancel", comment: "alert action"), style: .destructive) { _ in
|
||||
cancelChannelCreation(gInfo)
|
||||
}
|
||||
]}
|
||||
)
|
||||
}
|
||||
|
||||
// MARK: - Helpers
|
||||
|
||||
private func showInvalidChannelNameAlert() {
|
||||
|
||||
@@ -26,7 +26,7 @@ struct AddContactLearnMore: View {
|
||||
VStack(alignment: .leading, spacing: 18) {
|
||||
Text("To connect, your contact can scan QR code or use the link in the app.")
|
||||
Text("If you can't meet in person, show QR code in a video call, or share the link.")
|
||||
Text("Read more in [User Guide](https://simplex.chat/docs/guide/readme.html#connect-to-friends).")
|
||||
ExternalLink("Read more in User Guide.", destination: URL(string: "https://simplex.chat/docs/guide/readme.html#connect-to-friends")!)
|
||||
}
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
.listRowBackground(Color.clear)
|
||||
|
||||
@@ -68,6 +68,7 @@ struct AddGroupView: View {
|
||||
List {
|
||||
Group {
|
||||
HStack(spacing: 0) {
|
||||
Spacer(minLength: 0)
|
||||
ZStack(alignment: .center) {
|
||||
ZStack(alignment: .topTrailing) {
|
||||
ProfileImage(imageStr: profile.image, iconName: "person.2.circle.fill", size: 128)
|
||||
@@ -86,16 +87,16 @@ struct AddGroupView: View {
|
||||
editImageButton { showChooseSource = true }
|
||||
.buttonStyle(BorderlessButtonStyle()) // otherwise whole "list row" is clickable
|
||||
}
|
||||
.frame(maxWidth: .infinity)
|
||||
.padding(.horizontal, 10) // Offsets transparent space built into 3D asset
|
||||
#if SIMPLEX_ASSETS
|
||||
Spacer(minLength: 0)
|
||||
Image(colorScheme == .light ? "create-group" : "create-group-light")
|
||||
.resizable()
|
||||
.scaledToFit()
|
||||
.frame(height: 140)
|
||||
.frame(maxWidth: .infinity)
|
||||
#endif
|
||||
Spacer(minLength: 0)
|
||||
}
|
||||
.frame(maxWidth: .infinity)
|
||||
}
|
||||
.listRowBackground(Color.clear)
|
||||
.listRowSeparator(.hidden)
|
||||
|
||||
@@ -338,14 +338,6 @@ private struct InviteView: View {
|
||||
HStack(spacing: 8) {
|
||||
let link = connLinkInvitation.simplexChatUri(short: showShortLink)
|
||||
linkTextView(link)
|
||||
Button {
|
||||
UIPasteboard.general.string = link
|
||||
setInvitationUsed()
|
||||
} label: {
|
||||
Image(systemName: "doc.on.doc")
|
||||
.padding(.top, -7)
|
||||
.padding(.horizontal, 8)
|
||||
}
|
||||
Button {
|
||||
showShareSheet(items: [link])
|
||||
setInvitationUsed()
|
||||
|
||||
@@ -23,17 +23,19 @@ struct OnboardingCardView: View {
|
||||
let action: () -> Void
|
||||
|
||||
static let lightStops: [Gradient.Stop] = [
|
||||
.init(color: Color(red: 0.824, green: 0.910, blue: 1.0), location: 0.0),
|
||||
.init(color: Color(red: 0.800, green: 0.914, blue: 1.0), location: 0.5),
|
||||
.init(color: Color(red: 0.875, green: 1.0, blue: 1.0), location: 0.9),
|
||||
.init(color: Color(red: 1.0, green: 0.988, blue: 0.918), location: 1.0)
|
||||
.init(color: oklch(0.9219, 0.0431, 249.4), location: 0.0),
|
||||
.init(color: oklch(0.9198, 0.0471, 240.7), location: 0.5),
|
||||
.init(color: oklch(0.9772, 0.0358, 196.6), location: 0.9),
|
||||
.init(color: oklch(0.9829, 0.0104, 70.0), location: 0.95),
|
||||
.init(color: oklch(0.9886, 0.0272, 99.1), location: 1.0)
|
||||
]
|
||||
|
||||
static let darkStops: [Gradient.Stop] = [
|
||||
.init(color: Color(red: 0.016, green: 0.039, blue: 0.141), location: 0.4),
|
||||
.init(color: Color(red: 0.220, green: 0.329, blue: 0.671), location: 0.72),
|
||||
.init(color: Color(red: 0.659, green: 0.929, blue: 0.953), location: 0.9),
|
||||
.init(color: Color(red: 1.0, green: 0.965, blue: 0.878), location: 1.0)
|
||||
.init(color: oklch(0.1578, 0.0609, 267.3), location: 0.4),
|
||||
.init(color: oklch(0.4729, 0.1574, 267.3), location: 0.72),
|
||||
.init(color: oklch(0.9024, 0.0760, 202.8), location: 0.9),
|
||||
.init(color: oklch(0.9384, 0.0354, 65.0), location: 0.95),
|
||||
.init(color: oklch(0.9744, 0.0370, 88.4), location: 1.0)
|
||||
]
|
||||
|
||||
static let gradientAngle: Double = 80.0 * .pi / 180.0
|
||||
@@ -206,7 +208,7 @@ struct ConnectOnboardingView: View {
|
||||
.font(.largeTitle)
|
||||
.bold()
|
||||
.lineLimit(1)
|
||||
.minimumScaleFactor(0.75)
|
||||
.minimumScaleFactor(0.67)
|
||||
.frame(maxWidth: .infinity, alignment: .center)
|
||||
if isLandscape {
|
||||
ZStack(alignment: .leading) {
|
||||
@@ -277,7 +279,7 @@ struct ConnectOnboardingView: View {
|
||||
private var connectWithSomeonePage: some View {
|
||||
GeometryReader { geo in
|
||||
VStack(spacing: 0) {
|
||||
pageHeader("Connect with someone", showBack: true)
|
||||
pageHeader("Create your link", showBack: true)
|
||||
|
||||
Spacer(minLength: 16)
|
||||
|
||||
|
||||
@@ -44,160 +44,147 @@ struct OnboardingButtonStyle: ButtonStyle {
|
||||
}
|
||||
}
|
||||
|
||||
private enum OnboardingConditionsViewSheet: Identifiable {
|
||||
case showConditions
|
||||
case configureOperators
|
||||
|
||||
var id: String {
|
||||
switch self {
|
||||
case .showConditions: return "showConditions"
|
||||
case .configureOperators: return "configureOperators"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct OnboardingConditionsView: View {
|
||||
@EnvironmentObject var theme: AppTheme
|
||||
@State private var serverOperators: [ServerOperator] = []
|
||||
@State private var selectedOperatorIds = Set<Int64>()
|
||||
@State private var sheetItem: OnboardingConditionsViewSheet? = nil
|
||||
@State private var notificationsModeNavLinkActive = false
|
||||
@State private var justOpened = true
|
||||
@Environment(\.colorScheme) var colorScheme: ColorScheme
|
||||
@State private var showConditionsSheet = false
|
||||
var selectedOperatorIds: Set<Int64>
|
||||
|
||||
var body: some View {
|
||||
GeometryReader { g in
|
||||
let v = ScrollView {
|
||||
VStack(alignment: .leading, spacing: 20) {
|
||||
Text("Conditions of use")
|
||||
.font(.largeTitle)
|
||||
.bold()
|
||||
.frame(maxWidth: .infinity, alignment: .center)
|
||||
.padding(.top, 25)
|
||||
VStack(alignment: .leading, spacing: 10) {
|
||||
Spacer(minLength: 0)
|
||||
|
||||
Spacer()
|
||||
heroImage().frame(maxWidth: .infinity, minHeight: 80)
|
||||
|
||||
VStack(alignment: .leading, spacing: 20) {
|
||||
Text("Private chats, groups and your contacts are not accessible to server operators.")
|
||||
.lineSpacing(2)
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
Text("""
|
||||
By using SimpleX Chat you agree to:
|
||||
- send only legal content in public groups.
|
||||
- respect other users – no spam.
|
||||
""")
|
||||
.lineSpacing(2)
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
Text("Network commitments")
|
||||
.font(.largeTitle)
|
||||
.bold()
|
||||
.multilineTextAlignment(.center)
|
||||
.frame(maxWidth: .infinity, alignment: .center)
|
||||
.fixedSize(horizontal: false, vertical: true)
|
||||
|
||||
Button("Privacy policy and conditions of use.") {
|
||||
sheetItem = .showConditions
|
||||
}
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
}
|
||||
.padding(.horizontal, 4)
|
||||
Text("Operators commit to:\n- Be independent\n- Minimize metadata usage\n- Run verified open-source code")
|
||||
.font(.callout)
|
||||
.lineSpacing(2)
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
.padding(.leading, 4)
|
||||
.padding(.top, 10)
|
||||
.fixedSize(horizontal: false, vertical: true)
|
||||
|
||||
Spacer()
|
||||
Text("You commit to:\n- Only legal content in public groups\n- Respect other users - no spam")
|
||||
.font(.callout)
|
||||
.lineSpacing(2)
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
.padding(.leading, 4)
|
||||
.padding(.top, 10)
|
||||
.fixedSize(horizontal: false, vertical: true)
|
||||
|
||||
VStack(spacing: 12) {
|
||||
acceptConditionsButton()
|
||||
|
||||
Button("Configure server operators") {
|
||||
sheetItem = .configureOperators
|
||||
}
|
||||
.frame(minHeight: 40)
|
||||
}
|
||||
Button {
|
||||
showConditionsSheet = true
|
||||
} label: {
|
||||
Text("Privacy policy and conditions of use.")
|
||||
.fontWeight(.medium)
|
||||
.fixedSize(horizontal: false, vertical: true)
|
||||
}
|
||||
.padding(25)
|
||||
.frame(minHeight: g.size.height)
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
.padding(.leading, 4)
|
||||
.padding(.top, 10)
|
||||
.padding(.bottom, 15)
|
||||
|
||||
Spacer(minLength: 0)
|
||||
|
||||
acceptButton()
|
||||
.padding(.bottom, g.safeAreaInsets.bottom == 0 ? 20 : 0)
|
||||
}
|
||||
.onAppear {
|
||||
if justOpened {
|
||||
serverOperators = ChatModel.shared.conditions.serverOperators
|
||||
selectedOperatorIds = Set(serverOperators.filter { $0.enabled }.map { $0.operatorId })
|
||||
justOpened = false
|
||||
.padding(.horizontal, 25)
|
||||
.padding(.top, 25)
|
||||
.padding(.bottom, 25)
|
||||
.frame(minHeight: g.size.height)
|
||||
}
|
||||
.frame(maxHeight: .infinity)
|
||||
.navigationBarHidden(true)
|
||||
.sheet(isPresented: $showConditionsSheet) {
|
||||
NavigationView {
|
||||
VStack {
|
||||
ConditionsTextView()
|
||||
.padding()
|
||||
acceptButton()
|
||||
.padding(.horizontal, 25)
|
||||
.padding(.bottom, 20)
|
||||
}
|
||||
}
|
||||
.sheet(item: $sheetItem) { item in
|
||||
switch item {
|
||||
case .showConditions:
|
||||
SimpleConditionsView()
|
||||
.modifier(ThemedBackground(grouped: true))
|
||||
case .configureOperators:
|
||||
ChooseServerOperators(serverOperators: serverOperators, selectedOperatorIds: $selectedOperatorIds)
|
||||
.modifier(ThemedBackground())
|
||||
}
|
||||
}
|
||||
.frame(maxHeight: .infinity, alignment: .top)
|
||||
if #available(iOS 16.4, *) {
|
||||
v.scrollBounceBehavior(.basedOnSize)
|
||||
} else {
|
||||
v
|
||||
.navigationTitle("Conditions of use")
|
||||
.navigationBarTitleDisplayMode(.large)
|
||||
.toolbar { ToolbarItem(placement: .navigationBarTrailing, content: conditionsLinkButton) }
|
||||
.modifier(ThemedBackground(grouped: true))
|
||||
}
|
||||
}
|
||||
.frame(maxHeight: .infinity, alignment: .top)
|
||||
.navigationBarHidden(true) // necessary on iOS 15
|
||||
}
|
||||
|
||||
private func continueToNextStep() {
|
||||
onboardingStageDefault.set(.step4_SetNotificationsMode)
|
||||
notificationsModeNavLinkActive = true
|
||||
}
|
||||
|
||||
func notificationsModeNavLinkButton(_ button: @escaping (() -> some View)) -> some View {
|
||||
@ViewBuilder
|
||||
private func heroImage() -> some View {
|
||||
#if SIMPLEX_ASSETS
|
||||
Image(colorScheme == .light ? "network-commitments" : "network-commitments-light")
|
||||
.resizable()
|
||||
.scaledToFit()
|
||||
#else
|
||||
ZStack {
|
||||
button()
|
||||
|
||||
NavigationLink(isActive: $notificationsModeNavLinkActive) {
|
||||
notificationsModeDestinationView()
|
||||
} label: {
|
||||
EmptyView()
|
||||
}
|
||||
.frame(width: 1, height: 1)
|
||||
.hidden()
|
||||
let gp = OnboardingCardView.gradientPoints(aspectRatio: 1.5, scale: colorScheme == .light ? 1.2 : 1.5)
|
||||
LinearGradient(
|
||||
stops: colorScheme == .light ? OnboardingCardView.lightStops : OnboardingCardView.darkStops,
|
||||
startPoint: gp.start,
|
||||
endPoint: gp.end
|
||||
)
|
||||
Image(systemName: "checkmark.shield")
|
||||
.font(.system(size: 72))
|
||||
.foregroundColor(theme.colors.primary)
|
||||
}
|
||||
.aspectRatio(1.5, contentMode: .fit)
|
||||
.clipShape(RoundedRectangle(cornerRadius: 24))
|
||||
.padding(.horizontal, 25)
|
||||
#endif
|
||||
}
|
||||
|
||||
private func notificationsModeDestinationView() -> some View {
|
||||
SetNotificationsMode()
|
||||
.navigationBarBackButtonHidden(true)
|
||||
.modifier(ThemedBackground())
|
||||
}
|
||||
|
||||
private func acceptConditionsButton() -> some View {
|
||||
notificationsModeNavLinkButton {
|
||||
Button {
|
||||
Task {
|
||||
do {
|
||||
let conditionsId = ChatModel.shared.conditions.currentConditions.conditionsId
|
||||
let r = try await acceptConditions(conditionsId: conditionsId, operatorIds: Array(selectedOperatorIds))
|
||||
private func acceptButton() -> some View {
|
||||
Button {
|
||||
Task {
|
||||
do {
|
||||
let conditionsId = ChatModel.shared.conditions.currentConditions.conditionsId
|
||||
let r = try await acceptConditions(conditionsId: conditionsId, operatorIds: Array(selectedOperatorIds))
|
||||
await MainActor.run {
|
||||
ChatModel.shared.conditions = r
|
||||
}
|
||||
if let enabledOps = enabledOperators(r.serverOperators) {
|
||||
let r2 = try await setServerOperators(operators: enabledOps)
|
||||
await MainActor.run {
|
||||
ChatModel.shared.conditions = r
|
||||
ChatModel.shared.conditions = r2
|
||||
completeOnboarding()
|
||||
}
|
||||
if let enabledOperators = enabledOperators(r.serverOperators) {
|
||||
let r2 = try await setServerOperators(operators: enabledOperators)
|
||||
await MainActor.run {
|
||||
ChatModel.shared.conditions = r2
|
||||
continueToNextStep()
|
||||
}
|
||||
} else {
|
||||
await MainActor.run {
|
||||
continueToNextStep()
|
||||
}
|
||||
}
|
||||
} catch let error {
|
||||
} else {
|
||||
await MainActor.run {
|
||||
showAlert(
|
||||
NSLocalizedString("Error accepting conditions", comment: "alert title"),
|
||||
message: responseError(error)
|
||||
)
|
||||
completeOnboarding()
|
||||
}
|
||||
}
|
||||
} catch let error {
|
||||
await MainActor.run {
|
||||
showAlert(
|
||||
NSLocalizedString("Error accepting conditions", comment: "alert title"),
|
||||
message: responseError(error)
|
||||
)
|
||||
}
|
||||
}
|
||||
} label: {
|
||||
Text("Accept")
|
||||
}
|
||||
.buttonStyle(OnboardingButtonStyle(isDisabled: selectedOperatorIds.isEmpty))
|
||||
.disabled(selectedOperatorIds.isEmpty)
|
||||
} label: {
|
||||
Text("Accept")
|
||||
}
|
||||
.buttonStyle(OnboardingButtonStyle(isDisabled: selectedOperatorIds.isEmpty))
|
||||
.disabled(selectedOperatorIds.isEmpty)
|
||||
}
|
||||
|
||||
private func completeOnboarding() {
|
||||
let m = ChatModel.shared
|
||||
onboardingStageDefault.set(.onboardingComplete)
|
||||
m.onboardingStage = .onboardingComplete
|
||||
}
|
||||
|
||||
private func enabledOperators(_ operators: [ServerOperator]) -> [ServerOperator]? {
|
||||
@@ -222,7 +209,7 @@ struct OnboardingConditionsView: View {
|
||||
if !haveXFTPProxy { op.xftpRoles.proxy = true }
|
||||
ops[firstEnabledIndex] = op
|
||||
return ops
|
||||
} else { // Shouldn't happen - view doesn't let to proceed if no operators are enabled
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
} else {
|
||||
@@ -405,5 +392,5 @@ struct ChooseServerOperatorsInfoView: View {
|
||||
}
|
||||
|
||||
#Preview {
|
||||
OnboardingConditionsView()
|
||||
OnboardingConditionsView(selectedOperatorIds: [])
|
||||
}
|
||||
|
||||
@@ -83,6 +83,7 @@ struct ConnectBannerCard: View {
|
||||
.lineLimit(1)
|
||||
.minimumScaleFactor(0.75)
|
||||
}
|
||||
.frame(height: 20)
|
||||
.frame(maxWidth: .infinity)
|
||||
.padding(.vertical, 8)
|
||||
.background(ToolbarMaterial.material(toolbarMaterial))
|
||||
|
||||
@@ -29,45 +29,82 @@ enum UserProfileAlert: Identifiable {
|
||||
let MAX_BIO_LENGTH_BYTES = 160
|
||||
|
||||
struct CreateProfile: View {
|
||||
@Environment(\.colorScheme) var colorScheme
|
||||
@Environment(\.dismiss) var dismiss
|
||||
@EnvironmentObject var theme: AppTheme
|
||||
@State private var displayName: String = ""
|
||||
@State private var profileBio: String = ""
|
||||
@FocusState private var focusDisplayName
|
||||
@State private var alert: UserProfileAlert?
|
||||
@State private var showChooseSource = false
|
||||
@State private var showImagePicker = false
|
||||
@State private var showTakePhoto = false
|
||||
@State private var chosenImage: UIImage? = nil
|
||||
@State private var profileImage: String? = nil
|
||||
|
||||
var body: some View {
|
||||
List {
|
||||
Group {
|
||||
HStack(spacing: 0) {
|
||||
Spacer(minLength: 0)
|
||||
ZStack(alignment: .center) {
|
||||
ZStack(alignment: .topTrailing) {
|
||||
ProfileImage(imageStr: profileImage, size: 128)
|
||||
if profileImage != nil {
|
||||
Button {
|
||||
profileImage = nil
|
||||
} label: {
|
||||
Image(systemName: "multiply")
|
||||
.resizable()
|
||||
.aspectRatio(contentMode: .fit)
|
||||
.frame(width: 12)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
editImageButton { showChooseSource = true }
|
||||
.buttonStyle(BorderlessButtonStyle())
|
||||
}
|
||||
.padding(.horizontal, 10) // Offsets transparent space built into 3D asset
|
||||
Spacer(minLength: 0)
|
||||
#if SIMPLEX_ASSETS
|
||||
Image(colorScheme == .light ? "create-profile" : "create-profile-light")
|
||||
.resizable()
|
||||
.scaledToFit()
|
||||
.frame(height: 140)
|
||||
// No trailing spacer — asset image has empty space on the right
|
||||
#endif
|
||||
}
|
||||
}
|
||||
.listRowBackground(Color.clear)
|
||||
.listRowSeparator(.hidden)
|
||||
.listRowInsets(EdgeInsets(top: 8, leading: 0, bottom: 0, trailing: 0))
|
||||
|
||||
Section {
|
||||
TextField("Enter your name…", text: $displayName)
|
||||
.focused($focusDisplayName)
|
||||
TextField("Bio", text: $profileBio)
|
||||
Button {
|
||||
createProfile()
|
||||
} label: {
|
||||
Label("Create profile", systemImage: "checkmark")
|
||||
ZStack(alignment: .leading) {
|
||||
let name = displayName.trimmingCharacters(in: .whitespaces)
|
||||
if name != mkValidName(name) {
|
||||
Button {
|
||||
alert = .invalidNameError(validName: mkValidName(name))
|
||||
} label: {
|
||||
Image(systemName: "exclamationmark.circle").foregroundColor(.red)
|
||||
}
|
||||
} else {
|
||||
Image(systemName: "pencil").foregroundColor(theme.colors.secondary)
|
||||
}
|
||||
TextField("Enter your name…", text: $displayName)
|
||||
.padding(.leading, 36)
|
||||
.focused($focusDisplayName)
|
||||
}
|
||||
ZStack(alignment: .leading) {
|
||||
Image(systemName: "pencil").foregroundColor(theme.colors.secondary)
|
||||
TextField("Bio", text: $profileBio)
|
||||
.padding(.leading, 36)
|
||||
}
|
||||
Button(action: createProfile) {
|
||||
settingsRow("checkmark", color: theme.colors.primary) { Text("Create profile") }
|
||||
}
|
||||
.disabled(!canCreateProfile(displayName) || !bioFitsLimit())
|
||||
} header: {
|
||||
HStack {
|
||||
Text("Your profile")
|
||||
.foregroundColor(theme.colors.secondary)
|
||||
|
||||
let name = displayName.trimmingCharacters(in: .whitespaces)
|
||||
let validName = mkValidName(name)
|
||||
if name != validName {
|
||||
Spacer()
|
||||
validationErrorIndicator {
|
||||
alert = .invalidNameError(validName: validName)
|
||||
}
|
||||
} else if !bioFitsLimit() {
|
||||
Spacer()
|
||||
validationErrorIndicator {
|
||||
showAlert(NSLocalizedString("Bio too large", comment: "alert title"))
|
||||
}
|
||||
}
|
||||
}
|
||||
.frame(height: 20)
|
||||
} footer: {
|
||||
VStack(alignment: .leading, spacing: 8) {
|
||||
Text("Your profile is stored on your device and only shared with your contacts.")
|
||||
@@ -75,10 +112,42 @@ struct CreateProfile: View {
|
||||
.foregroundColor(theme.colors.secondary)
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
}
|
||||
.compactSectionSpacing()
|
||||
}
|
||||
.navigationTitle("Create your profile")
|
||||
.modifier(ThemedBackground(grouped: true))
|
||||
.alert(item: $alert) { a in userProfileAlert(a, $displayName) }
|
||||
.confirmationDialog("Profile image", isPresented: $showChooseSource, titleVisibility: .visible) {
|
||||
Button("Take picture") {
|
||||
showTakePhoto = true
|
||||
}
|
||||
Button("Choose from library") {
|
||||
showImagePicker = true
|
||||
}
|
||||
}
|
||||
.fullScreenCover(isPresented: $showTakePhoto) {
|
||||
ZStack {
|
||||
Color.black.edgesIgnoringSafeArea(.all)
|
||||
CameraImagePicker(image: $chosenImage)
|
||||
}
|
||||
}
|
||||
.sheet(isPresented: $showImagePicker) {
|
||||
LibraryImagePicker(image: $chosenImage) { _ in
|
||||
await MainActor.run {
|
||||
showImagePicker = false
|
||||
}
|
||||
}
|
||||
}
|
||||
.onChange(of: chosenImage) { image in
|
||||
Task {
|
||||
let resized: String? = if let image {
|
||||
await resizeImageToStrSize(cropToSquare(image), maxDataSize: 12500)
|
||||
} else {
|
||||
nil
|
||||
}
|
||||
await MainActor.run { profileImage = resized }
|
||||
}
|
||||
}
|
||||
.onAppear() {
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
|
||||
focusDisplayName = true
|
||||
@@ -86,14 +155,6 @@ struct CreateProfile: View {
|
||||
}
|
||||
}
|
||||
|
||||
private func validationErrorIndicator(_ onTap: @escaping () -> Void) -> some View {
|
||||
Image(systemName: "exclamationmark.circle")
|
||||
.foregroundColor(.red)
|
||||
.onTapGesture {
|
||||
onTap()
|
||||
}
|
||||
}
|
||||
|
||||
private func bioFitsLimit() -> Bool {
|
||||
chatJsonLength(profileBio) <= MAX_BIO_LENGTH_BYTES
|
||||
}
|
||||
@@ -104,7 +165,8 @@ struct CreateProfile: View {
|
||||
let profile = Profile(
|
||||
displayName: displayName.trimmingCharacters(in: .whitespaces),
|
||||
fullName: "",
|
||||
shortDescr: shortDescr
|
||||
shortDescr: shortDescr,
|
||||
image: profileImage
|
||||
)
|
||||
let m = ChatModel.shared
|
||||
do {
|
||||
@@ -133,61 +195,103 @@ struct CreateProfile: View {
|
||||
struct CreateFirstProfile: View {
|
||||
@EnvironmentObject var m: ChatModel
|
||||
@EnvironmentObject var theme: AppTheme
|
||||
@Environment(\.dismiss) var dismiss
|
||||
@Environment(\.colorScheme) var colorScheme: ColorScheme
|
||||
@State private var displayName: String = ""
|
||||
@FocusState private var focusDisplayName
|
||||
@State private var nextStepNavLinkActive = false
|
||||
|
||||
@State private var showMigrateSheet = false
|
||||
var body: some View {
|
||||
let v = VStack(alignment: .leading, spacing: 16) {
|
||||
VStack(alignment: .center, spacing: 16) {
|
||||
Text("Create profile")
|
||||
.font(.largeTitle)
|
||||
.bold()
|
||||
.multilineTextAlignment(.center)
|
||||
|
||||
Text("Your profile is stored on your device and only shared with your contacts.")
|
||||
.font(.callout)
|
||||
.foregroundColor(theme.colors.secondary)
|
||||
.multilineTextAlignment(.center)
|
||||
}
|
||||
.fixedSize(horizontal: false, vertical: true)
|
||||
.frame(maxWidth: .infinity) // Ensures it takes up the full width
|
||||
.padding(.horizontal, 10)
|
||||
.onTapGesture { focusDisplayName = false }
|
||||
|
||||
HStack {
|
||||
let name = displayName.trimmingCharacters(in: .whitespaces)
|
||||
let validName = mkValidName(name)
|
||||
ZStack(alignment: .trailing) {
|
||||
TextField("Enter your name…", text: $displayName)
|
||||
.focused($focusDisplayName)
|
||||
.padding(.horizontal)
|
||||
.padding(.trailing, 20)
|
||||
.padding(.vertical, 10)
|
||||
.background(
|
||||
RoundedRectangle(cornerRadius: 10, style: .continuous)
|
||||
.fill(Color(uiColor: .tertiarySystemFill))
|
||||
let spacing: CGFloat = 10
|
||||
let topPadding: CGFloat = 8
|
||||
let padding: CGFloat = 25
|
||||
GeometryReader { g in
|
||||
let v = ScrollView {
|
||||
VStack(alignment: .center, spacing: spacing) {
|
||||
#if SIMPLEX_ASSETS
|
||||
Image(colorScheme == .light ? "your-profile" : "your-profile-light")
|
||||
.resizable()
|
||||
.scaledToFit()
|
||||
.frame(maxWidth: .infinity)
|
||||
#else
|
||||
ZStack {
|
||||
let gp = OnboardingCardView.gradientPoints(aspectRatio: 1.0, scale: colorScheme == .light ? 1.2 : 1.5)
|
||||
LinearGradient(
|
||||
stops: colorScheme == .light ? OnboardingCardView.lightStops : OnboardingCardView.darkStops,
|
||||
startPoint: gp.start,
|
||||
endPoint: gp.end
|
||||
)
|
||||
if name != validName {
|
||||
Button {
|
||||
showAlert(.invalidNameError(validName: validName))
|
||||
} label: {
|
||||
Image(systemName: "exclamationmark.circle")
|
||||
.foregroundColor(.red)
|
||||
.padding(.horizontal, 10)
|
||||
}
|
||||
Image(systemName: "person.crop.rectangle")
|
||||
.font(.system(size: 72))
|
||||
.foregroundColor(theme.colors.primary)
|
||||
}
|
||||
.aspectRatio(1.0, contentMode: .fit)
|
||||
.clipShape(RoundedRectangle(cornerRadius: 24))
|
||||
.padding(.horizontal, 25)
|
||||
.frame(maxWidth: .infinity)
|
||||
#endif
|
||||
|
||||
Text("Your profile")
|
||||
.font(.largeTitle)
|
||||
.bold()
|
||||
.multilineTextAlignment(.center)
|
||||
.fixedSize(horizontal: false, vertical: true)
|
||||
|
||||
Text("On your phone, not on servers.")
|
||||
.font(.title3)
|
||||
.fontWeight(.medium)
|
||||
.foregroundColor(theme.colors.secondary)
|
||||
.multilineTextAlignment(.center)
|
||||
.fixedSize(horizontal: false, vertical: true)
|
||||
|
||||
Text("No account. No phone. No email. No ID.\nThe most secure encryption.")
|
||||
.font(.footnote)
|
||||
.foregroundColor(theme.colors.secondary)
|
||||
.multilineTextAlignment(.center)
|
||||
.fixedSize(horizontal: false, vertical: true)
|
||||
|
||||
profileNameField()
|
||||
.padding(.top)
|
||||
.padding(.bottom, 5)
|
||||
|
||||
Spacer(minLength: 0)
|
||||
|
||||
createProfileButton()
|
||||
.padding(.bottom, g.safeAreaInsets.bottom == 0 ? 20 : 0)
|
||||
}
|
||||
.padding(.horizontal, padding)
|
||||
.padding(.top, topPadding)
|
||||
.padding(.bottom, padding)
|
||||
.frame(minHeight: g.size.height)
|
||||
}
|
||||
.onTapGesture { focusDisplayName = false }
|
||||
.sheet(isPresented: $showMigrateSheet, onDismiss: { m.migrationState = nil }) {
|
||||
NavigationView {
|
||||
MigrateToDevice(migrationState: $m.migrationState)
|
||||
.navigationTitle("Migrate here")
|
||||
.modifier(ThemedBackground(grouped: true))
|
||||
}
|
||||
}
|
||||
.padding(.top)
|
||||
|
||||
Spacer()
|
||||
|
||||
VStack(spacing: 10) {
|
||||
createProfileButton()
|
||||
if !focusDisplayName {
|
||||
onboardingButtonPlaceholder()
|
||||
if #available(iOS 17, *) {
|
||||
v.scrollBounceBehavior(.basedOnSize).defaultScrollAnchor(.bottom)
|
||||
} else if #available(iOS 16.4, *) {
|
||||
v.scrollBounceBehavior(.basedOnSize)
|
||||
} else {
|
||||
v
|
||||
}
|
||||
}
|
||||
.toolbar {
|
||||
ToolbarItem(placement: .navigationBarTrailing) {
|
||||
Button {
|
||||
if m.migrationState == nil {
|
||||
m.migrationState = .pasteOrScanLink
|
||||
}
|
||||
showMigrateSheet = true
|
||||
} label: {
|
||||
HStack(spacing: 4) {
|
||||
Image(systemName: "tray.and.arrow.down")
|
||||
Text("Migrate")
|
||||
.fontWeight(.medium)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -195,23 +299,40 @@ struct CreateFirstProfile: View {
|
||||
if #available(iOS 16, *) {
|
||||
focusDisplayName = true
|
||||
} else {
|
||||
// it does not work before animation completes on iOS 15
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
|
||||
focusDisplayName = true
|
||||
}
|
||||
}
|
||||
}
|
||||
.padding(.horizontal, 25)
|
||||
.padding(.bottom, 25)
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
if #available(iOS 16, *) {
|
||||
return v.padding(.top, 10)
|
||||
} else {
|
||||
return v.padding(.top, 75).ignoresSafeArea(.all, edges: .top)
|
||||
.frame(maxHeight: .infinity)
|
||||
}
|
||||
|
||||
private func profileNameField() -> some View {
|
||||
let name = displayName.trimmingCharacters(in: .whitespaces)
|
||||
let validName = mkValidName(name)
|
||||
return ZStack(alignment: .trailing) {
|
||||
TextField("Enter profile name...", text: $displayName)
|
||||
.focused($focusDisplayName)
|
||||
.padding(.horizontal)
|
||||
.padding(.trailing, name != validName ? 20 : 0)
|
||||
.padding(.vertical, 10)
|
||||
.background(
|
||||
RoundedRectangle(cornerRadius: 10, style: .continuous)
|
||||
.fill(Color(uiColor: .tertiarySystemFill))
|
||||
)
|
||||
if name != validName {
|
||||
Button {
|
||||
showAlert(.invalidNameError(validName: validName))
|
||||
} label: {
|
||||
Image(systemName: "exclamationmark.circle")
|
||||
.foregroundColor(.red)
|
||||
.padding(.horizontal, 10)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func createProfileButton() -> some View {
|
||||
private func createProfileButton() -> some View {
|
||||
ZStack {
|
||||
Button {
|
||||
createProfile()
|
||||
@@ -236,7 +357,7 @@ struct CreateFirstProfile: View {
|
||||
}
|
||||
|
||||
private func nextStepDestinationView() -> some View {
|
||||
OnboardingConditionsView()
|
||||
YourNetworkView()
|
||||
.navigationBarBackButtonHidden(true)
|
||||
.modifier(ThemedBackground())
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
|
||||
import SwiftUI
|
||||
|
||||
struct HowItWorks: View {
|
||||
struct OldHowItWorks: View {
|
||||
@Environment(\.dismiss) var dismiss: DismissAction
|
||||
@EnvironmentObject var m: ChatModel
|
||||
var onboarding: Bool
|
||||
@@ -28,7 +28,7 @@ struct HowItWorks: View {
|
||||
Text("Only client devices store user profiles, contacts, groups, and messages.")
|
||||
Text("All messages and files are sent **end-to-end encrypted**, with post-quantum security in direct messages.")
|
||||
if !onboarding {
|
||||
Text("Read more in our [GitHub repository](https://github.com/simplex-chat/simplex-chat#readme).")
|
||||
ExternalLink("Read more in our GitHub repository.", destination: URL(string: "https://github.com/simplex-chat/simplex-chat#readme")!)
|
||||
}
|
||||
}
|
||||
.padding(.bottom)
|
||||
@@ -61,9 +61,57 @@ struct HowItWorks: View {
|
||||
}
|
||||
}
|
||||
|
||||
struct HowItWorks_Previews: PreviewProvider {
|
||||
struct WhySimpleX: View {
|
||||
@Environment(\.dismiss) var dismiss: DismissAction
|
||||
@EnvironmentObject var m: ChatModel
|
||||
var onboarding: Bool
|
||||
@Binding var createProfileNavLinkActive: Bool
|
||||
|
||||
var body: some View {
|
||||
VStack(alignment: .leading) {
|
||||
ScrollView {
|
||||
VStack(alignment: .leading, spacing: 16) {
|
||||
Text("You were born without an account")
|
||||
.font(.title)
|
||||
.bold()
|
||||
.padding(.top)
|
||||
Text("Nobody tracked your conversations. No one drew a map of where you'd been. Privacy was never a feature - it was the way of life.")
|
||||
Text("Then we moved online, and every platform asked for a piece of you - your name, your number, your friends. We accepted that the price of talking to others is letting someone know who we talk to. Every generation, people and tech, had it this way - telephone, email, messengers, social media. It seemed the only way possible.")
|
||||
Text("There is another way. A network with no phone numbers. No usernames. No accounts. No user identities of any kind. A network that connects people and carries encrypted messages without knowing who is connected.")
|
||||
Text("Not a better lock on someone else's door. Not a nicer landlord that respects your privacy, but still keeps the record of all visitors. You are not a guest. You are home. No king can enter it - you are sovereign.")
|
||||
Text("Your conversations belong to you, as it had always been before the Internet. The network is not a place you visit. It is a place you create and own. And nobody can take it from you, whether you make it private or public.")
|
||||
Text("The oldest human freedom - to speak to another person without being watched - built on infrastructure that cannot betray it.")
|
||||
Text("Because we destroyed the power to know who you are. So that your power can never be taken.")
|
||||
Text("Be free in your network.")
|
||||
}
|
||||
}
|
||||
.padding(.bottom, 16)
|
||||
|
||||
Spacer()
|
||||
|
||||
if onboarding {
|
||||
createFirstProfileButton()
|
||||
}
|
||||
}
|
||||
.padding(onboarding ? 25 : 16)
|
||||
.frame(maxHeight: .infinity, alignment: .top)
|
||||
.modifier(ThemedBackground())
|
||||
}
|
||||
|
||||
private func createFirstProfileButton() -> some View {
|
||||
Button {
|
||||
dismiss()
|
||||
createProfileNavLinkActive = true
|
||||
} label: {
|
||||
Text("Get started")
|
||||
}
|
||||
.buttonStyle(OnboardingButtonStyle(isDisabled: false))
|
||||
}
|
||||
}
|
||||
|
||||
struct WhySimpleX_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
HowItWorks(
|
||||
WhySimpleX(
|
||||
onboarding: true,
|
||||
createProfileNavLinkActive: Binding.constant(false)
|
||||
)
|
||||
|
||||
@@ -19,17 +19,18 @@ struct OnboardingView: View {
|
||||
case .step1_SimpleXInfo:
|
||||
SimpleXInfo(onboarding: true)
|
||||
.modifier(ThemedBackground())
|
||||
case .step2_CreateProfile: // deprecated
|
||||
case .step2_CreateProfile:
|
||||
CreateFirstProfile()
|
||||
.modifier(ThemedBackground())
|
||||
case .step3_CreateSimpleXAddress: // deprecated
|
||||
CreateSimpleXAddress()
|
||||
case .step3_ChooseServerOperators:
|
||||
OnboardingConditionsView()
|
||||
case .step3_ChooseServerOperators,
|
||||
.step4_SetNotificationsMode: // deprecated
|
||||
YourNetworkView()
|
||||
.navigationBarBackButtonHidden(true)
|
||||
.modifier(ThemedBackground())
|
||||
case .step4_SetNotificationsMode:
|
||||
SetNotificationsMode()
|
||||
case .step4_NetworkCommitments:
|
||||
OnboardingConditionsView(selectedOperatorIds: Set(ChatModel.shared.conditions.serverOperators.filter { $0.enabled }.map { $0.operatorId }))
|
||||
.navigationBarBackButtonHidden(true)
|
||||
.modifier(ThemedBackground())
|
||||
case .onboardingComplete: EmptyView()
|
||||
@@ -45,10 +46,11 @@ func onboardingButtonPlaceholder() -> some View {
|
||||
// Spec: spec/client/navigation.md#onboardingStage
|
||||
enum OnboardingStage: String, Identifiable {
|
||||
case step1_SimpleXInfo
|
||||
case step2_CreateProfile // deprecated
|
||||
case step2_CreateProfile
|
||||
case step3_CreateSimpleXAddress // deprecated
|
||||
case step3_ChooseServerOperators // changed to simplified conditions
|
||||
case step4_SetNotificationsMode
|
||||
case step3_ChooseServerOperators
|
||||
case step4_SetNotificationsMode // deprecated
|
||||
case step4_NetworkCommitments
|
||||
case onboardingComplete
|
||||
|
||||
public var id: Self { self }
|
||||
|
||||
@@ -11,45 +11,39 @@ import SwiftUI
|
||||
import SimpleXChat
|
||||
|
||||
struct SetNotificationsMode: View {
|
||||
@EnvironmentObject var m: ChatModel
|
||||
@State private var notificationMode = NotificationsMode.instant
|
||||
@State private var showAlert: NotificationAlert?
|
||||
@State private var showInfo: Bool = false
|
||||
@Environment(\.dismiss) var dismiss
|
||||
@Binding var notificationMode: NotificationsMode
|
||||
@State private var showInfo = false
|
||||
|
||||
var body: some View {
|
||||
GeometryReader { g in
|
||||
let v = ScrollView {
|
||||
ScrollView {
|
||||
VStack(alignment: .center, spacing: 20) {
|
||||
Text("Push notifications")
|
||||
.font(.largeTitle)
|
||||
.bold()
|
||||
.padding(.top, 25)
|
||||
|
||||
infoText()
|
||||
|
||||
|
||||
Button {
|
||||
showInfo = true
|
||||
} label: {
|
||||
Label("How it affects privacy", systemImage: "info.circle")
|
||||
.font(.headline)
|
||||
}
|
||||
|
||||
Spacer()
|
||||
|
||||
ForEach(NotificationsMode.values) { mode in
|
||||
NtfModeSelector(mode: mode, selection: $notificationMode)
|
||||
}
|
||||
|
||||
|
||||
Spacer()
|
||||
|
||||
|
||||
VStack(spacing: 10) {
|
||||
Button {
|
||||
if let token = m.deviceToken {
|
||||
setNotificationsMode(token, notificationMode)
|
||||
} else {
|
||||
AlertManager.shared.showAlertMsg(title: "No device token!")
|
||||
}
|
||||
onboardingStageDefault.set(.onboardingComplete)
|
||||
m.onboardingStage = .onboardingComplete
|
||||
dismiss()
|
||||
} label: {
|
||||
if case .off = notificationMode {
|
||||
Text("Use chat")
|
||||
} else {
|
||||
Text("Enable notifications")
|
||||
}
|
||||
Text("OK")
|
||||
}
|
||||
.buttonStyle(OnboardingButtonStyle())
|
||||
onboardingButtonPlaceholder()
|
||||
@@ -58,50 +52,11 @@ struct SetNotificationsMode: View {
|
||||
.padding(25)
|
||||
.frame(minHeight: g.size.height)
|
||||
}
|
||||
if #available(iOS 16.4, *) {
|
||||
v.scrollBounceBehavior(.basedOnSize)
|
||||
} else {
|
||||
v
|
||||
}
|
||||
}
|
||||
.frame(maxHeight: .infinity)
|
||||
.sheet(isPresented: $showInfo) {
|
||||
NotificationsInfoView()
|
||||
}
|
||||
.navigationBarHidden(true) // necessary on iOS 15
|
||||
}
|
||||
|
||||
private func setNotificationsMode(_ token: DeviceToken, _ mode: NotificationsMode) {
|
||||
switch mode {
|
||||
case .off:
|
||||
m.tokenStatus = .new
|
||||
m.notificationMode = .off
|
||||
default:
|
||||
Task {
|
||||
do {
|
||||
let status = try await apiRegisterToken(token: token, notificationMode: mode)
|
||||
await MainActor.run {
|
||||
m.tokenStatus = status
|
||||
m.notificationMode = mode
|
||||
}
|
||||
} catch let error {
|
||||
let a = getErrorAlert(error, "Error enabling notifications")
|
||||
AlertManager.shared.showAlertMsg(
|
||||
title: a.title,
|
||||
message: a.message
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func infoText() -> some View {
|
||||
Button {
|
||||
showInfo = true
|
||||
} label: {
|
||||
Label("How it affects privacy", systemImage: "info.circle")
|
||||
.font(.headline)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -180,6 +135,6 @@ struct NotificationsInfoView: View {
|
||||
|
||||
struct NotificationsModeView_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
SetNotificationsMode()
|
||||
SetNotificationsMode(notificationMode: .constant(.instant))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,68 +12,84 @@ import SimpleXChat
|
||||
|
||||
struct SimpleXInfo: View {
|
||||
@EnvironmentObject var m: ChatModel
|
||||
@EnvironmentObject var theme: AppTheme
|
||||
@Environment(\.colorScheme) var colorScheme: ColorScheme
|
||||
@State private var showHowItWorks = false
|
||||
@State private var showWhyBuilt = false
|
||||
@State private var createProfileNavLinkActive = false
|
||||
var onboarding: Bool
|
||||
|
||||
var body: some View {
|
||||
GeometryReader { g in
|
||||
let v = ScrollView {
|
||||
VStack(alignment: .leading) {
|
||||
VStack(alignment: .center, spacing: 10) {
|
||||
Image(colorScheme == .light ? "logo" : "logo-light")
|
||||
.resizable()
|
||||
.aspectRatio(contentMode: .fit)
|
||||
.frame(width: g.size.width * 0.67)
|
||||
.padding(.bottom, 8)
|
||||
.padding(.leading, 4)
|
||||
.frame(maxWidth: .infinity, minHeight: 48, alignment: .top)
|
||||
|
||||
Button {
|
||||
showHowItWorks = true
|
||||
} label: {
|
||||
Label("The future of messaging", systemImage: "info.circle")
|
||||
.font(.headline)
|
||||
}
|
||||
}
|
||||
VStack(alignment: .center, spacing: 10) {
|
||||
Image(colorScheme == .light ? "logo" : "logo-light")
|
||||
.resizable()
|
||||
.aspectRatio(contentMode: .fit)
|
||||
.frame(width: (g.size.width - 50) * 0.55)
|
||||
.padding(.leading, 4)
|
||||
.frame(maxWidth: .infinity, minHeight: 48, alignment: .top)
|
||||
|
||||
Spacer()
|
||||
#if SIMPLEX_ASSETS
|
||||
Image(colorScheme == .light ? "intro" : "intro-light")
|
||||
.resizable()
|
||||
.scaledToFit()
|
||||
.frame(maxWidth: .infinity)
|
||||
#else
|
||||
ZStack {
|
||||
let gp = OnboardingCardView.gradientPoints(aspectRatio: 1.0, scale: colorScheme == .light ? 1.2 : 1.5)
|
||||
LinearGradient(
|
||||
stops: colorScheme == .light ? OnboardingCardView.lightStops : OnboardingCardView.darkStops,
|
||||
startPoint: gp.start,
|
||||
endPoint: gp.end
|
||||
)
|
||||
Image(systemName: "bubble.left.and.bubble.right")
|
||||
.font(.system(size: 72))
|
||||
.foregroundColor(theme.colors.primary)
|
||||
}
|
||||
.aspectRatio(1.0, contentMode: .fit)
|
||||
.clipShape(RoundedRectangle(cornerRadius: 24))
|
||||
.padding(.horizontal, 25)
|
||||
.frame(maxWidth: .infinity)
|
||||
#endif
|
||||
|
||||
VStack(alignment: .leading) {
|
||||
onboardingInfoRow("privacy", "Privacy redefined",
|
||||
"No user identifiers.", width: 48)
|
||||
onboardingInfoRow("shield", "Immune to spam",
|
||||
"You decide who can connect.", width: 46)
|
||||
onboardingInfoRow(colorScheme == .light ? "decentralized" : "decentralized-light", "Decentralized",
|
||||
"Anybody can host servers.", width: 46)
|
||||
}
|
||||
.padding(.leading, 16)
|
||||
Text("Be free\nin your network")
|
||||
.font(.largeTitle)
|
||||
.bold()
|
||||
.multilineTextAlignment(.center)
|
||||
.fixedSize(horizontal: false, vertical: true)
|
||||
|
||||
Spacer()
|
||||
Text("Private and secure messaging.")
|
||||
.font(.title3)
|
||||
.fontWeight(.medium)
|
||||
.foregroundColor(theme.colors.secondary)
|
||||
.multilineTextAlignment(.center)
|
||||
.fixedSize(horizontal: false, vertical: true)
|
||||
|
||||
if onboarding {
|
||||
VStack(spacing: 10) {
|
||||
createFirstProfileButton()
|
||||
Text("The first network where you own\nyour contacts and groups.")
|
||||
.font(.footnote)
|
||||
.foregroundColor(theme.colors.secondary)
|
||||
.multilineTextAlignment(.center)
|
||||
.fixedSize(horizontal: false, vertical: true)
|
||||
|
||||
Button {
|
||||
m.migrationState = .pasteOrScanLink
|
||||
} label: {
|
||||
Label("Migrate from another device", systemImage: "tray.and.arrow.down")
|
||||
.font(.system(size: 17, weight: .semibold))
|
||||
.frame(minHeight: 40)
|
||||
}
|
||||
.frame(maxWidth: .infinity)
|
||||
}
|
||||
if onboarding {
|
||||
Spacer(minLength: 0)
|
||||
|
||||
createFirstProfileButton()
|
||||
.padding(.vertical, 10)
|
||||
|
||||
Button {
|
||||
showWhyBuilt = true
|
||||
} label: {
|
||||
Label("Why SimpleX is built.", systemImage: "info.circle")
|
||||
.font(.headline)
|
||||
}
|
||||
}
|
||||
.padding(.horizontal, 25)
|
||||
.padding(.top, 75)
|
||||
.padding(.bottom, 25)
|
||||
.frame(minHeight: g.size.height)
|
||||
}
|
||||
.padding(.horizontal, 25)
|
||||
.padding(.top, 28)
|
||||
.padding(.bottom, 20)
|
||||
.frame(minHeight: g.size.height)
|
||||
.sheet(isPresented: Binding(
|
||||
get: { m.migrationState != nil },
|
||||
get: { m.migrationState != nil && !createProfileNavLinkActive },
|
||||
set: { _ in
|
||||
m.migrationState = nil
|
||||
MigrationToDeviceState.save(nil) }
|
||||
@@ -86,17 +102,12 @@ struct SimpleXInfo: View {
|
||||
.modifier(ThemedBackground(grouped: true))
|
||||
}
|
||||
}
|
||||
.sheet(isPresented: $showHowItWorks) {
|
||||
HowItWorks(
|
||||
.sheet(isPresented: $showWhyBuilt) {
|
||||
WhySimpleX(
|
||||
onboarding: onboarding,
|
||||
createProfileNavLinkActive: $createProfileNavLinkActive
|
||||
)
|
||||
}
|
||||
if #available(iOS 16.4, *) {
|
||||
v.scrollBounceBehavior(.basedOnSize)
|
||||
} else {
|
||||
v
|
||||
}
|
||||
}
|
||||
.onAppear() {
|
||||
setLastVersionDefault()
|
||||
@@ -105,32 +116,12 @@ struct SimpleXInfo: View {
|
||||
.navigationBarHidden(true) // necessary on iOS 15
|
||||
}
|
||||
|
||||
private func onboardingInfoRow(_ image: String, _ title: LocalizedStringKey, _ text: LocalizedStringKey, width: CGFloat) -> some View {
|
||||
HStack(alignment: .top) {
|
||||
Image(image)
|
||||
.resizable()
|
||||
.scaledToFit()
|
||||
.frame(width: width, height: 54)
|
||||
.frame(width: 54)
|
||||
.padding(.trailing, 10)
|
||||
VStack(alignment: .leading, spacing: 4) {
|
||||
Text(title).font(.headline)
|
||||
Text(text).frame(minHeight: 40, alignment: .top)
|
||||
.font(.callout)
|
||||
.lineLimit(3)
|
||||
.fixedSize(horizontal: false, vertical: true)
|
||||
}
|
||||
.padding(.top, 4)
|
||||
}
|
||||
.padding(.bottom, 12)
|
||||
}
|
||||
|
||||
private func createFirstProfileButton() -> some View {
|
||||
ZStack {
|
||||
Button {
|
||||
createProfileNavLinkActive = true
|
||||
} label: {
|
||||
Text("Create your profile")
|
||||
Text("Get started")
|
||||
}
|
||||
.buttonStyle(OnboardingButtonStyle(isDisabled: false))
|
||||
|
||||
|
||||
@@ -634,7 +634,7 @@ private let versionDescriptions: [VersionDescription] = [
|
||||
),
|
||||
VersionDescription(
|
||||
version: "v6.5",
|
||||
post: URL(string: "https://simplex.chat/blog/20260428-simplex-channels-v6-5-consortium-crowdfunding-freedom-of-speech.html"),
|
||||
post: URL(string: "https://simplex.chat/blog/20260430-simplex-channels-v6-5-consortium-crowdfunding-freedom-of-speech.html"),
|
||||
features: [
|
||||
.feature(Description(
|
||||
icon: nil,
|
||||
@@ -791,7 +791,7 @@ struct WhatsNewView: View {
|
||||
}
|
||||
}
|
||||
if let post = v.post {
|
||||
Link(destination: post) {
|
||||
ExternalLink(destination: post) {
|
||||
HStack {
|
||||
Text("Read more")
|
||||
Image(systemName: "arrow.up.right.circle")
|
||||
|
||||
@@ -0,0 +1,193 @@
|
||||
//
|
||||
// YourNetwork.swift
|
||||
// SimpleX (iOS)
|
||||
//
|
||||
// Created by Evgeny on 22/04/2026.
|
||||
// Copyright © 2026 SimpleX Chat. All rights reserved.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
import SimpleXChat
|
||||
|
||||
private enum YourNetworkSheet: Identifiable {
|
||||
case configureOperators
|
||||
case configureNotifications
|
||||
|
||||
var id: String {
|
||||
switch self {
|
||||
case .configureOperators: return "configureOperators"
|
||||
case .configureNotifications: return "configureNotifications"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct YourNetworkView: View {
|
||||
@EnvironmentObject var theme: AppTheme
|
||||
@Environment(\.colorScheme) var colorScheme: ColorScheme
|
||||
@State private var serverOperators: [ServerOperator] = []
|
||||
@State private var selectedOperatorIds = Set<Int64>()
|
||||
@State private var notificationMode: NotificationsMode = .instant
|
||||
@State private var sheetItem: YourNetworkSheet? = nil
|
||||
@State private var nextStepNavLinkActive = false
|
||||
@State private var justOpened = true
|
||||
|
||||
var body: some View {
|
||||
GeometryReader { g in
|
||||
VStack(alignment: .center, spacing: 10) {
|
||||
Spacer(minLength: 0)
|
||||
|
||||
#if SIMPLEX_ASSETS
|
||||
Image(colorScheme == .light ? "your-network" : "your-network-light")
|
||||
.resizable()
|
||||
.scaledToFit()
|
||||
.frame(maxWidth: .infinity)
|
||||
#else
|
||||
ZStack {
|
||||
let gp = OnboardingCardView.gradientPoints(aspectRatio: 1.0, scale: colorScheme == .light ? 1.2 : 1.5)
|
||||
LinearGradient(
|
||||
stops: colorScheme == .light ? OnboardingCardView.lightStops : OnboardingCardView.darkStops,
|
||||
startPoint: gp.start,
|
||||
endPoint: gp.end
|
||||
)
|
||||
Image(systemName: "network")
|
||||
.font(.system(size: 72))
|
||||
.foregroundColor(theme.colors.primary)
|
||||
}
|
||||
.aspectRatio(1.0, contentMode: .fit)
|
||||
.clipShape(RoundedRectangle(cornerRadius: 24))
|
||||
.padding(.horizontal, 25)
|
||||
.frame(maxWidth: .infinity)
|
||||
#endif
|
||||
|
||||
Text("Your network")
|
||||
.font(.largeTitle)
|
||||
.bold()
|
||||
.multilineTextAlignment(.center)
|
||||
.fixedSize(horizontal: false, vertical: true)
|
||||
.padding(.top, 15)
|
||||
|
||||
Text("Network routers cannot know\nwho talks to whom")
|
||||
.font(.title3)
|
||||
.fontWeight(.medium)
|
||||
.foregroundColor(theme.colors.secondary)
|
||||
.multilineTextAlignment(.center)
|
||||
.fixedSize(horizontal: false, vertical: true)
|
||||
|
||||
VStack(alignment: .leading, spacing: 20) {
|
||||
configureRoutersButton()
|
||||
configureNotificationsButton()
|
||||
}
|
||||
.padding(.top, 15)
|
||||
.padding(.bottom, 15)
|
||||
|
||||
Spacer(minLength: 0)
|
||||
|
||||
continueButton()
|
||||
.padding(.bottom, g.safeAreaInsets.bottom == 0 ? 20 : 0)
|
||||
}
|
||||
.padding(.horizontal, 25)
|
||||
.padding(.top, 8)
|
||||
.padding(.bottom, 20)
|
||||
.frame(minHeight: g.size.height)
|
||||
}
|
||||
.onAppear {
|
||||
if justOpened {
|
||||
serverOperators = ChatModel.shared.conditions.serverOperators
|
||||
selectedOperatorIds = Set(serverOperators.filter { $0.enabled }.map { $0.operatorId })
|
||||
justOpened = false
|
||||
}
|
||||
}
|
||||
.sheet(item: $sheetItem) { item in
|
||||
switch item {
|
||||
case .configureOperators:
|
||||
ChooseServerOperators(serverOperators: serverOperators, selectedOperatorIds: $selectedOperatorIds)
|
||||
.modifier(ThemedBackground())
|
||||
case .configureNotifications:
|
||||
SetNotificationsMode(notificationMode: $notificationMode)
|
||||
.modifier(ThemedBackground())
|
||||
}
|
||||
}
|
||||
.frame(maxHeight: .infinity)
|
||||
.navigationBarHidden(true)
|
||||
}
|
||||
|
||||
private func configureRoutersButton() -> some View {
|
||||
Button {
|
||||
sheetItem = .configureOperators
|
||||
} label: {
|
||||
HStack(spacing: 6) {
|
||||
Text("Setup routers")
|
||||
.fontWeight(.medium)
|
||||
ForEach(serverOperators.reversed()) { op in
|
||||
Image(op.logo(colorScheme))
|
||||
.resizable()
|
||||
.scaledToFit()
|
||||
.frame(width: 22, height: 22)
|
||||
.grayscale(selectedOperatorIds.contains(op.operatorId) ? 0.0 : 1.0)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func configureNotificationsButton() -> some View {
|
||||
Button {
|
||||
sheetItem = .configureNotifications
|
||||
} label: {
|
||||
HStack(spacing: 4) {
|
||||
Text("Setup notifications")
|
||||
.fontWeight(.medium)
|
||||
Image(systemName: notificationMode.icon)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func continueButton() -> some View {
|
||||
ZStack {
|
||||
Button {
|
||||
applyNotificationMode()
|
||||
onboardingStageDefault.set(.step4_NetworkCommitments)
|
||||
nextStepNavLinkActive = true
|
||||
} label: {
|
||||
Text("Continue")
|
||||
}
|
||||
.buttonStyle(OnboardingButtonStyle())
|
||||
|
||||
NavigationLink(isActive: $nextStepNavLinkActive) {
|
||||
OnboardingConditionsView(selectedOperatorIds: selectedOperatorIds)
|
||||
.navigationBarBackButtonHidden(true)
|
||||
.modifier(ThemedBackground())
|
||||
} label: {
|
||||
EmptyView()
|
||||
}
|
||||
.frame(width: 1, height: 1)
|
||||
.hidden()
|
||||
}
|
||||
}
|
||||
|
||||
private func applyNotificationMode() {
|
||||
let m = ChatModel.shared
|
||||
if let token = m.deviceToken {
|
||||
switch notificationMode {
|
||||
case .off:
|
||||
m.tokenStatus = .new
|
||||
m.notificationMode = .off
|
||||
default:
|
||||
Task {
|
||||
do {
|
||||
let status = try await apiRegisterToken(token: token, notificationMode: notificationMode)
|
||||
await MainActor.run {
|
||||
m.tokenStatus = status
|
||||
m.notificationMode = notificationMode
|
||||
}
|
||||
} catch let error {
|
||||
let a = getErrorAlert(error, "Error enabling notifications")
|
||||
AlertManager.shared.showAlertMsg(
|
||||
title: a.title,
|
||||
message: a.message
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -22,14 +22,16 @@ struct DeveloperView: View {
|
||||
VStack {
|
||||
List {
|
||||
Section {
|
||||
ZStack(alignment: .leading) {
|
||||
Image(colorScheme == .dark ? "github_light" : "github")
|
||||
.resizable()
|
||||
.frame(width: 24, height: 24)
|
||||
.opacity(0.5)
|
||||
.colorMultiply(theme.colors.secondary)
|
||||
Text("Install [SimpleX Chat for terminal](https://github.com/simplex-chat/simplex-chat)")
|
||||
.padding(.leading, 36)
|
||||
ExternalLink(destination: URL(string: "https://github.com/simplex-chat/simplex-chat")!) {
|
||||
ZStack(alignment: .leading) {
|
||||
Image(colorScheme == .dark ? "github_light" : "github")
|
||||
.resizable()
|
||||
.frame(width: 24, height: 24)
|
||||
.opacity(0.5)
|
||||
.colorMultiply(theme.colors.secondary)
|
||||
Text("Install SimpleX Chat for terminal")
|
||||
.padding(.leading, 36)
|
||||
}
|
||||
}
|
||||
NavigationLink {
|
||||
TerminalView()
|
||||
|
||||
@@ -23,7 +23,7 @@ struct IncognitoHelp: View {
|
||||
Text("Incognito mode protects your privacy by using a new random profile for each contact.")
|
||||
Text("It allows having many anonymous connections without any shared data between them in a single chat profile.")
|
||||
Text("When you share an incognito profile with somebody, this profile will be used for the groups they invite you to.")
|
||||
Text("Read more in [User Guide](https://simplex.chat/docs/guide/chat-profiles.html#incognito-mode).")
|
||||
ExternalLink("Read more in User Guide.", destination: URL(string: "https://simplex.chat/docs/guide/chat-profiles.html#incognito-mode")!)
|
||||
}
|
||||
.listRowBackground(Color.clear)
|
||||
.listRowInsets(EdgeInsets(top: 0, leading: 0, bottom: 0, trailing: 0))
|
||||
|
||||
@@ -71,11 +71,7 @@ struct ConditionsWebView: UIViewRepresentable {
|
||||
switch navigationAction.navigationType {
|
||||
case .linkActivated:
|
||||
decisionHandler(.cancel)
|
||||
if url.absoluteString.starts(with: "https://simplex.chat/contact#") {
|
||||
ChatModel.shared.appOpenUrl = url
|
||||
} else {
|
||||
UIApplication.shared.open(url)
|
||||
}
|
||||
openExternalLink(url)
|
||||
default:
|
||||
decisionHandler(.allow)
|
||||
}
|
||||
|
||||
@@ -332,7 +332,7 @@ struct UsageConditionsView: View {
|
||||
@ViewBuilder private func conditionsDiffButton(_ font: Font? = nil) -> some View {
|
||||
let commit = ChatModel.shared.conditions.currentConditions.conditionsCommit
|
||||
if let commitUrl = URL(string: "https://github.com/simplex-chat/simplex-chat/commit/\(commit)") {
|
||||
Link(destination: commitUrl) {
|
||||
ExternalLink(destination: commitUrl) {
|
||||
HStack {
|
||||
Text("Open changes")
|
||||
Image(systemName: "arrow.up.right.circle")
|
||||
@@ -351,21 +351,6 @@ private func regularConditionsHeader() -> some View {
|
||||
}
|
||||
}
|
||||
|
||||
struct SimpleConditionsView: View {
|
||||
|
||||
var body: some View {
|
||||
VStack(alignment: .leading, spacing: 20) {
|
||||
regularConditionsHeader()
|
||||
.padding(.top)
|
||||
.padding(.top)
|
||||
ConditionsTextView()
|
||||
.padding(.bottom)
|
||||
.padding(.bottom)
|
||||
}
|
||||
.padding(.horizontal, 25)
|
||||
.frame(maxHeight: .infinity)
|
||||
}
|
||||
}
|
||||
|
||||
func validateServers_(
|
||||
_ userServers: Binding<[UserOperatorServers]>,
|
||||
|
||||
@@ -364,11 +364,15 @@ struct OperatorInfoView: View {
|
||||
Text(d)
|
||||
}
|
||||
}
|
||||
Link(serverOperator.info.website.absoluteString, destination: serverOperator.info.website)
|
||||
ExternalLink(destination: serverOperator.info.website) {
|
||||
Text(serverOperator.info.website.absoluteString)
|
||||
}
|
||||
}
|
||||
if let selfhost = serverOperator.info.selfhost {
|
||||
Section {
|
||||
Link(selfhost.text, destination: selfhost.link)
|
||||
ExternalLink(destination: selfhost.link) {
|
||||
Text(selfhost.text)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -432,7 +436,7 @@ struct ConditionsTextView: View {
|
||||
private func conditionsLinkView(_ conditionsLink: String) -> some View {
|
||||
VStack(alignment: .leading, spacing: 20) {
|
||||
Text("Current conditions text couldn't be loaded, you can review conditions via this link:")
|
||||
Link(destination: URL(string: conditionsLink)!) {
|
||||
ExternalLink(destination: URL(string: conditionsLink)!) {
|
||||
Text(conditionsLink)
|
||||
.multilineTextAlignment(.leading)
|
||||
}
|
||||
@@ -591,11 +595,11 @@ func conditionsLinkButton() -> some View {
|
||||
let commit = ChatModel.shared.conditions.currentConditions.conditionsCommit
|
||||
let mdUrl = URL(string: "https://github.com/simplex-chat/simplex-chat/blob/\(commit)/PRIVACY.md") ?? conditionsURL
|
||||
return Menu {
|
||||
Link(destination: mdUrl) {
|
||||
ExternalLink(destination: mdUrl) {
|
||||
Label("Open conditions", systemImage: "doc")
|
||||
}
|
||||
if let commitUrl = URL(string: "https://github.com/simplex-chat/simplex-chat/commit/\(commit)") {
|
||||
Link(destination: commitUrl) {
|
||||
ExternalLink(destination: commitUrl) {
|
||||
Label("Open changes", systemImage: "ellipsis")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -223,9 +223,7 @@ struct YourServersView: View {
|
||||
|
||||
func howToButton() -> some View {
|
||||
Button {
|
||||
DispatchQueue.main.async {
|
||||
UIApplication.shared.open(howToUrl)
|
||||
}
|
||||
openExternalLink(howToUrl)
|
||||
} label: {
|
||||
HStack {
|
||||
Text("How to use your servers")
|
||||
|
||||
@@ -139,9 +139,7 @@ struct RTCServers: View {
|
||||
|
||||
func howToButton() -> some View {
|
||||
Button {
|
||||
DispatchQueue.main.async {
|
||||
UIApplication.shared.open(howToUrl)
|
||||
}
|
||||
openExternalLink(howToUrl)
|
||||
} label: {
|
||||
HStack{
|
||||
Text("How to")
|
||||
|
||||
@@ -11,7 +11,7 @@ import SwiftUI
|
||||
import StoreKit
|
||||
import SimpleXChat
|
||||
|
||||
let simplexTeamURL = URL(string: "simplex:/contact#/?v=1&smp=smp%3A%2F%2FPQUV2eL0t7OStZOoAsPEV2QYWt4-xilbakvGUGOItUo%3D%40smp6.simplex.im%2FK1rslx-m5bpXVIdMZg9NLUZ_8JBm8xTt%23MCowBQYDK2VuAyEALDeVe-sG8mRY22LsXlPgiwTNs9dbiLrNuA7f3ZMAJ2w%3D")!
|
||||
let simplexTeamURL = URL(string: "simplex:/a#lrdvu2d8A1GumSmoKb2krQmtKhWXq-tyGpHuM7aMwsw?h=smp6.simplex.im")!
|
||||
|
||||
let appVersion = Bundle.main.object(forInfoDictionaryKey: "CFBundleShortVersionString") as? String
|
||||
|
||||
@@ -399,7 +399,9 @@ struct SettingsView: View {
|
||||
}
|
||||
|
||||
Section(header: Text("Support SimpleX Chat").foregroundColor(theme.colors.secondary)) {
|
||||
settingsRow("keyboard", color: theme.colors.secondary) { Text("[Contribute](https://github.com/simplex-chat/simplex-chat#contribute)") }
|
||||
settingsRow("keyboard", color: theme.colors.secondary) {
|
||||
ExternalLink("Contribute", destination: URL(string: "https://github.com/simplex-chat/simplex-chat#contribute")!)
|
||||
}
|
||||
settingsRow("star", color: theme.colors.secondary) {
|
||||
Button("Rate the app") {
|
||||
if let scene = sceneDelegate.windowScene {
|
||||
@@ -407,14 +409,16 @@ struct SettingsView: View {
|
||||
}
|
||||
}
|
||||
}
|
||||
ZStack(alignment: .leading) {
|
||||
Image(colorScheme == .dark ? "github_light" : "github")
|
||||
.resizable()
|
||||
.frame(width: 24, height: 24)
|
||||
.opacity(0.5)
|
||||
.colorMultiply(theme.colors.secondary)
|
||||
Text("[Star on GitHub](https://github.com/simplex-chat/simplex-chat)")
|
||||
.padding(.leading, indent)
|
||||
ExternalLink(destination: URL(string: "https://github.com/simplex-chat/simplex-chat")!) {
|
||||
ZStack(alignment: .leading) {
|
||||
Image(colorScheme == .dark ? "github_light" : "github")
|
||||
.resizable()
|
||||
.frame(width: 24, height: 24)
|
||||
.opacity(0.5)
|
||||
.colorMultiply(theme.colors.secondary)
|
||||
Text("Star on GitHub")
|
||||
.padding(.leading, indent)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -31,7 +31,7 @@ struct UserAddressLearnMore: View {
|
||||
.padding(.top)
|
||||
Text("SimpleX address and 1-time links are safe to share via any messenger.")
|
||||
Text("To protect against your link being replaced, you can compare contact security codes.")
|
||||
Text("Read more in [User Guide](https://simplex.chat/docs/guide/making-connections.html#comparison-of-1-time-invitation-links-and-simplex-contact-addresses).")
|
||||
ExternalLink("Read more in User Guide.", destination: URL(string: "https://simplex.chat/docs/guide/making-connections.html#comparison-of-1-time-invitation-links-and-simplex-contact-addresses")!)
|
||||
.padding(.top)
|
||||
}
|
||||
|
||||
|
||||
@@ -215,11 +215,6 @@ struct UserAddressView: View {
|
||||
HStack(spacing: 8) {
|
||||
let link = userAddress.connLinkContact.simplexChatUri(short: showShortLink)
|
||||
linkTextView(link)
|
||||
Button { UIPasteboard.general.string = link } label: {
|
||||
Image(systemName: "doc.on.doc")
|
||||
.padding(.top, -7)
|
||||
.padding(.horizontal, 8)
|
||||
}
|
||||
Button { showShareSheet(items: [link]) } label: {
|
||||
Image(systemName: "square.and.arrow.up")
|
||||
.padding(.top, -7)
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -68,7 +68,7 @@ func apiSendMessages(
|
||||
type: chatInfo.chatType,
|
||||
id: chatInfo.apiId,
|
||||
scope: chatInfo.groupChatScope(),
|
||||
sendAsGroup: chatInfo.groupInfo.map { $0.useRelays && $0.membership.memberRole >= .owner } ?? false,
|
||||
sendAsGroup: chatInfo.sendAsGroup,
|
||||
live: false,
|
||||
ttl: nil,
|
||||
composedMessages: composedMessages
|
||||
|
||||
@@ -80,7 +80,7 @@
|
||||
"Please create a profile in the SimpleX app" = "Bitte erstellen Sie in der SimpleX-App ein Profil";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Selected chat preferences prohibit this message." = "Die gewählten Chat-Einstellungen erlauben diese Nachricht nicht.";
|
||||
"Selected chat preferences prohibit this message." = "Diese Nachricht ist wegen der gewählten Chat-Präferenzen nicht erlaubt.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Sending a message takes longer than expected." = "Das Senden einer Nachricht dauert länger als erwartet.";
|
||||
|
||||
@@ -29,7 +29,7 @@
|
||||
"Database error" = "Ошибка базы данных";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Database passphrase is different from saved in the keychain." = "Пароль базы данных отличается от сохраненного в keychain.";
|
||||
"Database passphrase is different from saved in the keychain." = "Пароль базы данных отличается от сохранённого в keychain.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Database passphrase is required to open chat." = "Введите пароль базы данных, чтобы открыть чат.";
|
||||
@@ -77,7 +77,7 @@
|
||||
"Passphrase" = "Пароль";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Please create a profile in the SimpleX app" = "Пожалуйста, создайте профиль в приложении SimpleX.";
|
||||
"Please create a profile in the SimpleX app" = "Пожалуйста, создайте профиль в приложении SimpleX";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Selected chat preferences prohibit this message." = "Выбранные настройки чата запрещают это сообщение.";
|
||||
|
||||
@@ -182,8 +182,8 @@
|
||||
64C3B0212A0D359700E19930 /* CustomTimePicker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64C3B0202A0D359700E19930 /* CustomTimePicker.swift */; };
|
||||
64C8299D2D54AEEE006B9E89 /* libgmp.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 64C829982D54AEED006B9E89 /* libgmp.a */; };
|
||||
64C8299E2D54AEEE006B9E89 /* libffi.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 64C829992D54AEEE006B9E89 /* libffi.a */; };
|
||||
64C8299F2D54AEEE006B9E89 /* libHSsimplex-chat-6.5.0.15-7J6rfC1qLWr8QkAAXzi4Re-ghc9.6.3.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 64C8299A2D54AEEE006B9E89 /* libHSsimplex-chat-6.5.0.15-7J6rfC1qLWr8QkAAXzi4Re-ghc9.6.3.a */; };
|
||||
64C829A02D54AEEE006B9E89 /* libHSsimplex-chat-6.5.0.15-7J6rfC1qLWr8QkAAXzi4Re.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 64C8299B2D54AEEE006B9E89 /* libHSsimplex-chat-6.5.0.15-7J6rfC1qLWr8QkAAXzi4Re.a */; };
|
||||
64C8299F2D54AEEE006B9E89 /* libHSsimplex-chat-6.5.1.1-Fx4JRO2FuL8K4q8f3JAaMO-ghc9.6.3.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 64C8299A2D54AEEE006B9E89 /* libHSsimplex-chat-6.5.1.1-Fx4JRO2FuL8K4q8f3JAaMO-ghc9.6.3.a */; };
|
||||
64C829A02D54AEEE006B9E89 /* libHSsimplex-chat-6.5.1.1-Fx4JRO2FuL8K4q8f3JAaMO.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 64C8299B2D54AEEE006B9E89 /* libHSsimplex-chat-6.5.1.1-Fx4JRO2FuL8K4q8f3JAaMO.a */; };
|
||||
64C829A12D54AEEE006B9E89 /* libgmpxx.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 64C8299C2D54AEEE006B9E89 /* libgmpxx.a */; };
|
||||
64D0C2C029F9688300B38D5F /* UserAddressView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64D0C2BF29F9688300B38D5F /* UserAddressView.swift */; };
|
||||
64D0C2C229FA57AB00B38D5F /* UserAddressLearnMore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64D0C2C129FA57AB00B38D5F /* UserAddressLearnMore.swift */; };
|
||||
@@ -249,6 +249,7 @@
|
||||
D7F0E33929964E7E0068AF69 /* LZString in Frameworks */ = {isa = PBXBuildFile; productRef = D7F0E33829964E7E0068AF69 /* LZString */; };
|
||||
E51CC1E62C62085600DB91FE /* OneHandUICard.swift in Sources */ = {isa = PBXBuildFile; fileRef = E51CC1E52C62085600DB91FE /* OneHandUICard.swift */; };
|
||||
E559A0A12E3F77EE00B26F74 /* CommandsMenuView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E559A0A02E3F77EE00B26F74 /* CommandsMenuView.swift */; };
|
||||
E5A0B0012F960000AAAA0001 /* YourNetwork.swift in Sources */ = {isa = PBXBuildFile; fileRef = E5A0B0022F960000AAAA0001 /* YourNetwork.swift */; };
|
||||
E5AEC0AB2F91A6EB00270665 /* CIChatLinkHeader.swift in Sources */ = {isa = PBXBuildFile; fileRef = E5AEC0AA2F91A6EA00270665 /* CIChatLinkHeader.swift */; };
|
||||
E5AEC0AF2F91A73500270665 /* ComposeChatLinkView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E5AEC0AE2F91A73500270665 /* ComposeChatLinkView.swift */; };
|
||||
E5C0BBE82F82B45500EA7527 /* SimpleXAssets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = E5C0BBE72F82B45500EA7527 /* SimpleXAssets.xcassets */; };
|
||||
@@ -558,8 +559,8 @@
|
||||
64C3B0202A0D359700E19930 /* CustomTimePicker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomTimePicker.swift; sourceTree = "<group>"; };
|
||||
64C829982D54AEED006B9E89 /* libgmp.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmp.a; sourceTree = "<group>"; };
|
||||
64C829992D54AEEE006B9E89 /* libffi.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libffi.a; sourceTree = "<group>"; };
|
||||
64C8299A2D54AEEE006B9E89 /* libHSsimplex-chat-6.5.0.15-7J6rfC1qLWr8QkAAXzi4Re-ghc9.6.3.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-6.5.0.15-7J6rfC1qLWr8QkAAXzi4Re-ghc9.6.3.a"; sourceTree = "<group>"; };
|
||||
64C8299B2D54AEEE006B9E89 /* libHSsimplex-chat-6.5.0.15-7J6rfC1qLWr8QkAAXzi4Re.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-6.5.0.15-7J6rfC1qLWr8QkAAXzi4Re.a"; sourceTree = "<group>"; };
|
||||
64C8299A2D54AEEE006B9E89 /* libHSsimplex-chat-6.5.1.1-Fx4JRO2FuL8K4q8f3JAaMO-ghc9.6.3.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-6.5.1.1-Fx4JRO2FuL8K4q8f3JAaMO-ghc9.6.3.a"; sourceTree = "<group>"; };
|
||||
64C8299B2D54AEEE006B9E89 /* libHSsimplex-chat-6.5.1.1-Fx4JRO2FuL8K4q8f3JAaMO.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-6.5.1.1-Fx4JRO2FuL8K4q8f3JAaMO.a"; sourceTree = "<group>"; };
|
||||
64C8299C2D54AEEE006B9E89 /* libgmpxx.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmpxx.a; sourceTree = "<group>"; };
|
||||
64D0C2BF29F9688300B38D5F /* UserAddressView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserAddressView.swift; sourceTree = "<group>"; };
|
||||
64D0C2C129FA57AB00B38D5F /* UserAddressLearnMore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserAddressLearnMore.swift; sourceTree = "<group>"; };
|
||||
@@ -621,6 +622,7 @@
|
||||
D7AA2C3429A936B400737B40 /* MediaEncryption.playground */ = {isa = PBXFileReference; lastKnownFileType = file.playground; name = MediaEncryption.playground; path = Shared/MediaEncryption.playground; sourceTree = SOURCE_ROOT; xcLanguageSpecificationIdentifier = xcode.lang.swift; };
|
||||
E51CC1E52C62085600DB91FE /* OneHandUICard.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OneHandUICard.swift; sourceTree = "<group>"; };
|
||||
E559A0A02E3F77EE00B26F74 /* CommandsMenuView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CommandsMenuView.swift; sourceTree = "<group>"; };
|
||||
E5A0B0022F960000AAAA0001 /* YourNetwork.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = YourNetwork.swift; sourceTree = "<group>"; };
|
||||
E5AEC0AA2F91A6EA00270665 /* CIChatLinkHeader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CIChatLinkHeader.swift; sourceTree = "<group>"; };
|
||||
E5AEC0AE2F91A73500270665 /* ComposeChatLinkView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ComposeChatLinkView.swift; sourceTree = "<group>"; };
|
||||
E5C0BBE72F82B45500EA7527 /* SimpleXAssets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = SimpleXAssets.xcassets; sourceTree = "<group>"; };
|
||||
@@ -727,8 +729,8 @@
|
||||
64C8299D2D54AEEE006B9E89 /* libgmp.a in Frameworks */,
|
||||
64C8299E2D54AEEE006B9E89 /* libffi.a in Frameworks */,
|
||||
64C829A12D54AEEE006B9E89 /* libgmpxx.a in Frameworks */,
|
||||
64C8299F2D54AEEE006B9E89 /* libHSsimplex-chat-6.5.0.15-7J6rfC1qLWr8QkAAXzi4Re-ghc9.6.3.a in Frameworks */,
|
||||
64C829A02D54AEEE006B9E89 /* libHSsimplex-chat-6.5.0.15-7J6rfC1qLWr8QkAAXzi4Re.a in Frameworks */,
|
||||
64C8299F2D54AEEE006B9E89 /* libHSsimplex-chat-6.5.1.1-Fx4JRO2FuL8K4q8f3JAaMO-ghc9.6.3.a in Frameworks */,
|
||||
64C829A02D54AEEE006B9E89 /* libHSsimplex-chat-6.5.1.1-Fx4JRO2FuL8K4q8f3JAaMO.a in Frameworks */,
|
||||
CE38A29C2C3FCD72005ED185 /* SwiftyGif in Frameworks */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
@@ -814,8 +816,8 @@
|
||||
64C829992D54AEEE006B9E89 /* libffi.a */,
|
||||
64C829982D54AEED006B9E89 /* libgmp.a */,
|
||||
64C8299C2D54AEEE006B9E89 /* libgmpxx.a */,
|
||||
64C8299A2D54AEEE006B9E89 /* libHSsimplex-chat-6.5.0.15-7J6rfC1qLWr8QkAAXzi4Re-ghc9.6.3.a */,
|
||||
64C8299B2D54AEEE006B9E89 /* libHSsimplex-chat-6.5.0.15-7J6rfC1qLWr8QkAAXzi4Re.a */,
|
||||
64C8299A2D54AEEE006B9E89 /* libHSsimplex-chat-6.5.1.1-Fx4JRO2FuL8K4q8f3JAaMO-ghc9.6.3.a */,
|
||||
64C8299B2D54AEEE006B9E89 /* libHSsimplex-chat-6.5.1.1-Fx4JRO2FuL8K4q8f3JAaMO.a */,
|
||||
);
|
||||
path = Libraries;
|
||||
sourceTree = "<group>";
|
||||
@@ -957,6 +959,7 @@
|
||||
5C9A5BDA2871E05400A5B906 /* SetNotificationsMode.swift */,
|
||||
5CBD285B29575B8E00EC2CF4 /* WhatsNewView.swift */,
|
||||
640743602CD360E600158442 /* ChooseServerOperators.swift */,
|
||||
E5A0B0022F960000AAAA0001 /* YourNetwork.swift */,
|
||||
E5DBF1922F88169800E1D7FD /* ConnectBannerCard.swift */,
|
||||
);
|
||||
path = Onboarding;
|
||||
@@ -1509,6 +1512,7 @@
|
||||
640417CE2B29B8C200CCB412 /* NewChatView.swift in Sources */,
|
||||
6440CA03288AECA70062C672 /* AddGroupMembersView.swift in Sources */,
|
||||
640743612CD360E600158442 /* ChooseServerOperators.swift in Sources */,
|
||||
E5A0B0012F960000AAAA0001 /* YourNetwork.swift in Sources */,
|
||||
64A779FE2DC3AFF200FDEF2F /* MemberSupportChatToolbar.swift in Sources */,
|
||||
5C3F1D58284363C400EC8A82 /* PrivacySettings.swift in Sources */,
|
||||
E5E418012F83D2CA00252B9E /* OnboardingCards.swift in Sources */,
|
||||
@@ -2065,7 +2069,7 @@
|
||||
CLANG_TIDY_MISC_REDUNDANT_EXPRESSION = YES;
|
||||
CODE_SIGN_ENTITLEMENTS = "SimpleX (iOS).entitlements";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 326;
|
||||
CURRENT_PROJECT_VERSION = 331;
|
||||
DEAD_CODE_STRIPPING = YES;
|
||||
DEVELOPMENT_TEAM = 5NN7GUYB6T;
|
||||
ENABLE_BITCODE = NO;
|
||||
@@ -2090,7 +2094,7 @@
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
LLVM_LTO = YES_THIN;
|
||||
MARKETING_VERSION = 6.5;
|
||||
MARKETING_VERSION = 6.5.1;
|
||||
OTHER_LDFLAGS = "-Wl,-stack_size,0x1000000";
|
||||
PRODUCT_BUNDLE_IDENTIFIER = chat.simplex.app;
|
||||
PRODUCT_NAME = SimpleX;
|
||||
@@ -2115,7 +2119,7 @@
|
||||
CLANG_TIDY_MISC_REDUNDANT_EXPRESSION = YES;
|
||||
CODE_SIGN_ENTITLEMENTS = "SimpleX (iOS).entitlements";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 326;
|
||||
CURRENT_PROJECT_VERSION = 331;
|
||||
DEAD_CODE_STRIPPING = YES;
|
||||
DEVELOPMENT_TEAM = 5NN7GUYB6T;
|
||||
ENABLE_BITCODE = NO;
|
||||
@@ -2140,7 +2144,7 @@
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
LLVM_LTO = YES;
|
||||
MARKETING_VERSION = 6.5;
|
||||
MARKETING_VERSION = 6.5.1;
|
||||
OTHER_LDFLAGS = "-Wl,-stack_size,0x1000000";
|
||||
PRODUCT_BUNDLE_IDENTIFIER = chat.simplex.app;
|
||||
PRODUCT_NAME = SimpleX;
|
||||
@@ -2157,11 +2161,11 @@
|
||||
buildSettings = {
|
||||
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 326;
|
||||
CURRENT_PROJECT_VERSION = 331;
|
||||
DEVELOPMENT_TEAM = 5NN7GUYB6T;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 15.0;
|
||||
MARKETING_VERSION = 6.5;
|
||||
MARKETING_VERSION = 6.5.1;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = "chat.simplex.Tests-iOS";
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SDKROOT = iphoneos;
|
||||
@@ -2177,11 +2181,11 @@
|
||||
buildSettings = {
|
||||
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 326;
|
||||
CURRENT_PROJECT_VERSION = 331;
|
||||
DEVELOPMENT_TEAM = 5NN7GUYB6T;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 15.0;
|
||||
MARKETING_VERSION = 6.5;
|
||||
MARKETING_VERSION = 6.5.1;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = "chat.simplex.Tests-iOS";
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SDKROOT = iphoneos;
|
||||
@@ -2202,7 +2206,7 @@
|
||||
CODE_SIGN_ENTITLEMENTS = "SimpleX NSE/SimpleX NSE.entitlements";
|
||||
CODE_SIGN_IDENTITY = "Apple Development";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 326;
|
||||
CURRENT_PROJECT_VERSION = 331;
|
||||
DEVELOPMENT_TEAM = 5NN7GUYB6T;
|
||||
ENABLE_BITCODE = NO;
|
||||
GCC_OPTIMIZATION_LEVEL = s;
|
||||
@@ -2217,7 +2221,7 @@
|
||||
"@executable_path/../../Frameworks",
|
||||
);
|
||||
LLVM_LTO = YES;
|
||||
MARKETING_VERSION = 6.5;
|
||||
MARKETING_VERSION = 6.5.1;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = "chat.simplex.app.SimpleX-NSE";
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||
@@ -2239,7 +2243,7 @@
|
||||
CODE_SIGN_ENTITLEMENTS = "SimpleX NSE/SimpleX NSE.entitlements";
|
||||
CODE_SIGN_IDENTITY = "Apple Development";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 326;
|
||||
CURRENT_PROJECT_VERSION = 331;
|
||||
DEVELOPMENT_TEAM = 5NN7GUYB6T;
|
||||
ENABLE_BITCODE = NO;
|
||||
ENABLE_CODE_COVERAGE = NO;
|
||||
@@ -2254,7 +2258,7 @@
|
||||
"@executable_path/../../Frameworks",
|
||||
);
|
||||
LLVM_LTO = YES;
|
||||
MARKETING_VERSION = 6.5;
|
||||
MARKETING_VERSION = 6.5.1;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = "chat.simplex.app.SimpleX-NSE";
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||
@@ -2276,7 +2280,7 @@
|
||||
CLANG_TIDY_BUGPRONE_REDUNDANT_BRANCH_CONDITION = YES;
|
||||
CLANG_TIDY_MISC_REDUNDANT_EXPRESSION = YES;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 326;
|
||||
CURRENT_PROJECT_VERSION = 331;
|
||||
DEFINES_MODULE = YES;
|
||||
DEVELOPMENT_TEAM = 5NN7GUYB6T;
|
||||
DYLIB_COMPATIBILITY_VERSION = 1;
|
||||
@@ -2302,7 +2306,7 @@
|
||||
"$(PROJECT_DIR)/Libraries/sim",
|
||||
);
|
||||
LLVM_LTO = YES;
|
||||
MARKETING_VERSION = 6.5;
|
||||
MARKETING_VERSION = 6.5.1;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = chat.simplex.SimpleXChat;
|
||||
PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)";
|
||||
SDKROOT = iphoneos;
|
||||
@@ -2327,7 +2331,7 @@
|
||||
CLANG_TIDY_BUGPRONE_REDUNDANT_BRANCH_CONDITION = YES;
|
||||
CLANG_TIDY_MISC_REDUNDANT_EXPRESSION = YES;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 326;
|
||||
CURRENT_PROJECT_VERSION = 331;
|
||||
DEFINES_MODULE = YES;
|
||||
DEVELOPMENT_TEAM = 5NN7GUYB6T;
|
||||
DYLIB_COMPATIBILITY_VERSION = 1;
|
||||
@@ -2354,7 +2358,7 @@
|
||||
"$(PROJECT_DIR)/Libraries/sim",
|
||||
);
|
||||
LLVM_LTO = YES;
|
||||
MARKETING_VERSION = 6.5;
|
||||
MARKETING_VERSION = 6.5.1;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = chat.simplex.SimpleXChat;
|
||||
PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)";
|
||||
SDKROOT = iphoneos;
|
||||
@@ -2381,7 +2385,7 @@
|
||||
CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
|
||||
CODE_SIGN_ENTITLEMENTS = "SimpleX SE/SimpleX SE.entitlements";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 326;
|
||||
CURRENT_PROJECT_VERSION = 331;
|
||||
DEVELOPMENT_TEAM = 5NN7GUYB6T;
|
||||
ENABLE_USER_SCRIPT_SANDBOXING = YES;
|
||||
GCC_C_LANGUAGE_STANDARD = gnu17;
|
||||
@@ -2396,7 +2400,7 @@
|
||||
"@executable_path/../../Frameworks",
|
||||
);
|
||||
LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
|
||||
MARKETING_VERSION = 6.5;
|
||||
MARKETING_VERSION = 6.5.1;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = "chat.simplex.app.SimpleX-SE";
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SDKROOT = iphoneos;
|
||||
@@ -2415,7 +2419,7 @@
|
||||
CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
|
||||
CODE_SIGN_ENTITLEMENTS = "SimpleX SE/SimpleX SE.entitlements";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 326;
|
||||
CURRENT_PROJECT_VERSION = 331;
|
||||
DEVELOPMENT_TEAM = 5NN7GUYB6T;
|
||||
ENABLE_USER_SCRIPT_SANDBOXING = YES;
|
||||
GCC_C_LANGUAGE_STANDARD = gnu17;
|
||||
@@ -2430,7 +2434,7 @@
|
||||
"@executable_path/../../Frameworks",
|
||||
);
|
||||
LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
|
||||
MARKETING_VERSION = 6.5;
|
||||
MARKETING_VERSION = 6.5.1;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = "chat.simplex.app.SimpleX-SE";
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SDKROOT = iphoneos;
|
||||
|
||||
@@ -867,6 +867,7 @@ public enum GroupFeature: String, Decodable, Feature, Hashable {
|
||||
case simplexLinks
|
||||
case reports
|
||||
case history
|
||||
case support
|
||||
|
||||
public var id: Self { self }
|
||||
|
||||
@@ -888,10 +889,13 @@ public enum GroupFeature: String, Decodable, Feature, Hashable {
|
||||
case .simplexLinks: true
|
||||
case .reports: false
|
||||
case .history: false
|
||||
case .support: false
|
||||
}
|
||||
}
|
||||
|
||||
public var text: String {
|
||||
public var text: String { text(isChannel: false) }
|
||||
|
||||
public func text(isChannel: Bool) -> String {
|
||||
switch self {
|
||||
case .timedMessages: return NSLocalizedString("Disappearing messages", comment: "chat feature")
|
||||
case .directMessages: return NSLocalizedString("Direct messages", comment: "chat feature")
|
||||
@@ -900,8 +904,11 @@ public enum GroupFeature: String, Decodable, Feature, Hashable {
|
||||
case .voice: return NSLocalizedString("Voice messages", comment: "chat feature")
|
||||
case .files: return NSLocalizedString("Files and media", comment: "chat feature")
|
||||
case .simplexLinks: return NSLocalizedString("SimpleX links", comment: "chat feature")
|
||||
case .reports: return NSLocalizedString("Member reports", comment: "chat feature")
|
||||
case .reports: return isChannel
|
||||
? NSLocalizedString("Subscriber reports", comment: "chat feature")
|
||||
: NSLocalizedString("Member reports", comment: "chat feature")
|
||||
case .history: return NSLocalizedString("Visible history", comment: "chat feature")
|
||||
case .support: return NSLocalizedString("Chat with admins", comment: "chat feature")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -916,6 +923,7 @@ public enum GroupFeature: String, Decodable, Feature, Hashable {
|
||||
case .simplexLinks: return "link.circle"
|
||||
case .reports: return "flag"
|
||||
case .history: return "clock"
|
||||
case .support: return "questionmark.circle"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -930,6 +938,7 @@ public enum GroupFeature: String, Decodable, Feature, Hashable {
|
||||
case .simplexLinks: return "link.circle.fill"
|
||||
case .reports: return "flag.fill"
|
||||
case .history: return "clock.fill"
|
||||
case .support: return "questionmark.circle.fill"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -940,7 +949,7 @@ public enum GroupFeature: String, Decodable, Feature, Hashable {
|
||||
}
|
||||
}
|
||||
|
||||
public func enableDescription(_ enabled: GroupFeatureEnabled, _ canEdit: Bool) -> LocalizedStringKey {
|
||||
public func enableDescription(_ enabled: GroupFeatureEnabled, _ canEdit: Bool, isChannel: Bool = false) -> LocalizedStringKey {
|
||||
if canEdit {
|
||||
switch self {
|
||||
case .timedMessages:
|
||||
@@ -950,8 +959,12 @@ public enum GroupFeature: String, Decodable, Feature, Hashable {
|
||||
}
|
||||
case .directMessages:
|
||||
switch enabled {
|
||||
case .on: return "Allow sending direct messages to members."
|
||||
case .off: return "Prohibit sending direct messages to members."
|
||||
case .on: return isChannel
|
||||
? "Allow sending direct messages to subscribers."
|
||||
: "Allow sending direct messages to members."
|
||||
case .off: return isChannel
|
||||
? "Prohibit sending direct messages to subscribers."
|
||||
: "Prohibit sending direct messages to members."
|
||||
}
|
||||
case .fullDelete:
|
||||
switch enabled {
|
||||
@@ -985,56 +998,96 @@ public enum GroupFeature: String, Decodable, Feature, Hashable {
|
||||
}
|
||||
case .history:
|
||||
switch enabled {
|
||||
case .on: return "Send up to 100 last messages to new members."
|
||||
case .off: return "Do not send history to new members."
|
||||
case .on: return isChannel
|
||||
? "Send up to 100 last messages to new subscribers."
|
||||
: "Send up to 100 last messages to new members."
|
||||
case .off: return isChannel
|
||||
? "Do not send history to new subscribers."
|
||||
: "Do not send history to new members."
|
||||
}
|
||||
case .support:
|
||||
switch enabled {
|
||||
case .on: return isChannel
|
||||
? "Allow subscribers to chat with admins."
|
||||
: "Allow members to chat with admins."
|
||||
case .off: return "Prohibit chats with admins."
|
||||
}
|
||||
}
|
||||
} else {
|
||||
switch self {
|
||||
case .timedMessages:
|
||||
switch enabled {
|
||||
case .on: return "Members can send disappearing messages."
|
||||
case .on: return isChannel
|
||||
? "Subscribers can send disappearing messages."
|
||||
: "Members can send disappearing messages."
|
||||
case .off: return "Disappearing messages are prohibited."
|
||||
}
|
||||
case .directMessages:
|
||||
switch enabled {
|
||||
case .on: return "Members can send direct messages."
|
||||
case .off: return "Direct messages between members are prohibited."
|
||||
case .on: return isChannel
|
||||
? "Subscribers can send direct messages."
|
||||
: "Members can send direct messages."
|
||||
case .off: return isChannel
|
||||
? "Direct messages between subscribers are prohibited."
|
||||
: "Direct messages between members are prohibited."
|
||||
}
|
||||
case .fullDelete:
|
||||
switch enabled {
|
||||
case .on: return "Members can irreversibly delete sent messages. (24 hours)"
|
||||
case .on: return isChannel
|
||||
? "Subscribers can irreversibly delete sent messages. (24 hours)"
|
||||
: "Members can irreversibly delete sent messages. (24 hours)"
|
||||
case .off: return "Irreversible message deletion is prohibited."
|
||||
}
|
||||
case .reactions:
|
||||
switch enabled {
|
||||
case .on: return "Members can add message reactions."
|
||||
case .on: return isChannel
|
||||
? "Subscribers can add message reactions."
|
||||
: "Members can add message reactions."
|
||||
case .off: return "Message reactions are prohibited."
|
||||
}
|
||||
case .voice:
|
||||
switch enabled {
|
||||
case .on: return "Members can send voice messages."
|
||||
case .on: return isChannel
|
||||
? "Subscribers can send voice messages."
|
||||
: "Members can send voice messages."
|
||||
case .off: return "Voice messages are prohibited."
|
||||
}
|
||||
case .files:
|
||||
switch enabled {
|
||||
case .on: return "Members can send files and media."
|
||||
case .on: return isChannel
|
||||
? "Subscribers can send files and media."
|
||||
: "Members can send files and media."
|
||||
case .off: return "Files and media are prohibited."
|
||||
}
|
||||
case .simplexLinks:
|
||||
switch enabled {
|
||||
case .on: return "Members can send SimpleX links."
|
||||
case .on: return isChannel
|
||||
? "Subscribers can send SimpleX links."
|
||||
: "Members can send SimpleX links."
|
||||
case .off: return "SimpleX links are prohibited."
|
||||
}
|
||||
case .reports:
|
||||
switch enabled {
|
||||
case .on: return "Members can report messsages to moderators."
|
||||
case .on: return isChannel
|
||||
? "Subscribers can report messsages to moderators."
|
||||
: "Members can report messsages to moderators."
|
||||
case .off: return "Reporting messages to moderators is prohibited."
|
||||
}
|
||||
case .history:
|
||||
switch enabled {
|
||||
case .on: return "Up to 100 last messages are sent to new members."
|
||||
case .off: return "History is not sent to new members."
|
||||
case .on: return isChannel
|
||||
? "Up to 100 last messages are sent to new subscribers."
|
||||
: "Up to 100 last messages are sent to new members."
|
||||
case .off: return isChannel
|
||||
? "History is not sent to new subscribers."
|
||||
: "History is not sent to new members."
|
||||
}
|
||||
case .support:
|
||||
switch enabled {
|
||||
case .on: return isChannel
|
||||
? "Subscribers can chat with admins."
|
||||
: "Members can chat with admins."
|
||||
case .off: return "Chats with admins are prohibited."
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1190,6 +1243,7 @@ public struct FullGroupPreferences: Decodable, Equatable, Hashable {
|
||||
public var simplexLinks: RoleGroupPreference
|
||||
public var reports: GroupPreference
|
||||
public var history: GroupPreference
|
||||
public var support: GroupPreference
|
||||
public var commands: [ChatBotCommand]
|
||||
|
||||
public init(
|
||||
@@ -1202,6 +1256,7 @@ public struct FullGroupPreferences: Decodable, Equatable, Hashable {
|
||||
simplexLinks: RoleGroupPreference,
|
||||
reports: GroupPreference,
|
||||
history: GroupPreference,
|
||||
support: GroupPreference,
|
||||
commands: [ChatBotCommand]
|
||||
) {
|
||||
self.timedMessages = timedMessages
|
||||
@@ -1213,6 +1268,7 @@ public struct FullGroupPreferences: Decodable, Equatable, Hashable {
|
||||
self.simplexLinks = simplexLinks
|
||||
self.reports = reports
|
||||
self.history = history
|
||||
self.support = support
|
||||
self.commands = commands
|
||||
}
|
||||
|
||||
@@ -1226,6 +1282,7 @@ public struct FullGroupPreferences: Decodable, Equatable, Hashable {
|
||||
simplexLinks: RoleGroupPreference(enable: .on, role: nil),
|
||||
reports: GroupPreference(enable: .on),
|
||||
history: GroupPreference(enable: .on),
|
||||
support: GroupPreference(enable: .on),
|
||||
commands: []
|
||||
)
|
||||
}
|
||||
@@ -1240,6 +1297,7 @@ public struct GroupPreferences: Codable, Hashable {
|
||||
public var simplexLinks: RoleGroupPreference?
|
||||
public var reports: GroupPreference?
|
||||
public var history: GroupPreference?
|
||||
public var support: GroupPreference?
|
||||
public var commands: [ChatBotCommand]?
|
||||
|
||||
public init(
|
||||
@@ -1252,6 +1310,7 @@ public struct GroupPreferences: Codable, Hashable {
|
||||
simplexLinks: RoleGroupPreference? = nil,
|
||||
reports: GroupPreference? = nil,
|
||||
history: GroupPreference? = nil,
|
||||
support: GroupPreference? = nil,
|
||||
commands: [ChatBotCommand]? = nil
|
||||
) {
|
||||
self.timedMessages = timedMessages
|
||||
@@ -1263,6 +1322,7 @@ public struct GroupPreferences: Codable, Hashable {
|
||||
self.simplexLinks = simplexLinks
|
||||
self.reports = reports
|
||||
self.history = history
|
||||
self.support = support
|
||||
self.commands = commands
|
||||
}
|
||||
|
||||
@@ -1276,6 +1336,7 @@ public struct GroupPreferences: Codable, Hashable {
|
||||
simplexLinks: RoleGroupPreference(enable: .on, role: nil),
|
||||
reports: GroupPreference(enable: .on),
|
||||
history: GroupPreference(enable: .on),
|
||||
support: GroupPreference(enable: .on),
|
||||
commands: nil
|
||||
)
|
||||
}
|
||||
@@ -1760,6 +1821,18 @@ public enum ChatInfo: Identifiable, Decodable, NamedChat, Hashable {
|
||||
}
|
||||
}
|
||||
|
||||
public var sendAsGroup: Bool {
|
||||
if let g = groupInfo, g.useRelays && g.membership.memberRole >= .owner {
|
||||
switch groupChatScope() {
|
||||
case .none: true
|
||||
case .memberSupport: false
|
||||
case .reports: false
|
||||
}
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
public func ntfsEnabled(chatItem: ChatItem) -> Bool {
|
||||
ntfsEnabled(chatItem.meta.userMention)
|
||||
}
|
||||
@@ -4092,8 +4165,8 @@ public enum CIContent: Decodable, ItemContent, Hashable {
|
||||
case .rcvBlocked: return NSLocalizedString("blocked by admin", comment: "blocked chat item")
|
||||
case let .sndDirectE2EEInfo(e2eeInfo): return directE2EEInfoStr(e2eeInfo)
|
||||
case let .rcvDirectE2EEInfo(e2eeInfo): return directE2EEInfoStr(e2eeInfo)
|
||||
case .sndGroupE2EEInfo: return e2eeInfoNoPQStr
|
||||
case .rcvGroupE2EEInfo: return e2eeInfoNoPQStr
|
||||
case let .sndGroupE2EEInfo(e2eeInfo): return groupE2EEInfoStr(e2eeInfo)
|
||||
case let .rcvGroupE2EEInfo(e2eeInfo): return groupE2EEInfoStr(e2eeInfo)
|
||||
case .chatBanner: return ""
|
||||
case .invalidJSON: return NSLocalizedString("invalid data", comment: "invalid chat item")
|
||||
}
|
||||
@@ -4105,6 +4178,12 @@ public enum CIContent: Decodable, ItemContent, Hashable {
|
||||
: e2eeInfoNoPQStr
|
||||
}
|
||||
|
||||
private func groupE2EEInfoStr(_ e2eeInfo: E2EEInfo) -> String {
|
||||
e2eeInfo.public == true
|
||||
? NSLocalizedString("Messages in this channel are not end-to-end encrypted. Chat relays can see these messages.", comment: "E2EE info chat item")
|
||||
: e2eeInfoNoPQStr
|
||||
}
|
||||
|
||||
private var e2eeInfoNoPQStr: String {
|
||||
NSLocalizedString("This chat is protected by end-to-end encryption.", comment: "E2EE info chat item")
|
||||
}
|
||||
@@ -5319,6 +5398,7 @@ public enum CIGroupInvitationStatus: String, Decodable, Hashable {
|
||||
|
||||
public struct E2EEInfo: Decodable, Hashable {
|
||||
public var pqEnabled: Bool?
|
||||
public var `public`: Bool?
|
||||
}
|
||||
|
||||
public enum RcvDirectEvent: Decodable, Hashable {
|
||||
|
||||
@@ -25,6 +25,7 @@ extension ChatLike {
|
||||
case .files: p.files.on(for: groupInfo.membership)
|
||||
case .simplexLinks: p.simplexLinks.on(for: groupInfo.membership)
|
||||
case .history: p.history.on
|
||||
case .support: p.support.on
|
||||
case .reports: p.reports.on
|
||||
}
|
||||
} else {
|
||||
|
||||
@@ -44,30 +44,53 @@ private func srgbToP3(red r: Double, green g: Double, blue b: Double, opacity a:
|
||||
return Color(.sRGB, red: r, green: g, blue: b, opacity: a)
|
||||
}
|
||||
|
||||
/// Create a Display P3 Color from oklch components. H in degrees.
|
||||
func oklch(_ L: Double, _ C: Double, _ H: Double, alpha: Double = 1.0) -> Color {
|
||||
// Create a Display P3 Color from oklch components. H in degrees
|
||||
public func oklch(_ L: Double, _ C: Double, _ H: Double, alpha: Double = 1.0) -> Color {
|
||||
let hRad = H * .pi / 180.0
|
||||
let a = C * cos(hRad)
|
||||
let b = C * sin(hRad)
|
||||
// oklab → LMS (Ottosson 2021)
|
||||
let l_ = L + 0.3963377774 * a + 0.2158037573 * b
|
||||
let m_ = L - 0.1055613458 * a - 0.0638541728 * b
|
||||
let s_ = L - 0.0894841775 * a - 1.2914855480 * b
|
||||
let l = l_ * l_ * l_
|
||||
let m = m_ * m_ * m_
|
||||
let s = s_ * s_ * s_
|
||||
// LMS → linear Display P3 (direct, no sRGB clamping)
|
||||
let lr = 3.1281105148 * l - 2.2570749853 * m + 0.1293047593 * s
|
||||
let lg = -1.0911282009 * l + 2.4132668169 * m - 0.3221681599 * s
|
||||
let lb = -0.0260136845 * l - 0.5080276339 * m + 1.5333166364 * s
|
||||
let cosH = cos(hRad)
|
||||
let sinH = sin(hRad)
|
||||
|
||||
func linearP3(C: Double) -> (Double, Double, Double) {
|
||||
let a = C * cosH
|
||||
let b = C * sinH
|
||||
// oklab → LMS (Ottosson 2021)
|
||||
let l_ = L + 0.3963377774 * a + 0.2158037573 * b
|
||||
let m_ = L - 0.1055613458 * a - 0.0638541728 * b
|
||||
let s_ = L - 0.0894841775 * a - 1.2914855480 * b
|
||||
let l = l_ * l_ * l_
|
||||
let m = m_ * m_ * m_
|
||||
let s = s_ * s_ * s_
|
||||
// LMS → linear Display P3 (direct, no sRGB clamping)
|
||||
return (
|
||||
3.1281105148 * l - 2.2570749853 * m + 0.1293047593 * s,
|
||||
-1.0911282009 * l + 2.4132668169 * m - 0.3221681599 * s,
|
||||
-0.0260136845 * l - 0.5080276339 * m + 1.5333166364 * s
|
||||
)
|
||||
}
|
||||
|
||||
func inGamut(_ r: Double, _ g: Double, _ b: Double) -> Bool {
|
||||
r >= 0 && r <= 1 && g >= 0 && g <= 1 && b >= 0 && b <= 1
|
||||
}
|
||||
|
||||
// linear P3 → gamma-encoded P3 (same transfer function as sRGB)
|
||||
func gammaEncode(_ x: Double) -> Double { x >= 0.0031308 ? 1.055 * pow(x, 1.0 / 2.4) - 0.055 : 12.92 * x }
|
||||
return Color(.displayP3,
|
||||
red: gammaEncode(min(max(lr, 0), 1)),
|
||||
green: gammaEncode(min(max(lg, 0), 1)),
|
||||
blue: gammaEncode(min(max(lb, 0), 1)),
|
||||
opacity: alpha
|
||||
)
|
||||
func gammaEncode(_ x: Double) -> Double {
|
||||
x >= 0.0031308
|
||||
? 1.055 * pow(min(x, 1.0), 1.0 / 2.4) - 0.055
|
||||
: 12.92 * max(x, 0)
|
||||
}
|
||||
|
||||
var (r, g, b) = linearP3(C: C)
|
||||
if !inGamut(r, g, b) {
|
||||
var lo = 0.0, hi = C
|
||||
while hi - lo > 1e-5 {
|
||||
let mid = (lo + hi) / 2
|
||||
let (mr, mg, mb) = linearP3(C: mid)
|
||||
if inGamut(mr, mg, mb) { lo = mid; r = mr; g = mg; b = mb }
|
||||
else { hi = mid }
|
||||
}
|
||||
}
|
||||
|
||||
return Color(.displayP3, red: gammaEncode(r), green: gammaEncode(g), blue: gammaEncode(b), opacity: alpha)
|
||||
}
|
||||
|
||||
extension Color {
|
||||
@@ -155,7 +178,7 @@ extension Color {
|
||||
}
|
||||
}
|
||||
|
||||
extension String {
|
||||
public extension String {
|
||||
func colorFromReadableHex() -> Color {
|
||||
// https://stackoverflow.com/a/56874327
|
||||
let hex = self.trimmingCharacters(in: ["#", " "])
|
||||
|
||||
@@ -25,15 +25,9 @@
|
||||
/* No comment provided by engineer. */
|
||||
"(this device v%@)" = "(това устройство v%@)";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"[Contribute](https://github.com/simplex-chat/simplex-chat#contribute)" = "[Допринеси](https://github.com/simplex-chat/simplex-chat#contribute)";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"[Send us email](mailto:chat@simplex.chat)" = "[Изпратете ни имейл](mailto:chat@simplex.chat)";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"[Star on GitHub](https://github.com/simplex-chat/simplex-chat)" = "[Звезда в GitHub](https://github.com/simplex-chat/simplex-chat)";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"**Create 1-time link**: to create and share a new invitation link." = "**Добави контакт**: за създаване на нов линк.";
|
||||
|
||||
@@ -389,9 +383,6 @@ swipe action */
|
||||
/* No comment provided by engineer. */
|
||||
"Active connections" = "Активни връзки";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Add address to your profile, so that your contacts can share it with other people. Profile update will be sent to your contacts." = "Добавете адрес към вашия профил, така че вашите контакти да могат да го споделят с други хора. Актуализацията на профила ще бъде изпратена до вашите контакти.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Add friends" = "Добави приятели";
|
||||
|
||||
@@ -635,9 +626,6 @@ swipe action */
|
||||
/* No comment provided by engineer. */
|
||||
"Answer call" = "Отговор на повикване";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Anybody can host servers." = "Протокол и код с отворен код – всеки може да оперира собствени сървъри.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"App build: %@" = "Компилация на приложението: %@";
|
||||
|
||||
@@ -873,7 +861,7 @@ marked deleted chat item preview text */
|
||||
/* No comment provided by engineer. */
|
||||
"Bulgarian, Finnish, Thai and Ukrainian - thanks to the users and [Weblate](https://github.com/simplex-chat/simplex-chat/tree/stable#help-translating-simplex-chat)!" = "Български, финландски, тайландски и украински - благодарение на потребителите и [Weblate](https://github.com/simplex-chat/simplex-chat/tree/stable#help-translating-simplex-chat)!";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
/* chat link info line */
|
||||
"Business address" = "Бизнес адрес";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
@@ -888,9 +876,6 @@ marked deleted chat item preview text */
|
||||
/* No comment provided by engineer. */
|
||||
"By chat profile (default) or [by connection](https://simplex.chat/blog/20230204-simplex-chat-v4-5-user-chat-profiles.html#transport-isolation) (BETA)." = "Чрез чат профил (по подразбиране) или [чрез връзка](https://simplex.chat/blog/20230204-simplex-chat-v4-5-user-chat-profiles.html#transport-isolation) (БЕТА).";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"By using SimpleX Chat you agree to:\n- send only legal content in public groups.\n- respect other users – no spam." = "С използването на SimpleX Chat вие се съгласявате със:\n- изпращане само на легално съдържание в публични групи.\n- уважение към другите потребители – без спам.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Call already ended!" = "Разговорът вече приключи!";
|
||||
|
||||
@@ -1068,7 +1053,8 @@ set passcode view */
|
||||
/* No comment provided by engineer. */
|
||||
"Chat will be deleted for you - this cannot be undone!" = "Чатът ще бъде изтрит за вас - това не може да бъде отменено!";
|
||||
|
||||
/* chat toolbar */
|
||||
/* chat feature
|
||||
chat toolbar */
|
||||
"Chat with admins" = "Чат с администраторите";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
@@ -1182,9 +1168,6 @@ set passcode view */
|
||||
/* No comment provided by engineer. */
|
||||
"Configure ICE servers" = "Конфигурирай ICE сървъри";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Configure server operators" = "Конфигуриране на сървърни оператори";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Confirm" = "Потвърди";
|
||||
|
||||
@@ -1318,7 +1301,7 @@ server test step */
|
||||
/* alert title */
|
||||
"Connection error" = "Грешка при свързване";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
/* conn error description */
|
||||
"Connection error (AUTH)" = "Грешка при свързване (AUTH)";
|
||||
|
||||
/* chat list item title (it should not be shown */
|
||||
@@ -1384,6 +1367,9 @@ server test step */
|
||||
/* No comment provided by engineer. */
|
||||
"Continue" = "Продължи";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Contribute" = "Допринеси";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Copy" = "Копирай";
|
||||
|
||||
@@ -1393,9 +1379,6 @@ server test step */
|
||||
/* alert message */
|
||||
"Correct name to %@?" = "Поправи име на %@?";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Create" = "Създаване";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Create 1-time link" = "Създаване на еднократна препратка";
|
||||
|
||||
@@ -1522,9 +1505,6 @@ server test step */
|
||||
/* time unit */
|
||||
"days" = "дни";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Decentralized" = "Децентрализиран";
|
||||
|
||||
/* message decrypt error item */
|
||||
"Decryption error" = "Грешка при декриптиране";
|
||||
|
||||
@@ -1802,7 +1782,7 @@ chat item action */
|
||||
/* No comment provided by engineer. */
|
||||
"Edit group profile" = "Редактирай групов профил";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
/* alert button */
|
||||
"Enable" = "Активирай";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
@@ -1826,9 +1806,6 @@ chat item action */
|
||||
/* No comment provided by engineer. */
|
||||
"Enable lock" = "Активирай заключване";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Enable notifications" = "Активирай известията";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Enable periodic notifications?" = "Активирай периодични известия?";
|
||||
|
||||
@@ -1964,7 +1941,7 @@ chat item action */
|
||||
/* No comment provided by engineer. */
|
||||
"error" = "грешка";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
/* conn error description */
|
||||
"Error" = "Грешка при свързване със сървъра";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
@@ -2306,7 +2283,7 @@ server test error */
|
||||
/* No comment provided by engineer. */
|
||||
"Group invitation is no longer valid, it was removed by sender." = "Груповата покана вече е невалидна, премахната е от подателя.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
/* chat link info line */
|
||||
"Group link" = "Групов линк";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
@@ -2414,9 +2391,6 @@ server test error */
|
||||
/* No comment provided by engineer. */
|
||||
"Immediately" = "Веднага";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Immune to spam" = "Защитен от спам и злоупотреби";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Import" = "Импортиране";
|
||||
|
||||
@@ -2502,7 +2476,7 @@ server test error */
|
||||
"Initial role" = "Първоначална роля";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Install [SimpleX Chat for terminal](https://github.com/simplex-chat/simplex-chat)" = "Инсталирайте [SimpleX Chat за терминал](https://github.com/simplex-chat/simplex-chat)";
|
||||
"Install SimpleX Chat for terminal" = "Инсталирайте SimpleX Chat за терминал";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Instant" = "Мигновено";
|
||||
@@ -2519,7 +2493,7 @@ server test error */
|
||||
/* No comment provided by engineer. */
|
||||
"invalid chat data" = "невалидни данни за чат";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
/* conn error description */
|
||||
"Invalid connection link" = "Невалиден линк за връзка";
|
||||
|
||||
/* invalid chat item */
|
||||
@@ -2825,9 +2799,6 @@ server test error */
|
||||
/* No comment provided by engineer. */
|
||||
"Migrate device" = "Мигрирай устройството";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Migrate from another device" = "Мигриране от друго устройство";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Migrate here" = "Мигрирай тук";
|
||||
|
||||
@@ -3002,9 +2973,6 @@ server test error */
|
||||
/* copied message info in history */
|
||||
"no text" = "няма текст";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"No user identifiers." = "Първата платформа без никакви потребителски идентификатори – поверителна по дизайн.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Not compatible!" = "Несъвместим!";
|
||||
|
||||
@@ -3040,7 +3008,7 @@ alert button
|
||||
new chat action */
|
||||
"Ok" = "Ок";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
/* alert button */
|
||||
"OK" = "ОК";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
@@ -3103,7 +3071,8 @@ new chat action */
|
||||
/* No comment provided by engineer. */
|
||||
"Only your contact can send voice messages." = "Само вашият контакт може да изпраща гласови съобщения.";
|
||||
|
||||
/* alert action */
|
||||
/* alert action
|
||||
alert button */
|
||||
"Open" = "Отвори";
|
||||
|
||||
/* new chat action */
|
||||
@@ -3250,9 +3219,6 @@ new chat action */
|
||||
/* No comment provided by engineer. */
|
||||
"Privacy & security" = "Поверителност и сигурност";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Privacy redefined" = "Поверителността преосмислена";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Private filenames" = "Поверителни имена на файлове";
|
||||
|
||||
@@ -3271,9 +3237,6 @@ new chat action */
|
||||
/* No comment provided by engineer. */
|
||||
"Profile password" = "Профилна парола";
|
||||
|
||||
/* alert message */
|
||||
"Profile update will be sent to your contacts." = "Актуализацията на профила ще бъде изпратена до вашите контакти.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Prohibit audio/video calls." = "Забрани аудио/видео разговорите.";
|
||||
|
||||
@@ -3338,16 +3301,10 @@ new chat action */
|
||||
"Read more" = "Прочетете още";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Read more in [User Guide](https://simplex.chat/docs/guide/chat-profiles.html#incognito-mode)." = "Прочетете повече в [Ръководство за потребителя](https://simplex.chat/docs/guide/chat-profiles.html#incognito-mode).";
|
||||
"Read more in our GitHub repository." = "Прочетете повече в нашето GitHub хранилище.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Read more in [User Guide](https://simplex.chat/docs/guide/making-connections.html#comparison-of-1-time-invitation-links-and-simplex-contact-addresses)." = "Прочетете повече в [Ръководство за потребителя](https://simplex.chat/docs/guide/making-connections.html#comparison-of-1-time-invitation-links-and-simplex-contact-addresses).";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Read more in [User Guide](https://simplex.chat/docs/guide/readme.html#connect-to-friends)." = "Прочетете повече в [Ръководство на потребителя](https://simplex.chat/docs/guide/readme.html#connect-to-friends).";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Read more in our [GitHub repository](https://github.com/simplex-chat/simplex-chat#readme)." = "Прочетете повече в нашето [GitHub хранилище](https://github.com/simplex-chat/simplex-chat#readme).";
|
||||
"Read more in User Guide." = "Прочетете повече в Ръководство за потребителя.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Receipts are disabled" = "Потвърждениeто за доставка е деактивирано";
|
||||
@@ -3782,18 +3739,12 @@ chat item action */
|
||||
/* No comment provided by engineer. */
|
||||
"Share address" = "Сподели адрес";
|
||||
|
||||
/* alert title */
|
||||
"Share address with contacts?" = "Сподели адреса с контактите?";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Share link" = "Сподели линк";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Share this 1-time invite link" = "Сподели този еднократен линк за връзка";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Share with contacts" = "Сподели с контактите";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Show calls in phone history" = "Показване на обажданията в хронологията на телефона";
|
||||
|
||||
@@ -3881,6 +3832,9 @@ chat item action */
|
||||
/* chat item text */
|
||||
"standard end-to-end encryption" = "стандартно криптиране от край до край";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Star on GitHub" = "Звезда в GitHub";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Start chat" = "Започни чат";
|
||||
|
||||
@@ -4020,9 +3974,6 @@ server test failure */
|
||||
/* No comment provided by engineer. */
|
||||
"The encryption is working and the new encryption agreement is not required. It may result in connection errors!" = "Криптирането работи и новото споразумение за криптиране не е необходимо. Това може да доведе до грешки при свързване!";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"The future of messaging" = "Ново поколение поверителни съобщения";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"The hash of the previous message is different." = "Хешът на предишното съобщение е различен.";
|
||||
|
||||
@@ -4251,9 +4202,6 @@ server test failure */
|
||||
/* No comment provided by engineer. */
|
||||
"Use .onion hosts" = "Използвай .onion хостове";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Use chat" = "Използвай чата";
|
||||
|
||||
/* new chat action */
|
||||
"Use current profile" = "Използвай текущия профил";
|
||||
|
||||
@@ -4557,9 +4505,6 @@ server test failure */
|
||||
/* No comment provided by engineer. */
|
||||
"You could not be verified; please try again." = "Не можахте да бъдете потвърдени; Моля, опитайте отново.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"You decide who can connect." = "Хората могат да се свържат с вас само чрез ликовете, които споделяте.";
|
||||
|
||||
/* new chat sheet title */
|
||||
"You have already requested connection!\nRepeat connection request?" = "Вече сте направили заявката за връзка!\nИзпрати отново заявката за свързване?";
|
||||
|
||||
|
||||
@@ -25,15 +25,9 @@
|
||||
/* No comment provided by engineer. */
|
||||
"(this device v%@)" = "(toto zařízení v%@)";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"[Contribute](https://github.com/simplex-chat/simplex-chat#contribute)" = "[Přispějte](https://github.com/simplex-chat/simplex-chat#contribute)";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"[Send us email](mailto:chat@simplex.chat)" = "[Pošlete nám e-mail](mailto:chat@simplex.chat)";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"[Star on GitHub](https://github.com/simplex-chat/simplex-chat)" = "[Hvězda na GitHubu](https://github.com/simplex-chat/simplex-chat)";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"**Create 1-time link**: to create and share a new invitation link." = "**Vytvořit jednorázový odkaz**: pro vytvoření a sdílení nové pozvánky.";
|
||||
|
||||
@@ -374,9 +368,6 @@ swipe action */
|
||||
/* No comment provided by engineer. */
|
||||
"Active connections" = "Aktivní spojení";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Add address to your profile, so that your contacts can share it with other people. Profile update will be sent to your contacts." = "Přidejte adresu do svého profilu, aby ji vaše kontakty mohly sdílet s dalšími lidmi. Aktualizace profilu bude zaslána vašim kontaktům.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Add friends" = "Přidat přátele";
|
||||
|
||||
@@ -581,9 +572,6 @@ swipe action */
|
||||
/* No comment provided by engineer. */
|
||||
"Answer call" = "Přijmout hovor";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Anybody can host servers." = "Servery může provozovat kdokoli.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"App build: %@" = "Sestavení aplikace: %@";
|
||||
|
||||
@@ -683,6 +671,12 @@ swipe action */
|
||||
/* No comment provided by engineer. */
|
||||
"Bad message ID" = "Špatné ID zprávy";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Be free in your network." = "Buďte svobodní ve své síti.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Because we destroyed the power to know who you are. So that your power can never be taken." = "Protože jsme zničili sílu vědět, kdo jste. Aby vám vaši moc nikdo nemohl vzít.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Better calls" = "Lepší volání";
|
||||
|
||||
@@ -746,7 +740,7 @@ swipe action */
|
||||
/* No comment provided by engineer. */
|
||||
"Bulgarian, Finnish, Thai and Ukrainian - thanks to the users and [Weblate](https://github.com/simplex-chat/simplex-chat/tree/stable#help-translating-simplex-chat)!" = "Bulharský, finský, thajský a ukrajinský - díky uživatelům a [Weblate](https://github.com/simplex-chat/simplex-chat/tree/stable#help-translating-simplex-chat)!";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
/* chat link info line */
|
||||
"Business address" = "Obchodní adresa";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
@@ -1017,7 +1011,7 @@ server test step */
|
||||
/* alert title */
|
||||
"Connection error" = "Chyba připojení";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
/* conn error description */
|
||||
"Connection error (AUTH)" = "Chyba spojení (AUTH)";
|
||||
|
||||
/* chat list item title (it should not be shown */
|
||||
@@ -1065,15 +1059,15 @@ server test step */
|
||||
/* No comment provided by engineer. */
|
||||
"Continue" = "Pokračovat";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Contribute" = "Přispějte";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Copy" = "Kopírovat";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Core version: v%@" = "Verze jádra: v%@";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Create" = "Vytvořit";
|
||||
|
||||
/* server test step */
|
||||
"Create file" = "Vytvořit soubor";
|
||||
|
||||
@@ -1179,9 +1173,6 @@ server test step */
|
||||
/* time unit */
|
||||
"days" = "dní";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Decentralized" = "Decentralizované";
|
||||
|
||||
/* message decrypt error item */
|
||||
"Decryption error" = "Chyba dešifrování";
|
||||
|
||||
@@ -1416,7 +1407,7 @@ alert button */
|
||||
/* No comment provided by engineer. */
|
||||
"Edit group profile" = "Upravit profil skupiny";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
/* alert button */
|
||||
"Enable" = "Zapnout";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
@@ -1434,9 +1425,6 @@ alert button */
|
||||
/* No comment provided by engineer. */
|
||||
"Enable lock" = "Povolit zámek";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Enable notifications" = "Povolit upozornění";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Enable periodic notifications?" = "Povolit pravidelná oznámení?";
|
||||
|
||||
@@ -1548,7 +1536,7 @@ alert button */
|
||||
/* No comment provided by engineer. */
|
||||
"error" = "chyba";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
/* conn error description */
|
||||
"Error" = "Chyba";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
@@ -1824,7 +1812,7 @@ server test error */
|
||||
/* No comment provided by engineer. */
|
||||
"Group invitation is no longer valid, it was removed by sender." = "Skupinová pozvánka již není platná, byla odstraněna odesílatelem.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
/* chat link info line */
|
||||
"Group link" = "Odkaz na skupinu";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
@@ -1926,9 +1914,6 @@ server test error */
|
||||
/* No comment provided by engineer. */
|
||||
"Immediately" = "Ihned";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Immune to spam" = "Odolná vůči spamu a zneužití";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Import" = "Import";
|
||||
|
||||
@@ -1993,7 +1978,7 @@ server test error */
|
||||
"Initial role" = "Počáteční role";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Install [SimpleX Chat for terminal](https://github.com/simplex-chat/simplex-chat)" = "Nainstalujte [SimpleX Chat pro terminál](https://github.com/simplex-chat/simplex-chat)";
|
||||
"Install SimpleX Chat for terminal" = "Nainstalujte SimpleX Chat pro terminál";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Instant" = "Okamžitě";
|
||||
@@ -2010,7 +1995,7 @@ server test error */
|
||||
/* No comment provided by engineer. */
|
||||
"invalid chat data" = "neplatná chat data";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
/* conn error description */
|
||||
"Invalid connection link" = "Neplatný odkaz na spojení";
|
||||
|
||||
/* invalid chat item */
|
||||
@@ -2395,7 +2380,10 @@ server test error */
|
||||
"no text" = "žádný text";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"No user identifiers." = "Bez uživatelských identifikátorů.";
|
||||
"Nobody tracked your conversations. No one drew a map of where you'd been. Privacy was never a feature - it was the way of life." = "Nikdo nesledoval vaše konverzace. Nikdo nevytvořil mapu, kde jste byli. Soukromí nikdy nebylo funkcí - byl to způsob života.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Not a better lock on someone else's door. Not a nicer landlord that respects your privacy, but still keeps the record of all visitors. You are not a guest. You are home. No king can enter it - you are sovereign." = "Nejde o to mít lepší zámek na dveřích někoho jiného. Ani o to mít nájemce, který respektuje vaše soukromí, ale vede evidenci všech vašich návštěvníků. Nejste host. Jste doma. Ani král k vám nemůže vstoupit - jste suverén.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Notifications" = "Oznámení";
|
||||
@@ -2489,7 +2477,8 @@ new chat action */
|
||||
/* No comment provided by engineer. */
|
||||
"Only your contact can send voice messages." = "Hlasové zprávy může odesílat pouze váš kontakt.";
|
||||
|
||||
/* alert action */
|
||||
/* alert action
|
||||
alert button */
|
||||
"Open" = "Otevřít";
|
||||
|
||||
/* new chat action */
|
||||
@@ -2591,9 +2580,6 @@ new chat action */
|
||||
/* No comment provided by engineer. */
|
||||
"Privacy & security" = "Ochrana osobních údajů a zabezpečení";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Privacy redefined" = "Nové vymezení soukromí";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Private filenames" = "Soukromé názvy souborů";
|
||||
|
||||
@@ -2606,9 +2592,6 @@ new chat action */
|
||||
/* No comment provided by engineer. */
|
||||
"Profile password" = "Heslo profilu";
|
||||
|
||||
/* alert message */
|
||||
"Profile update will be sent to your contacts." = "Aktualizace profilu bude zaslána vašim kontaktům.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Prohibit audio/video calls." = "Zákaz audio/video hovorů.";
|
||||
|
||||
@@ -2661,16 +2644,10 @@ new chat action */
|
||||
"Read more" = "Přečíst více";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Read more in [User Guide](https://simplex.chat/docs/guide/chat-profiles.html#incognito-mode)." = "Více informací v [průvodci uživatele](https://simplex.chat/docs/guide/chat-profiles.html#incognito-mode).";
|
||||
"Read more in our GitHub repository." = "Přečtěte si více v našem GitHub repozitáři.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Read more in [User Guide](https://simplex.chat/docs/guide/making-connections.html#comparison-of-1-time-invitation-links-and-simplex-contact-addresses)." = "Další informace naleznete v [Uživatelské příručce](https://simplex.chat/docs/guide/making-connections.html#comparison-of-1-time-invitation-links-and-simplex-contact-addresses).";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Read more in [User Guide](https://simplex.chat/docs/guide/readme.html#connect-to-friends)." = "Přečtěte si více v [Uživatelské příručce](https://simplex.chat/docs/guide/readme.html#connect-to-friends).";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Read more in our [GitHub repository](https://github.com/simplex-chat/simplex-chat#readme)." = "Přečtěte si více v našem [GitHub repozitáři](https://github.com/simplex-chat/simplex-chat#readme).";
|
||||
"Read more in User Guide." = "Více informací v průvodci uživatele.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Receipts are disabled" = "Informace o dodání jsou zakázány";
|
||||
@@ -3036,15 +3013,9 @@ chat item action */
|
||||
/* No comment provided by engineer. */
|
||||
"Share address" = "Sdílet adresu";
|
||||
|
||||
/* alert title */
|
||||
"Share address with contacts?" = "Sdílet adresu s kontakty?";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Share link" = "Sdílet odkaz";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Share with contacts" = "Sdílet s kontakty";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Show calls in phone history" = "Ukaž hovory v historii telefonu";
|
||||
|
||||
@@ -3114,6 +3085,9 @@ chat item action */
|
||||
/* notification title */
|
||||
"Somebody" = "Někdo";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Star on GitHub" = "Hvězda na GitHubu";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Start chat" = "Začít chat";
|
||||
|
||||
@@ -3232,9 +3206,6 @@ server test failure */
|
||||
/* No comment provided by engineer. */
|
||||
"The encryption is working and the new encryption agreement is not required. It may result in connection errors!" = "Šifrování funguje a nové povolení šifrování není vyžadováno. To může vyvolat chybu v připojení!";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"The future of messaging" = "Nová generace soukromých zpráv";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"The hash of the previous message is different." = "Hash předchozí zprávy se liší.";
|
||||
|
||||
@@ -3250,6 +3221,9 @@ server test failure */
|
||||
/* No comment provided by engineer. */
|
||||
"The old database was not removed during the migration, it can be deleted." = "Stará databáze nebyla během přenášení odstraněna, lze ji smazat.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"The oldest human freedom - to speak to another person without being watched - built on infrastructure that cannot betray it." = "Nejstarší lidská svoboda - mluvit s druhým člověkem, aniž by byl sledován - postavena na infrastruktuře, která ji nemůže zradit.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"The second tick we missed! ✅" = "Druhé zaškrtnutí jsme přehlédli! ✅";
|
||||
|
||||
@@ -3259,6 +3233,12 @@ server test failure */
|
||||
/* No comment provided by engineer. */
|
||||
"The servers for new connections of your current chat profile **%@**." = "Servery pro nová připojení vašeho aktuálního profilu chatu **%@**.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Then we moved online, and every platform asked for a piece of you - your name, your number, your friends. We accepted that the price of talking to others is letting someone know who we talk to. Every generation, people and tech, had it this way - telephone, email, messengers, social media. It seemed the only way possible." = "Pak jsme se přesunuli na internet a každá platforma chtěla o vás něco vědět - vaše jméno, vaše číslo, vaše přátele. Smířili jsme se s tím, že cenou za komunikaci s ostatními je dát někomu vědět, s kým mluvíme. Každá generace, lidská i technická, to tak měla - telefon, e-mail, komunikátory, sociální sítě. Zdálo se, že je to jediný možný způsob.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"There is another way. A network with no phone numbers. No usernames. No accounts. No user identities of any kind. A network that connects people and carries encrypted messages without knowing who is connected." = "Existuje i jiný způsob. Síť bez telefonních čísel. Bez uživatelských jmen. Bez účtů. Bez jakékoli uživatelské identity. Síť, která spojuje lidi a přenáší šifrované zprávy, aniž by bylo známo, kdo je připojen.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"These settings are for your current profile **%@**." = "Toto nastavení je pro váš aktuální profil **%@**.";
|
||||
|
||||
@@ -3403,9 +3383,6 @@ server test failure */
|
||||
/* No comment provided by engineer. */
|
||||
"Use .onion hosts" = "Použít hostitele .onion";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Use chat" = "Použijte chat";
|
||||
|
||||
/* new chat action */
|
||||
"Use current profile" = "Použít aktuální profil";
|
||||
|
||||
@@ -3578,7 +3555,7 @@ server test failure */
|
||||
"You can set lock screen notification preview via settings." = "Náhled oznámení na zamykací obrazovce můžete změnit v nastavení.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"You can share a link or a QR code - anybody will be able to join the group. You won't lose members of the group if you later delete it." = "Můžete sdílet odkaz nebo QR kód - ke skupině se bude moci připojit kdokoli. O členy skupiny nepřijdete, pokud ji později odstraníte.";
|
||||
"You can share a link or a QR code - anybody will be able to join the group. You won't lose members of the group if you later delete it." = "Můžete sdílet odkaz nebo QR kód - ke skupině se bude moci připojit kdokoli. O členy skupiny nepřijdete, pokud odkaz později smažete.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"You can share this address with your contacts to let them connect with **%@**." = "Tuto adresu můžete sdílet s vašimi kontakty, abyse se mohli spojit s **%@**.";
|
||||
@@ -3610,9 +3587,6 @@ server test failure */
|
||||
/* No comment provided by engineer. */
|
||||
"You could not be verified; please try again." = "Nemohli jste být ověřeni; Zkuste to prosím znovu.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"You decide who can connect." = "Lidé se s vámi mohou spojit pouze prostřednictvím odkazu, který sdílíte.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"You have to enter passphrase every time the app starts - it is not stored on the device." = "Musíte zadat přístupovou frázi při každém spuštění aplikace - není uložena v zařízení.";
|
||||
|
||||
@@ -3649,6 +3623,9 @@ server test failure */
|
||||
/* chat list item description */
|
||||
"you shared one-time link incognito" = "sdíleli jste jednorázový odkaz inkognito";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"You were born without an account" = "Narodili jste se bez účtu.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"You will be connected to group when the group host's device is online, please wait or check later!" = "Ke skupině budete připojeni, až bude zařízení hostitele skupiny online, vyčkejte prosím nebo se podívejte později!";
|
||||
|
||||
@@ -3700,6 +3677,9 @@ server test failure */
|
||||
/* No comment provided by engineer. */
|
||||
"Your contacts will remain connected." = "Vaše kontakty zůstanou připojeny.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Your conversations belong to you, as it had always been before the Internet. The network is not a place you visit. It is a place you create and own. And nobody can take it from you, whether you make it private or public." = "Vaše konverzace patří vám, jako tomu bylo vždy před internetem. Síť není místo, které navštěvujete. Je to místo, které vytváříte a vlastníte. A nikdo vám ho nemůže vzít, ať už je soukromé, nebo veřejné.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Your current chat database will be DELETED and REPLACED with the imported one." = "Vaše aktuální chat databáze bude ODSTRANĚNA a NAHRAZENA importovanou.";
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -13,15 +13,9 @@
|
||||
/* No comment provided by engineer. */
|
||||
"!1 colored!" = "!1 värillinen!";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"[Contribute](https://github.com/simplex-chat/simplex-chat#contribute)" = "[Osallistu](https://github.com/simplex-chat/simplex-chat#contribute)";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"[Send us email](mailto:chat@simplex.chat)" = "[Lähetä meille sähköpostia](mailto:chat@simplex.chat)";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"[Star on GitHub](https://github.com/simplex-chat/simplex-chat)" = "[Tähti GitHubissa](https://github.com/simplex-chat/simplex-chat)";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"**e2e encrypted** audio call" = "**e2e-salattu** äänipuhelu";
|
||||
|
||||
@@ -257,9 +251,6 @@ swipe action */
|
||||
/* call status */
|
||||
"accepted call" = "hyväksytty puhelu";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Add address to your profile, so that your contacts can share it with other people. Profile update will be sent to your contacts." = "Lisää osoite profiiliisi, jotta kontaktisi voivat jakaa sen muiden kanssa. Profiilipäivitys lähetetään kontakteillesi.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Add profile" = "Lisää profiili";
|
||||
|
||||
@@ -386,9 +377,6 @@ swipe action */
|
||||
/* No comment provided by engineer. */
|
||||
"Answer call" = "Vastaa puheluun";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Anybody can host servers." = "Avoimen lähdekoodin protokolla ja koodi - kuka tahansa voi käyttää palvelimia.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"App build: %@" = "Sovellusversio: %@";
|
||||
|
||||
@@ -699,7 +687,7 @@ server test step */
|
||||
/* alert title */
|
||||
"Connection error" = "Yhteysvirhe";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
/* conn error description */
|
||||
"Connection error (AUTH)" = "Yhteysvirhe (AUTH)";
|
||||
|
||||
/* chat list item title (it should not be shown */
|
||||
@@ -747,15 +735,15 @@ server test step */
|
||||
/* No comment provided by engineer. */
|
||||
"Continue" = "Jatka";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Contribute" = "Osallistu";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Copy" = "Kopioi";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Core version: v%@" = "Ydinversio: v%@";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Create" = "Luo";
|
||||
|
||||
/* server test step */
|
||||
"Create file" = "Luo tiedosto";
|
||||
|
||||
@@ -861,9 +849,6 @@ server test step */
|
||||
/* time unit */
|
||||
"days" = "päivää";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Decentralized" = "Hajautettu";
|
||||
|
||||
/* message decrypt error item */
|
||||
"Decryption error" = "Salauksen purkuvirhe";
|
||||
|
||||
@@ -1098,7 +1083,7 @@ alert button */
|
||||
/* No comment provided by engineer. */
|
||||
"Edit group profile" = "Muokkaa ryhmäprofiilia";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
/* alert button */
|
||||
"Enable" = "Salli";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
@@ -1116,9 +1101,6 @@ alert button */
|
||||
/* No comment provided by engineer. */
|
||||
"Enable lock" = "Ota lukitus käyttöön";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Enable notifications" = "Salli ilmoitukset";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Enable periodic notifications?" = "Salli säännölliset ilmoitukset?";
|
||||
|
||||
@@ -1227,7 +1209,7 @@ alert button */
|
||||
/* No comment provided by engineer. */
|
||||
"error" = "virhe";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
/* conn error description */
|
||||
"Error" = "Virhe";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
@@ -1497,7 +1479,7 @@ server test error */
|
||||
/* No comment provided by engineer. */
|
||||
"Group invitation is no longer valid, it was removed by sender." = "Ryhmäkutsu ei ole enää voimassa, lähettäjä poisti sen.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
/* chat link info line */
|
||||
"Group link" = "Ryhmälinkki";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
@@ -1599,9 +1581,6 @@ server test error */
|
||||
/* No comment provided by engineer. */
|
||||
"Immediately" = "Heti";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Immune to spam" = "Immuuni roskapostille ja väärinkäytöksille";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Import" = "Tuo";
|
||||
|
||||
@@ -1666,7 +1645,7 @@ server test error */
|
||||
"Initial role" = "Alkuperäinen rooli";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Install [SimpleX Chat for terminal](https://github.com/simplex-chat/simplex-chat)" = "Asenna [SimpleX Chat terminaalille](https://github.com/simplex-chat/simplex-chat)";
|
||||
"Install SimpleX Chat for terminal" = "Asenna SimpleX Chat terminaalille";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Instant" = "Heti";
|
||||
@@ -1683,7 +1662,7 @@ server test error */
|
||||
/* No comment provided by engineer. */
|
||||
"invalid chat data" = "virheelliset keskustelu-tiedot";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
/* conn error description */
|
||||
"Invalid connection link" = "Virheellinen yhteyslinkki";
|
||||
|
||||
/* invalid chat item */
|
||||
@@ -2064,9 +2043,6 @@ server test error */
|
||||
/* copied message info in history */
|
||||
"no text" = "ei tekstiä";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"No user identifiers." = "Ensimmäinen alusta ilman käyttäjätunnisteita – suunniteltu yksityiseksi.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Notifications" = "Ilmoitukset";
|
||||
|
||||
@@ -2258,9 +2234,6 @@ new chat action */
|
||||
/* No comment provided by engineer. */
|
||||
"Privacy & security" = "Yksityisyys ja turvallisuus";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Privacy redefined" = "Yksityisyys uudelleen määritettynä";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Private filenames" = "Yksityiset tiedostonimet";
|
||||
|
||||
@@ -2273,9 +2246,6 @@ new chat action */
|
||||
/* No comment provided by engineer. */
|
||||
"Profile password" = "Profiilin salasana";
|
||||
|
||||
/* alert message */
|
||||
"Profile update will be sent to your contacts." = "Profiilipäivitys lähetetään kontakteillesi.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Prohibit audio/video calls." = "Estä ääni- ja videopuhelut.";
|
||||
|
||||
@@ -2328,13 +2298,10 @@ new chat action */
|
||||
"Read more" = "Lue lisää";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Read more in [User Guide](https://simplex.chat/docs/guide/making-connections.html#comparison-of-1-time-invitation-links-and-simplex-contact-addresses)." = "Lue lisää [Käyttöoppaasta](https://simplex.chat/docs/guide/making-connections.html#comparison-of-1-time-invitation-links-and-simplex-contact-addresses).";
|
||||
"Read more in our GitHub repository." = "Lue lisää GitHub-arkistosta.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Read more in [User Guide](https://simplex.chat/docs/guide/readme.html#connect-to-friends)." = "Lue lisää [Käyttöoppaasta](https://simplex.chat/docs/guide/readme.html#connect-to-friends).";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Read more in our [GitHub repository](https://github.com/simplex-chat/simplex-chat#readme)." = "Lue lisää [GitHub-arkistosta](https://github.com/simplex-chat/simplex-chat#readme).";
|
||||
"Read more in User Guide." = "Lue lisää Käyttöoppaasta.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Receipts are disabled" = "Kuittaukset pois käytöstä";
|
||||
@@ -2697,15 +2664,9 @@ chat item action */
|
||||
/* No comment provided by engineer. */
|
||||
"Share address" = "Jaa osoite";
|
||||
|
||||
/* alert title */
|
||||
"Share address with contacts?" = "Jaa osoite kontakteille?";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Share link" = "Jaa linkki";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Share with contacts" = "Jaa kontaktien kanssa";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Show calls in phone history" = "Näytä puhelut puhelinhistoriassa";
|
||||
|
||||
@@ -2772,6 +2733,9 @@ chat item action */
|
||||
/* notification title */
|
||||
"Somebody" = "Joku";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Star on GitHub" = "Tähti GitHubissa";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Start chat" = "Aloita keskustelu";
|
||||
|
||||
@@ -2890,9 +2854,6 @@ server test failure */
|
||||
/* No comment provided by engineer. */
|
||||
"The encryption is working and the new encryption agreement is not required. It may result in connection errors!" = "Salaus toimii ja uutta salaussopimusta ei tarvita. Tämä voi johtaa yhteysvirheisiin!";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"The future of messaging" = "Seuraavan sukupolven yksityisviestit";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"The hash of the previous message is different." = "Edellisen viestin tarkiste on erilainen.";
|
||||
|
||||
@@ -3058,9 +3019,6 @@ server test failure */
|
||||
/* No comment provided by engineer. */
|
||||
"Use .onion hosts" = "Käytä .onion-isäntiä";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Use chat" = "Käytä chattia";
|
||||
|
||||
/* new chat action */
|
||||
"Use current profile" = "Käytä nykyistä profiilia";
|
||||
|
||||
@@ -3265,9 +3223,6 @@ server test failure */
|
||||
/* No comment provided by engineer. */
|
||||
"You could not be verified; please try again." = "Sinua ei voitu todentaa; yritä uudelleen.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"You decide who can connect." = "Kimin bağlanabileceğine siz karar verirsiniz.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"You have to enter passphrase every time the app starts - it is not stored on the device." = "Sinun on annettava tunnuslause aina, kun sovellus käynnistyy - sitä ei tallenneta laitteeseen.";
|
||||
|
||||
|
||||
@@ -25,15 +25,9 @@
|
||||
/* No comment provided by engineer. */
|
||||
"(this device v%@)" = "(cet appareil v%@)";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"[Contribute](https://github.com/simplex-chat/simplex-chat#contribute)" = "[Contribuer](https://github.com/simplex-chat/simplex-chat#contribute)";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"[Send us email](mailto:chat@simplex.chat)" = "[Contact par mail](mailto:chat@simplex.chat)";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"[Star on GitHub](https://github.com/simplex-chat/simplex-chat)" = "[Star sur GitHub](https://github.com/simplex-chat/simplex-chat)";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"**Create 1-time link**: to create and share a new invitation link." = "**Ajouter un contact** : pour créer un nouveau lien d'invitation.";
|
||||
|
||||
@@ -392,9 +386,6 @@ swipe action */
|
||||
/* No comment provided by engineer. */
|
||||
"Active connections" = "Connections actives";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Add address to your profile, so that your contacts can share it with other people. Profile update will be sent to your contacts." = "Ajoutez une adresse à votre profil, afin que vos contacts puissent la partager avec d'autres personnes. La mise à jour du profil sera envoyée à vos contacts.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Add friends" = "Ajouter des amis";
|
||||
|
||||
@@ -638,9 +629,6 @@ swipe action */
|
||||
/* No comment provided by engineer. */
|
||||
"Answer call" = "Répondre à l'appel";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Anybody can host servers." = "N'importe qui peut heberger un serveur.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"App build: %@" = "Build de l'app : %@";
|
||||
|
||||
@@ -864,7 +852,7 @@ marked deleted chat item preview text */
|
||||
/* No comment provided by engineer. */
|
||||
"Bulgarian, Finnish, Thai and Ukrainian - thanks to the users and [Weblate](https://github.com/simplex-chat/simplex-chat/tree/stable#help-translating-simplex-chat)!" = "Bulgare, finnois, thaïlandais et ukrainien - grâce aux utilisateurs et à [Weblate](https://github.com/simplex-chat/simplex-chat/tree/stable#help-translating-simplex-chat) !";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
/* chat link info line */
|
||||
"Business address" = "Adresse professionnelle";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
@@ -876,9 +864,6 @@ marked deleted chat item preview text */
|
||||
/* No comment provided by engineer. */
|
||||
"By chat profile (default) or [by connection](https://simplex.chat/blog/20230204-simplex-chat-v4-5-user-chat-profiles.html#transport-isolation) (BETA)." = "Par profil de chat (par défaut) ou [par connexion](https://simplex.chat/blog/20230204-simplex-chat-v4-5-user-chat-profiles.html#transport-isolation) (BETA).";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"By using SimpleX Chat you agree to:\n- send only legal content in public groups.\n- respect other users – no spam." = "En utilisant SimpleX Chat, vous acceptez de :\n- n'envoyer que du contenu légal dans les groupes publics.\n- respecter les autres utilisateurs - pas de spam.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"call" = "appeler";
|
||||
|
||||
@@ -1158,9 +1143,6 @@ set passcode view */
|
||||
/* No comment provided by engineer. */
|
||||
"Configure ICE servers" = "Configurer les serveurs ICE";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Configure server operators" = "Configurer les opérateurs de serveur";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Confirm" = "Confirmer";
|
||||
|
||||
@@ -1291,7 +1273,7 @@ server test step */
|
||||
/* alert title */
|
||||
"Connection error" = "Erreur de connexion";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
/* conn error description */
|
||||
"Connection error (AUTH)" = "Erreur de connexion (AUTH)";
|
||||
|
||||
/* chat list item title (it should not be shown */
|
||||
@@ -1378,6 +1360,9 @@ server test step */
|
||||
/* No comment provided by engineer. */
|
||||
"Continue" = "Continuer";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Contribute" = "Contribuer";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Conversation deleted!" = "Conversation supprimée !";
|
||||
|
||||
@@ -1396,9 +1381,6 @@ server test step */
|
||||
/* alert message */
|
||||
"Correct name to %@?" = "Corriger le nom pour %@ ?";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Create" = "Créer";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Create 1-time link" = "Créer un lien unique";
|
||||
|
||||
@@ -1549,9 +1531,6 @@ server test step */
|
||||
/* No comment provided by engineer. */
|
||||
"Debug delivery" = "Livraison de débogage";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Decentralized" = "Décentralisé";
|
||||
|
||||
/* message decrypt error item */
|
||||
"Decryption error" = "Erreur de déchiffrement";
|
||||
|
||||
@@ -1937,7 +1916,7 @@ chat item action */
|
||||
/* No comment provided by engineer. */
|
||||
"Edit group profile" = "Modifier le profil du groupe";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
/* alert button */
|
||||
"Enable" = "Activer";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
@@ -1964,9 +1943,6 @@ chat item action */
|
||||
/* No comment provided by engineer. */
|
||||
"Enable lock" = "Activer le verrouillage";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Enable notifications" = "Activer les notifications";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Enable periodic notifications?" = "Activer les notifications périodiques ?";
|
||||
|
||||
@@ -2108,7 +2084,7 @@ chat item action */
|
||||
/* No comment provided by engineer. */
|
||||
"error" = "erreur";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
/* conn error description */
|
||||
"Error" = "Erreur";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
@@ -2610,7 +2586,7 @@ servers warning */
|
||||
/* No comment provided by engineer. */
|
||||
"Group invitation is no longer valid, it was removed by sender." = "L'invitation du groupe n'est plus valide, elle a été supprimé par l'expéditeur.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
/* chat link info line */
|
||||
"Group link" = "Lien du groupe";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
@@ -2724,9 +2700,6 @@ servers warning */
|
||||
/* No comment provided by engineer. */
|
||||
"Immediately" = "Immédiatement";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Immune to spam" = "Protégé du spam et des abus";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Import" = "Importer";
|
||||
|
||||
@@ -2821,7 +2794,7 @@ servers warning */
|
||||
"Initial role" = "Rôle initial";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Install [SimpleX Chat for terminal](https://github.com/simplex-chat/simplex-chat)" = "Installer [SimpleX Chat pour terminal](https://github.com/simplex-chat/simplex-chat)";
|
||||
"Install SimpleX Chat for terminal" = "Installer SimpleX Chat pour terminal";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Instant" = "Instantané";
|
||||
@@ -2841,7 +2814,7 @@ servers warning */
|
||||
/* No comment provided by engineer. */
|
||||
"invalid chat data" = "données de chat invalides";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
/* conn error description */
|
||||
"Invalid connection link" = "Lien de connection invalide";
|
||||
|
||||
/* invalid chat item */
|
||||
@@ -3225,9 +3198,6 @@ servers warning */
|
||||
/* No comment provided by engineer. */
|
||||
"Migrate device" = "Transférer l'appareil";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Migrate from another device" = "Transférer depuis un autre appareil";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Migrate here" = "Transférer ici";
|
||||
|
||||
@@ -3462,9 +3432,6 @@ servers warning */
|
||||
/* copied message info in history */
|
||||
"no text" = "aucun texte";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"No user identifiers." = "Aucun identifiant d'utilisateur.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Not compatible!" = "Non compatible !";
|
||||
|
||||
@@ -3509,7 +3476,7 @@ alert button
|
||||
new chat action */
|
||||
"Ok" = "Ok";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
/* alert button */
|
||||
"OK" = "OK";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
@@ -3578,7 +3545,8 @@ new chat action */
|
||||
/* No comment provided by engineer. */
|
||||
"Only your contact can send voice messages." = "Seul votre contact peut envoyer des messages vocaux.";
|
||||
|
||||
/* alert action */
|
||||
/* alert action
|
||||
alert button */
|
||||
"Open" = "Ouvrir";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
@@ -3779,9 +3747,6 @@ new chat action */
|
||||
/* No comment provided by engineer. */
|
||||
"Privacy for your customers." = "Respect de la vie privée de vos clients.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Privacy redefined" = "La vie privée redéfinie";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Private filenames" = "Noms de fichiers privés";
|
||||
|
||||
@@ -3815,9 +3780,6 @@ new chat action */
|
||||
/* No comment provided by engineer. */
|
||||
"Profile theme" = "Thème de profil";
|
||||
|
||||
/* alert message */
|
||||
"Profile update will be sent to your contacts." = "La mise à jour du profil sera envoyée à vos contacts.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Prohibit audio/video calls." = "Interdire les appels audio/vidéo.";
|
||||
|
||||
@@ -3900,16 +3862,10 @@ new chat action */
|
||||
"Read more" = "En savoir plus";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Read more in [User Guide](https://simplex.chat/docs/guide/chat-profiles.html#incognito-mode)." = "Pour en savoir plus, consultez le [Guide de l'utilisateur](https ://simplex.chat/docs/guide/chat-profiles.html#incognito-mode).";
|
||||
"Read more in our GitHub repository." = "Pour en savoir plus, consultez notre dépôt GitHub.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Read more in [User Guide](https://simplex.chat/docs/guide/making-connections.html#comparison-of-1-time-invitation-links-and-simplex-contact-addresses)." = "Pour en savoir plus, consultez le [Guide de l'utilisateur](https://simplex.chat/docs/guide/making-connections.html#comparison-of-1-time-invitation-links-and-simplex-contact-addresses).";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Read more in [User Guide](https://simplex.chat/docs/guide/readme.html#connect-to-friends)." = "Pour en savoir plus, consultez le [Guide de l'utilisateur](https://simplex.chat/docs/guide/readme.html#connect-to-friends).";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Read more in our [GitHub repository](https://github.com/simplex-chat/simplex-chat#readme)." = "Pour en savoir plus, consultez notre [dépôt GitHub](https://github.com/simplex-chat/simplex-chat#readme).";
|
||||
"Read more in User Guide." = "Pour en savoir plus, consultez le Guide de l'utilisateur.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Receipts are disabled" = "Les accusés de réception sont désactivés";
|
||||
@@ -4515,9 +4471,6 @@ chat item action */
|
||||
/* No comment provided by engineer. */
|
||||
"Share address publicly" = "Partager publiquement votre adresse";
|
||||
|
||||
/* alert title */
|
||||
"Share address with contacts?" = "Partager l'adresse avec vos contacts ?";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Share from other apps." = "Partager depuis d'autres applications.";
|
||||
|
||||
@@ -4536,9 +4489,6 @@ chat item action */
|
||||
/* No comment provided by engineer. */
|
||||
"Share to SimpleX" = "Partager sur SimpleX";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Share with contacts" = "Partager avec vos contacts";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Show → on messages sent via private routing." = "Afficher → sur les messages envoyés via le routage privé.";
|
||||
|
||||
@@ -4674,6 +4624,9 @@ chat item action */
|
||||
/* chat item text */
|
||||
"standard end-to-end encryption" = "chiffrement de bout en bout standard";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Star on GitHub" = "Star sur GitHub";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Start chat" = "Démarrer le chat";
|
||||
|
||||
@@ -4767,9 +4720,6 @@ chat item action */
|
||||
/* No comment provided by engineer. */
|
||||
"Tap button " = "Appuyez sur le bouton ";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Tap Create SimpleX address in the menu to create it later." = "Appuyez sur Créer une adresse SimpleX dans le menu pour la créer ultérieurement.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Tap to activate profile." = "Appuyez pour activer un profil.";
|
||||
|
||||
@@ -4858,9 +4808,6 @@ server test failure */
|
||||
/* No comment provided by engineer. */
|
||||
"The encryption is working and the new encryption agreement is not required. It may result in connection errors!" = "Le chiffrement fonctionne et le nouvel accord de chiffrement n'est pas nécessaire. Cela peut provoquer des erreurs de connexion !";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"The future of messaging" = "La nouvelle génération de messagerie privée";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"The hash of the previous message is different." = "Le hash du message précédent est différent.";
|
||||
|
||||
@@ -5011,9 +4958,6 @@ server test failure */
|
||||
/* No comment provided by engineer. */
|
||||
"To verify end-to-end encryption with your contact compare (or scan) the code on your devices." = "Pour vérifier le chiffrement de bout en bout avec votre contact, comparez (ou scannez) le code sur vos appareils.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Toggle chat list:" = "Afficher la liste des conversations :";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Toggle incognito when connecting." = "Basculer en mode incognito lors de la connexion.";
|
||||
|
||||
@@ -5179,9 +5123,6 @@ server test failure */
|
||||
/* No comment provided by engineer. */
|
||||
"Use %@" = "Utiliser %@";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Use chat" = "Utiliser le chat";
|
||||
|
||||
/* new chat action */
|
||||
"Use current profile" = "Utiliser le profil actuel";
|
||||
|
||||
@@ -5569,9 +5510,6 @@ server test failure */
|
||||
/* No comment provided by engineer. */
|
||||
"You could not be verified; please try again." = "Vous n'avez pas pu être vérifié·e ; veuillez réessayer.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"You decide who can connect." = "Vous choisissez qui peut se connecter.";
|
||||
|
||||
/* new chat sheet title */
|
||||
"You have already requested connection!\nRepeat connection request?" = "Vous avez déjà demandé une connexion !\nRépéter la demande de connexion ?";
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -2,7 +2,7 @@
|
||||
"CFBundleName" = "SimpleX";
|
||||
|
||||
/* Privacy - Camera Usage Description */
|
||||
"NSCameraUsageDescription" = "A SimpleXnek kamera-hozzáférésre van szüksége a QR-kódok beolvasásához, hogy kapcsolódhasson más felhasználókhoz és videohívásokhoz.";
|
||||
"NSCameraUsageDescription" = "A SimpleXnek hozzáférésre van szüksége a kamerához a QR-kódok beolvasásához, hogy kapcsolódhasson más felhasználókhoz és videohívásokhoz.";
|
||||
|
||||
/* Privacy - Face ID Usage Description */
|
||||
"NSFaceIDUsageDescription" = "A SimpleX Face ID-t használ a helyi hitelesítéshez";
|
||||
@@ -11,7 +11,7 @@
|
||||
"NSLocalNetworkUsageDescription" = "A SimpleX helyi hálózati hozzáférést használ, hogy lehetővé tegye a felhasználói csevegési profil használatát számítógépen keresztül ugyanazon a hálózaton.";
|
||||
|
||||
/* Privacy - Microphone Usage Description */
|
||||
"NSMicrophoneUsageDescription" = "A SimpleXnek mikrofon-hozzáférésre van szüksége hang- és videohívásokhoz, valamint hangüzenetek rögzítéséhez.";
|
||||
"NSMicrophoneUsageDescription" = "A SimpleXnek hozzáférésre van szüksége a mikrofonhoz a hang- és videohívásokhoz, valamint hangüzenetek rögzítéséhez.";
|
||||
|
||||
/* Privacy - Photo Library Additions Usage Description */
|
||||
"NSPhotoLibraryAddUsageDescription" = "A SimpleXnek hozzáférésre van szüksége a galériához a rögzített és fogadott média mentéséhez";
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -25,15 +25,9 @@
|
||||
/* No comment provided by engineer. */
|
||||
"(this device v%@)" = "(このデバイス v%@)";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"[Contribute](https://github.com/simplex-chat/simplex-chat#contribute)" = "[貢献する](https://github.com/simplex-chat/simplex-chat#contribute)";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"[Send us email](mailto:chat@simplex.chat)" = "[メールを送信](mailto:chat@simplex.chat)";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"[Star on GitHub](https://github.com/simplex-chat/simplex-chat)" = "[GitHub でスターを付ける](https://github.com/simplex-chat/simplex-chat)";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"**Create 1-time link**: to create and share a new invitation link." = "**コンタクトの追加**: 新しい招待リンクを作成するか、受け取ったリンクから接続します。";
|
||||
|
||||
@@ -377,9 +371,6 @@ swipe action */
|
||||
/* No comment provided by engineer. */
|
||||
"Active connections" = "アクティブな接続";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Add address to your profile, so that your contacts can share it with other people. Profile update will be sent to your contacts." = "プロフィールにアドレスを追加し、連絡先があなたのアドレスを他の人と共有できるようにします。プロフィールの更新は連絡先に送信されます。";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Add friends" = "友達を追加";
|
||||
|
||||
@@ -566,9 +557,6 @@ swipe action */
|
||||
/* No comment provided by engineer. */
|
||||
"Answer call" = "通話に応答";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Anybody can host servers." = "プロトコル技術とコードはオープンソースで、どなたでもご自分のサーバを運用できます。";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"App build: %@" = "アプリのビルド: %@";
|
||||
|
||||
@@ -969,7 +957,7 @@ server test step */
|
||||
/* alert title */
|
||||
"Connection error" = "接続エラー";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
/* conn error description */
|
||||
"Connection error (AUTH)" = "接続エラー (AUTH)";
|
||||
|
||||
/* chat list item title (it should not be shown */
|
||||
@@ -1020,15 +1008,15 @@ server test step */
|
||||
/* No comment provided by engineer. */
|
||||
"Continue" = "続ける";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Contribute" = "貢献する";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Copy" = "コピー";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Core version: v%@" = "コアのバージョン: v%@";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Create" = "作成";
|
||||
|
||||
/* server test step */
|
||||
"Create file" = "ファイルを作成";
|
||||
|
||||
@@ -1143,9 +1131,6 @@ server test step */
|
||||
/* No comment provided by engineer. */
|
||||
"Debug delivery" = "配信のデバッグ";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Decentralized" = "分散型";
|
||||
|
||||
/* message decrypt error item */
|
||||
"Decryption error" = "復号化エラー";
|
||||
|
||||
@@ -1386,7 +1371,7 @@ alert button */
|
||||
/* No comment provided by engineer. */
|
||||
"Edit group profile" = "グループのプロフィールを編集";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
/* alert button */
|
||||
"Enable" = "有効";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
@@ -1404,9 +1389,6 @@ alert button */
|
||||
/* No comment provided by engineer. */
|
||||
"Enable lock" = "ロックモード";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Enable notifications" = "通知を有効化";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Enable periodic notifications?" = "定期的な通知を有効にしますか?";
|
||||
|
||||
@@ -1518,7 +1500,7 @@ alert button */
|
||||
/* No comment provided by engineer. */
|
||||
"error" = "エラー";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
/* conn error description */
|
||||
"Error" = "エラー";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
@@ -1788,7 +1770,7 @@ server test error */
|
||||
/* No comment provided by engineer. */
|
||||
"Group invitation is no longer valid, it was removed by sender." = "グループ招待が無効となり、送信元によって取り消されました。";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
/* chat link info line */
|
||||
"Group link" = "グループのリンク";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
@@ -1890,9 +1872,6 @@ server test error */
|
||||
/* No comment provided by engineer. */
|
||||
"Immediately" = "即座に";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Immune to spam" = "スパムや悪質送信を防止";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Import" = "読み込む";
|
||||
|
||||
@@ -1957,7 +1936,7 @@ server test error */
|
||||
"Initial role" = "初期の役割";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Install [SimpleX Chat for terminal](https://github.com/simplex-chat/simplex-chat)" = "インストール [ターミナル用SimpleX Chat](https://github.com/simplex-chat/simplex-chat)";
|
||||
"Install SimpleX Chat for terminal" = "インストール ターミナル用SimpleX Chat";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Instant" = "即時";
|
||||
@@ -1974,7 +1953,7 @@ server test error */
|
||||
/* No comment provided by engineer. */
|
||||
"invalid chat data" = "無効なチャットデータ";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
/* conn error description */
|
||||
"Invalid connection link" = "無効な接続リンク";
|
||||
|
||||
/* invalid chat item */
|
||||
@@ -2217,9 +2196,6 @@ server test error */
|
||||
/* No comment provided by engineer. */
|
||||
"Messages, files and calls are protected by **quantum resistant e2e encryption** with perfect forward secrecy, repudiation and break-in recovery." = "メッセージ、ファイル、通話は、前方秘匿性、否認可能性および侵入復元性を備えた**耐量子E2E暗号化**によって保護されます。";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Migrate from another device" = "別の端末から移行";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Migrating database archive…" = "データベースのアーカイブを移行しています…";
|
||||
|
||||
@@ -2364,9 +2340,6 @@ server test error */
|
||||
/* copied message info in history */
|
||||
"no text" = "テキストなし";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"No user identifiers." = "世界初のユーザーIDのないプラットフォーム|設計も元からプライベート。";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Notifications" = "通知";
|
||||
|
||||
@@ -2459,7 +2432,8 @@ new chat action */
|
||||
/* No comment provided by engineer. */
|
||||
"Only your contact can send voice messages." = "音声メッセージを送れるのはあなたの連絡相手だけです。";
|
||||
|
||||
/* alert action */
|
||||
/* alert action
|
||||
alert button */
|
||||
"Open" = "開く";
|
||||
|
||||
/* new chat action */
|
||||
@@ -2561,9 +2535,6 @@ new chat action */
|
||||
/* No comment provided by engineer. */
|
||||
"Privacy & security" = "プライバシーとセキュリティ";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Privacy redefined" = "プライバシーの基準を新境地に";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Private filenames" = "プライベートなファイル名";
|
||||
|
||||
@@ -2579,9 +2550,6 @@ new chat action */
|
||||
/* No comment provided by engineer. */
|
||||
"Profile password" = "プロフィールのパスワード";
|
||||
|
||||
/* alert message */
|
||||
"Profile update will be sent to your contacts." = "連絡先にプロフィール更新のお知らせが届きます。";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Prohibit audio/video calls." = "音声/ビデオ通話を禁止する 。";
|
||||
|
||||
@@ -2634,13 +2602,10 @@ new chat action */
|
||||
"Read more" = "続きを読む";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Read more in [User Guide](https://simplex.chat/docs/guide/making-connections.html#comparison-of-1-time-invitation-links-and-simplex-contact-addresses)." = "詳しくは[ユーザーガイド](https://simplex.chat/docs/guide/making-connections.html#comparison-of-1-time-invitation-links-and-simplex-contact-addresses)をご覧ください。";
|
||||
"Read more in our GitHub repository." = "詳しくはGitHubリポジトリをご覧ください。";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Read more in [User Guide](https://simplex.chat/docs/guide/readme.html#connect-to-friends)." = "詳しくは[ユーザーガイド](https://simplex.chat/docs/guide/readme.html#connect-to-friends)をご覧ください。";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Read more in our [GitHub repository](https://github.com/simplex-chat/simplex-chat#readme)." = "詳しくは[GitHubリポジトリ](https://github.com/simplex-chat/simplex-chat#readme)をご覧ください。";
|
||||
"Read more in User Guide." = "詳しくはユーザーガイドをご覧ください。";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"received answer…" = "回答を受け取りました…";
|
||||
@@ -2979,15 +2944,9 @@ chat item action */
|
||||
/* No comment provided by engineer. */
|
||||
"Share address" = "アドレスを共有する";
|
||||
|
||||
/* alert title */
|
||||
"Share address with contacts?" = "アドレスを連絡先と共有しますか?";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Share link" = "リンクを送る";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Share with contacts" = "連絡先と共有する";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Show calls in phone history" = "通話履歴を表示";
|
||||
|
||||
@@ -3057,6 +3016,9 @@ chat item action */
|
||||
/* notification title */
|
||||
"Somebody" = "誰か";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Star on GitHub" = "GitHub でスターを付ける";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Start chat" = "チャットを開始する";
|
||||
|
||||
@@ -3175,9 +3137,6 @@ server test failure */
|
||||
/* No comment provided by engineer. */
|
||||
"The encryption is working and the new encryption agreement is not required. It may result in connection errors!" = "暗号化は機能しており、新しい暗号化への同意は必要ありません。接続エラーが発生する可能性があります!";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"The future of messaging" = "次世代のプライバシー・メッセンジャー";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"The hash of the previous message is different." = "以前のメッセージとハッシュ値が異なります。";
|
||||
|
||||
@@ -3340,9 +3299,6 @@ server test failure */
|
||||
/* No comment provided by engineer. */
|
||||
"Use .onion hosts" = ".onionホストを使う";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Use chat" = "チャット";
|
||||
|
||||
/* new chat action */
|
||||
"Use current profile" = "現在のプロファイルを使用する";
|
||||
|
||||
@@ -3550,9 +3506,6 @@ server test failure */
|
||||
/* No comment provided by engineer. */
|
||||
"You could not be verified; please try again." = "確認できませんでした。 もう一度お試しください。";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"You decide who can connect." = "あなたと繋がることができるのは、あなたからリンクを頂いた方のみです。";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"You have to enter passphrase every time the app starts - it is not stored on the device." = "アプリ起動時にパスフレーズを入力しなければなりません。端末に保存されてません。";
|
||||
|
||||
|
||||
@@ -25,15 +25,9 @@
|
||||
/* No comment provided by engineer. */
|
||||
"(this device v%@)" = "(dit apparaat v%@)";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"[Contribute](https://github.com/simplex-chat/simplex-chat#contribute)" = "[Bijdragen](https://github.com/simplex-chat/simplex-chat#contribute)";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"[Send us email](mailto:chat@simplex.chat)" = "[Stuur ons een e-mail](mailto:chat@simplex.chat)";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"[Star on GitHub](https://github.com/simplex-chat/simplex-chat)" = "[Star on GitHub](https://github.com/simplex-chat/simplex-chat)";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"**Create 1-time link**: to create and share a new invitation link." = "**Contact toevoegen**: om een nieuwe uitnodigingslink aan te maken, of verbinding te maken via een link die u heeft ontvangen.";
|
||||
|
||||
@@ -395,9 +389,6 @@ swipe action */
|
||||
/* No comment provided by engineer. */
|
||||
"Active connections" = "Actieve verbindingen";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Add address to your profile, so that your contacts can share it with other people. Profile update will be sent to your contacts." = "Voeg een adres toe aan uw profiel, zodat uw contacten het met andere mensen kunnen delen. Profiel update wordt naar uw contacten verzonden.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Add friends" = "Vrienden toevoegen";
|
||||
|
||||
@@ -635,9 +626,6 @@ swipe action */
|
||||
/* No comment provided by engineer. */
|
||||
"Answer call" = "Beantwoord oproep";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Anybody can host servers." = "Iedereen kan servers hosten.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"App build: %@" = "App build: %@";
|
||||
|
||||
@@ -867,7 +855,7 @@ marked deleted chat item preview text */
|
||||
/* No comment provided by engineer. */
|
||||
"Bulgarian, Finnish, Thai and Ukrainian - thanks to the users and [Weblate](https://github.com/simplex-chat/simplex-chat/tree/stable#help-translating-simplex-chat)!" = "Bulgaars, Fins, Thais en Oekraïens - dankzij de gebruikers en [Weblate](https://github.com/simplex-chat/simplex-chat/tree/stable#help-translating-simplex-chat)!";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
/* chat link info line */
|
||||
"Business address" = "Zakelijk adres";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
@@ -879,9 +867,6 @@ marked deleted chat item preview text */
|
||||
/* No comment provided by engineer. */
|
||||
"By chat profile (default) or [by connection](https://simplex.chat/blog/20230204-simplex-chat-v4-5-user-chat-profiles.html#transport-isolation) (BETA)." = "Via chatprofiel (standaard) of [via verbinding](https://simplex.chat/blog/20230204-simplex-chat-v4-5-user-chat-profiles.html#transport-isolation) (BETA).";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"By using SimpleX Chat you agree to:\n- send only legal content in public groups.\n- respect other users – no spam." = "Door SimpleX Chat te gebruiken, gaat u ermee akkoord:\n- alleen legale content te versturen in openbare groepen.\n- andere gebruikers te respecteren – geen spam.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"call" = "bellen";
|
||||
|
||||
@@ -1062,7 +1047,8 @@ set passcode view */
|
||||
/* No comment provided by engineer. */
|
||||
"Chat will be deleted for you - this cannot be undone!" = "De chat wordt voor je verwijderd - dit kan niet ongedaan worden gemaakt!";
|
||||
|
||||
/* chat toolbar */
|
||||
/* chat feature
|
||||
chat toolbar */
|
||||
"Chat with admins" = "Chat met beheerders";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
@@ -1173,9 +1159,6 @@ set passcode view */
|
||||
/* No comment provided by engineer. */
|
||||
"Configure ICE servers" = "ICE servers configureren";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Configure server operators" = "Serveroperators configureren";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Confirm" = "Bevestigen";
|
||||
|
||||
@@ -1306,7 +1289,7 @@ server test step */
|
||||
/* alert title */
|
||||
"Connection error" = "Verbindingsfout";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
/* conn error description */
|
||||
"Connection error (AUTH)" = "Verbindingsfout (AUTH)";
|
||||
|
||||
/* chat list item title (it should not be shown */
|
||||
@@ -1402,6 +1385,9 @@ server test step */
|
||||
/* No comment provided by engineer. */
|
||||
"Continue" = "Doorgaan";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Contribute" = "Bijdragen";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Conversation deleted!" = "Gesprek verwijderd!";
|
||||
|
||||
@@ -1420,9 +1406,6 @@ server test step */
|
||||
/* alert message */
|
||||
"Correct name to %@?" = "Juiste naam voor %@?";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Create" = "Maak";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Create 1-time link" = "Eenmalige link maken";
|
||||
|
||||
@@ -1573,9 +1556,6 @@ server test step */
|
||||
/* No comment provided by engineer. */
|
||||
"Debug delivery" = "Foutopsporing bezorging";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Decentralized" = "Gedecentraliseerd";
|
||||
|
||||
/* message decrypt error item */
|
||||
"Decryption error" = "Decodering fout";
|
||||
|
||||
@@ -1964,7 +1944,7 @@ chat item action */
|
||||
/* No comment provided by engineer. */
|
||||
"Edit group profile" = "Groep profiel bewerken";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
/* alert button */
|
||||
"Enable" = "Inschakelen";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
@@ -1991,9 +1971,6 @@ chat item action */
|
||||
/* No comment provided by engineer. */
|
||||
"Enable lock" = "Vergrendeling inschakelen";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Enable notifications" = "Meldingen aanzetten";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Enable periodic notifications?" = "Periodieke meldingen inschakelen?";
|
||||
|
||||
@@ -2135,7 +2112,7 @@ chat item action */
|
||||
/* No comment provided by engineer. */
|
||||
"error" = "fout";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
/* conn error description */
|
||||
"Error" = "Fout";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
@@ -2655,7 +2632,7 @@ servers warning */
|
||||
/* No comment provided by engineer. */
|
||||
"group is deleted" = "groep is verwijderd";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
/* chat link info line */
|
||||
"Group link" = "Groep link";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
@@ -2778,9 +2755,6 @@ servers warning */
|
||||
/* No comment provided by engineer. */
|
||||
"Immediately" = "Onmiddellijk";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Immune to spam" = "Immuun voor spam en misbruik";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Import" = "Importeren";
|
||||
|
||||
@@ -2881,7 +2855,7 @@ servers warning */
|
||||
"Initial role" = "Initiële rol";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Install [SimpleX Chat for terminal](https://github.com/simplex-chat/simplex-chat)" = "Installeer [SimpleX Chat voor terminal](https://github.com/simplex-chat/simplex-chat)";
|
||||
"Install SimpleX Chat for terminal" = "Installeer SimpleX Chat voor terminal";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Instant" = "Direct";
|
||||
@@ -2916,7 +2890,7 @@ servers warning */
|
||||
/* No comment provided by engineer. */
|
||||
"invalid chat data" = "ongeldige gesprek gegevens";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
/* conn error description */
|
||||
"Invalid connection link" = "Ongeldige verbinding link";
|
||||
|
||||
/* invalid chat item */
|
||||
@@ -3330,9 +3304,6 @@ servers warning */
|
||||
/* No comment provided by engineer. */
|
||||
"Migrate device" = "Apparaat migreren";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Migrate from another device" = "Migreer vanaf een ander apparaat";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Migrate here" = "Migreer hierheen";
|
||||
|
||||
@@ -3603,9 +3574,6 @@ servers warning */
|
||||
/* No comment provided by engineer. */
|
||||
"No unread chats" = "Geen ongelezen chats";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"No user identifiers." = "Geen gebruikers-ID's.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Not compatible!" = "Niet compatibel!";
|
||||
|
||||
@@ -3662,7 +3630,7 @@ alert button
|
||||
new chat action */
|
||||
"Ok" = "OK";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
/* alert button */
|
||||
"OK" = "OK";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
@@ -3737,7 +3705,8 @@ new chat action */
|
||||
/* No comment provided by engineer. */
|
||||
"Only your contact can send voice messages." = "Alleen uw contact kan spraak berichten verzenden.";
|
||||
|
||||
/* alert action */
|
||||
/* alert action
|
||||
alert button */
|
||||
"Open" = "Open";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
@@ -3968,12 +3937,6 @@ new chat action */
|
||||
/* No comment provided by engineer. */
|
||||
"Privacy policy and conditions of use." = "Privacybeleid en gebruiksvoorwaarden.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Privacy redefined" = "Privacy opnieuw gedefinieerd";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Private chats, groups and your contacts are not accessible to server operators." = "Privéchats, groepen en uw contacten zijn niet toegankelijk voor serverbeheerders.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Private filenames" = "Privé bestandsnamen";
|
||||
|
||||
@@ -4010,9 +3973,6 @@ new chat action */
|
||||
/* No comment provided by engineer. */
|
||||
"Profile theme" = "Profiel thema";
|
||||
|
||||
/* alert message */
|
||||
"Profile update will be sent to your contacts." = "Profiel update wordt naar uw contacten verzonden.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Prohibit audio/video calls." = "Audio/video gesprekken verbieden.";
|
||||
|
||||
@@ -4098,16 +4058,10 @@ new chat action */
|
||||
"Read more" = "Lees meer";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Read more in [User Guide](https://simplex.chat/docs/guide/chat-profiles.html#incognito-mode)." = "Lees meer in de [Gebruikershandleiding](https://simplex.chat/docs/guide/chat-profiles.html#incognito-mode).";
|
||||
"Read more in our GitHub repository." = "Lees meer in onze GitHub-repository.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Read more in [User Guide](https://simplex.chat/docs/guide/making-connections.html#comparison-of-1-time-invitation-links-and-simplex-contact-addresses)." = "Lees meer in de [Gebruikershandleiding](https://simplex.chat/docs/guide/app-settings.html#uw-simplex-contactadres).";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Read more in [User Guide](https://simplex.chat/docs/guide/readme.html#connect-to-friends)." = "Lees meer in de [Gebruikershandleiding](https://simplex.chat/docs/guide/readme.html#connect-to-friends).";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Read more in our [GitHub repository](https://github.com/simplex-chat/simplex-chat#readme)." = "Lees meer in onze [GitHub-repository](https://github.com/simplex-chat/simplex-chat#readme).";
|
||||
"Read more in User Guide." = "Lees meer in de Gebruikershandleiding.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Receipts are disabled" = "Bevestigingen zijn uitgeschakeld";
|
||||
@@ -4797,9 +4751,6 @@ chat item action */
|
||||
/* No comment provided by engineer. */
|
||||
"Share address publicly" = "Adres openbaar delen";
|
||||
|
||||
/* alert title */
|
||||
"Share address with contacts?" = "Adres delen met contacten?";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Share from other apps." = "Delen vanuit andere apps.";
|
||||
|
||||
@@ -4818,9 +4769,6 @@ chat item action */
|
||||
/* No comment provided by engineer. */
|
||||
"Share to SimpleX" = "Delen op SimpleX";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Share with contacts" = "Delen met contacten";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Short link" = "Korte link";
|
||||
|
||||
@@ -4966,6 +4914,9 @@ report reason */
|
||||
/* chat item text */
|
||||
"standard end-to-end encryption" = "standaard end-to-end encryptie";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Star on GitHub" = "Star on GitHub";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Start chat" = "Begin gesprek";
|
||||
|
||||
@@ -5062,9 +5013,6 @@ report reason */
|
||||
/* No comment provided by engineer. */
|
||||
"Tap button " = "Tik op de knop ";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Tap Create SimpleX address in the menu to create it later." = "Tik op SimpleX-adres maken in het menu om het later te maken.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Tap to activate profile." = "Tik hier om profiel te activeren.";
|
||||
|
||||
@@ -5159,9 +5107,6 @@ server test failure */
|
||||
/* No comment provided by engineer. */
|
||||
"The encryption is working and the new encryption agreement is not required. It may result in connection errors!" = "De versleuteling werkt en de nieuwe versleutelingsovereenkomst is niet vereist. Dit kan leiden tot verbindingsfouten!";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"The future of messaging" = "De volgende generatie privéberichten";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"The hash of the previous message is different." = "De hash van het vorige bericht is anders.";
|
||||
|
||||
@@ -5321,9 +5266,6 @@ server test failure */
|
||||
/* No comment provided by engineer. */
|
||||
"To verify end-to-end encryption with your contact compare (or scan) the code on your devices." = "Vergelijk (of scan) de code op uw apparaten om end-to-end-codering met uw contact te verifiëren.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Toggle chat list:" = "Chatlijst wisselen:";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Toggle incognito when connecting." = "Schakel incognito in tijdens het verbinden.";
|
||||
|
||||
@@ -5441,7 +5383,7 @@ server test failure */
|
||||
/* swipe action */
|
||||
"Unread" = "Ongelezen";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
/* conn error description */
|
||||
"Unsupported connection link" = "Niet-ondersteunde verbindingslink";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
@@ -5498,9 +5440,6 @@ server test failure */
|
||||
/* No comment provided by engineer. */
|
||||
"Use %@" = "Gebruik %@";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Use chat" = "Gebruik chat";
|
||||
|
||||
/* new chat action */
|
||||
"Use current profile" = "Gebruik het huidige profiel";
|
||||
|
||||
@@ -5903,9 +5842,6 @@ server test failure */
|
||||
/* No comment provided by engineer. */
|
||||
"You could not be verified; please try again." = "U kon niet worden geverifieerd; probeer het opnieuw.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"You decide who can connect." = "Jij bepaalt wie er verbinding mag maken.";
|
||||
|
||||
/* new chat sheet title */
|
||||
"You have already requested connection!\nRepeat connection request?" = "Je hebt al verbinding aangevraagd!\nVerbindingsverzoek herhalen?";
|
||||
|
||||
|
||||
@@ -25,15 +25,9 @@
|
||||
/* No comment provided by engineer. */
|
||||
"(this device v%@)" = "(to urządzenie v%@)";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"[Contribute](https://github.com/simplex-chat/simplex-chat#contribute)" = "[Przyczyń się](https://github.com/simplex-chat/simplex-chat#contribute)";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"[Send us email](mailto:chat@simplex.chat)" = "[Wyślij do nas email](mailto:chat@simplex.chat)";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"[Star on GitHub](https://github.com/simplex-chat/simplex-chat)" = "[Daj gwiazdkę na GitHub](https://github.com/simplex-chat/simplex-chat)";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"**Create 1-time link**: to create and share a new invitation link." = "**Dodaj kontakt**: aby utworzyć nowy link z zaproszeniem lub połączyć się za pomocą otrzymanego linku.";
|
||||
|
||||
@@ -398,9 +392,6 @@ swipe action */
|
||||
/* No comment provided by engineer. */
|
||||
"Active connections" = "Aktywne połączenia";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Add address to your profile, so that your contacts can share it with other people. Profile update will be sent to your contacts." = "Dodaj adres do swojego profilu, aby Twoje kontakty mogły go udostępnić innym osobom. Aktualizacja profilu zostanie wysłana do Twoich kontaktów.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Add friends" = "Dodaj znajomych";
|
||||
|
||||
@@ -650,9 +641,6 @@ swipe action */
|
||||
/* No comment provided by engineer. */
|
||||
"Answer call" = "Odbierz połączenie";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Anybody can host servers." = "Każdy może hostować serwery.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"App build: %@" = "Kompilacja aplikacji: %@";
|
||||
|
||||
@@ -794,6 +782,12 @@ swipe action */
|
||||
/* No comment provided by engineer. */
|
||||
"Bad message ID" = "Zły identyfikator wiadomości";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Be free in your network." = "Ciesz się swobodą w swojej sieci.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Because we destroyed the power to know who you are. So that your power can never be taken." = "Ponieważ zniszczyliśmy moc pozwalającą poznać, kim jesteś. Więc twoja moc nigdy nie będzie Ci odebrana.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Better calls" = "Lepsze połączenia";
|
||||
|
||||
@@ -897,7 +891,7 @@ marked deleted chat item preview text */
|
||||
/* No comment provided by engineer. */
|
||||
"Bulgarian, Finnish, Thai and Ukrainian - thanks to the users and [Weblate](https://github.com/simplex-chat/simplex-chat/tree/stable#help-translating-simplex-chat)!" = "Bułgarski, fiński, tajski i ukraiński – dzięki użytkownikom i [Weblate](https://github.com/simplex-chat/simplex-chat/tree/stable#help-translating-simplex-chat)!";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
/* chat link info line */
|
||||
"Business address" = "Adres firmowy";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
@@ -912,9 +906,6 @@ marked deleted chat item preview text */
|
||||
/* No comment provided by engineer. */
|
||||
"By chat profile (default) or [by connection](https://simplex.chat/blog/20230204-simplex-chat-v4-5-user-chat-profiles.html#transport-isolation) (BETA)." = "Według profilu czatu (domyślnie) lub [według połączenia](https://simplex.chat/blog/20230204-simplex-chat-v4-5-user-chat-profiles.html#transport-isolation) (BETA).";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"By using SimpleX Chat you agree to:\n- send only legal content in public groups.\n- respect other users – no spam." = "Korzystając z SimpleX Chat, zgadzasz się:\n- wysyłać tylko legalne treści w grupach publicznych.\n- szanować innych użytkowników – nie spamować.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"call" = "zadzwoń";
|
||||
|
||||
@@ -1098,7 +1089,8 @@ set passcode view */
|
||||
/* No comment provided by engineer. */
|
||||
"Chat will be deleted for you - this cannot be undone!" = "Czat zostanie usunięty dla Ciebie – tej operacji nie można cofnąć!";
|
||||
|
||||
/* chat toolbar */
|
||||
/* chat feature
|
||||
chat toolbar */
|
||||
"Chat with admins" = "Czatuj z administratorami";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
@@ -1212,9 +1204,6 @@ set passcode view */
|
||||
/* No comment provided by engineer. */
|
||||
"Configure ICE servers" = "Skonfiguruj serwery ICE";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Configure server operators" = "Skonfiguruj operatorów serwerów";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Confirm" = "Potwierdź";
|
||||
|
||||
@@ -1348,7 +1337,7 @@ server test step */
|
||||
/* alert title */
|
||||
"Connection error" = "Błąd połączenia";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
/* conn error description */
|
||||
"Connection error (AUTH)" = "Błąd połączenia (UWIERZYTELNIANIE)";
|
||||
|
||||
/* chat list item title (it should not be shown */
|
||||
@@ -1453,6 +1442,9 @@ server test step */
|
||||
/* No comment provided by engineer. */
|
||||
"Continue" = "Kontynuuj";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Contribute" = "Przyczyń się";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Conversation deleted!" = "Rozmowa usunięta!";
|
||||
|
||||
@@ -1471,9 +1463,6 @@ server test step */
|
||||
/* alert message */
|
||||
"Correct name to %@?" = "Poprawić imię na %@?";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Create" = "Utwórz";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Create 1-time link" = "Utwórz jednorazowy link";
|
||||
|
||||
@@ -1627,9 +1616,6 @@ server test step */
|
||||
/* No comment provided by engineer. */
|
||||
"Debug delivery" = "Dostarczenie debugowania";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Decentralized" = "Zdecentralizowane";
|
||||
|
||||
/* message decrypt error item */
|
||||
"Decryption error" = "Błąd odszyfrowania";
|
||||
|
||||
@@ -2033,7 +2019,7 @@ chat item action */
|
||||
/* No comment provided by engineer. */
|
||||
"Empty message!" = "Pusta wiadomość!";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
/* alert button */
|
||||
"Enable" = "Włącz";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
@@ -2063,9 +2049,6 @@ chat item action */
|
||||
/* No comment provided by engineer. */
|
||||
"Enable lock" = "Włącz blokadę";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Enable notifications" = "Włącz powiadomienia";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Enable periodic notifications?" = "Włączyć okresowe powiadomienia?";
|
||||
|
||||
@@ -2207,7 +2190,7 @@ chat item action */
|
||||
/* No comment provided by engineer. */
|
||||
"error" = "błąd";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
/* conn error description */
|
||||
"Error" = "Błąd";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
@@ -2767,7 +2750,7 @@ servers warning */
|
||||
/* No comment provided by engineer. */
|
||||
"group is deleted" = "grupa została usunięta";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
/* chat link info line */
|
||||
"Group link" = "Link do grupy";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
@@ -2899,9 +2882,6 @@ servers warning */
|
||||
/* No comment provided by engineer. */
|
||||
"Immediately" = "Natychmiast";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Immune to spam" = "Odporność na spam i nadużycia";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Import" = "Importuj";
|
||||
|
||||
@@ -3002,7 +2982,7 @@ servers warning */
|
||||
"Initial role" = "Rola początkowa";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Install [SimpleX Chat for terminal](https://github.com/simplex-chat/simplex-chat)" = "Zainstaluj [SimpleX Chat na terminal](https://github.com/simplex-chat/simplex-chat)";
|
||||
"Install SimpleX Chat for terminal" = "Zainstaluj SimpleX Chat na terminal";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Instant" = "Natychmiastowo";
|
||||
@@ -3037,7 +3017,7 @@ servers warning */
|
||||
/* No comment provided by engineer. */
|
||||
"invalid chat data" = "nieprawidłowe dane czatu";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
/* conn error description */
|
||||
"Invalid connection link" = "Nieprawidłowy link połączenia";
|
||||
|
||||
/* invalid chat item */
|
||||
@@ -3481,9 +3461,6 @@ servers warning */
|
||||
/* No comment provided by engineer. */
|
||||
"Migrate device" = "Zmigruj urządzenie";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Migrate from another device" = "Zmigruj z innego urządzenia";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Migrate here" = "Zmigruj tutaj";
|
||||
|
||||
@@ -3764,7 +3741,10 @@ servers warning */
|
||||
"No unread chats" = "Brak nieprzeczytanych czatów";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"No user identifiers." = "Brak identyfikatorów użytkownika.";
|
||||
"Nobody tracked your conversations. No one drew a map of where you'd been. Privacy was never a feature - it was the way of life." = "Nikt nie śledził twoich rozmów. Nikt nie rysował mapy miejsc, w których byłeś. Prywatność nigdy nie była funkcją - była sposobem na życie.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Not a better lock on someone else's door. Not a nicer landlord that respects your privacy, but still keeps the record of all visitors. You are not a guest. You are home. No king can enter it - you are sovereign." = "Nie chodzi o lepszy zamek w drzwiach kogoś innego. Nie chodzi o milszego właściciela, który szanuje twoją prywatność, ale nadal prowadzi rejestr wszystkich odwiedzających. Nie jesteś gościem. Jesteś w domu. Żaden król nie może do niego wejść - jesteś suwerenem.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Not compatible!" = "Nie kompatybilny!";
|
||||
@@ -3822,7 +3802,7 @@ alert button
|
||||
new chat action */
|
||||
"Ok" = "Ok";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
/* alert button */
|
||||
"OK" = "OK";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
@@ -3903,7 +3883,8 @@ new chat action */
|
||||
/* No comment provided by engineer. */
|
||||
"Only your contact can send voice messages." = "Tylko Twój kontakt może wysyłać wiadomości głosowe.";
|
||||
|
||||
/* alert action */
|
||||
/* alert action
|
||||
alert button */
|
||||
"Open" = "Otwórz";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
@@ -4158,12 +4139,6 @@ new chat action */
|
||||
/* No comment provided by engineer. */
|
||||
"Privacy policy and conditions of use." = "Polityka prywatności i warunki korzystania.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Privacy redefined" = "Redefinicja prywatności";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Private chats, groups and your contacts are not accessible to server operators." = "Prywatne czaty, grupy i Twoje kontakty nie są dostępne dla operatorów serwerów.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Private filenames" = "Prywatne nazwy plików";
|
||||
|
||||
@@ -4203,9 +4178,6 @@ new chat action */
|
||||
/* No comment provided by engineer. */
|
||||
"Profile theme" = "Motyw profilu";
|
||||
|
||||
/* alert message */
|
||||
"Profile update will be sent to your contacts." = "Aktualizacja profilu zostanie wysłana do Twoich kontaktów.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Prohibit audio/video calls." = "Zabroń połączeń audio/wideo.";
|
||||
|
||||
@@ -4294,16 +4266,10 @@ new chat action */
|
||||
"Read more" = "Przeczytaj więcej";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Read more in [User Guide](https://simplex.chat/docs/guide/chat-profiles.html#incognito-mode)." = "Przeczytaj więcej w [Poradniku Użytkownika](https://simplex.chat/docs/guide/chat-profiles.html#incognito-mode).";
|
||||
"Read more in our GitHub repository." = "Przeczytaj więcej na naszym repozytorium GitHub.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Read more in [User Guide](https://simplex.chat/docs/guide/making-connections.html#comparison-of-1-time-invitation-links-and-simplex-contact-addresses)." = "Przeczytaj więcej w [Podręczniku Użytkownika](https://simplex.chat/docs/guide/making-connections.html#comparison-of-1-time-invitation-links-and-simplex-contact-addresses).";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Read more in [User Guide](https://simplex.chat/docs/guide/readme.html#connect-to-friends)." = "Przeczytaj więcej w [Podręczniku Użytkownika](https://simplex.chat/docs/guide/readme.html#connect-to-friends).";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Read more in our [GitHub repository](https://github.com/simplex-chat/simplex-chat#readme)." = "Przeczytaj więcej na naszym [repozytorium GitHub](https://github.com/simplex-chat/simplex-chat#readme).";
|
||||
"Read more in User Guide." = "Przeczytaj więcej w Poradniku Użytkownika.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Receipts are disabled" = "Potwierdzenia są wyłączone";
|
||||
@@ -5050,9 +5016,6 @@ chat item action */
|
||||
/* No comment provided by engineer. */
|
||||
"Share address publicly" = "Udostępnij adres publicznie";
|
||||
|
||||
/* alert title */
|
||||
"Share address with contacts?" = "Udostępnić adres kontaktom?";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Share from other apps." = "Udostępnij z innych aplikacji.";
|
||||
|
||||
@@ -5077,9 +5040,6 @@ chat item action */
|
||||
/* No comment provided by engineer. */
|
||||
"Share to SimpleX" = "Udostępnij do SimpleX";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Share with contacts" = "Udostępnij kontaktom";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Share your address" = "Udostępnij swój adres";
|
||||
|
||||
@@ -5234,6 +5194,9 @@ report reason */
|
||||
/* chat item text */
|
||||
"standard end-to-end encryption" = "standardowe szyfrowanie end-to-end";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Star on GitHub" = "Daj gwiazdkę na GitHub";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Start chat" = "Rozpocznij czat";
|
||||
|
||||
@@ -5339,9 +5302,6 @@ report reason */
|
||||
/* No comment provided by engineer. */
|
||||
"Tap Connect to use bot" = "Dotknij Połącz aby użyć bota";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Tap Create SimpleX address in the menu to create it later." = "Dotknij Stwórz adres SimpleX w menu aby utworzyć go później.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Tap Join group" = "Dotknij Dołącz do grupy";
|
||||
|
||||
@@ -5445,9 +5405,6 @@ server test failure */
|
||||
/* No comment provided by engineer. */
|
||||
"The encryption is working and the new encryption agreement is not required. It may result in connection errors!" = "Szyfrowanie działa, a nowe uzgodnienie szyfrowania nie jest wymagane. Może to spowodować błędy w połączeniu!";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"The future of messaging" = "Następna generacja prywatnych wiadomości";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"The hash of the previous message is different." = "Hash poprzedniej wiadomości jest inny.";
|
||||
|
||||
@@ -5472,6 +5429,9 @@ server test failure */
|
||||
/* No comment provided by engineer. */
|
||||
"The old database was not removed during the migration, it can be deleted." = "Stara baza danych nie została usunięta podczas migracji, można ją usunąć.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"The oldest human freedom - to speak to another person without being watched - built on infrastructure that cannot betray it." = "Najstarsza ludzka wolność - możliwość rozmowy z inną osobą bez bycia obserwowanym - opiera się na infrastrukturze, która nie może jej zdradzić.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"The same conditions will apply to operator **%@**." = "Te same warunki będą miały zastosowanie do operatora **%@**.";
|
||||
|
||||
@@ -5499,6 +5459,12 @@ server test failure */
|
||||
/* No comment provided by engineer. */
|
||||
"Themes" = "Motywy";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Then we moved online, and every platform asked for a piece of you - your name, your number, your friends. We accepted that the price of talking to others is letting someone know who we talk to. Every generation, people and tech, had it this way - telephone, email, messengers, social media. It seemed the only way possible." = "Następnie przenieśliśmy się do sieci, a każda platforma prosiła o podanie danych osobowych - imienia i nazwiska, numeru telefonu, znajomych. Zaakceptowaliśmy fakt, że ceną za możliwość komunikowania się z innymi jest ujawnienie komuś, z kim rozmawiamy. Tak było w przypadku każdego pokolenia, ludzi i technologii - telefonu, poczty elektronicznej, komunikatorów, mediów społecznościowych. Wydawało się to jedyną możliwą opcją.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"There is another way. A network with no phone numbers. No usernames. No accounts. No user identities of any kind. A network that connects people and carries encrypted messages without knowing who is connected." = "Jest jeszcze inny sposób. Sieć bez numerów telefonów. Bez nazw użytkowników. Bez kont. Bez jakichkolwiek tożsamości użytkowników. Sieć, która łączy ludzi i przesyła zaszyfrowane wiadomości, nie wiedząc, kto jest podłączony.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"These conditions will also apply for: **%@**." = "Warunki te będą miały również zastosowanie w przypadku: **%@**.";
|
||||
|
||||
@@ -5622,9 +5588,6 @@ server test failure */
|
||||
/* No comment provided by engineer. */
|
||||
"To verify end-to-end encryption with your contact compare (or scan) the code on your devices." = "Aby zweryfikować szyfrowanie end-to-end z Twoim kontaktem porównaj (lub zeskanuj) kod na waszych urządzeniach.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Toggle chat list:" = "Przełącz listę czatów:";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Toggle incognito when connecting." = "Przełącz incognito przy połączeniu.";
|
||||
|
||||
@@ -5745,7 +5708,7 @@ server test failure */
|
||||
/* swipe action */
|
||||
"Unread" = "Nieprzeczytane";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
/* conn error description */
|
||||
"Unsupported connection link" = "Nieobsługiwane łącze połączenia";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
@@ -5820,9 +5783,6 @@ server test failure */
|
||||
/* No comment provided by engineer. */
|
||||
"Use %@" = "Użyj %@";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Use chat" = "Użyj czatu";
|
||||
|
||||
/* new chat action */
|
||||
"Use current profile" = "Użyj obecnego profilu";
|
||||
|
||||
@@ -6240,9 +6200,6 @@ server test failure */
|
||||
/* No comment provided by engineer. */
|
||||
"You could not be verified; please try again." = "Nie można zweryfikować użytkownika; proszę spróbować ponownie.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"You decide who can connect." = "Ty decydujesz, kto może się połączyć.";
|
||||
|
||||
/* new chat sheet title */
|
||||
"You have already requested connection!\nRepeat connection request?" = "Już prosiłeś o połączenie!\nPowtórzyć prośbę połączenia?";
|
||||
|
||||
@@ -6297,6 +6254,9 @@ server test failure */
|
||||
/* snd group event chat item */
|
||||
"you unblocked %@" = "odblokowałeś %@";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"You were born without an account" = "Urodziłeś się bez konta.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"You will be able to send messages **only after your request is accepted**." = "Będziesz mógł wysyłać wiadomości **dopiero po zaakceptowaniu Twojej prośby**.";
|
||||
|
||||
@@ -6372,6 +6332,9 @@ server test failure */
|
||||
/* No comment provided by engineer. */
|
||||
"Your contacts will remain connected." = "Twoje kontakty pozostaną połączone.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Your conversations belong to you, as it had always been before the Internet. The network is not a place you visit. It is a place you create and own. And nobody can take it from you, whether you make it private or public." = "Twoje rozmowy należą do Ciebie, tak jak zawsze było przed pojawieniem się Internetu. Sieć nie jest miejscem, które odwiedzasz. Jest miejscem, które tworzysz i które należy do Ciebie. Nikt nie może Ci tego odebrać, niezależnie od tego, czy jest to miejsce prywatne, czy publiczne.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Your credentials may be sent unencrypted." = "Twoje poświadczenia mogą zostać wysłane niezaszyfrowane.";
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -13,15 +13,9 @@
|
||||
/* No comment provided by engineer. */
|
||||
"!1 colored!" = "!1 มีสี!";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"[Contribute](https://github.com/simplex-chat/simplex-chat#contribute)" = "[มีส่วนร่วม](https://github.com/simplex-chat/simplex-chat#contribute)";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"[Send us email](mailto:chat@simplex.chat)" = "[ส่งอีเมลถึงเรา](mailto:chat@simplex.chat)";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"[Star on GitHub](https://github.com/simplex-chat/simplex-chat)" = "[ติดดาวบน GitHub](https://github.com/simplex-chat/simplex-chat)";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"**e2e encrypted** audio call" = "การโทรเสียงแบบ **encrypted จากต้นจนจบ**";
|
||||
|
||||
@@ -233,9 +227,6 @@ swipe action */
|
||||
/* call status */
|
||||
"accepted call" = "รับสายแล้ว";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Add address to your profile, so that your contacts can share it with other people. Profile update will be sent to your contacts." = "เพิ่มที่อยู่ลงในโปรไฟล์ของคุณ เพื่อให้ผู้ติดต่อของคุณสามารถแชร์กับผู้อื่นได้ การอัปเดตโปรไฟล์จะถูกส่งไปยังผู้ติดต่อของคุณ";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Add profile" = "เพิ่มโปรไฟล์";
|
||||
|
||||
@@ -362,9 +353,6 @@ swipe action */
|
||||
/* No comment provided by engineer. */
|
||||
"Answer call" = "รับสาย";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Anybody can host servers." = "โปรโตคอลและโค้ดโอเพ่นซอร์ส – ใคร ๆ ก็สามารถเปิดใช้เซิร์ฟเวอร์ได้";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"App build: %@" = "รุ่นแอป: %@";
|
||||
|
||||
@@ -672,7 +660,7 @@ server test step */
|
||||
/* alert title */
|
||||
"Connection error" = "การเชื่อมต่อผิดพลาด";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
/* conn error description */
|
||||
"Connection error (AUTH)" = "การเชื่อมต่อผิดพลาด (AUTH)";
|
||||
|
||||
/* chat list item title (it should not be shown */
|
||||
@@ -720,15 +708,15 @@ server test step */
|
||||
/* No comment provided by engineer. */
|
||||
"Continue" = "ดำเนินการต่อ";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Contribute" = "มีส่วนร่วม";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Copy" = "คัดลอก";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Core version: v%@" = "รุ่นหลัก: v%@";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Create" = "สร้าง";
|
||||
|
||||
/* server test step */
|
||||
"Create file" = "สร้างไฟล์";
|
||||
|
||||
@@ -828,9 +816,6 @@ server test step */
|
||||
/* time unit */
|
||||
"days" = "วัน";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Decentralized" = "กระจายอำนาจแล้ว";
|
||||
|
||||
/* message decrypt error item */
|
||||
"Decryption error" = "ข้อผิดพลาดในการ decrypt";
|
||||
|
||||
@@ -1056,7 +1041,7 @@ alert button */
|
||||
/* No comment provided by engineer. */
|
||||
"Edit group profile" = "แก้ไขโปรไฟล์กลุ่ม";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
/* alert button */
|
||||
"Enable" = "เปิดใช้งาน";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
@@ -1074,9 +1059,6 @@ alert button */
|
||||
/* No comment provided by engineer. */
|
||||
"Enable lock" = "เปิดใช้งานการล็อค";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Enable notifications" = "เปิดใช้งานการแจ้งเตือน";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Enable periodic notifications?" = "เปิดใช้การแจ้งเตือนเป็นระยะๆ ไหม?";
|
||||
|
||||
@@ -1182,7 +1164,7 @@ alert button */
|
||||
/* No comment provided by engineer. */
|
||||
"error" = "ผิดพลาด";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
/* conn error description */
|
||||
"Error" = "ผิดพลาด";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
@@ -1449,7 +1431,7 @@ server test error */
|
||||
/* No comment provided by engineer. */
|
||||
"Group invitation is no longer valid, it was removed by sender." = "คำเชิญเข้าร่วมกลุ่มใช้ไม่ถูกต้องอีกต่อไป คำเชิญถูกลบโดยผู้ส่ง";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
/* chat link info line */
|
||||
"Group link" = "ลิงค์กลุ่ม";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
@@ -1551,9 +1533,6 @@ server test error */
|
||||
/* No comment provided by engineer. */
|
||||
"Immediately" = "โดยทันที";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Immune to spam" = "มีภูมิคุ้มกันต่อสแปมและการละเมิด";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Import" = "นำเข้า";
|
||||
|
||||
@@ -1615,7 +1594,7 @@ server test error */
|
||||
"Initial role" = "บทบาทเริ่มต้น";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Install [SimpleX Chat for terminal](https://github.com/simplex-chat/simplex-chat)" = "ติดตั้ง [SimpleX Chat สำหรับเทอร์มินัล](https://github.com/simplex-chat/simplex-chat)";
|
||||
"Install SimpleX Chat for terminal" = "ติดตั้ง SimpleX Chat สำหรับเทอร์มินัล";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Instant" = "ทันที";
|
||||
@@ -1632,7 +1611,7 @@ server test error */
|
||||
/* No comment provided by engineer. */
|
||||
"invalid chat data" = "ข้อมูลแชทไม่ถูกต้อง";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
/* conn error description */
|
||||
"Invalid connection link" = "ลิงค์เชื่อมต่อไม่ถูกต้อง";
|
||||
|
||||
/* invalid chat item */
|
||||
@@ -2004,9 +1983,6 @@ server test error */
|
||||
/* copied message info in history */
|
||||
"no text" = "ไม่มีข้อความ";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"No user identifiers." = "แพลตฟอร์มแรกที่ไม่มีตัวระบุผู้ใช้ - ถูกออกแบบให้เป็นส่วนตัว";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Notifications" = "การแจ้งเตือน";
|
||||
|
||||
@@ -2198,9 +2174,6 @@ new chat action */
|
||||
/* No comment provided by engineer. */
|
||||
"Privacy & security" = "ความเป็นส่วนตัวและความปลอดภัย";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Privacy redefined" = "นิยามความเป็นส่วนตัวใหม่";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Private filenames" = "ชื่อไฟล์ส่วนตัว";
|
||||
|
||||
@@ -2213,9 +2186,6 @@ new chat action */
|
||||
/* No comment provided by engineer. */
|
||||
"Profile password" = "รหัสผ่านโปรไฟล์";
|
||||
|
||||
/* alert message */
|
||||
"Profile update will be sent to your contacts." = "การอัปเดตโปรไฟล์จะถูกส่งไปยังผู้ติดต่อของคุณ";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Prohibit audio/video calls." = "ห้ามการโทรด้วยเสียง/วิดีโอ";
|
||||
|
||||
@@ -2268,13 +2238,10 @@ new chat action */
|
||||
"Read more" = "อ่านเพิ่มเติม";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Read more in [User Guide](https://simplex.chat/docs/guide/making-connections.html#comparison-of-1-time-invitation-links-and-simplex-contact-addresses)." = "อ่านเพิ่มเติมใน[คู่มือผู้ใช้](https://simplex.chat/docs/guide/making-connections.html#comparison-of-1-time-invitation-links-and-simplex-contact-addresses)";
|
||||
"Read more in our GitHub repository." = "อ่านเพิ่มเติมในพื้นที่เก็บข้อมูล GitHub";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Read more in [User Guide](https://simplex.chat/docs/guide/readme.html#connect-to-friends)." = "อ่านเพิ่มเติมใน[คู่มือผู้ใช้](https://simplex.chat/docs/guide/readme.html#connect-to-friends)";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Read more in our [GitHub repository](https://github.com/simplex-chat/simplex-chat#readme)." = "อ่านเพิ่มเติมใน[พื้นที่เก็บข้อมูล GitHub](https://github.com/simplex-chat/simplex-chat#readme)";
|
||||
"Read more in User Guide." = "อ่านเพิ่มเติมในคู่มือผู้ใช้";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"received answer…" = "ได้รับคำตอบ…";
|
||||
@@ -2625,15 +2592,9 @@ chat item action */
|
||||
/* No comment provided by engineer. */
|
||||
"Share address" = "แชร์ที่อยู่";
|
||||
|
||||
/* alert title */
|
||||
"Share address with contacts?" = "แชร์ที่อยู่กับผู้ติดต่อ?";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Share link" = "แชร์ลิงก์";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Share with contacts" = "แชร์กับผู้ติดต่อ";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Show calls in phone history" = "แสดงการโทรในประวัติการโทร";
|
||||
|
||||
@@ -2694,6 +2655,9 @@ chat item action */
|
||||
/* notification title */
|
||||
"Somebody" = "ใครบางคน";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Star on GitHub" = "ติดดาวบน GitHub";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Start chat" = "เริ่มแชท";
|
||||
|
||||
@@ -2812,9 +2776,6 @@ server test failure */
|
||||
/* No comment provided by engineer. */
|
||||
"The encryption is working and the new encryption agreement is not required. It may result in connection errors!" = "encryption กำลังทำงานและไม่จำเป็นต้องใช้ข้อตกลง encryption ใหม่ อาจทำให้การเชื่อมต่อผิดพลาดได้!";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"The future of messaging" = "การส่งข้อความส่วนตัวรุ่นต่อไป";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"The hash of the previous message is different." = "แฮชของข้อความก่อนหน้านี้แตกต่างกัน";
|
||||
|
||||
@@ -2974,9 +2935,6 @@ server test failure */
|
||||
/* No comment provided by engineer. */
|
||||
"Use .onion hosts" = "ใช้โฮสต์ .onion";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Use chat" = "ใช้แชท";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Use for new connections" = "ใช้สำหรับการเชื่อมต่อใหม่";
|
||||
|
||||
@@ -3175,9 +3133,6 @@ server test failure */
|
||||
/* No comment provided by engineer. */
|
||||
"You could not be verified; please try again." = "เราไม่สามารถตรวจสอบคุณได้ กรุณาลองอีกครั้ง.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"You decide who can connect." = "ผู้คนสามารถเชื่อมต่อกับคุณผ่านลิงก์ที่คุณแบ่งปันเท่านั้น";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"You have to enter passphrase every time the app starts - it is not stored on the device." = "คุณต้องใส่รหัสผ่านทุกครั้งที่เริ่มแอป - รหัสผ่านไม่ได้จัดเก็บไว้ในอุปกรณ์";
|
||||
|
||||
|
||||
@@ -25,15 +25,9 @@
|
||||
/* No comment provided by engineer. */
|
||||
"(this device v%@)" = "(bu cihaz v%@)";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"[Contribute](https://github.com/simplex-chat/simplex-chat#contribute)" = "[Katkıda bulun](https://github.com/simplex-chat/simplex-chat#contribute)";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"[Send us email](mailto:chat@simplex.chat)" = "[Bize e-posta gönder](mailto:chat@simplex.chat)";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"[Star on GitHub](https://github.com/simplex-chat/simplex-chat)" = "[Bize GitHub'da yıldız verin](https://github.com/simplex-chat/simplex-chat)";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"**Create 1-time link**: to create and share a new invitation link." = "**Kişi ekle**: yeni bir davet bağlantısı oluşturmak için, ya da aldığın bağlantıyla bağlan.";
|
||||
|
||||
@@ -398,9 +392,6 @@ swipe action */
|
||||
/* No comment provided by engineer. */
|
||||
"Active connections" = "Aktif bağlantılar";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Add address to your profile, so that your contacts can share it with other people. Profile update will be sent to your contacts." = "Kişilerinizin başkalarıyla paylaşabilmesi için profilinize adres ekleyin. Profil güncellemesi kişilerinize gönderilecek.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Add friends" = "Arkadaş ekle";
|
||||
|
||||
@@ -647,9 +638,6 @@ swipe action */
|
||||
/* No comment provided by engineer. */
|
||||
"Answer call" = "Aramayı cevapla";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Anybody can host servers." = "Açık kaynak protokolü ve kodu - herhangi biri sunucuları çalıştırabilir.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"App build: %@" = "Uygulama sürümü: %@";
|
||||
|
||||
@@ -891,7 +879,7 @@ marked deleted chat item preview text */
|
||||
/* No comment provided by engineer. */
|
||||
"Bulgarian, Finnish, Thai and Ukrainian - thanks to the users and [Weblate](https://github.com/simplex-chat/simplex-chat/tree/stable#help-translating-simplex-chat)!" = "Bulgarca, Fince, Tayca ve Ukraynaca - kullanıcılara ve [Weblate] e teşekkürler! (https://github.com/simplex-chat/simplex-chat/tree/stable#help-translating-simplex-chat)!";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
/* chat link info line */
|
||||
"Business address" = "İş adresi";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
@@ -906,9 +894,6 @@ marked deleted chat item preview text */
|
||||
/* No comment provided by engineer. */
|
||||
"By chat profile (default) or [by connection](https://simplex.chat/blog/20230204-simplex-chat-v4-5-user-chat-profiles.html#transport-isolation) (BETA)." = "Sohbet profiline göre (varsayılan) veya [bağlantıya göre](https://simplex.chat/blog/20230204-simplex-chat-v4-5-user-chat-profiles.html#transport-isolation) (BETA).";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"By using SimpleX Chat you agree to:\n- send only legal content in public groups.\n- respect other users – no spam." = "SimpleX Chat'i kullanarak şunları kabul etmiş olursunuz:\n- herkese açık gruplarda yalnızca yasal içerik göndermek.\n- diğer kullanıcılara saygı göstermek – spam yapmamak.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"call" = "Ara";
|
||||
|
||||
@@ -1092,7 +1077,8 @@ set passcode view */
|
||||
/* No comment provided by engineer. */
|
||||
"Chat will be deleted for you - this cannot be undone!" = "Sohbet senden silinecek - bu geri alınamaz!";
|
||||
|
||||
/* chat toolbar */
|
||||
/* chat feature
|
||||
chat toolbar */
|
||||
"Chat with admins" = "Yöneticilerle sohbet et";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
@@ -1206,9 +1192,6 @@ set passcode view */
|
||||
/* No comment provided by engineer. */
|
||||
"Configure ICE servers" = "ICE sunucularını ayarla";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Configure server operators" = "Sunucu operatörlerini yapılandır";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Confirm" = "Onayla";
|
||||
|
||||
@@ -1342,7 +1325,7 @@ server test step */
|
||||
/* alert title */
|
||||
"Connection error" = "Bağlantı hatası";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
/* conn error description */
|
||||
"Connection error (AUTH)" = "Bağlantı hatası (DOĞRULAMA)";
|
||||
|
||||
/* chat list item title (it should not be shown */
|
||||
@@ -1444,6 +1427,9 @@ server test step */
|
||||
/* No comment provided by engineer. */
|
||||
"Continue" = "Devam et";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Contribute" = "Katkıda bulun";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Conversation deleted!" = "Sohbet silindi!";
|
||||
|
||||
@@ -1462,9 +1448,6 @@ server test step */
|
||||
/* alert message */
|
||||
"Correct name to %@?" = "İsim %@ olarak düzeltilsin mi?";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Create" = "Oluştur";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Create 1-time link" = "Tek kullanımlık bağlantı oluştur";
|
||||
|
||||
@@ -1618,9 +1601,6 @@ server test step */
|
||||
/* No comment provided by engineer. */
|
||||
"Debug delivery" = "Hata ayıklama teslimatı";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Decentralized" = "Merkezi Olmayan";
|
||||
|
||||
/* message decrypt error item */
|
||||
"Decryption error" = "Şifre çözme hatası";
|
||||
|
||||
@@ -2018,7 +1998,7 @@ chat item action */
|
||||
/* No comment provided by engineer. */
|
||||
"Empty message!" = "Boş mesaj!";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
/* alert button */
|
||||
"Enable" = "Etkinleştir";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
@@ -2048,9 +2028,6 @@ chat item action */
|
||||
/* No comment provided by engineer. */
|
||||
"Enable lock" = "Kilidi etkinleştir";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Enable notifications" = "Bildirimleri etkinleştir";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Enable periodic notifications?" = "Periyodik bildirimler etkinleştirilsin mi?";
|
||||
|
||||
@@ -2192,7 +2169,7 @@ chat item action */
|
||||
/* No comment provided by engineer. */
|
||||
"error" = "hata";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
/* conn error description */
|
||||
"Error" = "Hata";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
@@ -2730,7 +2707,7 @@ servers warning */
|
||||
/* No comment provided by engineer. */
|
||||
"group is deleted" = "grup silindi";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
/* chat link info line */
|
||||
"Group link" = "Grup bağlantısı";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
@@ -2856,9 +2833,6 @@ servers warning */
|
||||
/* No comment provided by engineer. */
|
||||
"Immediately" = "Hemen";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Immune to spam" = "Spam ve kötüye kullanıma karşı bağışıklı";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Import" = "İçe aktar";
|
||||
|
||||
@@ -2959,7 +2933,7 @@ servers warning */
|
||||
"Initial role" = "Başlangıç rolü";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Install [SimpleX Chat for terminal](https://github.com/simplex-chat/simplex-chat)" = "[Terminal için SimpleX Chat]i indir(https://github.com/simplex-chat/simplex-chat)";
|
||||
"Install SimpleX Chat for terminal" = "Terminal için SimpleX Chat'i indir";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Instant" = "Anında";
|
||||
@@ -2994,7 +2968,7 @@ servers warning */
|
||||
/* No comment provided by engineer. */
|
||||
"invalid chat data" = "geçersi̇z sohbet verisi";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
/* conn error description */
|
||||
"Invalid connection link" = "Geçersiz bağlanma bağlantısı";
|
||||
|
||||
/* invalid chat item */
|
||||
@@ -3429,9 +3403,6 @@ servers warning */
|
||||
/* No comment provided by engineer. */
|
||||
"Migrate device" = "Cihazı taşıma";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Migrate from another device" = "Başka bir cihazdan geçiş yapın";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Migrate here" = "Buraya göç edin";
|
||||
|
||||
@@ -3708,9 +3679,6 @@ servers warning */
|
||||
/* No comment provided by engineer. */
|
||||
"No unread chats" = "Okunmamış sohbet yok";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"No user identifiers." = "Herhangi bir kullanıcı tanımlayıcısı yok.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Not compatible!" = "Uyumlu değil!";
|
||||
|
||||
@@ -3767,7 +3735,7 @@ alert button
|
||||
new chat action */
|
||||
"Ok" = "Tamam";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
/* alert button */
|
||||
"OK" = "TAMAM";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
@@ -3848,7 +3816,8 @@ new chat action */
|
||||
/* No comment provided by engineer. */
|
||||
"Only your contact can send voice messages." = "Sadece karşıdaki kişi sesli mesajlar gönderebilir.";
|
||||
|
||||
/* alert action */
|
||||
/* alert action
|
||||
alert button */
|
||||
"Open" = "Aç";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
@@ -4103,12 +4072,6 @@ new chat action */
|
||||
/* No comment provided by engineer. */
|
||||
"Privacy policy and conditions of use." = "Gizlilik politikası ve kullanım koşulları.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Privacy redefined" = "Gizlilik yeniden tanımlandı";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Private chats, groups and your contacts are not accessible to server operators." = "Özel sohbetler, gruplar ve kişilerinize sunucu operatörleri tarafından erişilemez.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Private filenames" = "Gizli dosya adları";
|
||||
|
||||
@@ -4148,9 +4111,6 @@ new chat action */
|
||||
/* No comment provided by engineer. */
|
||||
"Profile theme" = "Profil teması";
|
||||
|
||||
/* alert message */
|
||||
"Profile update will be sent to your contacts." = "Profil güncellemesi kişilerinize gönderilecektir.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Prohibit audio/video calls." = "Sesli/görüntülü aramaları yasakla.";
|
||||
|
||||
@@ -4239,16 +4199,10 @@ new chat action */
|
||||
"Read more" = "Dahasını oku";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Read more in [User Guide](https://simplex.chat/docs/guide/chat-profiles.html#incognito-mode)." = "[Kullanıcı Rehberi]nde daha fazlasını okuyun(https://simplex.chat/docs/guide/chat-profiles.html#incognito-mode).";
|
||||
"Read more in our GitHub repository." = "GitHub deposunda daha fazlasını okuyun.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Read more in [User Guide](https://simplex.chat/docs/guide/making-connections.html#comparison-of-1-time-invitation-links-and-simplex-contact-addresses)." = "[Kullanıcı Rehberi]nde daha fazlasını okuyun(https://simplex.chat/docs/guide/making-connections.html#comparison-of-1-time-invitation-links-and-simplex-contact-addresses).";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Read more in [User Guide](https://simplex.chat/docs/guide/readme.html#connect-to-friends)." = "[Kullanıcı Rehberi]nde daha fazlasını okuyun(https://simplex.chat/docs/guide/readme.html#connect-to-friends).";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Read more in our [GitHub repository](https://github.com/simplex-chat/simplex-chat#readme)." = "[GitHub deposu]nda daha fazlasını okuyun(https://github.com/simplex-chat/simplex-chat#readme).";
|
||||
"Read more in User Guide." = "Kullanıcı Rehberinde daha fazlasını okuyun.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Receipts are disabled" = "Alındı onayları devre dışı bırakıldı";
|
||||
@@ -4977,9 +4931,6 @@ chat item action */
|
||||
/* No comment provided by engineer. */
|
||||
"Share address publicly" = "Adresinizi herkese açık olarak paylaşın";
|
||||
|
||||
/* alert title */
|
||||
"Share address with contacts?" = "Kişilerle adres paylaşılsın mı?";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Share from other apps." = "Diğer uygulamalardan paylaşın.";
|
||||
|
||||
@@ -5004,9 +4955,6 @@ chat item action */
|
||||
/* No comment provided by engineer. */
|
||||
"Share to SimpleX" = "SimpleX ile paylaş";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Share with contacts" = "Kişilerle paylaş";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Share your address" = "Adresini paylaş";
|
||||
|
||||
@@ -5161,6 +5109,9 @@ report reason */
|
||||
/* chat item text */
|
||||
"standard end-to-end encryption" = "standart uçtan uca şifreleme";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Star on GitHub" = "Bize GitHub'da yıldız verin";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Start chat" = "Sohbeti başlat";
|
||||
|
||||
@@ -5266,9 +5217,6 @@ report reason */
|
||||
/* No comment provided by engineer. */
|
||||
"Tap Connect to use bot" = "Botu kullanmak için Bağlan tuşuna bas";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Tap Create SimpleX address in the menu to create it later." = "Daha sonra oluşturmak için menüden BasitX adresi oluştur'a dokunun.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Tap Join group" = "Gruba katıl'a dokunun";
|
||||
|
||||
@@ -5372,9 +5320,6 @@ server test failure */
|
||||
/* No comment provided by engineer. */
|
||||
"The encryption is working and the new encryption agreement is not required. It may result in connection errors!" = "Şifreleme çalışıyor ve yeni şifreleme anlaşması gerekli değil. Bağlantı hatalarına neden olabilir!";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"The future of messaging" = "Gizli mesajlaşmanın yeni nesli";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"The hash of the previous message is different." = "Önceki mesajın hash'i farklı.";
|
||||
|
||||
@@ -5549,9 +5494,6 @@ server test failure */
|
||||
/* No comment provided by engineer. */
|
||||
"To verify end-to-end encryption with your contact compare (or scan) the code on your devices." = "Kişinizle uçtan uca şifrelemeyi doğrulamak için cihazlarınızdaki kodu karşılaştırın (veya tarayın).";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Toggle chat list:" = "Sohbet listesini değiştir:";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Toggle incognito when connecting." = "Bağlanırken gizli moda geçiş yap.";
|
||||
|
||||
@@ -5669,7 +5611,7 @@ server test failure */
|
||||
/* swipe action */
|
||||
"Unread" = "Okunmamış";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
/* conn error description */
|
||||
"Unsupported connection link" = "Desteklenmeyen bağlantı bağlantısı";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
@@ -5744,9 +5686,6 @@ server test failure */
|
||||
/* No comment provided by engineer. */
|
||||
"Use %@" = "%@ kullan";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Use chat" = "Sohbeti kullan";
|
||||
|
||||
/* new chat action */
|
||||
"Use current profile" = "Şu anki profili kullan";
|
||||
|
||||
@@ -6155,9 +6094,6 @@ server test failure */
|
||||
/* No comment provided by engineer. */
|
||||
"You could not be verified; please try again." = "Doğrulanamadınız; lütfen tekrar deneyin.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"You decide who can connect." = "Kimin bağlanabileceğine siz karar verirsiniz.";
|
||||
|
||||
/* new chat sheet title */
|
||||
"You have already requested connection!\nRepeat connection request?" = "Zaten bağlantı isteğinde bulundunuz!\nBağlantı isteği tekrarlansın mı?";
|
||||
|
||||
|
||||
@@ -25,15 +25,9 @@
|
||||
/* No comment provided by engineer. */
|
||||
"(this device v%@)" = "(цей пристрій v%@)";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"[Contribute](https://github.com/simplex-chat/simplex-chat#contribute)" = "[Внесок](https://github.com/simplex-chat/simplex-chat#contribute)";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"[Send us email](mailto:chat@simplex.chat)" = "[Напишіть нам електронною поштою](mailto:chat@simplex.chat)";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"[Star on GitHub](https://github.com/simplex-chat/simplex-chat)" = "[Зірка на GitHub](https://github.com/simplex-chat/simplex-chat)";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"**Create 1-time link**: to create and share a new invitation link." = "**Додати контакт**: створити нове посилання-запрошення.";
|
||||
|
||||
@@ -398,9 +392,6 @@ swipe action */
|
||||
/* No comment provided by engineer. */
|
||||
"Active connections" = "Активні з'єднання";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Add address to your profile, so that your contacts can share it with other people. Profile update will be sent to your contacts." = "Додайте адресу до свого профілю, щоб ваші контакти могли поділитися нею з іншими людьми. Повідомлення про оновлення профілю буде надіслано вашим контактам.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Add friends" = "Додайте друзів";
|
||||
|
||||
@@ -641,9 +632,6 @@ swipe action */
|
||||
/* No comment provided by engineer. */
|
||||
"Answer call" = "Відповісти на дзвінок";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Anybody can host servers." = "Кожен може хостити сервери.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"App build: %@" = "Збірка програми: %@";
|
||||
|
||||
@@ -879,7 +867,7 @@ marked deleted chat item preview text */
|
||||
/* No comment provided by engineer. */
|
||||
"Bulgarian, Finnish, Thai and Ukrainian - thanks to the users and [Weblate](https://github.com/simplex-chat/simplex-chat/tree/stable#help-translating-simplex-chat)!" = "Болгарською, фінською, тайською та українською мовами - завдяки користувачам та [Weblate](https://github.com/simplex-chat/simplex-chat/tree/stable#help-translating-simplex-chat)!";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
/* chat link info line */
|
||||
"Business address" = "Адреса підприємства";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
@@ -894,9 +882,6 @@ marked deleted chat item preview text */
|
||||
/* No comment provided by engineer. */
|
||||
"By chat profile (default) or [by connection](https://simplex.chat/blog/20230204-simplex-chat-v4-5-user-chat-profiles.html#transport-isolation) (BETA)." = "Через профіль чату (за замовчуванням) або [за з'єднанням](https://simplex.chat/blog/20230204-simplex-chat-v4-5-user-chat-profiles.html#transport-isolation) (BETA).";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"By using SimpleX Chat you agree to:\n- send only legal content in public groups.\n- respect other users – no spam." = "Використовуючи SimpleX Chat, ви погоджуєтеся:\n- надсилати лише легальний контент у публічних групах.\n- поважати інших користувачів - без спаму.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"call" = "дзвонити";
|
||||
|
||||
@@ -1080,7 +1065,8 @@ set passcode view */
|
||||
/* No comment provided by engineer. */
|
||||
"Chat will be deleted for you - this cannot be undone!" = "Чат буде видалено для вас - цю дію неможливо скасувати!";
|
||||
|
||||
/* chat toolbar */
|
||||
/* chat feature
|
||||
chat toolbar */
|
||||
"Chat with admins" = "Чат з адміністраторами";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
@@ -1194,9 +1180,6 @@ set passcode view */
|
||||
/* No comment provided by engineer. */
|
||||
"Configure ICE servers" = "Налаштування серверів ICE";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Configure server operators" = "Налаштувати операторів сервера";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Confirm" = "Підтвердити";
|
||||
|
||||
@@ -1330,7 +1313,7 @@ server test step */
|
||||
/* alert title */
|
||||
"Connection error" = "Помилка підключення";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
/* conn error description */
|
||||
"Connection error (AUTH)" = "Помилка підключення (AUTH)";
|
||||
|
||||
/* chat list item title (it should not be shown */
|
||||
@@ -1429,6 +1412,9 @@ server test step */
|
||||
/* No comment provided by engineer. */
|
||||
"Continue" = "Продовжуйте";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Contribute" = "Внесок";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Conversation deleted!" = "Розмова видалена!";
|
||||
|
||||
@@ -1447,9 +1433,6 @@ server test step */
|
||||
/* alert message */
|
||||
"Correct name to %@?" = "Виправити ім'я на %@?";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Create" = "Створити";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Create 1-time link" = "Створити одноразове посилання";
|
||||
|
||||
@@ -1603,9 +1586,6 @@ server test step */
|
||||
/* No comment provided by engineer. */
|
||||
"Debug delivery" = "Доставка налагодження";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Decentralized" = "Децентралізований";
|
||||
|
||||
/* message decrypt error item */
|
||||
"Decryption error" = "Помилка розшифровки";
|
||||
|
||||
@@ -2000,7 +1980,7 @@ chat item action */
|
||||
/* No comment provided by engineer. */
|
||||
"Empty message!" = "Порожнє повідомлення!";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
/* alert button */
|
||||
"Enable" = "Увімкнути";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
@@ -2030,9 +2010,6 @@ chat item action */
|
||||
/* No comment provided by engineer. */
|
||||
"Enable lock" = "Увімкнути блокування";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Enable notifications" = "Увімкнути сповіщення";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Enable periodic notifications?" = "Увімкнути періодичні сповіщення?";
|
||||
|
||||
@@ -2174,7 +2151,7 @@ chat item action */
|
||||
/* No comment provided by engineer. */
|
||||
"error" = "помилка";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
/* conn error description */
|
||||
"Error" = "Помилка";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
@@ -2706,7 +2683,7 @@ servers warning */
|
||||
/* No comment provided by engineer. */
|
||||
"group is deleted" = "групу видалено";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
/* chat link info line */
|
||||
"Group link" = "Посилання на групу";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
@@ -2832,9 +2809,6 @@ servers warning */
|
||||
/* No comment provided by engineer. */
|
||||
"Immediately" = "Негайно";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Immune to spam" = "Імунітет до спаму та зловживань";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Import" = "Імпорт";
|
||||
|
||||
@@ -2935,7 +2909,7 @@ servers warning */
|
||||
"Initial role" = "Початкова роль";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Install [SimpleX Chat for terminal](https://github.com/simplex-chat/simplex-chat)" = "Встановіть [SimpleX Chat для терміналу](https://github.com/simplex-chat/simplex-chat)";
|
||||
"Install SimpleX Chat for terminal" = "Встановіть SimpleX Chat для терміналу";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Instant" = "Миттєво";
|
||||
@@ -2970,7 +2944,7 @@ servers warning */
|
||||
/* No comment provided by engineer. */
|
||||
"invalid chat data" = "невірні дані чату";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
/* conn error description */
|
||||
"Invalid connection link" = "Неправильне посилання для підключення";
|
||||
|
||||
/* invalid chat item */
|
||||
@@ -3399,9 +3373,6 @@ servers warning */
|
||||
/* No comment provided by engineer. */
|
||||
"Migrate device" = "Перенести пристрій";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Migrate from another device" = "Перехід з іншого пристрою";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Migrate here" = "Мігруйте сюди";
|
||||
|
||||
@@ -3678,9 +3649,6 @@ servers warning */
|
||||
/* No comment provided by engineer. */
|
||||
"No unread chats" = "Немає непрочитаних чатів";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"No user identifiers." = "Ніяких ідентифікаторів користувачів.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Not compatible!" = "Не сумісні!";
|
||||
|
||||
@@ -3737,7 +3705,7 @@ alert button
|
||||
new chat action */
|
||||
"Ok" = "Гаразд";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
/* alert button */
|
||||
"OK" = "ОК";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
@@ -3812,7 +3780,8 @@ new chat action */
|
||||
/* No comment provided by engineer. */
|
||||
"Only your contact can send voice messages." = "Тільки ваш контакт може надсилати голосові повідомлення.";
|
||||
|
||||
/* alert action */
|
||||
/* alert action
|
||||
alert button */
|
||||
"Open" = "Відкрито";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
@@ -4058,12 +4027,6 @@ new chat action */
|
||||
/* No comment provided by engineer. */
|
||||
"Privacy policy and conditions of use." = "Політика конфіденційності та умови використання.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Privacy redefined" = "Конфіденційність переглянута";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Private chats, groups and your contacts are not accessible to server operators." = "Приватні чати, групи та ваші контакти недоступні для операторів сервера.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Private filenames" = "Приватні імена файлів";
|
||||
|
||||
@@ -4103,9 +4066,6 @@ new chat action */
|
||||
/* No comment provided by engineer. */
|
||||
"Profile theme" = "Тема профілю";
|
||||
|
||||
/* alert message */
|
||||
"Profile update will be sent to your contacts." = "Оновлення профілю буде надіслано вашим контактам.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Prohibit audio/video calls." = "Заборонити аудіо/відеодзвінки.";
|
||||
|
||||
@@ -4194,16 +4154,10 @@ new chat action */
|
||||
"Read more" = "Читати далі";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Read more in [User Guide](https://simplex.chat/docs/guide/chat-profiles.html#incognito-mode)." = "Читайте більше в [User Guide](https://simplex.chat/docs/guide/chat-profiles.html#incognito-mode).";
|
||||
"Read more in our GitHub repository." = "Читайте більше в нашому GitHub репозиторії.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Read more in [User Guide](https://simplex.chat/docs/guide/making-connections.html#comparison-of-1-time-invitation-links-and-simplex-contact-addresses)." = "Читайте більше в [Посібнику користувача](https://simplex.chat/docs/guide/making-connections.html#comparison-of-1-time-invitation-links-and-simplex-contact-addresses).";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Read more in [User Guide](https://simplex.chat/docs/guide/readme.html#connect-to-friends)." = "Читайте більше в [Посібнику користувача](https://simplex.chat/docs/guide/readme.html#connect-to-friends).";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Read more in our [GitHub repository](https://github.com/simplex-chat/simplex-chat#readme)." = "Читайте більше в нашому [GitHub репозиторії](https://github.com/simplex-chat/simplex-chat#readme).";
|
||||
"Read more in User Guide." = "Читайте більше в User Guide.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Receipts are disabled" = "Підтвердження виключені";
|
||||
@@ -4923,9 +4877,6 @@ chat item action */
|
||||
/* No comment provided by engineer. */
|
||||
"Share address publicly" = "Поділіться адресою публічно";
|
||||
|
||||
/* alert title */
|
||||
"Share address with contacts?" = "Поділіться адресою з контактами?";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Share from other apps." = "Діліться з інших програм.";
|
||||
|
||||
@@ -4950,9 +4901,6 @@ chat item action */
|
||||
/* No comment provided by engineer. */
|
||||
"Share to SimpleX" = "Поділіться з SimpleX";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Share with contacts" = "Поділіться з контактами";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Share your address" = "Поділіться своєю адресою";
|
||||
|
||||
@@ -5107,6 +5055,9 @@ report reason */
|
||||
/* chat item text */
|
||||
"standard end-to-end encryption" = "стандартне наскрізне шифрування";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Star on GitHub" = "Зірка на GitHub";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Start chat" = "Почати чат";
|
||||
|
||||
@@ -5209,9 +5160,6 @@ report reason */
|
||||
/* No comment provided by engineer. */
|
||||
"Tap Connect to send request" = "Натисніть Підключитися, щоб відправити запит";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Tap Create SimpleX address in the menu to create it later." = "Натисніть «Створити адресу SimpleX» у меню, щоб створити її пізніше.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Tap Join group" = "Натисніть Приєднатися до групи";
|
||||
|
||||
@@ -5315,9 +5263,6 @@ server test failure */
|
||||
/* No comment provided by engineer. */
|
||||
"The encryption is working and the new encryption agreement is not required. It may result in connection errors!" = "Шифрування працює і нова угода про шифрування не потрібна. Це може призвести до помилок з'єднання!";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"The future of messaging" = "Наступне покоління приватних повідомлень";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"The hash of the previous message is different." = "Хеш попереднього повідомлення відрізняється.";
|
||||
|
||||
@@ -5486,9 +5431,6 @@ server test failure */
|
||||
/* No comment provided by engineer. */
|
||||
"To verify end-to-end encryption with your contact compare (or scan) the code on your devices." = "Щоб перевірити наскрізне шифрування з вашим контактом, порівняйте (або відскануйте) код на ваших пристроях.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Toggle chat list:" = "Перемикання списку чату:";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Toggle incognito when connecting." = "Увімкніть інкогніто при підключенні.";
|
||||
|
||||
@@ -5606,7 +5548,7 @@ server test failure */
|
||||
/* swipe action */
|
||||
"Unread" = "Непрочитане";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
/* conn error description */
|
||||
"Unsupported connection link" = "Несумісне посилання для підключення";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
@@ -5681,9 +5623,6 @@ server test failure */
|
||||
/* No comment provided by engineer. */
|
||||
"Use %@" = "Використовуйте %@";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Use chat" = "Використовуйте чат";
|
||||
|
||||
/* new chat action */
|
||||
"Use current profile" = "Використовувати поточний профіль";
|
||||
|
||||
@@ -6092,9 +6031,6 @@ server test failure */
|
||||
/* No comment provided by engineer. */
|
||||
"You could not be verified; please try again." = "Вас не вдалося верифікувати, спробуйте ще раз.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"You decide who can connect." = "Ви вирішуєте, хто може під'єднатися.";
|
||||
|
||||
/* new chat sheet title */
|
||||
"You have already requested connection!\nRepeat connection request?" = "Ви вже надіслали запит на підключення!\nПовторити запит на підключення?";
|
||||
|
||||
|
||||
@@ -25,15 +25,9 @@
|
||||
/* No comment provided by engineer. */
|
||||
"(this device v%@)" = "(此设备 v%@)";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"[Contribute](https://github.com/simplex-chat/simplex-chat#contribute)" = "[贡献](https://github.com/simplex-chat/simplex-chat#contribute)";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"[Send us email](mailto:chat@simplex.chat)" = "[给我们发电邮](mailto:chat@simplex.chat)";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"[Star on GitHub](https://github.com/simplex-chat/simplex-chat)" = "[在 GitHub 上加星](https://github.com/simplex-chat/simplex-chat)";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"**Create 1-time link**: to create and share a new invitation link." = "**添加联系人**: 创建新的邀请链接,或通过您收到的链接进行连接.";
|
||||
|
||||
@@ -395,9 +389,6 @@ swipe action */
|
||||
/* No comment provided by engineer. */
|
||||
"Active connections" = "活动连接";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Add address to your profile, so that your contacts can share it with other people. Profile update will be sent to your contacts." = "将地址添加到您的个人资料,以便您的联系人可以与其他人共享。个人资料更新将发送给您的联系人。";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Add friends" = "添加好友";
|
||||
|
||||
@@ -647,9 +638,6 @@ swipe action */
|
||||
/* No comment provided by engineer. */
|
||||
"Answer call" = "接听来电";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Anybody can host servers." = "任何人都可以托管服务器。";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"App build: %@" = "应用程序构建:%@";
|
||||
|
||||
@@ -791,6 +779,12 @@ swipe action */
|
||||
/* No comment provided by engineer. */
|
||||
"Bad message ID" = "错误消息 ID";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Be free in your network." = "在你的网络中自由畅行。";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Because we destroyed the power to know who you are. So that your power can never be taken." = "因为我们摧毁了知道你是谁的权力,因而您的权利永远不会被夺走。";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Better calls" = "更佳的通话";
|
||||
|
||||
@@ -894,7 +888,7 @@ marked deleted chat item preview text */
|
||||
/* No comment provided by engineer. */
|
||||
"Bulgarian, Finnish, Thai and Ukrainian - thanks to the users and [Weblate](https://github.com/simplex-chat/simplex-chat/tree/stable#help-translating-simplex-chat)!" = "保加利亚语、芬兰语、泰语和乌克兰语——感谢用户和[Weblate](https://github.com/simplex-chat/simplex-chat/tree/stable#help-translating-simplex-chat)!";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
/* chat link info line */
|
||||
"Business address" = "企业地址";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
@@ -909,9 +903,6 @@ marked deleted chat item preview text */
|
||||
/* No comment provided by engineer. */
|
||||
"By chat profile (default) or [by connection](https://simplex.chat/blog/20230204-simplex-chat-v4-5-user-chat-profiles.html#transport-isolation) (BETA)." = "通过聊天资料(默认)或者[通过连接](https://simplex.chat/blog/20230204-simplex-chat-v4-5-user-chat-profiles.html#transport-isolation) (BETA)。";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"By using SimpleX Chat you agree to:\n- send only legal content in public groups.\n- respect other users – no spam." = "使用 SimpleX Chat 代表您同意:\n- 在公开群中只发送合法内容\n- 尊重其他用户 – 没有垃圾信息。";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"call" = "呼叫";
|
||||
|
||||
@@ -1095,7 +1086,8 @@ set passcode view */
|
||||
/* No comment provided by engineer. */
|
||||
"Chat will be deleted for you - this cannot be undone!" = "将为你删除聊天 - 此操作无法撤销!";
|
||||
|
||||
/* chat toolbar */
|
||||
/* chat feature
|
||||
chat toolbar */
|
||||
"Chat with admins" = "和管理员聊天";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
@@ -1209,9 +1201,6 @@ set passcode view */
|
||||
/* No comment provided by engineer. */
|
||||
"Configure ICE servers" = "配置 ICE 服务器";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Configure server operators" = "配置服务器运营方";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Confirm" = "确认";
|
||||
|
||||
@@ -1345,7 +1334,7 @@ server test step */
|
||||
/* alert title */
|
||||
"Connection error" = "连接错误";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
/* conn error description */
|
||||
"Connection error (AUTH)" = "连接错误(AUTH)";
|
||||
|
||||
/* chat list item title (it should not be shown */
|
||||
@@ -1447,6 +1436,9 @@ server test step */
|
||||
/* No comment provided by engineer. */
|
||||
"Continue" = "继续";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Contribute" = "贡献";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Conversation deleted!" = "对话已删除!";
|
||||
|
||||
@@ -1465,9 +1457,6 @@ server test step */
|
||||
/* alert message */
|
||||
"Correct name to %@?" = "将名称更正为 %@?";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Create" = "创建";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Create 1-time link" = "创建一次性链接";
|
||||
|
||||
@@ -1621,9 +1610,6 @@ server test step */
|
||||
/* No comment provided by engineer. */
|
||||
"Debug delivery" = "调试交付";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Decentralized" = "分散式";
|
||||
|
||||
/* message decrypt error item */
|
||||
"Decryption error" = "解密错误";
|
||||
|
||||
@@ -2024,7 +2010,7 @@ chat item action */
|
||||
/* No comment provided by engineer. */
|
||||
"Empty message!" = "空消息!";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
/* alert button */
|
||||
"Enable" = "启用";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
@@ -2054,9 +2040,6 @@ chat item action */
|
||||
/* No comment provided by engineer. */
|
||||
"Enable lock" = "启用锁定";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Enable notifications" = "启用通知";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Enable periodic notifications?" = "启用定期通知?";
|
||||
|
||||
@@ -2198,7 +2181,7 @@ chat item action */
|
||||
/* No comment provided by engineer. */
|
||||
"error" = "错误";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
/* conn error description */
|
||||
"Error" = "错误";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
@@ -2752,7 +2735,7 @@ servers warning */
|
||||
/* No comment provided by engineer. */
|
||||
"group is deleted" = "群被删除了";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
/* chat link info line */
|
||||
"Group link" = "群组链接";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
@@ -2881,9 +2864,6 @@ servers warning */
|
||||
/* No comment provided by engineer. */
|
||||
"Immediately" = "立即";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Immune to spam" = "不受垃圾和骚扰消息影响";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Import" = "导入";
|
||||
|
||||
@@ -2984,7 +2964,7 @@ servers warning */
|
||||
"Initial role" = "初始角色";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Install [SimpleX Chat for terminal](https://github.com/simplex-chat/simplex-chat)" = "安装[用于终端的 SimpleX Chat](https://github.com/simplex-chat/simplex-chat)";
|
||||
"Install SimpleX Chat for terminal" = "安装用于终端的 SimpleX Chat";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Instant" = "即时";
|
||||
@@ -3019,7 +2999,7 @@ servers warning */
|
||||
/* No comment provided by engineer. */
|
||||
"invalid chat data" = "无效聊天数据";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
/* conn error description */
|
||||
"Invalid connection link" = "无效的连接链接";
|
||||
|
||||
/* invalid chat item */
|
||||
@@ -3460,9 +3440,6 @@ servers warning */
|
||||
/* No comment provided by engineer. */
|
||||
"Migrate device" = "迁移设备";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Migrate from another device" = "从另一台设备迁移";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Migrate here" = "迁移到此处";
|
||||
|
||||
@@ -3743,7 +3720,10 @@ servers warning */
|
||||
"No unread chats" = "没有未读聊天";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"No user identifiers." = "没有用户标识符。";
|
||||
"Nobody tracked your conversations. No one drew a map of where you'd been. Privacy was never a feature - it was the way of life." = "没有人追踪你的谈话内容。没有人绘制你去过的地方的地图。隐私从来都不是一项功能--而是一种生活方式。";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Not a better lock on someone else's door. Not a nicer landlord that respects your privacy, but still keeps the record of all visitors. You are not a guest. You are home. No king can enter it - you are sovereign." = "别人家的门锁再好也比不上这里。房东再好也比不上这里,他既尊重你的隐私,又保留着所有访客的记录。你不是客人,你是家。没有国王能闯入--你是主人。";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Not compatible!" = "不兼容!";
|
||||
@@ -3801,7 +3781,7 @@ alert button
|
||||
new chat action */
|
||||
"Ok" = "好的";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
/* alert button */
|
||||
"OK" = "好的";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
@@ -3882,7 +3862,8 @@ new chat action */
|
||||
/* No comment provided by engineer. */
|
||||
"Only your contact can send voice messages." = "只有您的联系人可以发送语音消息。";
|
||||
|
||||
/* alert action */
|
||||
/* alert action
|
||||
alert button */
|
||||
"Open" = "打开";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
@@ -4134,12 +4115,6 @@ new chat action */
|
||||
/* No comment provided by engineer. */
|
||||
"Privacy policy and conditions of use." = "隐私政策和使用条款。";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Privacy redefined" = "重新定义隐私";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Private chats, groups and your contacts are not accessible to server operators." = "服务器运营方无法访问私密聊天、群组和你的联系人。";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Private filenames" = "私密文件名";
|
||||
|
||||
@@ -4179,9 +4154,6 @@ new chat action */
|
||||
/* No comment provided by engineer. */
|
||||
"Profile theme" = "个人资料主题";
|
||||
|
||||
/* alert message */
|
||||
"Profile update will be sent to your contacts." = "个人资料更新将被发送给您的联系人。";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Prohibit audio/video calls." = "禁止音频/视频通话。";
|
||||
|
||||
@@ -4270,16 +4242,10 @@ new chat action */
|
||||
"Read more" = "阅读更多";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Read more in [User Guide](https://simplex.chat/docs/guide/chat-profiles.html#incognito-mode)." = "阅读更多[User Guide](https://simplex.chat/docs/guide/chat-profiles.html#incognito-mode)。";
|
||||
"Read more in our GitHub repository." = "在我们的 GitHub 仓库 中阅读更多信息。";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Read more in [User Guide](https://simplex.chat/docs/guide/making-connections.html#comparison-of-1-time-invitation-links-and-simplex-contact-addresses)." = "在 [用户指南](https://simplex.chat/docs/guide/making-connections.html#comparison-of-1-time-invitation-links-and-simplex-contact-addresses) 中阅读更多内容。";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Read more in [User Guide](https://simplex.chat/docs/guide/readme.html#connect-to-friends)." = "在 [用户指南](https://simplex.chat/docs/guide/readme.html#connect-to-friends) 中阅读更多内容。";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Read more in our [GitHub repository](https://github.com/simplex-chat/simplex-chat#readme)." = "在我们的 [GitHub 仓库](https://github.com/simplex-chat/simplex-chat#readme) 中阅读更多信息。";
|
||||
"Read more in User Guide." = "阅读更多User Guide。";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Receipts are disabled" = "回执已禁用";
|
||||
@@ -5020,9 +4986,6 @@ chat item action */
|
||||
/* No comment provided by engineer. */
|
||||
"Share address publicly" = "公开分享地址";
|
||||
|
||||
/* alert title */
|
||||
"Share address with contacts?" = "与联系人分享地址?";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Share from other apps." = "从其他应用程序共享。";
|
||||
|
||||
@@ -5047,9 +5010,6 @@ chat item action */
|
||||
/* No comment provided by engineer. */
|
||||
"Share to SimpleX" = "分享到 SimpleX";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Share with contacts" = "与联系人分享";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Share your address" = "分享地址";
|
||||
|
||||
@@ -5204,6 +5164,9 @@ report reason */
|
||||
/* chat item text */
|
||||
"standard end-to-end encryption" = "标准端到端加密";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Star on GitHub" = "在 GitHub 上加星";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Start chat" = "开始聊天";
|
||||
|
||||
@@ -5306,9 +5269,6 @@ report reason */
|
||||
/* No comment provided by engineer. */
|
||||
"Tap Connect to use bot" = "轻按“连接”使用机器人";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Tap Create SimpleX address in the menu to create it later." = "要稍后创建 SimpleX 地址,请在菜单中轻按“创建 SimpleX 地址”";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Tap Join group" = "轻按加入群";
|
||||
|
||||
@@ -5412,9 +5372,6 @@ server test failure */
|
||||
/* No comment provided by engineer. */
|
||||
"The encryption is working and the new encryption agreement is not required. It may result in connection errors!" = "加密正在运行,不需要新的加密协议。这可能会导致连接错误!";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"The future of messaging" = "下一代私密通讯软件";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"The hash of the previous message is different." = "上一条消息的散列不同。";
|
||||
|
||||
@@ -5439,6 +5396,9 @@ server test failure */
|
||||
/* No comment provided by engineer. */
|
||||
"The old database was not removed during the migration, it can be deleted." = "旧数据库在迁移过程中没有被移除,可以删除。";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"The oldest human freedom - to speak to another person without being watched - built on infrastructure that cannot betray it." = "人类最古老的自由--与他人交谈而不被监视--建立在不会背叛它的基础设施之上。";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"The second preset operator in the app!" = "应用中的第二个预设运营方!";
|
||||
|
||||
@@ -5460,6 +5420,12 @@ server test failure */
|
||||
/* No comment provided by engineer. */
|
||||
"Themes" = "主题";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Then we moved online, and every platform asked for a piece of you - your name, your number, your friends. We accepted that the price of talking to others is letting someone know who we talk to. Every generation, people and tech, had it this way - telephone, email, messengers, social media. It seemed the only way possible." = "然后我们转向线上,每个平台都要求你提供一些信息--你的姓名、电话号码、好友列表。我们接受了这样一个事实:与人交流的代价就是让别人知道我们在和谁交流。每一代人,每一代科技,都遵循着这样的模式--电话、电子邮件、即时通讯、社交媒体。这似乎是唯一可行的方式。";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"There is another way. A network with no phone numbers. No usernames. No accounts. No user identities of any kind. A network that connects people and carries encrypted messages without knowing who is connected." = "还有另一种方法。一个没有电话号码、没有用户名、没有账户、没有任何用户身份的网络。一个连接人们并传输加密信息的网络,而无需知道谁连接了。";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"These conditions will also apply for: **%@**." = "这些条件将同样适用于: **%@**。";
|
||||
|
||||
@@ -5583,9 +5549,6 @@ server test failure */
|
||||
/* No comment provided by engineer. */
|
||||
"To verify end-to-end encryption with your contact compare (or scan) the code on your devices." = "要与您的联系人验证端到端加密,请比较(或扫描)您设备上的代码。";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Toggle chat list:" = "切换聊天列表:";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Toggle incognito when connecting." = "在连接时切换隐身模式。";
|
||||
|
||||
@@ -5703,7 +5666,7 @@ server test failure */
|
||||
/* swipe action */
|
||||
"Unread" = "未读";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
/* conn error description */
|
||||
"Unsupported connection link" = "不支持的连接链接";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
@@ -5778,9 +5741,6 @@ server test failure */
|
||||
/* No comment provided by engineer. */
|
||||
"Use %@" = "使用 %@";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Use chat" = "使用聊天";
|
||||
|
||||
/* new chat action */
|
||||
"Use current profile" = "使用当前配置文件";
|
||||
|
||||
@@ -6198,9 +6158,6 @@ server test failure */
|
||||
/* No comment provided by engineer. */
|
||||
"You could not be verified; please try again." = "您的身份无法验证,请再试一次。";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"You decide who can connect." = "你决定谁可以连接。";
|
||||
|
||||
/* new chat sheet title */
|
||||
"You have already requested connection!\nRepeat connection request?" = "您已经请求连接了!\n重复连接请求?";
|
||||
|
||||
@@ -6252,6 +6209,9 @@ server test failure */
|
||||
/* snd group event chat item */
|
||||
"you unblocked %@" = "您解封了 %@";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"You were born without an account" = "你生来就没有账户。";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"You will be able to send messages **only after your request is accepted**." = "**只有在你的请求被接受后**你才能发送消息。";
|
||||
|
||||
@@ -6321,6 +6281,9 @@ server test failure */
|
||||
/* No comment provided by engineer. */
|
||||
"Your contacts will remain connected." = "与您的联系人保持连接。";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Your conversations belong to you, as it had always been before the Internet. The network is not a place you visit. It is a place you create and own. And nobody can take it from you, whether you make it private or public." = "你的对话内容始终属于你,就像互联网出现之前一样。网络不是一个你访问的地方,而是一个你创建并拥有的地方。无论你将其设为私密还是公开,任何人都无法将其夺走。";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Your credentials may be sent unencrypted." = "你的凭据可能以未经加密的方式被发送。";
|
||||
|
||||
|
||||
@@ -16,13 +16,19 @@ val simplexAssetsLocal = file("src/commonMain/resources/assets/simplex")
|
||||
val hasSimplexAssets = simplexAssetsDir != null
|
||||
|
||||
if (simplexAssetsDir != null) {
|
||||
val resolvedAssetsDir = rootProject.rootDir.resolve(simplexAssetsDir).absolutePath
|
||||
tasks.register<Exec>("copySimplexAssets") {
|
||||
commandLine(
|
||||
"${rootProject.rootDir}/../../scripts/android/copy-assets.sh",
|
||||
resolvedAssetsDir,
|
||||
simplexAssetsLocal.absolutePath
|
||||
)
|
||||
val resolvedAssetsDir = rootProject.rootDir.resolve(simplexAssetsDir)
|
||||
val srcImagesDir = resolvedAssetsDir.resolve("multiplatform/resources/MR/images")
|
||||
val verifySimplexAssets = tasks.register("verifySimplexAssets") {
|
||||
doLast {
|
||||
if (!srcImagesDir.isDirectory) {
|
||||
throw GradleException("Source assets not found: $srcImagesDir (run resize.sh first)")
|
||||
}
|
||||
}
|
||||
}
|
||||
tasks.register<Sync>("copySimplexAssets") {
|
||||
dependsOn(verifySimplexAssets)
|
||||
from(srcImagesDir)
|
||||
into(simplexAssetsLocal.resolve("MR/images"))
|
||||
}
|
||||
} else {
|
||||
tasks.register<Delete>("cleanSimplexAssets") {
|
||||
|
||||
+1
-1
@@ -394,7 +394,7 @@ private fun ActiveCallOverlayLayout(
|
||||
DisabledBackgroundCallsButton()
|
||||
}
|
||||
|
||||
BoxWithConstraints(Modifier.padding(start = 6.dp, end = 6.dp, bottom = DEFAULT_PADDING).align(Alignment.CenterHorizontally)) {
|
||||
BoxWithConstraints(Modifier.navigationBarsPadding().padding(start = 6.dp, end = 6.dp, bottom = DEFAULT_PADDING).align(Alignment.CenterHorizontally)) {
|
||||
val size = ((maxWidth - DEFAULT_PADDING_HALF * 4) / 5).coerceIn(0.dp, 60.dp)
|
||||
// limiting max width for tablets/wide screens, will be displayed in the center
|
||||
val padding = ((min(420.dp, maxWidth) - size * 5) / 4).coerceAtLeast(0.dp)
|
||||
|
||||
+1
-1
@@ -10,7 +10,7 @@ import chat.simplex.res.MR
|
||||
@Composable
|
||||
actual fun OnboardingActionButton(user: User?, onboardingStage: SharedPreference<OnboardingStage>, onclick: (() -> Unit)?) {
|
||||
if (user == null) {
|
||||
OnboardingActionButton(Modifier.fillMaxWidth(), labelId = MR.strings.create_your_profile, onboarding = OnboardingStage.Step2_CreateProfile, onclick = onclick)
|
||||
OnboardingActionButton(Modifier.fillMaxWidth(), labelId = MR.strings.get_started, onboarding = OnboardingStage.Step2_CreateProfile, onclick = onclick)
|
||||
} else {
|
||||
OnboardingActionButton(Modifier.fillMaxWidth(), labelId = MR.strings.make_private_connection, onboarding = OnboardingStage.OnboardingComplete, onclick = onclick)
|
||||
}
|
||||
|
||||
@@ -142,10 +142,8 @@ fun MainScreen() {
|
||||
when {
|
||||
onboarding == OnboardingStage.Step1_SimpleXInfo && chatModel.migrationState.value != null -> {
|
||||
// In migration process. Nothing should interrupt it, that's why it's the first branch in when()
|
||||
SimpleXInfo(chatModel, onboarding = true)
|
||||
if (appPlatform.isDesktop) {
|
||||
ModalManager.fullscreen.showInView()
|
||||
}
|
||||
if (appPlatform.isDesktop) DesktopOnboarding(onboarding, chatModel)
|
||||
else SimpleXInfo(chatModel, onboarding = true)
|
||||
}
|
||||
chatModel.dbMigrationInProgress.value -> DefaultProgressView(stringResource(MR.strings.database_migration_in_progress))
|
||||
chatModel.chatDbStatus.value == null && showInitializationView -> DefaultProgressView(stringResource(MR.strings.opening_database))
|
||||
@@ -175,36 +173,31 @@ fun MainScreen() {
|
||||
}
|
||||
}
|
||||
}
|
||||
else -> AnimatedContent(targetState = onboarding,
|
||||
transitionSpec = {
|
||||
if (targetState > initialState) {
|
||||
fromEndToStartTransition()
|
||||
} else {
|
||||
fromStartToEndTransition()
|
||||
}.using(SizeTransform(clip = false))
|
||||
}
|
||||
) { state ->
|
||||
when (state) {
|
||||
OnboardingStage.OnboardingComplete -> { /* handled out of AnimatedContent block */}
|
||||
OnboardingStage.Step1_SimpleXInfo -> {
|
||||
SimpleXInfo(chatModel, onboarding = true)
|
||||
if (appPlatform.isDesktop) {
|
||||
ModalManager.fullscreen.showInView()
|
||||
else -> {
|
||||
if (appPlatform.isDesktop) {
|
||||
DesktopOnboarding(onboarding, chatModel)
|
||||
} else {
|
||||
AnimatedContent(targetState = onboarding,
|
||||
transitionSpec = {
|
||||
if (targetState > initialState) {
|
||||
fromEndToStartTransition()
|
||||
} else {
|
||||
fromStartToEndTransition()
|
||||
}.using(SizeTransform(clip = false))
|
||||
}
|
||||
) { state ->
|
||||
when (state) {
|
||||
OnboardingStage.OnboardingComplete -> {}
|
||||
OnboardingStage.Step1_SimpleXInfo -> SimpleXInfo(chatModel, onboarding = true)
|
||||
OnboardingStage.Step2_CreateProfile -> CreateFirstProfile(chatModel) {}
|
||||
OnboardingStage.LinkAMobile -> LinkAMobile()
|
||||
OnboardingStage.Step2_5_SetupDatabasePassphrase -> SetupDatabasePassphrase(chatModel)
|
||||
OnboardingStage.Step3_ChooseServerOperators,
|
||||
OnboardingStage.Step3_CreateSimpleXAddress,
|
||||
OnboardingStage.Step4_SetNotificationsMode -> YourNetworkView(chatModel)
|
||||
OnboardingStage.Step4_NetworkCommitments -> OnboardingConditionsView(chatModel)
|
||||
}
|
||||
}
|
||||
OnboardingStage.Step2_CreateProfile -> CreateFirstProfile(chatModel) {}
|
||||
OnboardingStage.LinkAMobile -> LinkAMobile()
|
||||
OnboardingStage.Step2_5_SetupDatabasePassphrase -> SetupDatabasePassphrase(chatModel)
|
||||
OnboardingStage.Step3_ChooseServerOperators -> {
|
||||
val modalData = remember { ModalData() }
|
||||
modalData.OnboardingConditionsView()
|
||||
if (appPlatform.isDesktop) {
|
||||
ModalManager.fullscreen.showInView()
|
||||
}
|
||||
}
|
||||
// Ensure backwards compatibility with old onboarding stage for address creation, otherwise notification setup would be skipped
|
||||
OnboardingStage.Step3_CreateSimpleXAddress -> SetNotificationsMode(chatModel)
|
||||
OnboardingStage.Step4_SetNotificationsMode -> SetNotificationsMode(chatModel)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -276,6 +269,27 @@ fun MainScreen() {
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun DesktopOnboarding(onboarding: OnboardingStage, chatModel: ChatModel) {
|
||||
if (onboarding == OnboardingStage.LinkAMobile) {
|
||||
LinkAMobile()
|
||||
ModalManager.fullscreen.showInView()
|
||||
} else {
|
||||
DesktopOnboardingShell(onboarding) {
|
||||
when (onboarding) {
|
||||
OnboardingStage.Step1_SimpleXInfo -> SimpleXInfo(chatModel, onboarding = true)
|
||||
OnboardingStage.Step2_CreateProfile -> CreateFirstProfile(chatModel) {}
|
||||
OnboardingStage.Step2_5_SetupDatabasePassphrase -> SetupDatabasePassphrase(chatModel)
|
||||
OnboardingStage.Step3_ChooseServerOperators,
|
||||
OnboardingStage.Step3_CreateSimpleXAddress,
|
||||
OnboardingStage.Step4_SetNotificationsMode -> YourNetworkView(chatModel)
|
||||
OnboardingStage.Step4_NetworkCommitments -> OnboardingConditionsView(chatModel)
|
||||
else -> {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val ANDROID_CALL_TOP_PADDING = 40.dp
|
||||
|
||||
@Composable
|
||||
|
||||
+19
-3
@@ -1687,6 +1687,18 @@ sealed class ChatInfo: SomeChat, NamedChat {
|
||||
else -> null
|
||||
}
|
||||
|
||||
val sendAsGroup: Boolean get() {
|
||||
val g = (this as? Group)?.groupInfo
|
||||
return if (g != null && g.useRelays && g.membership.memberRole >= GroupMemberRole.Owner) {
|
||||
when (groupChatScope()) {
|
||||
null -> true
|
||||
is GroupChatScope.MemberSupport -> false
|
||||
}
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
fun ntfsEnabled(ci: ChatItem): Boolean =
|
||||
ntfsEnabled(ci.meta.userMention)
|
||||
|
||||
@@ -2133,6 +2145,7 @@ data class GroupInfo (
|
||||
GroupFeature.SimplexLinks -> p.simplexLinks.on(membership)
|
||||
GroupFeature.Reports -> p.reports.on
|
||||
GroupFeature.History -> p.history.on
|
||||
GroupFeature.Support -> p.support.on
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3800,8 +3813,8 @@ sealed class CIContent: ItemContent {
|
||||
is RcvBlocked -> generalGetString(MR.strings.blocked_by_admin_item_description)
|
||||
is SndDirectE2EEInfo -> directE2EEInfoStr(e2eeInfo)
|
||||
is RcvDirectE2EEInfo -> directE2EEInfoStr(e2eeInfo)
|
||||
is SndGroupE2EEInfo -> e2eeInfoNoPQStr
|
||||
is RcvGroupE2EEInfo -> e2eeInfoNoPQStr
|
||||
is SndGroupE2EEInfo -> groupE2EEInfoStr(e2eeInfo)
|
||||
is RcvGroupE2EEInfo -> groupE2EEInfoStr(e2eeInfo)
|
||||
is ChatBanner -> ""
|
||||
is InvalidJSON -> "invalid data"
|
||||
}
|
||||
@@ -3838,6 +3851,9 @@ sealed class CIContent: ItemContent {
|
||||
|
||||
private val e2eeInfoNoPQStr: String = generalGetString(MR.strings.e2ee_info_no_pq_short)
|
||||
|
||||
fun groupE2EEInfoStr(e2EEInfo: E2EEInfo): String =
|
||||
if (e2EEInfo.public == true) generalGetString(MR.strings.e2ee_info_no_e2ee) else e2eeInfoNoPQStr
|
||||
|
||||
fun featureText(feature: Feature, enabled: String, param: Int?, role: GroupMemberRole? = null): String =
|
||||
(if (feature.hasParam) {
|
||||
"${feature.text}: ${timeText(param)}"
|
||||
@@ -4364,7 +4380,7 @@ enum class CIGroupInvitationStatus {
|
||||
}
|
||||
|
||||
@Serializable
|
||||
class E2EEInfo (val pqEnabled: Boolean?) {}
|
||||
class E2EEInfo (val pqEnabled: Boolean?, val public: Boolean? = null) {}
|
||||
|
||||
object MsgContentSerializer : KSerializer<MsgContent> {
|
||||
override val descriptor: SerialDescriptor = buildSerialDescriptor("MsgContent", PolymorphicKind.SEALED) {
|
||||
|
||||
+39
-21
@@ -213,7 +213,7 @@ class AppPreferences {
|
||||
val shouldImportAppSettings = mkBoolPreference(SHARED_PREFS_SHOULD_IMPORT_APP_SETTINGS, false)
|
||||
|
||||
val currentTheme = mkStrPreference(SHARED_PREFS_CURRENT_THEME, DefaultTheme.SYSTEM_THEME_NAME)
|
||||
val systemDarkTheme = mkStrPreference(SHARED_PREFS_SYSTEM_DARK_THEME, DefaultTheme.SIMPLEX.themeName)
|
||||
val systemDarkTheme = mkStrPreference(SHARED_PREFS_SYSTEM_DARK_THEME, DefaultTheme.DARK.themeName)
|
||||
val currentThemeIds = mkMapPreference(SHARED_PREFS_CURRENT_THEME_IDs, mapOf(), encode = {
|
||||
json.encodeToString(MapSerializer(String.serializer(), String.serializer()), it)
|
||||
}, decode = {
|
||||
@@ -5693,7 +5693,8 @@ enum class GroupFeature: Feature {
|
||||
@SerialName("files") Files,
|
||||
@SerialName("simplexLinks") SimplexLinks,
|
||||
@SerialName("reports") Reports,
|
||||
@SerialName("history") History;
|
||||
@SerialName("history") History,
|
||||
@SerialName("support") Support;
|
||||
|
||||
override val hasParam: Boolean get() = when(this) {
|
||||
TimedMessages -> true
|
||||
@@ -5711,10 +5712,12 @@ enum class GroupFeature: Feature {
|
||||
SimplexLinks -> true
|
||||
Reports -> false
|
||||
History -> false
|
||||
Support -> false
|
||||
}
|
||||
|
||||
override val text: String
|
||||
get() = when(this) {
|
||||
override val text: String get() = text(isChannel = false)
|
||||
|
||||
fun text(isChannel: Boolean): String = when(this) {
|
||||
TimedMessages -> generalGetString(MR.strings.timed_messages)
|
||||
DirectMessages -> generalGetString(MR.strings.direct_messages)
|
||||
FullDelete -> generalGetString(MR.strings.full_deletion)
|
||||
@@ -5722,8 +5725,9 @@ enum class GroupFeature: Feature {
|
||||
Voice -> generalGetString(MR.strings.voice_messages)
|
||||
Files -> generalGetString(MR.strings.files_and_media)
|
||||
SimplexLinks -> generalGetString(MR.strings.simplex_links)
|
||||
Reports -> generalGetString(MR.strings.group_reports_member_reports)
|
||||
Reports -> generalGetString(if (isChannel) MR.strings.group_reports_subscriber_reports else MR.strings.group_reports_member_reports)
|
||||
History -> generalGetString(MR.strings.recent_history)
|
||||
Support -> generalGetString(MR.strings.chat_with_admins)
|
||||
}
|
||||
|
||||
val icon: Painter
|
||||
@@ -5737,6 +5741,7 @@ enum class GroupFeature: Feature {
|
||||
SimplexLinks -> painterResource(MR.images.ic_link)
|
||||
Reports -> painterResource(MR.images.ic_flag)
|
||||
History -> painterResource(MR.images.ic_schedule)
|
||||
Support -> painterResource(MR.images.ic_help)
|
||||
}
|
||||
|
||||
@Composable
|
||||
@@ -5750,9 +5755,10 @@ enum class GroupFeature: Feature {
|
||||
SimplexLinks -> painterResource(MR.images.ic_link)
|
||||
Reports -> painterResource(MR.images.ic_flag_filled)
|
||||
History -> painterResource(MR.images.ic_schedule_filled)
|
||||
Support -> painterResource(MR.images.ic_help_filled)
|
||||
}
|
||||
|
||||
fun enableDescription(enabled: GroupFeatureEnabled, canEdit: Boolean): String =
|
||||
fun enableDescription(enabled: GroupFeatureEnabled, canEdit: Boolean, isChannel: Boolean = false): String =
|
||||
if (canEdit) {
|
||||
when(this) {
|
||||
TimedMessages -> when(enabled) {
|
||||
@@ -5760,8 +5766,8 @@ enum class GroupFeature: Feature {
|
||||
GroupFeatureEnabled.OFF -> generalGetString(MR.strings.prohibit_sending_disappearing)
|
||||
}
|
||||
DirectMessages -> when(enabled) {
|
||||
GroupFeatureEnabled.ON -> generalGetString(MR.strings.allow_direct_messages)
|
||||
GroupFeatureEnabled.OFF -> generalGetString(MR.strings.prohibit_direct_messages)
|
||||
GroupFeatureEnabled.ON -> generalGetString(if (isChannel) MR.strings.allow_direct_messages_channel else MR.strings.allow_direct_messages)
|
||||
GroupFeatureEnabled.OFF -> generalGetString(if (isChannel) MR.strings.prohibit_direct_messages_channel else MR.strings.prohibit_direct_messages)
|
||||
}
|
||||
FullDelete -> when(enabled) {
|
||||
GroupFeatureEnabled.ON -> generalGetString(MR.strings.allow_to_delete_messages)
|
||||
@@ -5788,47 +5794,55 @@ enum class GroupFeature: Feature {
|
||||
GroupFeatureEnabled.OFF -> generalGetString(MR.strings.disable_sending_member_reports)
|
||||
}
|
||||
History -> when(enabled) {
|
||||
GroupFeatureEnabled.ON -> generalGetString(MR.strings.enable_sending_recent_history)
|
||||
GroupFeatureEnabled.OFF -> generalGetString(MR.strings.disable_sending_recent_history)
|
||||
GroupFeatureEnabled.ON -> generalGetString(if (isChannel) MR.strings.enable_sending_recent_history_channel else MR.strings.enable_sending_recent_history)
|
||||
GroupFeatureEnabled.OFF -> generalGetString(if (isChannel) MR.strings.disable_sending_recent_history_channel else MR.strings.disable_sending_recent_history)
|
||||
}
|
||||
Support -> when(enabled) {
|
||||
GroupFeatureEnabled.ON -> generalGetString(if (isChannel) MR.strings.allow_chat_with_admins_channel else MR.strings.allow_chat_with_admins)
|
||||
GroupFeatureEnabled.OFF -> generalGetString(MR.strings.prohibit_chat_with_admins)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
when(this) {
|
||||
TimedMessages -> when(enabled) {
|
||||
GroupFeatureEnabled.ON -> generalGetString(MR.strings.group_members_can_send_disappearing)
|
||||
GroupFeatureEnabled.ON -> generalGetString(if (isChannel) MR.strings.group_members_can_send_disappearing_channel else MR.strings.group_members_can_send_disappearing)
|
||||
GroupFeatureEnabled.OFF -> generalGetString(MR.strings.disappearing_messages_are_prohibited)
|
||||
}
|
||||
DirectMessages -> when(enabled) {
|
||||
GroupFeatureEnabled.ON -> generalGetString(MR.strings.group_members_can_send_dms)
|
||||
GroupFeatureEnabled.OFF -> generalGetString(MR.strings.direct_messages_are_prohibited)
|
||||
GroupFeatureEnabled.ON -> generalGetString(if (isChannel) MR.strings.group_members_can_send_dms_channel else MR.strings.group_members_can_send_dms)
|
||||
GroupFeatureEnabled.OFF -> generalGetString(if (isChannel) MR.strings.direct_messages_are_prohibited_channel else MR.strings.direct_messages_are_prohibited)
|
||||
}
|
||||
FullDelete -> when(enabled) {
|
||||
GroupFeatureEnabled.ON -> generalGetString(MR.strings.group_members_can_delete)
|
||||
GroupFeatureEnabled.ON -> generalGetString(if (isChannel) MR.strings.group_members_can_delete_channel else MR.strings.group_members_can_delete)
|
||||
GroupFeatureEnabled.OFF -> generalGetString(MR.strings.message_deletion_prohibited_in_chat)
|
||||
}
|
||||
Reactions -> when(enabled) {
|
||||
GroupFeatureEnabled.ON -> generalGetString(MR.strings.group_members_can_add_message_reactions)
|
||||
GroupFeatureEnabled.ON -> generalGetString(if (isChannel) MR.strings.group_members_can_add_message_reactions_channel else MR.strings.group_members_can_add_message_reactions)
|
||||
GroupFeatureEnabled.OFF -> generalGetString(MR.strings.message_reactions_are_prohibited)
|
||||
}
|
||||
Voice -> when(enabled) {
|
||||
GroupFeatureEnabled.ON -> generalGetString(MR.strings.group_members_can_send_voice)
|
||||
GroupFeatureEnabled.ON -> generalGetString(if (isChannel) MR.strings.group_members_can_send_voice_channel else MR.strings.group_members_can_send_voice)
|
||||
GroupFeatureEnabled.OFF -> generalGetString(MR.strings.voice_messages_are_prohibited)
|
||||
}
|
||||
Files -> when(enabled) {
|
||||
GroupFeatureEnabled.ON -> generalGetString(MR.strings.group_members_can_send_files)
|
||||
GroupFeatureEnabled.ON -> generalGetString(if (isChannel) MR.strings.group_members_can_send_files_channel else MR.strings.group_members_can_send_files)
|
||||
GroupFeatureEnabled.OFF -> generalGetString(MR.strings.files_are_prohibited_in_group)
|
||||
}
|
||||
SimplexLinks -> when(enabled) {
|
||||
GroupFeatureEnabled.ON -> generalGetString(MR.strings.group_members_can_send_simplex_links)
|
||||
GroupFeatureEnabled.ON -> generalGetString(if (isChannel) MR.strings.group_members_can_send_simplex_links_channel else MR.strings.group_members_can_send_simplex_links)
|
||||
GroupFeatureEnabled.OFF -> generalGetString(MR.strings.simplex_links_are_prohibited_in_group)
|
||||
}
|
||||
Reports -> when(enabled) {
|
||||
GroupFeatureEnabled.ON -> generalGetString(MR.strings.group_members_can_send_reports)
|
||||
GroupFeatureEnabled.ON -> generalGetString(if (isChannel) MR.strings.group_members_can_send_reports_channel else MR.strings.group_members_can_send_reports)
|
||||
GroupFeatureEnabled.OFF -> generalGetString(MR.strings.member_reports_are_prohibited)
|
||||
}
|
||||
History -> when(enabled) {
|
||||
GroupFeatureEnabled.ON -> generalGetString(MR.strings.recent_history_is_sent_to_new_members)
|
||||
GroupFeatureEnabled.OFF -> generalGetString(MR.strings.recent_history_is_not_sent_to_new_members)
|
||||
GroupFeatureEnabled.ON -> generalGetString(if (isChannel) MR.strings.recent_history_is_sent_to_new_members_channel else MR.strings.recent_history_is_sent_to_new_members)
|
||||
GroupFeatureEnabled.OFF -> generalGetString(if (isChannel) MR.strings.recent_history_is_not_sent_to_new_members_channel else MR.strings.recent_history_is_not_sent_to_new_members)
|
||||
}
|
||||
Support -> when(enabled) {
|
||||
GroupFeatureEnabled.ON -> generalGetString(if (isChannel) MR.strings.members_can_chat_with_admins_channel else MR.strings.members_can_chat_with_admins)
|
||||
GroupFeatureEnabled.OFF -> generalGetString(MR.strings.chat_with_admins_is_prohibited)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -5955,6 +5969,7 @@ data class FullGroupPreferences(
|
||||
val simplexLinks: RoleGroupPreference,
|
||||
val reports: GroupPreference,
|
||||
val history: GroupPreference,
|
||||
val support: GroupPreference,
|
||||
val commands: List<ChatBotCommand>,
|
||||
) {
|
||||
fun toGroupPreferences(): GroupPreferences =
|
||||
@@ -5968,6 +5983,7 @@ data class FullGroupPreferences(
|
||||
simplexLinks = simplexLinks,
|
||||
reports = reports,
|
||||
history = history,
|
||||
support = support,
|
||||
commands = commands,
|
||||
)
|
||||
|
||||
@@ -5982,6 +5998,7 @@ data class FullGroupPreferences(
|
||||
simplexLinks = RoleGroupPreference(GroupFeatureEnabled.ON, role = null),
|
||||
reports = GroupPreference(GroupFeatureEnabled.ON),
|
||||
history = GroupPreference(GroupFeatureEnabled.ON),
|
||||
support = GroupPreference(GroupFeatureEnabled.ON),
|
||||
commands = listOf()
|
||||
)
|
||||
}
|
||||
@@ -5998,6 +6015,7 @@ data class GroupPreferences(
|
||||
val simplexLinks: RoleGroupPreference? = null,
|
||||
val reports: GroupPreference? = null,
|
||||
val history: GroupPreference? = null,
|
||||
val support: GroupPreference? = null,
|
||||
val commands: List<ChatBotCommand>? = null
|
||||
) {
|
||||
companion object {
|
||||
|
||||
+1
-5
@@ -162,11 +162,7 @@ suspend fun initChatController(useKey: String? = null, confirmMigrations: Migrat
|
||||
} else if (startChat().await()) {
|
||||
val savedOnboardingStage = appPreferences.onboardingStage.get()
|
||||
val newStage = if (listOf(OnboardingStage.Step1_SimpleXInfo, OnboardingStage.Step2_CreateProfile).contains(savedOnboardingStage) && chatModel.users.size == 1) {
|
||||
if (appPlatform.isAndroid) {
|
||||
OnboardingStage.Step4_SetNotificationsMode
|
||||
} else {
|
||||
OnboardingStage.OnboardingComplete
|
||||
}
|
||||
OnboardingStage.Step4_NetworkCommitments
|
||||
} else {
|
||||
savedOnboardingStage
|
||||
}
|
||||
|
||||
+1
-32
@@ -3,47 +3,16 @@ package chat.simplex.common.ui.theme
|
||||
import androidx.compose.material.LocalContentColor
|
||||
import androidx.compose.material.MaterialTheme
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.ui.graphics.*
|
||||
import androidx.compose.ui.graphics.colorspace.ColorSpaces
|
||||
import chat.simplex.common.views.helpers.mixWith
|
||||
import kotlin.math.cos
|
||||
import kotlin.math.min
|
||||
import kotlin.math.pow
|
||||
import kotlin.math.sin
|
||||
|
||||
/** Create a Display P3 Color from oklch components. H in degrees. */
|
||||
fun oklch(L: Float, C: Float, H: Float, alpha: Float = 1f): Color {
|
||||
val hRad = H * (Math.PI.toFloat() / 180f)
|
||||
val a = C * cos(hRad)
|
||||
val b = C * sin(hRad)
|
||||
// oklab → LMS (Ottosson 2021)
|
||||
val l_ = L + 0.3963377774f * a + 0.2158037573f * b
|
||||
val m_ = L - 0.1055613458f * a - 0.0638541728f * b
|
||||
val s_ = L - 0.0894841775f * a - 1.2914855480f * b
|
||||
val l = l_ * l_ * l_
|
||||
val m = m_ * m_ * m_
|
||||
val s = s_ * s_ * s_
|
||||
// LMS → linear Display P3 (direct, no sRGB clamping)
|
||||
val r = 3.1281105148f * l - 2.2570749853f * m + 0.1293047593f * s
|
||||
val g = -1.0911282009f * l + 2.4132668169f * m - 0.3221681599f * s
|
||||
val bl = -0.0260136845f * l - 0.5080276339f * m + 1.5333166364f * s
|
||||
// linear P3 → gamma-encoded P3 (same transfer function as sRGB)
|
||||
fun gammaEncode(x: Float) = if (x >= 0.0031308f) 1.055f * x.pow(1f / 2.4f) - 0.055f else 12.92f * x
|
||||
return Color(
|
||||
red = gammaEncode(r.coerceIn(0f, 1f)),
|
||||
green = gammaEncode(g.coerceIn(0f, 1f)),
|
||||
blue = gammaEncode(bl.coerceIn(0f, 1f)),
|
||||
alpha = alpha,
|
||||
colorSpace = ColorSpaces.DisplayP3
|
||||
)
|
||||
return Color(L, C * cos(hRad), C * sin(hRad), alpha, ColorSpaces.Oklab)
|
||||
}
|
||||
|
||||
val Purple200 = Color(0xFFBB86FC)
|
||||
val Purple500 = Color(0xFF6200EE)
|
||||
val Purple700 = Color(0xFF3700B3)
|
||||
val Teal200 = Color(0xFF03DAC5)
|
||||
val Gray = Color(0x22222222)
|
||||
val Indigo = Color(0xFF9966FF)
|
||||
val SimplexBlue = oklch(0.6320536f, 0.2017874f, 254.0879f) // If this value changes also need to update #0088ff in string resource files
|
||||
val SimplexGreen = oklch(0.7871495f, 0.1979258f, 146.6814f) // #ff4dda67
|
||||
|
||||
+1
-1
@@ -10,7 +10,7 @@ val Typography = Typography(
|
||||
h1 = TextStyle(
|
||||
fontFamily = Inter,
|
||||
fontWeight = FontWeight.Bold,
|
||||
fontSize = 32.sp,
|
||||
fontSize = 33.5.sp,
|
||||
),
|
||||
h2 = TextStyle(
|
||||
fontFamily = Inter,
|
||||
|
||||
+223
-55
@@ -4,6 +4,7 @@ import SectionTextFooter
|
||||
import androidx.compose.foundation.*
|
||||
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.foundation.text.BasicTextField
|
||||
import androidx.compose.material.*
|
||||
import androidx.compose.material.MaterialTheme.colors
|
||||
@@ -11,28 +12,44 @@ import androidx.compose.runtime.*
|
||||
import androidx.compose.runtime.saveable.rememberSaveable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.draw.drawBehind
|
||||
import androidx.compose.ui.focus.*
|
||||
import androidx.compose.ui.platform.LocalFocusManager
|
||||
import androidx.compose.ui.geometry.Offset
|
||||
import androidx.compose.ui.graphics.Brush
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.SolidColor
|
||||
import androidx.compose.ui.layout.ContentScale
|
||||
import dev.icerock.moko.resources.compose.painterResource
|
||||
import dev.icerock.moko.resources.compose.stringResource
|
||||
import androidx.compose.ui.text.TextStyle
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.text.input.VisualTransformation
|
||||
import androidx.compose.ui.text.style.*
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import chat.simplex.common.BuildConfigCommon
|
||||
import chat.simplex.common.model.*
|
||||
import chat.simplex.common.model.ChatController.appPrefs
|
||||
import chat.simplex.common.model.ChatModel.controller
|
||||
import chat.simplex.common.platform.*
|
||||
import chat.simplex.common.ui.theme.*
|
||||
import chat.simplex.common.views.helpers.*
|
||||
import chat.simplex.common.views.migration.MigrateToDeviceView
|
||||
import chat.simplex.common.views.migration.MigrationToState
|
||||
import chat.simplex.common.views.newchat.darkStops
|
||||
import chat.simplex.common.views.newchat.gradientPoints
|
||||
import chat.simplex.common.views.newchat.lightStops
|
||||
import chat.simplex.common.views.onboarding.*
|
||||
import chat.simplex.common.views.usersettings.DeleteImageButton
|
||||
import chat.simplex.common.views.usersettings.EditImageButton
|
||||
import chat.simplex.common.views.usersettings.SettingsActionItem
|
||||
import chat.simplex.res.MR
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||
import kotlinx.coroutines.launch
|
||||
import java.net.URI
|
||||
|
||||
const val MAX_BIO_LENGTH_BYTES = 160
|
||||
|
||||
@@ -46,18 +63,63 @@ fun CreateProfile(chatModel: ChatModel, close: () -> Unit) {
|
||||
val scrollState = rememberScrollState()
|
||||
val keyboardState by getKeyboardState()
|
||||
var savedKeyboardState by remember { mutableStateOf(keyboardState) }
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.padding(top = 20.dp)
|
||||
) {
|
||||
val displayName = rememberSaveable { mutableStateOf("") }
|
||||
val shortDescr = rememberSaveable { mutableStateOf("") }
|
||||
val focusRequester = remember { FocusRequester() }
|
||||
val bottomSheetModalState = rememberModalBottomSheetState(initialValue = ModalBottomSheetValue.Hidden)
|
||||
val displayName = rememberSaveable { mutableStateOf("") }
|
||||
val shortDescr = rememberSaveable { mutableStateOf("") }
|
||||
val chosenImage = rememberSaveable { mutableStateOf<URI?>(null) }
|
||||
val profileImage = rememberSaveable { mutableStateOf<String?>(null) }
|
||||
val focusRequester = remember { FocusRequester() }
|
||||
|
||||
ModalBottomSheetLayout(
|
||||
scrimColor = Color.Black.copy(alpha = 0.12F),
|
||||
modifier = Modifier.imePadding(),
|
||||
sheetContent = {
|
||||
GetImageBottomSheet(
|
||||
chosenImage,
|
||||
onImageChange = { bitmap -> profileImage.value = resizeImageToStrSize(cropToSquare(bitmap), maxDataSize = 12500) },
|
||||
hideBottomSheet = {
|
||||
scope.launch { bottomSheetModalState.hide() }
|
||||
})
|
||||
},
|
||||
sheetState = bottomSheetModalState,
|
||||
sheetShape = RoundedCornerShape(topStart = 18.dp, topEnd = 18.dp)
|
||||
) {
|
||||
Box(
|
||||
modifier = Modifier.fillMaxSize()
|
||||
) {
|
||||
ColumnWithScrollBar {
|
||||
AppBarTitle(stringResource(MR.strings.create_profile), bottomPadding = DEFAULT_PADDING_HALF)
|
||||
Row(
|
||||
Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(vertical = DEFAULT_PADDING_HALF),
|
||||
horizontalArrangement = Arrangement.Center,
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
Box(
|
||||
modifier = if (BuildConfigCommon.SIMPLEX_ASSETS) Modifier.padding(horizontal = 3.dp) else Modifier,
|
||||
contentAlignment = Alignment.Center
|
||||
) {
|
||||
Box(contentAlignment = Alignment.TopEnd) {
|
||||
Box(contentAlignment = Alignment.Center) {
|
||||
ProfileImage(128.dp, image = profileImage.value)
|
||||
EditImageButton { scope.launch { bottomSheetModalState.show() } }
|
||||
}
|
||||
if (profileImage.value != null) {
|
||||
DeleteImageButton { profileImage.value = null }
|
||||
}
|
||||
}
|
||||
}
|
||||
if (BuildConfigCommon.SIMPLEX_ASSETS) {
|
||||
Image(
|
||||
painterResource(if (isInDarkTheme()) MR.images.create_profile_light else MR.images.create_profile),
|
||||
contentDescription = null,
|
||||
contentScale = ContentScale.Fit,
|
||||
modifier = Modifier.height(140.dp)
|
||||
)
|
||||
}
|
||||
}
|
||||
Column(Modifier.padding(horizontal = DEFAULT_PADDING)) {
|
||||
AppBarTitle(stringResource(MR.strings.create_profile), withPadding = false, bottomPadding = DEFAULT_PADDING)
|
||||
Row(Modifier.padding(bottom = DEFAULT_PADDING_HALF).fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween) {
|
||||
Text(
|
||||
stringResource(MR.strings.display_name),
|
||||
@@ -100,9 +162,9 @@ fun CreateProfile(chatModel: ChatModel, close: () -> Unit) {
|
||||
iconColor = MaterialTheme.colors.primary,
|
||||
click = {
|
||||
if (chatModel.localUserCreated.value == true) {
|
||||
createProfileInProfiles(chatModel, displayName.value, shortDescr.value, close)
|
||||
createProfileInProfiles(chatModel, displayName.value, shortDescr.value, profileImage.value, close)
|
||||
} else {
|
||||
createProfileInNoProfileSetup(displayName.value, close)
|
||||
createProfileInNoProfileSetup(displayName.value, profileImage.value, close)
|
||||
}
|
||||
},
|
||||
)
|
||||
@@ -123,49 +185,119 @@ fun CreateProfile(chatModel: ChatModel, close: () -> Unit) {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun CreateFirstProfile(chatModel: ChatModel, close: () -> Unit) {
|
||||
val scope = rememberCoroutineScope()
|
||||
val scrollState = rememberScrollState()
|
||||
val keyboardState by getKeyboardState()
|
||||
var savedKeyboardState by remember { mutableStateOf(keyboardState) }
|
||||
CompositionLocalProvider(LocalAppBarHandler provides rememberAppBarHandler()) {
|
||||
ModalView({
|
||||
if (chatModel.users.none { !it.user.hidden }) {
|
||||
appPrefs.onboardingStage.set(OnboardingStage.Step1_SimpleXInfo)
|
||||
} else {
|
||||
close()
|
||||
if (appPlatform.isDesktop) {
|
||||
CreateFirstProfileDesktop(chatModel, close)
|
||||
} else {
|
||||
CreateFirstProfileMobile(chatModel, close)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun RowScope.MigrateButton(refocusTrigger: MutableState<Int>) {
|
||||
val focusManager = LocalFocusManager.current
|
||||
TextButton(
|
||||
onClick = {
|
||||
focusManager.clearFocus()
|
||||
if (chatModel.migrationState.value == null) {
|
||||
chatModel.migrationState.value = MigrationToState.PasteOrScanLink
|
||||
}
|
||||
}) {
|
||||
ColumnWithScrollBar {
|
||||
val displayName = rememberSaveable { mutableStateOf("") }
|
||||
val focusRequester = remember { FocusRequester() }
|
||||
Column(if (appPlatform.isAndroid) Modifier.fillMaxSize().padding(start = DEFAULT_ONBOARDING_HORIZONTAL_PADDING * 2, end = DEFAULT_ONBOARDING_HORIZONTAL_PADDING * 2, bottom = DEFAULT_PADDING) else Modifier.widthIn(max = 600.dp).fillMaxHeight().padding(horizontal = DEFAULT_PADDING).align(Alignment.CenterHorizontally), horizontalAlignment = Alignment.CenterHorizontally) {
|
||||
Box(Modifier.align(Alignment.CenterHorizontally)) {
|
||||
AppBarTitle(stringResource(MR.strings.create_your_profile), bottomPadding = DEFAULT_PADDING, withPadding = false)
|
||||
}
|
||||
ReadableText(MR.strings.your_profile_is_stored_on_your_device, TextAlign.Center, padding = PaddingValues(), style = MaterialTheme.typography.body1.copy(color = MaterialTheme.colors.secondary))
|
||||
Spacer(Modifier.height(DEFAULT_PADDING))
|
||||
ReadableText(MR.strings.profile_is_only_shared_with_your_contacts, TextAlign.Center, style = MaterialTheme.typography.body1.copy(color = MaterialTheme.colors.secondary))
|
||||
Spacer(Modifier.height(DEFAULT_PADDING))
|
||||
ProfileNameField(displayName, stringResource(MR.strings.display_name), { it.trim() == mkValidName(it) }, focusRequester)
|
||||
ModalManager.fullscreen.showCustomModal(animated = false, forceAnimated = appPlatform.isDesktop) { close ->
|
||||
MigrateToDeviceView {
|
||||
close()
|
||||
refocusTrigger.value++
|
||||
}
|
||||
Spacer(Modifier.fillMaxHeight().weight(1f))
|
||||
Column(Modifier.widthIn(max = if (appPlatform.isAndroid) 450.dp else 1000.dp).align(Alignment.CenterHorizontally), horizontalAlignment = Alignment.CenterHorizontally) {
|
||||
}
|
||||
},
|
||||
modifier = Modifier.padding(end = DEFAULT_PADDING_HALF)
|
||||
) {
|
||||
Icon(painterResource(MR.images.ic_download), null, Modifier.size(22.dp), tint = MaterialTheme.colors.primary)
|
||||
Spacer(Modifier.width(4.dp))
|
||||
Text(
|
||||
stringResource(if (appPlatform.isDesktop) MR.strings.migrate_from_another_device else MR.strings.migrate),
|
||||
color = MaterialTheme.colors.primary, fontWeight = FontWeight.Medium
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun onboardingBackAction(chatModel: ChatModel, close: () -> Unit) {
|
||||
if (chatModel.users.none { !it.user.hidden }) {
|
||||
appPrefs.onboardingStage.set(OnboardingStage.Step1_SimpleXInfo)
|
||||
} else {
|
||||
close()
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun CreateFirstProfileMobile(chatModel: ChatModel, close: () -> Unit) {
|
||||
CompositionLocalProvider(LocalAppBarHandler provides rememberAppBarHandler()) {
|
||||
val focusRequester = remember { FocusRequester() }
|
||||
val refocusTrigger = remember { mutableStateOf(0) }
|
||||
ModalView(
|
||||
close = { onboardingBackAction(chatModel, close) },
|
||||
endButtons = { MigrateButton(refocusTrigger) }
|
||||
) {
|
||||
val displayName = rememberSaveable { mutableStateOf("") }
|
||||
val keyboardState by getKeyboardState()
|
||||
val imageHeightModifier = if (keyboardState == KeyboardState.Opened) {
|
||||
Modifier.heightIn(max = 100.dp)
|
||||
} else {
|
||||
Modifier
|
||||
}
|
||||
ColumnWithScrollBar(Modifier.padding(horizontal = DEFAULT_ONBOARDING_HORIZONTAL_PADDING), horizontalAlignment = Alignment.CenterHorizontally, maxIntrinsicSize = true) {
|
||||
Spacer(Modifier.weight(1f))
|
||||
|
||||
OnboardingImage(
|
||||
MR.images.your_profile, MR.images.your_profile_light, MR.images.ic_person,
|
||||
modifier = Modifier
|
||||
.then(if (keyboardState != KeyboardState.Opened) Modifier.fillMaxWidth() else Modifier)
|
||||
.then(imageHeightModifier)
|
||||
)
|
||||
|
||||
Text(
|
||||
stringResource(MR.strings.onboarding_your_profile),
|
||||
style = MaterialTheme.typography.h1,
|
||||
fontWeight = FontWeight.Bold,
|
||||
textAlign = TextAlign.Center,
|
||||
modifier = Modifier.padding(top = DEFAULT_PADDING_HALF)
|
||||
)
|
||||
Text(
|
||||
stringResource(MR.strings.onboarding_on_your_phone),
|
||||
style = MaterialTheme.typography.h3,
|
||||
fontWeight = FontWeight.Medium,
|
||||
color = MaterialTheme.colors.secondary,
|
||||
lineHeight = 25.sp,
|
||||
textAlign = TextAlign.Center,
|
||||
modifier = Modifier.padding(top = 14.dp)
|
||||
)
|
||||
Text(
|
||||
stringResource(MR.strings.onboarding_no_account),
|
||||
style = MaterialTheme.typography.body2,
|
||||
color = MaterialTheme.colors.secondary,
|
||||
textAlign = TextAlign.Center,
|
||||
lineHeight = 20.sp,
|
||||
modifier = Modifier.padding(top = DEFAULT_PADDING_HALF)
|
||||
)
|
||||
Spacer(Modifier.height(DEFAULT_PADDING_HALF))
|
||||
ProfileNameField(displayName, stringResource(MR.strings.enter_profile_name), { it.trim() == mkValidName(it) }, focusRequester)
|
||||
|
||||
Spacer(Modifier.weight(1f))
|
||||
|
||||
Column(Modifier.widthIn(max = 450.dp).padding(bottom = DEFAULT_PADDING * 2).align(Alignment.CenterHorizontally), horizontalAlignment = Alignment.CenterHorizontally) {
|
||||
OnboardingActionButton(
|
||||
if (appPlatform.isAndroid) Modifier.padding(horizontal = DEFAULT_ONBOARDING_HORIZONTAL_PADDING).fillMaxWidth() else Modifier.widthIn(min = 300.dp),
|
||||
labelId = MR.strings.create_profile_button,
|
||||
Modifier.fillMaxWidth(),
|
||||
labelId = MR.strings.create_profile,
|
||||
onboarding = null,
|
||||
enabled = canCreateProfile(displayName.value),
|
||||
onclick = { createProfileOnboarding(chat.simplex.common.platform.chatModel, displayName.value, close) }
|
||||
onclick = { createProfileOnboarding(chatModel, displayName.value, close) }
|
||||
)
|
||||
// Reserve space
|
||||
TextButtonBelowOnboardingButton("", null)
|
||||
}
|
||||
|
||||
LaunchedEffect(Unit) {
|
||||
LaunchedEffect(refocusTrigger.value) {
|
||||
delay(300)
|
||||
focusRequester.requestFocus()
|
||||
}
|
||||
@@ -173,21 +305,57 @@ fun CreateFirstProfile(chatModel: ChatModel, close: () -> Unit) {
|
||||
LaunchedEffect(Unit) {
|
||||
setLastVersionDefault(chatModel)
|
||||
}
|
||||
if (savedKeyboardState != keyboardState) {
|
||||
LaunchedEffect(keyboardState) {
|
||||
scope.launch {
|
||||
savedKeyboardState = keyboardState
|
||||
scrollState.animateScrollTo(scrollState.maxValue)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun createProfileInNoProfileSetup(displayName: String, close: () -> Unit) {
|
||||
@Composable
|
||||
private fun CreateFirstProfileDesktop(chatModel: ChatModel, close: () -> Unit) {
|
||||
val focusRequester = remember { FocusRequester() }
|
||||
val refocusTrigger = remember { mutableStateOf(0) }
|
||||
val displayName = rememberSaveable { mutableStateOf("") }
|
||||
CompositionLocalProvider(LocalAppBarHandler provides rememberAppBarHandler()) {
|
||||
ModalView(
|
||||
close = { onboardingBackAction(chatModel, close) },
|
||||
endButtons = { MigrateButton(refocusTrigger) }
|
||||
) {
|
||||
ColumnWithScrollBar(horizontalAlignment = Alignment.CenterHorizontally) {
|
||||
Column(Modifier.widthIn(max = 600.dp).fillMaxHeight().padding(horizontal = DEFAULT_PADDING).align(Alignment.CenterHorizontally), horizontalAlignment = Alignment.CenterHorizontally) {
|
||||
Box(Modifier.align(Alignment.CenterHorizontally)) {
|
||||
AppBarTitle(stringResource(MR.strings.onboarding_your_profile), bottomPadding = DEFAULT_PADDING, withPadding = false, overrideTitleColor = MaterialTheme.colors.onBackground, textAlign = TextAlign.Center, lineHeight = 42.sp)
|
||||
}
|
||||
Text(stringResource(MR.strings.onboarding_on_your_phone), style = MaterialTheme.typography.h3, fontWeight = FontWeight.Medium, color = MaterialTheme.colors.secondary, lineHeight = 25.sp, textAlign = TextAlign.Center)
|
||||
Spacer(Modifier.height(DEFAULT_PADDING))
|
||||
ReadableText(MR.strings.onboarding_no_account, TextAlign.Center, style = MaterialTheme.typography.body2.copy(color = MaterialTheme.colors.secondary))
|
||||
Spacer(Modifier.height(DEFAULT_PADDING))
|
||||
ProfileNameField(displayName, stringResource(MR.strings.enter_profile_name), { it.trim() == mkValidName(it) }, focusRequester)
|
||||
}
|
||||
Spacer(Modifier.fillMaxHeight().weight(1f))
|
||||
Column(Modifier.widthIn(max = 1000.dp).align(Alignment.CenterHorizontally), horizontalAlignment = Alignment.CenterHorizontally) {
|
||||
OnboardingActionButton(
|
||||
Modifier.widthIn(min = 300.dp),
|
||||
labelId = MR.strings.create_profile,
|
||||
onboarding = null,
|
||||
enabled = canCreateProfile(displayName.value),
|
||||
onclick = { createProfileOnboarding(chatModel, displayName.value, close) }
|
||||
)
|
||||
TextButtonBelowOnboardingButton("", null)
|
||||
}
|
||||
}
|
||||
LaunchedEffect(Unit) {
|
||||
setLastVersionDefault(chatModel)
|
||||
}
|
||||
}
|
||||
}
|
||||
LaunchedEffect(refocusTrigger.value) {
|
||||
delay(300)
|
||||
focusRequester.requestFocus()
|
||||
}
|
||||
}
|
||||
|
||||
fun createProfileInNoProfileSetup(displayName: String, image: String? = null, close: () -> Unit) {
|
||||
withBGApi {
|
||||
val user = controller.apiCreateActiveUser(null, Profile(displayName.trim(), "", null, null)) ?: return@withBGApi
|
||||
val user = controller.apiCreateActiveUser(null, Profile(displayName.trim(), "", null, image)) ?: return@withBGApi
|
||||
if (!chatModel.connectedToRemote()) {
|
||||
chatModel.localUserCreated.value = true
|
||||
}
|
||||
@@ -198,16 +366,16 @@ fun createProfileInNoProfileSetup(displayName: String, close: () -> Unit) {
|
||||
}
|
||||
}
|
||||
|
||||
fun createProfileInProfiles(chatModel: ChatModel, displayName: String, shortDescr: String, close: () -> Unit) {
|
||||
fun createProfileInProfiles(chatModel: ChatModel, displayName: String, shortDescr: String, image: String? = null, close: () -> Unit) {
|
||||
withBGApi {
|
||||
val rhId = chatModel.remoteHostId()
|
||||
val user = chatModel.controller.apiCreateActiveUser(
|
||||
rhId, Profile(displayName.trim(), "", shortDescr.trim().ifEmpty { null }, null)
|
||||
rhId, Profile(displayName.trim(), "", shortDescr.trim().ifEmpty { null }, image)
|
||||
) ?: return@withBGApi
|
||||
chatModel.currentUser.value = user
|
||||
if (chatModel.users.isEmpty()) {
|
||||
chatModel.controller.startChat(user)
|
||||
chatModel.controller.appPrefs.onboardingStage.set(OnboardingStage.Step4_SetNotificationsMode)
|
||||
chatModel.controller.appPrefs.onboardingStage.set(OnboardingStage.Step4_NetworkCommitments)
|
||||
} else {
|
||||
val users = chatModel.controller.listUsers(rhId)
|
||||
chatModel.users.clear()
|
||||
|
||||
+16
-11
@@ -419,9 +419,9 @@ fun ComposeView(
|
||||
withLongRunningApi(slow = 60_000) {
|
||||
if (wait != null) delay(wait)
|
||||
if (pendingLinkUrl.value != url) return@withLongRunningApi
|
||||
if (chatModel.controller.appPrefs.privacyLinkPreviewsShowAlert.get()
|
||||
&& !chatModel.controller.appPrefs.networkUseSocksProxy.get()) {
|
||||
showLinkPreviewsConfirmAlert { enable ->
|
||||
if (chatModel.controller.appPrefs.privacyLinkPreviewsShowAlert.get()) {
|
||||
val socksEnabled = chatModel.controller.appPrefs.networkUseSocksProxy.get()
|
||||
showLinkPreviewsConfirmAlert(socksEnabled) { enable ->
|
||||
if (enable != null) {
|
||||
chatModel.controller.appPrefs.privacyLinkPreviewsShowAlert.set(false)
|
||||
chatModel.controller.appPrefs.privacyLinkPreviews.set(enable)
|
||||
@@ -534,7 +534,7 @@ fun ComposeView(
|
||||
type = cInfo.chatType,
|
||||
id = cInfo.apiId,
|
||||
scope = cInfo.groupChatScope(),
|
||||
sendAsGroup = (cInfo as? ChatInfo.Group)?.groupInfo?.let { it.useRelays && it.membership.memberRole >= GroupMemberRole.Owner } ?: false,
|
||||
sendAsGroup = cInfo.sendAsGroup,
|
||||
live = live,
|
||||
ttl = ttl,
|
||||
composedMessages = listOf(ComposedMessage(file, quoted, mc, mentions))
|
||||
@@ -665,7 +665,7 @@ fun ComposeView(
|
||||
toChatType = chat.chatInfo.chatType,
|
||||
toChatId = chat.chatInfo.apiId,
|
||||
toScope = chat.chatInfo.groupChatScope(),
|
||||
sendAsGroup = (chat.chatInfo as? ChatInfo.Group)?.groupInfo?.let { it.useRelays && it.membership.memberRole >= GroupMemberRole.Owner } ?: false,
|
||||
sendAsGroup = chat.chatInfo.sendAsGroup,
|
||||
fromChatType = fromChatInfo.chatType,
|
||||
fromChatId = fromChatInfo.apiId,
|
||||
fromScope = fromChatInfo.groupChatScope(),
|
||||
@@ -1496,7 +1496,7 @@ fun ComposeView(
|
||||
)
|
||||
is SharedContent.ChatLink -> {
|
||||
val cInfo = chat.chatInfo
|
||||
val sendAsGroup = (cInfo as? ChatInfo.Group)?.groupInfo?.let { it.useRelays && it.membership.memberRole >= GroupMemberRole.Owner } ?: false
|
||||
val sendAsGroup = cInfo.sendAsGroup
|
||||
withBGApi {
|
||||
val mc = chatModel.controller.apiShareChatMsgContent(
|
||||
chat.remoteHostId, ChatType.Group, shared.groupInfo.groupId,
|
||||
@@ -1693,7 +1693,7 @@ fun ComposeView(
|
||||
Row(Modifier.padding(end = 8.dp), verticalAlignment = Alignment.Bottom) {
|
||||
AttachmentAndCommandsButtons()
|
||||
val broadcastPlaceholder = (chat.chatInfo as? ChatInfo.Group)?.groupInfo?.let { gi ->
|
||||
if (gi.useRelays && gi.membership.memberRole >= GroupMemberRole.Owner) generalGetString(MR.strings.compose_view_broadcast)
|
||||
if (gi.useRelays && gi.membership.memberRole >= GroupMemberRole.Owner && chat.chatInfo.groupChatScope() == null) generalGetString(MR.strings.compose_view_broadcast)
|
||||
else null
|
||||
}
|
||||
SendMsgView_(disableSendButton = disableSendButton, placeholder = broadcastPlaceholder)
|
||||
@@ -1703,10 +1703,15 @@ fun ComposeView(
|
||||
}
|
||||
}
|
||||
|
||||
private fun showLinkPreviewsConfirmAlert(onChoice: (Boolean?) -> Unit) {
|
||||
private fun showLinkPreviewsConfirmAlert(socksEnabled: Boolean, onChoice: (Boolean?) -> Unit) {
|
||||
AlertManager.shared.showAlertDialogButtonsColumn(
|
||||
title = generalGetString(MR.strings.link_previews_alert_title),
|
||||
text = AnnotatedString(generalGetString(MR.strings.link_previews_alert_desc)),
|
||||
text = AnnotatedString(
|
||||
if (socksEnabled)
|
||||
generalGetString(MR.strings.link_previews_alert_desc) + "\n\n" + generalGetString(MR.strings.link_previews_alert_desc_socks)
|
||||
else
|
||||
generalGetString(MR.strings.link_previews_alert_desc)
|
||||
),
|
||||
onDismissRequest = { onChoice(null) },
|
||||
buttons = {
|
||||
Column {
|
||||
@@ -1714,13 +1719,13 @@ private fun showLinkPreviewsConfirmAlert(onChoice: (Boolean?) -> Unit) {
|
||||
AlertManager.shared.hideAlert()
|
||||
onChoice(false)
|
||||
}) {
|
||||
Text(stringResource(MR.strings.link_previews_alert_disable), Modifier.fillMaxWidth(), textAlign = TextAlign.Center, color = Color.Red)
|
||||
Text(stringResource(MR.strings.link_previews_alert_disable), Modifier.fillMaxWidth(), textAlign = TextAlign.Center, color = MaterialTheme.colors.primary)
|
||||
}
|
||||
SectionItemView({
|
||||
AlertManager.shared.hideAlert()
|
||||
onChoice(true)
|
||||
}) {
|
||||
Text(stringResource(MR.strings.link_previews_alert_enable), Modifier.fillMaxWidth(), textAlign = TextAlign.Center, color = MaterialTheme.colors.primary)
|
||||
Text(stringResource(MR.strings.link_previews_alert_enable), Modifier.fillMaxWidth(), textAlign = TextAlign.Center, color = if (socksEnabled) MaterialTheme.colors.primary else Color.Red)
|
||||
}
|
||||
// SectionItemView({
|
||||
// AlertManager.shared.hideAlert()
|
||||
|
||||
+13
-4
@@ -546,6 +546,10 @@ fun ModalData.GroupChatInfoLayout(
|
||||
|
||||
var anyTopSectionRowShow = false
|
||||
val channelLink = groupInfo.groupProfile.publicGroup?.groupLink
|
||||
val showUserSupportChat = groupInfo.membership.memberActive &&
|
||||
((groupInfo.fullGroupPreferences.support.on && groupInfo.membership.memberRole < GroupMemberRole.Moderator)
|
||||
|| groupInfo.membership.supportChat != null)
|
||||
|
||||
if (groupInfo.useRelays) {
|
||||
SectionView {
|
||||
if (groupInfo.isOwner && groupLink != null) {
|
||||
@@ -564,6 +568,14 @@ fun ModalData.GroupChatInfoLayout(
|
||||
anyTopSectionRowShow = true
|
||||
ChannelMembersButton(chat.remoteHostId, groupInfo, showMemberInfo)
|
||||
}
|
||||
if (groupInfo.membership.memberRole >= GroupMemberRole.Moderator) {
|
||||
anyTopSectionRowShow = true
|
||||
MemberSupportButton(chat, openMemberSupport)
|
||||
}
|
||||
if (showUserSupportChat) {
|
||||
anyTopSectionRowShow = true
|
||||
UserSupportChatButton(chat, groupInfo, scrollToItemId)
|
||||
}
|
||||
}
|
||||
if (!groupInfo.isOwner && channelLink != null) {
|
||||
SectionTextFooter(stringResource(MR.strings.you_can_share_channel_link_anybody_will_be_able_to_connect))
|
||||
@@ -590,10 +602,7 @@ fun ModalData.GroupChatInfoLayout(
|
||||
}
|
||||
}
|
||||
}
|
||||
if (
|
||||
groupInfo.membership.memberActive &&
|
||||
(groupInfo.membership.memberRole < GroupMemberRole.Moderator || groupInfo.membership.supportChat != null)
|
||||
) {
|
||||
if (showUserSupportChat) {
|
||||
anyTopSectionRowShow = true
|
||||
UserSupportChatButton(chat, groupInfo, scrollToItemId)
|
||||
}
|
||||
|
||||
+12
-6
@@ -483,14 +483,15 @@ fun GroupMemberInfoLayout(
|
||||
SectionSpacer()
|
||||
}
|
||||
|
||||
val showMemberSupportChat = !openedFromSupportChat &&
|
||||
groupInfo.membership.memberRole >= GroupMemberRole.Moderator &&
|
||||
member.memberRole != GroupMemberRole.Relay &&
|
||||
((groupInfo.fullGroupPreferences.support.on && member.memberRole < GroupMemberRole.Moderator)
|
||||
|| member.supportChat != null)
|
||||
|
||||
if (member.memberActive) {
|
||||
SectionView {
|
||||
if (
|
||||
!openedFromSupportChat &&
|
||||
groupInfo.membership.memberRole >= GroupMemberRole.Moderator &&
|
||||
member.memberRole != GroupMemberRole.Relay &&
|
||||
(member.memberRole < GroupMemberRole.Moderator || member.supportChat != null)
|
||||
) {
|
||||
if (showMemberSupportChat) {
|
||||
SupportChatButton()
|
||||
}
|
||||
if (connectionCode != null && !(groupInfo.useRelays && member.memberRole == GroupMemberRole.Relay)) {
|
||||
@@ -504,6 +505,11 @@ fun GroupMemberInfoLayout(
|
||||
// }
|
||||
}
|
||||
SectionDividerSpaced()
|
||||
} else if (groupInfo.useRelays && member.memberCurrent && showMemberSupportChat) {
|
||||
SectionView {
|
||||
SupportChatButton()
|
||||
}
|
||||
SectionDividerSpaced()
|
||||
}
|
||||
|
||||
if (member.contactLink != null) {
|
||||
|
||||
+33
-5
@@ -155,7 +155,7 @@ private fun GroupPreferencesLayout(
|
||||
}
|
||||
@Composable fun ReportsPreference() {
|
||||
val enableReports = remember(preferences) { mutableStateOf(preferences.reports.enable) }
|
||||
FeatureSection(GroupFeature.Reports, enableReports, null, groupInfo, preferences, onTTLUpdated) { enable, _ ->
|
||||
FeatureSection(GroupFeature.Reports, enableReports, null, groupInfo, preferences, onTTLUpdated, disabled = true) { enable, _ -> // enable reports in 7.0 once directory support added
|
||||
applyPrefs(preferences.copy(reports = GroupPreference(enable = enable)))
|
||||
}
|
||||
}
|
||||
@@ -165,6 +165,16 @@ private fun GroupPreferencesLayout(
|
||||
applyPrefs(preferences.copy(history = GroupPreference(enable = enable)))
|
||||
}
|
||||
}
|
||||
@Composable fun SupportPreference(disabled: Boolean = false, notice: String? = null, onEnable: ((() -> Unit) -> Unit)? = null) {
|
||||
val enableSupport = remember(preferences) { mutableStateOf(preferences.support.enable) }
|
||||
FeatureSection(GroupFeature.Support, enableSupport, null, groupInfo, preferences, onTTLUpdated, disabled = disabled, notice = notice) { enable, _ ->
|
||||
applyPrefs(preferences.copy(support = GroupPreference(enable = enable)))
|
||||
if (enable == GroupFeatureEnabled.ON) onEnable?.invoke {
|
||||
enableSupport.value = GroupFeatureEnabled.OFF
|
||||
applyPrefs(preferences.copy(support = GroupPreference(enable = GroupFeatureEnabled.OFF)))
|
||||
}
|
||||
}
|
||||
}
|
||||
ColumnWithScrollBar {
|
||||
val titleId = if (groupInfo.useRelays) MR.strings.channel_preferences
|
||||
else if (groupInfo.businessChat == null) MR.strings.group_preferences
|
||||
@@ -192,6 +202,8 @@ private fun GroupPreferencesLayout(
|
||||
ReportsPreference()
|
||||
SectionDividerSpaced(true, maxBottomPadding = false)
|
||||
HistoryPreference()
|
||||
SectionDividerSpaced(true, maxBottomPadding = false)
|
||||
SupportPreference(disabled = true)
|
||||
} else {
|
||||
TimedMessagesPreference()
|
||||
SectionDividerSpaced(true, maxBottomPadding = false)
|
||||
@@ -200,6 +212,17 @@ private fun GroupPreferencesLayout(
|
||||
ReactionsPreference()
|
||||
SectionDividerSpaced(true, maxBottomPadding = false)
|
||||
HistoryPreference()
|
||||
SectionDividerSpaced(true, maxBottomPadding = false)
|
||||
SupportPreference(notice = generalGetString(MR.strings.chat_with_admins_relay_note), onEnable = { revert ->
|
||||
AlertManager.shared.showAlertDialog(
|
||||
title = generalGetString(MR.strings.enable_chats_with_admins_question),
|
||||
text = generalGetString(MR.strings.chat_with_admins_relay_note),
|
||||
confirmText = generalGetString(MR.strings.enable_chats_with_admins),
|
||||
destructive = true,
|
||||
onDismiss = revert,
|
||||
onDismissRequest = revert,
|
||||
)
|
||||
})
|
||||
}
|
||||
if (groupInfo.isOwner) {
|
||||
SectionDividerSpaced(maxTopPadding = true, maxBottomPadding = false)
|
||||
@@ -233,6 +256,8 @@ private fun FeatureSection(
|
||||
groupInfo: GroupInfo,
|
||||
preferences: FullGroupPreferences,
|
||||
onTTLUpdated: (Int?) -> Unit,
|
||||
disabled: Boolean = false,
|
||||
notice: String? = null,
|
||||
onSelected: (GroupFeatureEnabled, GroupMemberRole?) -> Unit
|
||||
) {
|
||||
SectionView {
|
||||
@@ -242,10 +267,10 @@ private fun FeatureSection(
|
||||
val timedOn = feature == GroupFeature.TimedMessages && enableFeature.value == GroupFeatureEnabled.ON
|
||||
if (groupInfo.isOwner) {
|
||||
PreferenceToggleWithIcon(
|
||||
feature.text,
|
||||
feature.text(groupInfo.isChannel),
|
||||
icon,
|
||||
iconTint,
|
||||
disabled = feature == GroupFeature.Reports, // remove in 6.4
|
||||
disabled = disabled,
|
||||
checked = enableFeature.value == GroupFeatureEnabled.ON,
|
||||
) { checked ->
|
||||
onSelected(if (checked) GroupFeatureEnabled.ON else GroupFeatureEnabled.OFF, enableForRole?.value)
|
||||
@@ -274,7 +299,7 @@ private fun FeatureSection(
|
||||
}
|
||||
} else {
|
||||
InfoRow(
|
||||
feature.text,
|
||||
feature.text(groupInfo.isChannel),
|
||||
enableFeature.value.text,
|
||||
icon = icon,
|
||||
iconTint = iconTint,
|
||||
@@ -292,7 +317,10 @@ private fun FeatureSection(
|
||||
onSelected(enableFeature.value, null)
|
||||
}
|
||||
}
|
||||
SectionTextFooter(feature.enableDescription(enableFeature.value, groupInfo.isOwner))
|
||||
SectionTextFooter(feature.enableDescription(enableFeature.value, groupInfo.isOwner, groupInfo.isChannel))
|
||||
if (notice != null) {
|
||||
SectionTextFooter(notice)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
|
||||
+4
-1
@@ -116,7 +116,10 @@ private fun ModalData.MemberSupportViewLayout(
|
||||
if (membersWithChats.isEmpty()) {
|
||||
item {
|
||||
Box(Modifier.fillMaxSize().padding(horizontal = DEFAULT_PADDING), contentAlignment = Alignment.Center) {
|
||||
Text(generalGetString(MR.strings.no_support_chats), color = MaterialTheme.colors.secondary, textAlign = TextAlign.Center)
|
||||
Text(
|
||||
generalGetString(if (groupInfo.fullGroupPreferences.support.on) MR.strings.no_support_chats else MR.strings.support_chats_disabled),
|
||||
color = MaterialTheme.colors.secondary, textAlign = TextAlign.Center
|
||||
)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
|
||||
+10
-14
@@ -680,21 +680,17 @@ fun ChatItemView(
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun E2EEInfoNoPQText() {
|
||||
e2eeInfoText(MR.strings.e2ee_info_no_pq)
|
||||
fun DirectE2EEInfoText(e2EEInfo: E2EEInfo) {
|
||||
e2eeInfoText(when (e2EEInfo.pqEnabled) {
|
||||
true -> MR.strings.e2ee_info_pq
|
||||
false -> MR.strings.e2ee_info_no_pq
|
||||
null -> MR.strings.e2ee_info_e2ee
|
||||
})
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun DirectE2EEInfoText(e2EEInfo: E2EEInfo) {
|
||||
if (e2EEInfo.pqEnabled != null) {
|
||||
if (e2EEInfo.pqEnabled) {
|
||||
e2eeInfoText(MR.strings.e2ee_info_pq)
|
||||
} else {
|
||||
E2EEInfoNoPQText()
|
||||
}
|
||||
} else {
|
||||
e2eeInfoText(MR.strings.e2ee_info_e2ee)
|
||||
}
|
||||
fun GroupE2EEInfoText(e2EEInfo: E2EEInfo) {
|
||||
e2eeInfoText(if (e2EEInfo.public == true) MR.strings.e2ee_info_no_e2ee else MR.strings.e2ee_info_no_pq)
|
||||
}
|
||||
|
||||
if (cItem.meta.itemDeleted != null && (!revealed.value || cItem.isDeletedContent)) {
|
||||
@@ -794,8 +790,8 @@ fun ChatItemView(
|
||||
is CIContent.RcvBlocked -> DeletedItem()
|
||||
is CIContent.SndDirectE2EEInfo -> DirectE2EEInfoText(c.e2eeInfo)
|
||||
is CIContent.RcvDirectE2EEInfo -> DirectE2EEInfoText(c.e2eeInfo)
|
||||
is CIContent.SndGroupE2EEInfo -> E2EEInfoNoPQText()
|
||||
is CIContent.RcvGroupE2EEInfo -> E2EEInfoNoPQText()
|
||||
is CIContent.SndGroupE2EEInfo -> GroupE2EEInfoText(c.e2eeInfo)
|
||||
is CIContent.RcvGroupE2EEInfo -> GroupE2EEInfoText(c.e2eeInfo)
|
||||
is CIContent.ChatBanner -> Spacer(modifier = Modifier.size(0.dp))
|
||||
is CIContent.InvalidJSON -> {
|
||||
CIInvalidJSONView(c.json)
|
||||
|
||||
+99
-58
@@ -90,41 +90,85 @@ private fun showNewChatSheet(oneHandUI: State<Boolean>) {
|
||||
|
||||
@Composable
|
||||
fun ToggleChatListCard() {
|
||||
ChatListCard(
|
||||
close = {
|
||||
appPrefs.oneHandUICardShown.set(true)
|
||||
AlertManager.shared.showAlertMsg(
|
||||
title = generalGetString(MR.strings.one_hand_ui),
|
||||
text = generalGetString(MR.strings.one_hand_ui_change_instruction),
|
||||
val oneHandUI = remember { appPrefs.oneHandUI.state }
|
||||
val onClose = {
|
||||
appPrefs.oneHandUICardShown.set(true)
|
||||
AlertManager.shared.showAlertMsg(
|
||||
title = generalGetString(MR.strings.one_hand_ui),
|
||||
text = generalGetString(MR.strings.one_hand_ui_change_instruction),
|
||||
)
|
||||
}
|
||||
val activeBg = MaterialTheme.colors.background.mixWith(MaterialTheme.colors.onBackground, 0.97f)
|
||||
.copy(alpha = appPrefs.inAppBarsAlpha.get())
|
||||
val selectedBg = MaterialTheme.colors.background.mixWith(MaterialTheme.colors.onBackground, 0.92f)
|
||||
Row(
|
||||
Modifier
|
||||
.padding(horizontal = 16.dp, vertical = 12.dp)
|
||||
.fillMaxWidth()
|
||||
.height(IntrinsicSize.Min)
|
||||
.clip(RoundedCornerShape(percent = 50)),
|
||||
horizontalArrangement = Arrangement.spacedBy(2.dp)
|
||||
) {
|
||||
ToolbarSegment(
|
||||
icon = MR.images.ic_mobile_3,
|
||||
text = stringResource(MR.strings.one_hand_ui_bottom_bar),
|
||||
isSelected = oneHandUI.value,
|
||||
selectedBg = selectedBg,
|
||||
activeBg = activeBg,
|
||||
modifier = Modifier.weight(1f)
|
||||
) { appPrefs.oneHandUI.set(true) }
|
||||
Box(Modifier.weight(1f).fillMaxHeight()) {
|
||||
ToolbarSegment(
|
||||
icon = MR.images.ic_mobile_4,
|
||||
text = stringResource(MR.strings.one_hand_ui_top_bar),
|
||||
isSelected = !oneHandUI.value,
|
||||
selectedBg = selectedBg,
|
||||
activeBg = activeBg,
|
||||
modifier = Modifier.fillMaxSize()
|
||||
) { appPrefs.oneHandUI.set(false) }
|
||||
Icon(
|
||||
painterResource(MR.images.ic_close), null,
|
||||
Modifier
|
||||
.align(Alignment.CenterEnd)
|
||||
.padding(end = 4.dp)
|
||||
.clip(CircleShape)
|
||||
.clickable(onClick = onClose)
|
||||
.padding(8.dp)
|
||||
.size(16.dp),
|
||||
tint = MaterialTheme.colors.secondary
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun ToolbarSegment(
|
||||
icon: ImageResource,
|
||||
text: String,
|
||||
isSelected: Boolean,
|
||||
selectedBg: Color,
|
||||
activeBg: Color,
|
||||
modifier: Modifier = Modifier,
|
||||
onClick: () -> Unit
|
||||
) {
|
||||
Row(
|
||||
modifier
|
||||
.fillMaxHeight()
|
||||
.background(if (isSelected) selectedBg else activeBg)
|
||||
.then(if (!isSelected) Modifier.clickable(onClick = onClick) else Modifier)
|
||||
.padding(start = 16.dp, top = 8.dp, bottom = 8.dp),
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.padding(horizontal = DEFAULT_PADDING)
|
||||
.padding(top = DEFAULT_PADDING)
|
||||
) {
|
||||
Row(
|
||||
horizontalArrangement = Arrangement.Start,
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
) {
|
||||
Text(stringResource(MR.strings.one_hand_ui_card_title), style = MaterialTheme.typography.h3)
|
||||
}
|
||||
Row(
|
||||
Modifier.fillMaxWidth().padding(top = 6.dp, bottom = 12.dp),
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
Text(stringResource(MR.strings.one_hand_ui), Modifier.weight(10f), style = MaterialTheme.typography.body1)
|
||||
|
||||
Spacer(Modifier.fillMaxWidth().weight(1f))
|
||||
|
||||
SharedPreferenceToggle(
|
||||
appPrefs.oneHandUI,
|
||||
enabled = true
|
||||
)
|
||||
}
|
||||
}
|
||||
Icon(
|
||||
painterResource(icon), null, Modifier.size(20.dp),
|
||||
tint = if (isSelected) MaterialTheme.colors.secondary else MaterialTheme.colors.primary
|
||||
)
|
||||
Spacer(Modifier.width(8.dp))
|
||||
Text(
|
||||
text,
|
||||
color = if (isSelected) MaterialTheme.colors.secondary else MaterialTheme.colors.onBackground,
|
||||
style = MaterialTheme.typography.body1
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -307,7 +351,7 @@ private fun ConnectBannerCard() {
|
||||
painterResource(if (isDark) MR.images.banner_create_link_light else MR.images.banner_create_link),
|
||||
contentDescription = null,
|
||||
contentScale = ContentScale.FillWidth,
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
modifier = Modifier.fillMaxWidth().aspectRatio(BANNER_IMAGE_RATIO)
|
||||
)
|
||||
} else {
|
||||
BannerGradientBox(isDark) {
|
||||
@@ -338,7 +382,7 @@ private fun ConnectBannerCard() {
|
||||
painterResource(if (isDark) MR.images.banner_paste_link_light else MR.images.banner_paste_link),
|
||||
contentDescription = null,
|
||||
contentScale = ContentScale.FillWidth,
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
modifier = Modifier.fillMaxWidth().aspectRatio(BANNER_IMAGE_RATIO)
|
||||
)
|
||||
} else {
|
||||
BannerGradientBox(isDark) {
|
||||
@@ -918,13 +962,18 @@ private fun BoxScope.ChatList(searchText: MutableState<TextFieldValue>, listStat
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!oneHandUICardShown.value) {
|
||||
item {
|
||||
ToggleChatListCard()
|
||||
}
|
||||
}
|
||||
itemsIndexed(chats, key = { _, chat -> chat.remoteHostId to chat.id }) { index, chat ->
|
||||
val nextChatSelected = remember(chat.id, chats) { derivedStateOf {
|
||||
chatModel.chatId.value != null && chats.getOrNull(index + 1)?.id == chatModel.chatId.value
|
||||
} }
|
||||
ChatListNavLinkView(chat, nextChatSelected)
|
||||
}
|
||||
if (!oneHandUICardShown.value || !addressCreationCardShown.value) {
|
||||
if (!addressCreationCardShown.value) {
|
||||
item {
|
||||
ChatListFeatureCards()
|
||||
}
|
||||
@@ -989,20 +1038,12 @@ private fun NoChatsView(searchText: MutableState<TextFieldValue>) {
|
||||
|
||||
@Composable
|
||||
private fun ChatListFeatureCards() {
|
||||
val oneHandUI = remember { appPrefs.oneHandUI.state }
|
||||
val oneHandUICardShown = remember { appPrefs.oneHandUICardShown.state }
|
||||
val addressCreationCardShown = remember { appPrefs.addressCreationCardShown.state }
|
||||
|
||||
Column(modifier = Modifier.padding(16.dp), verticalArrangement = Arrangement.spacedBy(16.dp)) {
|
||||
if (!oneHandUICardShown.value && !oneHandUI.value) {
|
||||
ToggleChatListCard()
|
||||
}
|
||||
if (!addressCreationCardShown.value && hasConversations(chatModel.chats.value)) {
|
||||
if (!addressCreationCardShown.value && hasConversations(chatModel.chats.value)) {
|
||||
Column(modifier = Modifier.padding(16.dp)) {
|
||||
ConnectBannerCard()
|
||||
}
|
||||
if (!oneHandUICardShown.value && oneHandUI.value) {
|
||||
ToggleChatListCard()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1144,7 +1185,7 @@ private fun ExpandedTagFilterView(tag: PresetTagKind) {
|
||||
is ActiveFilter.PresetTag -> af.tag == tag
|
||||
else -> false
|
||||
}
|
||||
val (icon, text) = presetTagLabel(tag, active)
|
||||
val (icon, menuIcon, text) = presetTagLabel(tag, active)
|
||||
val color = if (active) MaterialTheme.colors.primary else MaterialTheme.colors.secondary
|
||||
|
||||
Row(
|
||||
@@ -1164,7 +1205,7 @@ private fun ExpandedTagFilterView(tag: PresetTagKind) {
|
||||
horizontalArrangement = Arrangement.Center
|
||||
) {
|
||||
Icon(
|
||||
painterResource(icon),
|
||||
painterResource(menuIcon ?: icon),
|
||||
stringResource(text),
|
||||
Modifier.size(18.sp.toDp()),
|
||||
tint = color
|
||||
@@ -1206,9 +1247,9 @@ private fun CollapsedTagsFilterView(searchText: MutableState<TextFieldValue>) {
|
||||
contentAlignment = Alignment.Center
|
||||
) {
|
||||
if (selectedPresetTag != null) {
|
||||
val (icon, text) = presetTagLabel(selectedPresetTag, true)
|
||||
val (icon, menuIcon, text) = presetTagLabel(selectedPresetTag, true)
|
||||
Icon(
|
||||
painterResource(icon),
|
||||
painterResource(menuIcon ?: icon),
|
||||
stringResource(text),
|
||||
Modifier.size(18.sp.toDp()),
|
||||
tint = MaterialTheme.colors.primary
|
||||
@@ -1254,7 +1295,7 @@ fun ItemPresetFilterAction(
|
||||
showMenu: MutableState<Boolean>,
|
||||
onCloseMenuAction: MutableState<(() -> Unit)>
|
||||
) {
|
||||
val (icon, text) = presetTagLabel(presetTag, active)
|
||||
val (icon, _, text) = presetTagLabel(presetTag, active)
|
||||
ItemAction(
|
||||
stringResource(text),
|
||||
painterResource(icon),
|
||||
@@ -1336,15 +1377,15 @@ fun presetTagMatchesChat(tag: PresetTagKind, chatInfo: ChatInfo, chatStats: Chat
|
||||
}
|
||||
}
|
||||
|
||||
private fun presetTagLabel(tag: PresetTagKind, active: Boolean): Pair<ImageResource, StringResource> =
|
||||
private fun presetTagLabel(tag: PresetTagKind, active: Boolean): Triple<ImageResource, ImageResource?, StringResource> =
|
||||
when (tag) {
|
||||
PresetTagKind.GROUP_REPORTS -> (if (active) MR.images.ic_flag_filled else MR.images.ic_flag) to MR.strings.chat_list_group_reports
|
||||
PresetTagKind.FAVORITES -> (if (active) MR.images.ic_star_filled else MR.images.ic_star) to MR.strings.chat_list_favorites
|
||||
PresetTagKind.CONTACTS -> (if (active) MR.images.ic_person_filled else MR.images.ic_person) to MR.strings.chat_list_contacts
|
||||
PresetTagKind.GROUPS -> (if (active) MR.images.ic_group_filled else MR.images.ic_group) to MR.strings.chat_list_groups
|
||||
PresetTagKind.CHANNELS -> (if (active) MR.images.ic_bigtop_updates_circle_filled else MR.images.ic_bigtop_updates) to MR.strings.chat_list_channels
|
||||
PresetTagKind.BUSINESS -> (if (active) MR.images.ic_work_filled else MR.images.ic_work) to MR.strings.chat_list_businesses
|
||||
PresetTagKind.NOTES -> (if (active) MR.images.ic_folder_closed_filled else MR.images.ic_folder_closed) to MR.strings.chat_list_notes
|
||||
PresetTagKind.GROUP_REPORTS -> Triple(if (active) MR.images.ic_flag_filled else MR.images.ic_flag, null, MR.strings.chat_list_group_reports)
|
||||
PresetTagKind.FAVORITES -> Triple(if (active) MR.images.ic_star_filled else MR.images.ic_star, null, MR.strings.chat_list_favorites)
|
||||
PresetTagKind.CONTACTS -> Triple(if (active) MR.images.ic_person_filled else MR.images.ic_person, null, MR.strings.chat_list_contacts)
|
||||
PresetTagKind.GROUPS -> Triple(if (active) MR.images.ic_group_filled else MR.images.ic_group, null, MR.strings.chat_list_groups)
|
||||
PresetTagKind.CHANNELS -> Triple(if (active) MR.images.ic_bigtop_updates_circle_filled else MR.images.ic_bigtop_updates, MR.images.ic_bigtop_updates, MR.strings.chat_list_channels)
|
||||
PresetTagKind.BUSINESS -> Triple(if (active) MR.images.ic_work_filled else MR.images.ic_work, null, MR.strings.chat_list_businesses)
|
||||
PresetTagKind.NOTES -> Triple(if (active) MR.images.ic_folder_closed_filled else MR.images.ic_folder_closed, null, MR.strings.chat_list_notes)
|
||||
}
|
||||
|
||||
private fun presetCanBeCollapsed(tag: PresetTagKind): Boolean = when (tag) {
|
||||
|
||||
+11
-6
@@ -22,7 +22,10 @@ fun AppBarTitle(
|
||||
hostDevice: Pair<Long?, String>? = null,
|
||||
withPadding: Boolean = true,
|
||||
bottomPadding: Dp = DEFAULT_PADDING * 1.5f + 8.dp,
|
||||
enableAlphaChanges: Boolean = true
|
||||
enableAlphaChanges: Boolean = true,
|
||||
overrideTitleColor: Color? = null,
|
||||
textAlign: TextAlign = TextAlign.Start,
|
||||
lineHeight: TextUnit = TextUnit.Unspecified
|
||||
) {
|
||||
val handler = LocalAppBarHandler.current
|
||||
val connection = if (enableAlphaChanges) handler?.connection else null
|
||||
@@ -34,10 +37,12 @@ fun AppBarTitle(
|
||||
}
|
||||
}
|
||||
val theme = CurrentColors.collectAsState()
|
||||
val titleColor = MaterialTheme.appColors.title
|
||||
val brush = if (theme.value.base == DefaultTheme.SIMPLEX)
|
||||
val titleColor = overrideTitleColor ?: MaterialTheme.appColors.title
|
||||
val brush = if (overrideTitleColor != null)
|
||||
Brush.linearGradient(listOf(titleColor, titleColor), Offset(0f, Float.POSITIVE_INFINITY), Offset(Float.POSITIVE_INFINITY, 0f))
|
||||
else if (theme.value.base == DefaultTheme.SIMPLEX)
|
||||
Brush.linearGradient(listOf(titleColor.darker(0.2f), titleColor.lighter(0.35f)), Offset(0f, Float.POSITIVE_INFINITY), Offset(Float.POSITIVE_INFINITY, 0f))
|
||||
else // color is not updated when changing themes if I pass null here
|
||||
else
|
||||
Brush.linearGradient(listOf(titleColor, titleColor), Offset(0f, Float.POSITIVE_INFINITY), Offset(Float.POSITIVE_INFINITY, 0f))
|
||||
Column {
|
||||
Text(
|
||||
@@ -48,9 +53,9 @@ fun AppBarTitle(
|
||||
alpha = bottomTitleAlpha(connection)
|
||||
},
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
style = MaterialTheme.typography.h1.copy(brush = brush),
|
||||
style = MaterialTheme.typography.h1.copy(brush = brush, lineHeight = lineHeight),
|
||||
color = MaterialTheme.colors.primaryVariant,
|
||||
textAlign = TextAlign.Start
|
||||
textAlign = textAlign
|
||||
)
|
||||
if (hostDevice != null) {
|
||||
Box(Modifier.padding(start = if (withPadding) DEFAULT_PADDING else 0.dp, end = if (withPadding) DEFAULT_PADDING else 0.dp).graphicsLayer {
|
||||
|
||||
+102
-44
@@ -17,6 +17,7 @@ import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import chat.simplex.common.model.ChatController.appPrefs
|
||||
import chat.simplex.common.model.LinkPreview
|
||||
import chat.simplex.common.model.NetworkProxyAuth
|
||||
import chat.simplex.common.platform.*
|
||||
import chat.simplex.common.ui.theme.*
|
||||
import chat.simplex.common.views.chat.chatViewScrollState
|
||||
@@ -24,67 +25,124 @@ import chat.simplex.common.views.chat.item.CHAT_IMAGE_LAYOUT_ID
|
||||
import chat.simplex.common.views.chat.item.imageViewFullWidth
|
||||
import chat.simplex.res.MR
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.sync.Mutex
|
||||
import kotlinx.coroutines.sync.withLock
|
||||
import kotlinx.coroutines.withContext
|
||||
import org.jsoup.Jsoup
|
||||
import java.net.Authenticator
|
||||
import java.net.InetSocketAddress
|
||||
import java.net.PasswordAuthentication
|
||||
import java.net.Proxy
|
||||
import java.net.URL
|
||||
import java.util.UUID
|
||||
|
||||
private const val OG_SELECT_QUERY = "meta[property^=og:]"
|
||||
private const val ICON_SELECT_QUERY = "link[rel^=icon],link[rel^=apple-touch-icon],link[rel^=shortcut icon]"
|
||||
private val IMAGE_SUFFIXES = listOf(".jpg", ".png", ".ico", ".webp", ".gif")
|
||||
|
||||
// Authenticator.setDefault is process-global. The mutex serializes preview fetches
|
||||
// so concurrent calls cannot clobber each other's authenticator, and so the
|
||||
// snapshot/restore in getLinkPreview is race-free.
|
||||
private val previewMutex = Mutex()
|
||||
|
||||
suspend fun getLinkPreview(url: String): LinkPreview? {
|
||||
return withContext(Dispatchers.IO) {
|
||||
try {
|
||||
val title: String?
|
||||
val u = kotlin.runCatching { URL(url) }.getOrNull() ?: return@withContext null
|
||||
var imageUri = when {
|
||||
IMAGE_SUFFIXES.any { u.path.lowercase().endsWith(it) } -> {
|
||||
title = u.path.substringAfterLast("/")
|
||||
url
|
||||
}
|
||||
else -> {
|
||||
val connection = Jsoup.connect(url)
|
||||
.ignoreContentType(true)
|
||||
.timeout(10000)
|
||||
.followRedirects(true)
|
||||
|
||||
val response = if (url.lowercase().startsWith("https://x.com/")) {
|
||||
// Apple sends request with special user-agent which handled differently by X.com.
|
||||
// Different response that includes video poster from post
|
||||
connection
|
||||
.userAgent("Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_1) AppleWebKit/601.2.4 (KHTML, like Gecko) Version/9.0.1 Safari/601.2.4 facebookexternalhit/1.1 Facebot Twitterbot/1.0")
|
||||
.execute()
|
||||
} else {
|
||||
connection
|
||||
.execute()
|
||||
}
|
||||
val doc = response.parse()
|
||||
val ogTags = doc.select(OG_SELECT_QUERY)
|
||||
title = ogTags.firstOrNull { it.attr("property") == "og:title" }?.attr("content") ?: doc.title()
|
||||
ogTags.firstOrNull { it.attr("property") == "og:image" }?.attr("content")
|
||||
?: doc.select(ICON_SELECT_QUERY).firstOrNull { it.attr("rel").contains("icon") }?.attr("href")
|
||||
}
|
||||
}
|
||||
if (imageUri != null) {
|
||||
imageUri = normalizeImageUri(u, imageUri)
|
||||
previewMutex.withLock {
|
||||
try {
|
||||
try {
|
||||
val stream = URL(imageUri).openStream()
|
||||
val image = resizeImageToStrSize(stream.use(::loadImageBitmap), maxDataSize = 14000)
|
||||
// TODO add once supported in iOS
|
||||
// val description = ogTags.firstOrNull {
|
||||
// it.attr("property") == "og:description"
|
||||
// }?.attr("content") ?: ""
|
||||
if (title != null) {
|
||||
return@withContext LinkPreview(url, title, description = "", image)
|
||||
val title: String?
|
||||
val u = kotlin.runCatching { URL(url) }.getOrNull() ?: return@withLock null
|
||||
val useSocksProxy = appPrefs.networkUseSocksProxy.get()
|
||||
val proxy: Proxy?
|
||||
if (useSocksProxy) {
|
||||
val networkProxy = appPrefs.networkProxy.get()
|
||||
proxy = Proxy(Proxy.Type.SOCKS, InetSocketAddress(networkProxy.host, networkProxy.port))
|
||||
val (authUser, authPass) = when (networkProxy.auth) {
|
||||
NetworkProxyAuth.USERNAME ->
|
||||
if (networkProxy.username.isNotEmpty() && networkProxy.password.isNotEmpty())
|
||||
networkProxy.username to networkProxy.password
|
||||
else
|
||||
null to null
|
||||
// Per-call random credentials drive Tor-style stream isolation: each
|
||||
// preview gets its own circuit, and previews don't share a circuit
|
||||
// with other unauthenticated traffic on the proxy.
|
||||
NetworkProxyAuth.ISOLATE ->
|
||||
UUID.randomUUID().toString() to UUID.randomUUID().toString()
|
||||
}
|
||||
if (authUser != null && authPass != null) {
|
||||
Authenticator.setDefault(object : Authenticator() {
|
||||
override fun getPasswordAuthentication(): PasswordAuthentication? =
|
||||
// Only respond when the SOCKS proxy itself challenges. A destination
|
||||
// server returning 401 also triggers RequestorType.SERVER; without
|
||||
// this gate, the JDK's auto-retry would post our SOCKS credentials
|
||||
// in an Authorization header to the destination.
|
||||
if (requestingHost == networkProxy.host && requestingPort == networkProxy.port)
|
||||
PasswordAuthentication(authUser, authPass.toCharArray())
|
||||
else null
|
||||
})
|
||||
} else {
|
||||
Authenticator.setDefault(null)
|
||||
}
|
||||
} else {
|
||||
proxy = null
|
||||
Authenticator.setDefault(null)
|
||||
}
|
||||
var imageUri = when {
|
||||
IMAGE_SUFFIXES.any { u.path.lowercase().endsWith(it) } -> {
|
||||
title = u.path.substringAfterLast("/")
|
||||
url
|
||||
}
|
||||
else -> {
|
||||
val connection = Jsoup.connect(url)
|
||||
.ignoreContentType(true)
|
||||
.timeout(10000)
|
||||
.followRedirects(true)
|
||||
.proxy(proxy)
|
||||
|
||||
val response = if (url.lowercase().startsWith("https://x.com/")) {
|
||||
// Apple sends request with special user-agent which handled differently by X.com.
|
||||
// Different response that includes video poster from post
|
||||
connection
|
||||
.userAgent("Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_1) AppleWebKit/601.2.4 (KHTML, like Gecko) Version/9.0.1 Safari/601.2.4 facebookexternalhit/1.1 Facebot Twitterbot/1.0")
|
||||
.execute()
|
||||
} else {
|
||||
connection
|
||||
.execute()
|
||||
}
|
||||
val doc = response.parse()
|
||||
val ogTags = doc.select(OG_SELECT_QUERY)
|
||||
title = ogTags.firstOrNull { it.attr("property") == "og:title" }?.attr("content") ?: doc.title()
|
||||
ogTags.firstOrNull { it.attr("property") == "og:image" }?.attr("content")
|
||||
?: doc.select(ICON_SELECT_QUERY).firstOrNull { it.attr("rel").contains("icon") }?.attr("href")
|
||||
}
|
||||
}
|
||||
if (imageUri != null) {
|
||||
imageUri = normalizeImageUri(u, imageUri)
|
||||
try {
|
||||
val conn = URL(imageUri).openConnection(proxy ?: Proxy.NO_PROXY)
|
||||
conn.connectTimeout = 20_000
|
||||
conn.readTimeout = 20_000
|
||||
val stream = conn.getInputStream()
|
||||
val image = resizeImageToStrSize(stream.use(::loadImageBitmap), maxDataSize = 14000)
|
||||
// TODO add once supported in iOS
|
||||
// val description = ogTags.firstOrNull {
|
||||
// it.attr("property") == "og:description"
|
||||
// }?.attr("content") ?: ""
|
||||
if (title != null) {
|
||||
return@withLock LinkPreview(url, title, description = "", image)
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
return@withLock null
|
||||
} finally {
|
||||
Authenticator.setDefault(null)
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
return@withContext null
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+4
-4
@@ -111,8 +111,8 @@ class ModalManager(private val placement: ModalPlacement? = null) {
|
||||
|
||||
fun isLastModalOpen(id: ModalViewId): Boolean = modalViews.lastOrNull()?.id == id
|
||||
|
||||
fun showModal(settings: Boolean = false, showClose: Boolean = true, id: ModalViewId? = null, endButtons: @Composable RowScope.() -> Unit = {}, content: @Composable ModalData.() -> Unit) {
|
||||
showCustomModal(id = id) { close ->
|
||||
fun showModal(settings: Boolean = false, showClose: Boolean = true, id: ModalViewId? = null, forceAnimated: Boolean = false, endButtons: @Composable RowScope.() -> Unit = {}, content: @Composable ModalData.() -> Unit) {
|
||||
showCustomModal(id = id, forceAnimated = forceAnimated) { close ->
|
||||
ModalView(close, showClose = showClose, endButtons = endButtons, content = { content() })
|
||||
}
|
||||
}
|
||||
@@ -123,7 +123,7 @@ class ModalManager(private val placement: ModalPlacement? = null) {
|
||||
}
|
||||
}
|
||||
|
||||
fun showCustomModal(animated: Boolean = true, keyboardCoversBar: Boolean = true, id: ModalViewId? = null, modal: @Composable ModalData.(close: () -> Unit) -> Unit) {
|
||||
fun showCustomModal(animated: Boolean = true, keyboardCoversBar: Boolean = true, id: ModalViewId? = null, forceAnimated: Boolean = false, modal: @Composable ModalData.(close: () -> Unit) -> Unit) {
|
||||
Log.d(TAG, "ModalManager.showCustomModal")
|
||||
val data = ModalData(keyboardCoversBar = keyboardCoversBar)
|
||||
// Means, animation is in progress or not started yet. Do not wait until animation finishes, just remove all from screen.
|
||||
@@ -133,7 +133,7 @@ class ModalManager(private val placement: ModalPlacement? = null) {
|
||||
}
|
||||
// Make animated appearance only on Android (everytime) and on Desktop (when it's on the start part of the screen or modals > 0)
|
||||
// to prevent unneeded animation on different situations
|
||||
val anim = if (appPlatform.isAndroid) animated else animated && (modalCount.value > 0 || placement == ModalPlacement.START)
|
||||
val anim = if (appPlatform.isAndroid) animated else (animated && (modalCount.value > 0 || placement == ModalPlacement.START)) || forceAnimated
|
||||
modalViews.add(ModalViewHolder(id, anim, data, modal))
|
||||
_modalCount.value = modalViews.size - toRemove.size
|
||||
|
||||
|
||||
+14
@@ -537,6 +537,20 @@ fun UriHandler.openUriCatching(uri: String) {
|
||||
}
|
||||
}
|
||||
|
||||
fun UriHandler.openExternalLink(uri: String) {
|
||||
val uriHandler = this
|
||||
if (uri.startsWith("https://simplex.chat/contact#") || (uri.startsWith("https://smp") && ".simplex.im/a#" in uri)) {
|
||||
uriHandler.openVerifiedSimplexUri(uri)
|
||||
} else {
|
||||
AlertManager.shared.showAlertDialog(
|
||||
title = generalGetString(MR.strings.open_external_link_title),
|
||||
text = uri,
|
||||
confirmText = generalGetString(MR.strings.open_verb),
|
||||
onConfirm = { uriHandler.openUriCatching(uri) }
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fun IntSize.Companion.Saver(): Saver<IntSize, *> = Saver(
|
||||
save = { it.width to it.height },
|
||||
restore = { IntSize(it.first, it.second) }
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user