From c7697b9d7389f3da51a3d9c5eb96988b68eb7057 Mon Sep 17 00:00:00 2001 From: Doug Date: Tue, 7 Feb 2023 15:11:59 +0000 Subject: [PATCH 01/45] Prepare for new sprint --- Config/AppVersion.xcconfig | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Config/AppVersion.xcconfig b/Config/AppVersion.xcconfig index f1efc9679..20e1da599 100644 --- a/Config/AppVersion.xcconfig +++ b/Config/AppVersion.xcconfig @@ -15,5 +15,5 @@ // // Version -MARKETING_VERSION = 1.10.1 -CURRENT_PROJECT_VERSION = 1.10.1 +MARKETING_VERSION = 1.10.2 +CURRENT_PROJECT_VERSION = 1.10.2 From 744c4a393383ac8f7ef0d34f90e019f6783651cf Mon Sep 17 00:00:00 2001 From: Alfonso Grillo Date: Wed, 8 Feb 2023 10:53:45 +0100 Subject: [PATCH 02/45] Add TimelinePollMessageView --- .../Coordinator/TimelinePollProvider.swift | 14 +++++- .../View/TimelinePollMessageView.swift | 45 +++++++++++++++++++ 2 files changed, 58 insertions(+), 1 deletion(-) create mode 100644 RiotSwiftUI/Modules/Room/TimelinePoll/View/TimelinePollMessageView.swift diff --git a/RiotSwiftUI/Modules/Room/TimelinePoll/Coordinator/TimelinePollProvider.swift b/RiotSwiftUI/Modules/Room/TimelinePoll/Coordinator/TimelinePollProvider.swift index 8e5a04cd9..0c7233298 100644 --- a/RiotSwiftUI/Modules/Room/TimelinePoll/Coordinator/TimelinePollProvider.swift +++ b/RiotSwiftUI/Modules/Room/TimelinePoll/Coordinator/TimelinePollProvider.swift @@ -15,6 +15,7 @@ // import Foundation +import SwiftUI @objcMembers class TimelinePollProvider: NSObject { @@ -45,7 +46,7 @@ class TimelinePollProvider: NSObject { let parameters = TimelinePollCoordinatorParameters(session: session, room: room, pollEvent: event) guard let coordinator = try? TimelinePollCoordinator(parameters: parameters) else { - return nil + return messageViewController(for: event) } coordinatorsForEventIdentifiers[event.eventId] = coordinator @@ -62,3 +63,14 @@ class TimelinePollProvider: NSObject { coordinatorsForEventIdentifiers.removeAll() } } + +private extension TimelinePollProvider { + func messageViewController(for event: MXEvent) -> UIViewController? { + switch event.eventType { + case .pollEnd: + return VectorHostingController(rootView: TimelinePollMessageView(message: VectorL10n.pollTimelineReplyEndedPoll)) + default: + return nil + } + } +} diff --git a/RiotSwiftUI/Modules/Room/TimelinePoll/View/TimelinePollMessageView.swift b/RiotSwiftUI/Modules/Room/TimelinePoll/View/TimelinePollMessageView.swift new file mode 100644 index 000000000..758cbd490 --- /dev/null +++ b/RiotSwiftUI/Modules/Room/TimelinePoll/View/TimelinePollMessageView.swift @@ -0,0 +1,45 @@ +// +// Copyright 2023 New Vector Ltd +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import SwiftUI + +/// A view for showing polls' related messages whenever there aren't enough information to show a full poll in the timeline. +struct TimelinePollMessageView: View { + @Environment(\.theme) private var theme + private let imageSize: CGFloat = 16 + + let message: String + + var body: some View { + HStack { + Image(uiImage: Asset.Images.pollHistory.image) + .resizable() + .frame(width: imageSize, height: imageSize) + + Text(message) + .font(.system(size: 15)) + .foregroundColor(theme.colors.primaryContent) + } + .padding(.vertical, 8) + .frame(maxWidth: .infinity, alignment: .leading) + } +} + +struct TimelinePollMessageView_Previews: PreviewProvider { + static var previews: some View { + TimelinePollMessageView(message: VectorL10n.pollTimelineReplyEndedPoll) + } +} From fc577d1111e8261a1c36a221435439d8ed62eb3d Mon Sep 17 00:00:00 2001 From: Nicolas Mauri Date: Wed, 8 Feb 2023 15:07:40 +0100 Subject: [PATCH 03/45] Fix an issue where a voice message disappears after being sent --- .../VoiceMessageController.swift | 26 ++++++++++++++++--- changelog.d/7326.bugfix | 1 + 2 files changed, 24 insertions(+), 3 deletions(-) create mode 100644 changelog.d/7326.bugfix diff --git a/Riot/Modules/Room/VoiceMessages/VoiceMessageController.swift b/Riot/Modules/Room/VoiceMessages/VoiceMessageController.swift index fa5058a3f..94d93dedb 100644 --- a/Riot/Modules/Room/VoiceMessages/VoiceMessageController.swift +++ b/Riot/Modules/Room/VoiceMessages/VoiceMessageController.swift @@ -187,7 +187,10 @@ public class VoiceMessageController: NSObject, VoiceMessageToolbarViewDelegate, audioPlayer?.stop() audioRecorder?.stopRecording() - sendRecordingAtURL(temporaryFileURL) + // As we only use a single temporary file, we have to rename it, otherwise it will be deleted once the file is sent and if another recording has been started meanwhile, it will fail. + if let finalFileURL = finalizeRecordingAtURL(temporaryFileURL) { + sendRecordingAtURL(finalFileURL) + } isInLockedMode = false updateUI() @@ -260,8 +263,8 @@ public class VoiceMessageController: NSObject, VoiceMessageToolbarViewDelegate, audioRecorder?.stopRecording() guard isInLockedMode else { - if recordDuration ?? 0 >= Constants.minimumRecordingDuration { - sendRecordingAtURL(temporaryFileURL) + if recordDuration ?? 0 >= Constants.minimumRecordingDuration, let finalRecordingURL = finalizeRecordingAtURL(temporaryFileURL) { + sendRecordingAtURL(finalRecordingURL) } else { cancelRecording() } @@ -371,6 +374,23 @@ public class VoiceMessageController: NSObject, VoiceMessageToolbarViewDelegate, } } + private func finalizeRecordingAtURL(_ url: URL?) -> URL? { + guard let url = url, FileManager.default.fileExists(atPath: url.path) else { + return nil + } + + // We rename the file to something unique, so that we can start a new recording without having to wait for this record to be sent. + let newPath = url.deletingPathExtension().path + "-\(UUID().uuidString)" + let destinationUrl = URL(fileURLWithPath: newPath).appendingPathExtension(url.pathExtension) + do { + try FileManager.default.moveItem(at: url, to: destinationUrl) + } catch { + MXLog.error("[VoiceMessageController] finalizeRecordingAtURL:", context: error) + return nil + } + return destinationUrl + } + private func deleteRecordingAtURL(_ url: URL?) { // Fix: use url.path instead of url.absoluteString when using FileManager otherwise the url seems to be percent encoded and the file is not found. guard let url = url, FileManager.default.fileExists(atPath: url.path) else { diff --git a/changelog.d/7326.bugfix b/changelog.d/7326.bugfix new file mode 100644 index 000000000..f9a6891c8 --- /dev/null +++ b/changelog.d/7326.bugfix @@ -0,0 +1 @@ +Fix an issue where a voice message disappears after being sent. From a7fec65b338aa91100dca72b898423ea45dedd1d Mon Sep 17 00:00:00 2001 From: Alfonso Grillo Date: Wed, 8 Feb 2023 15:30:05 +0100 Subject: [PATCH 04/45] Fix last message for m.poll.end events --- Riot/Modules/MatrixKit/Models/MXKAppSettings.m | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Riot/Modules/MatrixKit/Models/MXKAppSettings.m b/Riot/Modules/MatrixKit/Models/MXKAppSettings.m index ed523160e..5442a0f2a 100644 --- a/Riot/Modules/MatrixKit/Models/MXKAppSettings.m +++ b/Riot/Modules/MatrixKit/Models/MXKAppSettings.m @@ -199,7 +199,9 @@ static NSString *const kMXAppGroupID = @"group.org.matrix"; kMXEventTypeStringCallHangup, kMXEventTypeStringSticker, kMXEventTypeStringPollStart, - kMXEventTypeStringPollStartMSC3381 + kMXEventTypeStringPollStartMSC3381, + kMXEventTypeStringPollEnd, + kMXEventTypeStringPollEndMSC3381 ].mutableCopy; _messageDetailsAllowSharing = YES; From f47ad395b0e8eb0f3cd244d4de399438634843d4 Mon Sep 17 00:00:00 2001 From: Alfonso Grillo Date: Wed, 8 Feb 2023 16:57:58 +0100 Subject: [PATCH 05/45] Sync with matrix feature branch --- .../Models/Room/MXKSendReplyEventStringLocalizer.swift | 2 +- Riot/Modules/MatrixKit/Utils/EventFormatter/MXKEventFormatter.m | 2 +- .../Styles/Bubble/Cells/Poll/PollBaseBubbleCell.swift | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Riot/Modules/MatrixKit/Models/Room/MXKSendReplyEventStringLocalizer.swift b/Riot/Modules/MatrixKit/Models/Room/MXKSendReplyEventStringLocalizer.swift index 4c942bccd..197096e86 100644 --- a/Riot/Modules/MatrixKit/Models/Room/MXKSendReplyEventStringLocalizer.swift +++ b/Riot/Modules/MatrixKit/Models/Room/MXKSendReplyEventStringLocalizer.swift @@ -49,7 +49,7 @@ class MXKSendReplyEventStringLocalizer: NSObject, MXSendReplyEventStringLocalize VectorL10n.messageReplyToMessageToReplyToPrefix } - func replyToEndedPoll() -> String { + func endedPollMessage() -> String { VectorL10n.pollTimelineReplyEndedPoll } } diff --git a/Riot/Modules/MatrixKit/Utils/EventFormatter/MXKEventFormatter.m b/Riot/Modules/MatrixKit/Utils/EventFormatter/MXKEventFormatter.m index da231c119..9f34bcbb6 100644 --- a/Riot/Modules/MatrixKit/Utils/EventFormatter/MXKEventFormatter.m +++ b/Riot/Modules/MatrixKit/Utils/EventFormatter/MXKEventFormatter.m @@ -1900,7 +1900,7 @@ static NSString *const kRepliedTextPattern = @".*
.*
(.* repliedEventContent = [MXEventContentPollStart modelFromJSON:repliedEvent.content].question; } if (!repliedEventContent && repliedEvent.eventType == MXEventTypePollEnd) { - repliedEventContent = MXSendReplyEventDefaultStringLocalizer.new.replyToEndedPoll; + repliedEventContent = MXSendReplyEventDefaultStringLocalizer.new.endedPollMessage; } } diff --git a/Riot/Modules/Room/TimelineCells/Styles/Bubble/Cells/Poll/PollBaseBubbleCell.swift b/Riot/Modules/Room/TimelineCells/Styles/Bubble/Cells/Poll/PollBaseBubbleCell.swift index 993b606c5..f39abcd40 100644 --- a/Riot/Modules/Room/TimelineCells/Styles/Bubble/Cells/Poll/PollBaseBubbleCell.swift +++ b/Riot/Modules/Room/TimelineCells/Styles/Bubble/Cells/Poll/PollBaseBubbleCell.swift @@ -50,7 +50,7 @@ class PollBaseBubbleCell: PollPlainCell { return } - self.addBubbleBackgroundView( messageBubbleBackgroundView, to: pollView) + self.addBubbleBackgroundView(messageBubbleBackgroundView, to: pollView) messageBubbleBackgroundView.backgroundColor = self.bubbleBackgroundColor } From 87ce4fb6ad30312c80c2c5a477f88558608fc0b2 Mon Sep 17 00:00:00 2001 From: Alfonso Grillo Date: Wed, 8 Feb 2023 17:37:10 +0100 Subject: [PATCH 06/45] Add changelog.d file --- changelog.d/pr-7353.change | 1 + 1 file changed, 1 insertion(+) create mode 100644 changelog.d/pr-7353.change diff --git a/changelog.d/pr-7353.change b/changelog.d/pr-7353.change new file mode 100644 index 000000000..ac7539cc7 --- /dev/null +++ b/changelog.d/pr-7353.change @@ -0,0 +1 @@ +Polls: add fallback text for poll ended events. \ No newline at end of file From cba9dcb4d0edaef963b3569281305edc508c3695 Mon Sep 17 00:00:00 2001 From: Nicolas Mauri Date: Thu, 9 Feb 2023 11:50:11 +0100 Subject: [PATCH 07/45] Fix some voice message issues (#7325, #7217) Fix #7325: prevent setting the audio session to inactive during recording Fix #7217: ensure that an audio player has its content loaded when it reaches the end to allow seek and replay. --- .../VoiceMessageAudioPlayer.swift | 11 ++++++- .../VoiceMessageAudioRecorder.swift | 17 ++++++---- .../VoiceMessageController.swift | 32 ++++++++++++++++++- .../VoiceMessageMediaServiceProvider.swift | 4 ++- .../VoiceMessagePlaybackController.swift | 2 ++ changelog.d/7217.bugfix | 1 + changelog.d/7325.bugfix | 1 + 7 files changed, 58 insertions(+), 10 deletions(-) create mode 100644 changelog.d/7217.bugfix create mode 100644 changelog.d/7325.bugfix diff --git a/Riot/Modules/Room/VoiceMessages/VoiceMessageAudioPlayer.swift b/Riot/Modules/Room/VoiceMessages/VoiceMessageAudioPlayer.swift index 4a242a91c..920e0aeb4 100644 --- a/Riot/Modules/Room/VoiceMessages/VoiceMessageAudioPlayer.swift +++ b/Riot/Modules/Room/VoiceMessages/VoiceMessageAudioPlayer.swift @@ -53,7 +53,7 @@ class VoiceMessageAudioPlayer: NSObject { return false } - return (audioPlayer.rate > 0) + return audioPlayer.currentItem != nil && (audioPlayer.rate > 0) } var duration: TimeInterval { @@ -118,6 +118,13 @@ class VoiceMessageAudioPlayer: NSObject { } } + func reloadContentIfNeeded() { + if let url, let audioPlayer, audioPlayer.currentItem == nil { + self.url = nil + loadContentFromURL(url) + } + } + func removeAllPlayerItems() { audioPlayer?.removeAllItems() } @@ -130,6 +137,8 @@ class VoiceMessageAudioPlayer: NSObject { func play() { isStopped = false + reloadContentIfNeeded() + do { try AVAudioSession.sharedInstance().setCategory(AVAudioSession.Category.playback) try AVAudioSession.sharedInstance().setActive(true) diff --git a/Riot/Modules/Room/VoiceMessages/VoiceMessageAudioRecorder.swift b/Riot/Modules/Room/VoiceMessages/VoiceMessageAudioRecorder.swift index d82200109..9ab618e97 100644 --- a/Riot/Modules/Room/VoiceMessages/VoiceMessageAudioRecorder.swift +++ b/Riot/Modules/Room/VoiceMessages/VoiceMessageAudioRecorder.swift @@ -73,15 +73,18 @@ class VoiceMessageAudioRecorder: NSObject, AVAudioRecorderDelegate { } } - func stopRecording() { + func stopRecording(releaseAudioSession: Bool = true) { audioRecorder?.stop() - do { - try AVAudioSession.sharedInstance().setActive(false) - } catch { - delegateContainer.notifyDelegatesWithBlock { delegate in - (delegate as? VoiceMessageAudioRecorderDelegate)?.audioRecorder(self, didFailWithError: VoiceMessageAudioRecorderError.genericError) } + + if releaseAudioSession { + MXLog.debug("[VoiceMessageAudioRecorder] stopRecording() - releasing audio session") + do { + try AVAudioSession.sharedInstance().setActive(false) + } catch { + delegateContainer.notifyDelegatesWithBlock { delegate in + (delegate as? VoiceMessageAudioRecorderDelegate)?.audioRecorder(self, didFailWithError: VoiceMessageAudioRecorderError.genericError) } + } } - } func peakPowerForChannelNumber(_ channelNumber: Int) -> Float { diff --git a/Riot/Modules/Room/VoiceMessages/VoiceMessageController.swift b/Riot/Modules/Room/VoiceMessages/VoiceMessageController.swift index 94d93dedb..bd2ef8bf6 100644 --- a/Riot/Modules/Room/VoiceMessages/VoiceMessageController.swift +++ b/Riot/Modules/Room/VoiceMessages/VoiceMessageController.swift @@ -199,15 +199,25 @@ public class VoiceMessageController: NSObject, VoiceMessageToolbarViewDelegate, // MARK: - AudioRecorderDelegate func audioRecorderDidStartRecording(_ audioRecorder: VoiceMessageAudioRecorder) { + guard self.audioRecorder === audioRecorder else { + return + } notifiedRemainingTime = false updateUI() } func audioRecorderDidFinishRecording(_ audioRecorder: VoiceMessageAudioRecorder) { + guard self.audioRecorder === audioRecorder else { + return + } updateUI() } func audioRecorder(_ audioRecorder: VoiceMessageAudioRecorder, didFailWithError: Error) { + guard self.audioRecorder === audioRecorder else { + MXLog.error("[VoiceMessageController] audioRecorder failed but it's not the current one.") + return + } isInLockedMode = false updateUI() @@ -217,20 +227,34 @@ public class VoiceMessageController: NSObject, VoiceMessageToolbarViewDelegate, // MARK: - VoiceMessageAudioPlayerDelegate func audioPlayerDidStartPlaying(_ audioPlayer: VoiceMessageAudioPlayer) { + guard self.audioPlayer === audioPlayer else { + return + } updateUI() } func audioPlayerDidPausePlaying(_ audioPlayer: VoiceMessageAudioPlayer) { + guard self.audioPlayer === audioPlayer else { + return + } updateUI() } func audioPlayerDidStopPlaying(_ audioPlayer: VoiceMessageAudioPlayer) { + guard self.audioPlayer === audioPlayer else { + return + } updateUI() } func audioPlayerDidFinishPlaying(_ audioPlayer: VoiceMessageAudioPlayer) { + guard self.audioPlayer === audioPlayer else { + return + } audioPlayer.seekToTime(0.0) { [weak self] _ in self?.updateUI() + // Reload its content if necessary, otherwise the seek won't work + self?.audioPlayer?.reloadContentIfNeeded() } } @@ -280,7 +304,13 @@ public class VoiceMessageController: NSObject, VoiceMessageToolbarViewDelegate, isInLockedMode = false audioPlayer?.stop() - audioRecorder?.stopRecording() + + // Check if we are recording before stopping the recording, because it will try to pause the audio session and it can be problematic if another player or recorder is running + if let audioRecorder, audioRecorder.isRecording { + audioRecorder.stopRecording() + } + // Also, we can release it now, which will prevent the service provider from trying to manage an old audio recorder. + audioRecorder = nil deleteRecordingAtURL(temporaryFileURL) diff --git a/Riot/Modules/Room/VoiceMessages/VoiceMessageMediaServiceProvider.swift b/Riot/Modules/Room/VoiceMessages/VoiceMessageMediaServiceProvider.swift index c6dc8b8b4..0a9283a3c 100644 --- a/Riot/Modules/Room/VoiceMessages/VoiceMessageMediaServiceProvider.swift +++ b/Riot/Modules/Room/VoiceMessages/VoiceMessageMediaServiceProvider.swift @@ -191,7 +191,9 @@ import MediaPlayer continue } - audioRecorder.stopRecording() + // We should release the audio session only if we want to pause all services + let shouldReleaseAudioSession = (service == nil) + audioRecorder.stopRecording(releaseAudioSession: shouldReleaseAudioSession) } guard let audioPlayersEnumerator = audioPlayers.objectEnumerator() else { diff --git a/Riot/Modules/Room/VoiceMessages/VoiceMessagePlaybackController.swift b/Riot/Modules/Room/VoiceMessages/VoiceMessagePlaybackController.swift index 9bc0b705f..59912aa48 100644 --- a/Riot/Modules/Room/VoiceMessages/VoiceMessagePlaybackController.swift +++ b/Riot/Modules/Room/VoiceMessages/VoiceMessagePlaybackController.swift @@ -144,6 +144,8 @@ class VoiceMessagePlaybackController: VoiceMessageAudioPlayerDelegate, VoiceMess audioPlayer.seekToTime(0.0) { [weak self] _ in guard let self = self else { return } self.state = .stopped + // Reload its content if necessary, otherwise the seek won't work + self.audioPlayer?.reloadContentIfNeeded() } } diff --git a/changelog.d/7217.bugfix b/changelog.d/7217.bugfix new file mode 100644 index 000000000..aaa993008 --- /dev/null +++ b/changelog.d/7217.bugfix @@ -0,0 +1 @@ +A voice message is now replayable. diff --git a/changelog.d/7325.bugfix b/changelog.d/7325.bugfix new file mode 100644 index 000000000..5edaaba0d --- /dev/null +++ b/changelog.d/7325.bugfix @@ -0,0 +1 @@ +Fix an issue where a voice message recording was failing. From a269c0fad1c65487b7ecf930e29205c04557aa3f Mon Sep 17 00:00:00 2001 From: Kat Gerasimova Date: Fri, 10 Feb 2023 13:24:04 +0000 Subject: [PATCH 08/45] Update issue template config --- .github/ISSUE_TEMPLATE/config.yaml | 8 -------- .github/ISSUE_TEMPLATE/config.yml | 11 +++++++++++ 2 files changed, 11 insertions(+), 8 deletions(-) delete mode 100644 .github/ISSUE_TEMPLATE/config.yaml create mode 100644 .github/ISSUE_TEMPLATE/config.yml diff --git a/.github/ISSUE_TEMPLATE/config.yaml b/.github/ISSUE_TEMPLATE/config.yaml deleted file mode 100644 index b30282798..000000000 --- a/.github/ISSUE_TEMPLATE/config.yaml +++ /dev/null @@ -1,8 +0,0 @@ -blank_issues_enabled: true -contact_links: - - name: Element iOS Community Support - url: "https://matrix.to/#/#element-ios:matrix.org" - about: General Element iOS support questions can be asked here. - - name: Matrix Security Policy - url: https://www.matrix.org/security-disclosure-policy/ - about: Learn more about our security disclosure policy. diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 000000000..31875913c --- /dev/null +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1,11 @@ +blank_issues_enabled: true +contact_links: + - name: Propose an enhancement or feature request + url: https://github.com/vector-im/element-meta/discussions/categories/ideas + about: You can propose a new feature or an improvement to our apps + - name: Element iOS Community Support + url: https://matrix.to/#/#element-ios:matrix.org + about: General Element iOS support questions can be asked here + - name: Matrix Security Policy + url: https://www.matrix.org/security-disclosure-policy/ + about: Learn more about our security disclosure policy From 8516bd0d62688f7a97c7fb6198772149730674c3 Mon Sep 17 00:00:00 2001 From: Kat Gerasimova Date: Fri, 10 Feb 2023 13:30:46 +0000 Subject: [PATCH 09/45] Polish the new issue view (#7363) * Polish the new issue view * Redirect from to discussions for enhancements Remove issue template for enhancements as we collect them in Element meta discussions for a while now --- .github/ISSUE_TEMPLATE/config.yml | 9 +++---- .github/ISSUE_TEMPLATE/enhancement.yml | 36 -------------------------- 2 files changed, 3 insertions(+), 42 deletions(-) delete mode 100644 .github/ISSUE_TEMPLATE/enhancement.yml diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml index 31875913c..8cbef5ecc 100644 --- a/.github/ISSUE_TEMPLATE/config.yml +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -1,11 +1,8 @@ blank_issues_enabled: true contact_links: - - name: Propose an enhancement or feature request + - name: Enhancement or feature request url: https://github.com/vector-im/element-meta/discussions/categories/ideas - about: You can propose a new feature or an improvement to our apps + about: Do you have a suggestion or feature request? - name: Element iOS Community Support url: https://matrix.to/#/#element-ios:matrix.org - about: General Element iOS support questions can be asked here - - name: Matrix Security Policy - url: https://www.matrix.org/security-disclosure-policy/ - about: Learn more about our security disclosure policy + about: General Element iOS support questions can be asked in the app Matrix room diff --git a/.github/ISSUE_TEMPLATE/enhancement.yml b/.github/ISSUE_TEMPLATE/enhancement.yml deleted file mode 100644 index e776173fa..000000000 --- a/.github/ISSUE_TEMPLATE/enhancement.yml +++ /dev/null @@ -1,36 +0,0 @@ -name: Enhancement request -description: Do you have a suggestion or feature request? -labels: [T-Enhancement] -body: - - type: markdown - attributes: - value: | - Thank you for taking the time to propose an enhancement to an existing feature. If you would like to propose a new feature or a major cross-platform change, please [start a discussion here](https://github.com/vector-im/element-meta/discussions/new?category=ideas) - - type: textarea - id: usecase - attributes: - label: Your use case - description: What would you like to be able to do? Please feel welcome to include screenshots or mock ups. - placeholder: Tell us what you would like to do! - value: | - #### What would you like to do? - - #### Why would you like to do it? - - #### How would you like to achieve it? - validations: - required: true - - type: textarea - id: alternative - attributes: - label: Have you considered any alternatives? - placeholder: A clear and concise description of any alternative solutions or features you've considered. - validations: - required: false - - type: textarea - id: additional-context - attributes: - label: Additional context - placeholder: Is there anything else you'd like to add? - validations: - required: false From e1b82bba60a96831147dbd31cd4b89d8acd384e2 Mon Sep 17 00:00:00 2001 From: Doug Date: Fri, 10 Feb 2023 13:39:26 +0000 Subject: [PATCH 10/45] Prepare for new sprint --- Config/AppVersion.xcconfig | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Config/AppVersion.xcconfig b/Config/AppVersion.xcconfig index 20e1da599..7430ec454 100644 --- a/Config/AppVersion.xcconfig +++ b/Config/AppVersion.xcconfig @@ -15,5 +15,5 @@ // // Version -MARKETING_VERSION = 1.10.2 -CURRENT_PROJECT_VERSION = 1.10.2 +MARKETING_VERSION = 1.10.3 +CURRENT_PROJECT_VERSION = 1.10.3 From 24257216145f79f718288481761a9a9a4612db4d Mon Sep 17 00:00:00 2001 From: aringenbach <80891108+aringenbach@users.noreply.github.com> Date: Mon, 13 Feb 2023 10:44:03 +0100 Subject: [PATCH 11/45] Update composer library version to 1.1.0 (#7365) * Update composer library version to 1.1.0 * Add changelog file --- .../xcshareddata/swiftpm/Package.resolved | 4 +-- .../WysiwygInputToolbarView.swift | 36 ++++++++++--------- .../Room/Composer/Model/ComposerModels.swift | 28 ++++++++++----- .../Composer/Test/UI/ComposerUITests.swift | 28 +++++++++++++++ .../Modules/Room/Composer/View/Composer.swift | 22 ++++++++---- changelog.d/pr-7365.change | 1 + project.yml | 2 +- 7 files changed, 88 insertions(+), 33 deletions(-) create mode 100644 changelog.d/pr-7365.change diff --git a/Riot.xcworkspace/xcshareddata/swiftpm/Package.resolved b/Riot.xcworkspace/xcshareddata/swiftpm/Package.resolved index 1e8132370..132150a17 100644 --- a/Riot.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/Riot.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -41,8 +41,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/matrix-org/matrix-wysiwyg-composer-swift", "state" : { - "revision" : "3f72aeab7d7e04b52ff3f735ab79a75993f97ef2", - "version" : "0.22.0" + "revision" : "f25189fb9b37eb77fad016ee8f0b0346c5310644", + "version" : "1.1.0" } }, { diff --git a/Riot/Modules/Room/Views/WYSIWYGInputToolbar/WysiwygInputToolbarView.swift b/Riot/Modules/Room/Views/WYSIWYGInputToolbar/WysiwygInputToolbarView.swift index d611fb06a..db6cc8193 100644 --- a/Riot/Modules/Room/Views/WYSIWYGInputToolbar/WysiwygInputToolbarView.swift +++ b/Riot/Modules/Room/Views/WYSIWYGInputToolbar/WysiwygInputToolbarView.swift @@ -44,15 +44,26 @@ class WysiwygInputToolbarView: MXKRoomInputToolbarView, NibLoadable, HtmlRoomInp private var voiceMessageBottomConstraint: NSLayoutConstraint? private var hostingViewController: VectorHostingController! private var wysiwygViewModel = WysiwygComposerViewModel( - parserStyle: HTMLParserStyle(textColor: ThemeService.shared().theme.colors.primaryContent, - linkColor: ThemeService.shared().theme.colors.links, - codeBackgroundColor: ThemeService.shared().theme.selectedBackgroundColor, - codeBorderColor: ThemeService.shared().theme.textQuinaryColor, - quoteBackgroundColor: ThemeService.shared().theme.selectedBackgroundColor, - quoteBorderColor: ThemeService.shared().theme.textQuinaryColor, - borderWidth: 1.0, - cornerRadius: 4.0) + parserStyle: WysiwygInputToolbarView.parserStyle ) + /// Compute current HTML parser style for composer. + private static var parserStyle: HTMLParserStyle { + return HTMLParserStyle( + textColor: ThemeService.shared().theme.colors.primaryContent, + linkColor: ThemeService.shared().theme.colors.links, + codeBlockStyle: BlockStyle(backgroundColor: ThemeService.shared().theme.selectedBackgroundColor, + borderColor: ThemeService.shared().theme.textQuinaryColor, + borderWidth: 1.0, + cornerRadius: 4.0, + padding: .init(horizontal: 10.0, vertical: 12.0), + type: .background), + quoteBlockStyle: BlockStyle(backgroundColor: ThemeService.shared().theme.selectedBackgroundColor, + borderColor: ThemeService.shared().theme.selectedBackgroundColor, + borderWidth: 0.0, + cornerRadius: 0.0, + padding: .init(horizontal: 25.0, vertical: 12.0), + type: .side(offset: 5, width: 4))) + } private var viewModel: ComposerViewModelProtocol! private var isLandscapePhone: Bool { @@ -304,14 +315,7 @@ class WysiwygInputToolbarView: MXKRoomInputToolbarView, NibLoadable, HtmlRoomInp private func update(theme: Theme) { hostingViewController.view.backgroundColor = theme.colors.background - wysiwygViewModel.parserStyle = HTMLParserStyle(textColor: ThemeService.shared().theme.colors.primaryContent, - linkColor: ThemeService.shared().theme.colors.links, - codeBackgroundColor: ThemeService.shared().theme.selectedBackgroundColor, - codeBorderColor: ThemeService.shared().theme.textQuinaryColor, - quoteBackgroundColor: ThemeService.shared().theme.selectedBackgroundColor, - quoteBorderColor: ThemeService.shared().theme.textQuinaryColor, - borderWidth: 1.0, - cornerRadius: 4.0) + wysiwygViewModel.parserStyle = WysiwygInputToolbarView.parserStyle } private func updateTextViewHeight() { diff --git a/RiotSwiftUI/Modules/Room/Composer/Model/ComposerModels.swift b/RiotSwiftUI/Modules/Room/Composer/Model/ComposerModels.swift index 800494c5d..98d7febf6 100644 --- a/RiotSwiftUI/Modules/Room/Composer/Model/ComposerModels.swift +++ b/RiotSwiftUI/Modules/Room/Composer/Model/ComposerModels.swift @@ -37,7 +37,7 @@ enum FormatType { case unorderedList case orderedList case indent - case unIndent + case unindent case inlineCode case codeBlock case quote @@ -48,6 +48,18 @@ extension FormatType: CaseIterable, Identifiable { var id: Self { self } } +extension FormatType { + /// Return true if the format type is an indentation action. + var isIndentType: Bool { + switch self { + case .indent, .unindent: + return true + default: + return false + } + } +} + extension FormatItem: Identifiable { var id: FormatType { type } } @@ -70,7 +82,7 @@ extension FormatItem { return Asset.Images.numberedList.name case .indent: return Asset.Images.indentIncrease.name - case .unIndent: + case .unindent: return Asset.Images.indentDecrease.name case .inlineCode: return Asset.Images.code.name @@ -99,7 +111,7 @@ extension FormatItem { return "orderedListButton" case .indent: return "indentListButton" - case .unIndent: + case .unindent: return "unIndentButton" case .inlineCode: return "inlineCodeButton" @@ -128,7 +140,7 @@ extension FormatItem { return VectorL10n.wysiwygComposerFormatActionOrderedList case .indent: return VectorL10n.wysiwygComposerFormatActionIndent - case .unIndent: + case .unindent: return VectorL10n.wysiwygComposerFormatActionUnIndent case .inlineCode: return VectorL10n.wysiwygComposerFormatActionInlineCode @@ -160,8 +172,8 @@ extension FormatType { return .orderedList case .indent: return .indent - case .unIndent: - return .unIndent + case .unindent: + return .unindent case .inlineCode: return .inlineCode case .codeBlock: @@ -191,8 +203,8 @@ extension FormatType { return .orderedList case .indent: return .indent - case .unIndent: - return .unIndent + case .unindent: + return .unindent case .inlineCode: return .inlineCode case .codeBlock: diff --git a/RiotSwiftUI/Modules/Room/Composer/Test/UI/ComposerUITests.swift b/RiotSwiftUI/Modules/Room/Composer/Test/UI/ComposerUITests.swift index aae6e1682..72a2fb2ad 100644 --- a/RiotSwiftUI/Modules/Room/Composer/Test/UI/ComposerUITests.swift +++ b/RiotSwiftUI/Modules/Room/Composer/Test/UI/ComposerUITests.swift @@ -158,4 +158,32 @@ final class ComposerUITests: MockScreenTestCase { XCTAssertFalse(minimiseButton.exists) XCTAssertTrue(maximiseButton.exists) } + + func testCreatingListDisplaysIndentButtons() throws { + app.goToScreenWithIdentifier(MockComposerScreenState.send.title) + + XCTAssertFalse(composerToolbarButton(in: app, for: .indent).exists) + XCTAssertFalse(composerToolbarButton(in: app, for: .indent).exists) + // Create a list. + composerToolbarButton(in: app, for: .orderedList).tap() + XCTAssertTrue(composerToolbarButton(in: app, for: .indent).exists) + XCTAssertTrue(composerToolbarButton(in: app, for: .indent).exists) + // Remove the list + composerToolbarButton(in: app, for: .orderedList).tap() + XCTAssertFalse(composerToolbarButton(in: app, for: .indent).exists) + XCTAssertFalse(composerToolbarButton(in: app, for: .indent).exists) + } +} + +private extension ComposerUITests { + /// Returns the button of the composer toolbar associated with given format type. + /// + /// - Parameters: + /// - app: the running app + /// - formatType: format type to look for + /// - Returns: XCUIElement for the button + func composerToolbarButton(in app: XCUIApplication, for formatType: FormatType) -> XCUIElement { + // Note: state is irrelevant here, we're just building this to retrieve the accessibility identifier. + app.buttons[FormatItem(type: formatType, state: .enabled).accessibilityIdentifier] + } } diff --git a/RiotSwiftUI/Modules/Room/Composer/View/Composer.swift b/RiotSwiftUI/Modules/Room/Composer/View/Composer.swift index 6163f384a..1413912c2 100644 --- a/RiotSwiftUI/Modules/Room/Composer/View/Composer.swift +++ b/RiotSwiftUI/Modules/Room/Composer/View/Composer.swift @@ -71,12 +71,15 @@ struct Composer: View { } private var formatItems: [FormatItem] { - FormatType.allCases.map { type in - FormatItem( - type: type, - state: wysiwygViewModel.actionStates[type.composerAction] ?? .disabled - ) - } + return FormatType.allCases + // Exclude indent type outside of lists. + .filter { wysiwygViewModel.isInList || !$0.isIndentType } + .map { type in + FormatItem( + type: type, + state: wysiwygViewModel.actionStates[type.composerAction] ?? .disabled + ) + } } private var composerContainer: some View { @@ -257,6 +260,13 @@ struct Composer: View { } } +private extension WysiwygComposerViewModel { + /// Return true if the selection of the composer is currently located in a list. + var isInList: Bool { + actionStates[.orderedList] == .reversed || actionStates[.unorderedList] == .reversed + } +} + // MARK: Previews struct Composer_Previews: PreviewProvider { diff --git a/changelog.d/pr-7365.change b/changelog.d/pr-7365.change new file mode 100644 index 000000000..17c2965b6 --- /dev/null +++ b/changelog.d/pr-7365.change @@ -0,0 +1 @@ +Labs: Rich Text Editor: Update to version 1.1.0 diff --git a/project.yml b/project.yml index de099a507..91ec35475 100644 --- a/project.yml +++ b/project.yml @@ -53,7 +53,7 @@ packages: branch: main WysiwygComposer: url: https://github.com/matrix-org/matrix-wysiwyg-composer-swift - version: 0.22.0 + version: 1.1.0 DeviceKit: url: https://github.com/devicekit/DeviceKit majorVersion: 4.7.0 From 7cdb771c63a4d14746c9cc3928cbcbceb8952c20 Mon Sep 17 00:00:00 2001 From: Flescio Date: Mon, 13 Feb 2023 11:13:59 +0100 Subject: [PATCH 12/45] Apply (polls) push rules client side for encrypted rooms (#7356) * managing dont notify push rule * private extension for push rule * remove mentions and keyword notice footer --- RiotNSE/NotificationService.swift | 15 ++++++++++++++- .../MentionsAndKeywordNotificationSettings.swift | 3 +-- changelog.d/pr-7356.change | 1 + 3 files changed, 16 insertions(+), 3 deletions(-) create mode 100644 changelog.d/pr-7356.change diff --git a/RiotNSE/NotificationService.swift b/RiotNSE/NotificationService.swift index 6560290ab..f0c0b1fab 100644 --- a/RiotNSE/NotificationService.swift +++ b/RiotNSE/NotificationService.swift @@ -382,7 +382,13 @@ class NotificationService: UNNotificationServiceExtension { let currentUserId = account.mxCredentials.userId let roomDisplayName = roomSummary?.displayname let pushRule = NotificationService.backgroundSyncService.pushRule(matching: event, roomState: roomState) - + + // if the push rule must not be notified we complete and return + if pushRule?.dontNotify == true { + onComplete(nil, false) + return + } + switch event.eventType { case .callInvite: let offer = event.content["offer"] as? [AnyHashable: Any] @@ -887,3 +893,10 @@ class NotificationService: UNNotificationServiceExtension { return String(format: format, locale: locale, arguments: args) } } + +private extension MXPushRule { + var dontNotify: Bool { + let actions = (actions as? [MXPushRuleAction]) ?? [] + return actions.contains { $0.actionType == MXPushRuleActionTypeDontNotify } + } +} diff --git a/RiotSwiftUI/Modules/Settings/Notifications/View/MentionsAndKeywordNotificationSettings.swift b/RiotSwiftUI/Modules/Settings/Notifications/View/MentionsAndKeywordNotificationSettings.swift index ac1316bfd..6139eb72b 100644 --- a/RiotSwiftUI/Modules/Settings/Notifications/View/MentionsAndKeywordNotificationSettings.swift +++ b/RiotSwiftUI/Modules/Settings/Notifications/View/MentionsAndKeywordNotificationSettings.swift @@ -21,8 +21,7 @@ struct MentionsAndKeywordNotificationSettings: View { var keywordSection: some View { SwiftUI.Section( - header: FormSectionHeader(text: VectorL10n.settingsYourKeywords), - footer: FormSectionFooter(text: VectorL10n.settingsMentionsAndKeywordsEncryptionNotice) + header: FormSectionHeader(text: VectorL10n.settingsYourKeywords) ) { NotificationSettingsKeywords(viewModel: viewModel) } diff --git a/changelog.d/pr-7356.change b/changelog.d/pr-7356.change new file mode 100644 index 000000000..d0f12bb92 --- /dev/null +++ b/changelog.d/pr-7356.change @@ -0,0 +1 @@ +Push Rules: Apply push rules client side for encrypted rooms, hiding in case of dont_notify action \ No newline at end of file From b30a3d92cb845c6c8102efc8f5fb57e0d277d7ad Mon Sep 17 00:00:00 2001 From: Kat Gerasimova Date: Fri, 10 Feb 2023 13:35:56 +0000 Subject: [PATCH 13/45] Remove broken link --- .github/ISSUE_TEMPLATE/bug.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/ISSUE_TEMPLATE/bug.yml b/.github/ISSUE_TEMPLATE/bug.yml index 88458a696..0c1301c1f 100644 --- a/.github/ISSUE_TEMPLATE/bug.yml +++ b/.github/ISSUE_TEMPLATE/bug.yml @@ -1,5 +1,5 @@ name: Bug report for the Element iOS app -description: Report any issues that you have found with the Element app. Please [check open issues](https://github.com/vector-im/element-ios/issues) first, in case it has already been reported. +description: Report any issues that you have found with the Element app. Please check open issues first, in case it has already been reported. labels: [T-Defect] body: - type: markdown From 2bc314e994ab4a9dda46a067f1ed5ec24dfb04a4 Mon Sep 17 00:00:00 2001 From: Andy Uhnak Date: Fri, 10 Feb 2023 11:30:49 +0000 Subject: [PATCH 14/45] Do not reset device keys if migrating to CryptoSDK --- Riot/Modules/MatrixKit/Models/Account/MXKAccount.m | 5 +++-- changelog.d/pr-7369.change | 1 + 2 files changed, 4 insertions(+), 2 deletions(-) create mode 100644 changelog.d/pr-7369.change diff --git a/Riot/Modules/MatrixKit/Models/Account/MXKAccount.m b/Riot/Modules/MatrixKit/Models/Account/MXKAccount.m index 36e4989f9..548442ab7 100644 --- a/Riot/Modules/MatrixKit/Models/Account/MXKAccount.m +++ b/Riot/Modules/MatrixKit/Models/Account/MXKAccount.m @@ -947,9 +947,10 @@ static NSArray *initialSyncSilentErrorsHTTPStatusCodes; if (clearStore) { - // Force a reload of device keys at the next session start. + // Force a reload of device keys at the next session start, unless we are just about to migrate + // all data and device keys into CryptoSDK. // This will fix potential UISIs other peoples receive for our messages. - if ([mxSession.crypto isKindOfClass:[MXLegacyCrypto class]]) + if ([mxSession.crypto isKindOfClass:[MXLegacyCrypto class]] && !MXSDKOptions.sharedInstance.enableCryptoSDK) { [(MXLegacyCrypto *)mxSession.crypto resetDeviceKeys]; } diff --git a/changelog.d/pr-7369.change b/changelog.d/pr-7369.change new file mode 100644 index 000000000..a38028df9 --- /dev/null +++ b/changelog.d/pr-7369.change @@ -0,0 +1 @@ +Do not reset device keys if migrating to CryptoSDK From 53dcecbe2f5708485a7aa51115bed9cfdc40e69c Mon Sep 17 00:00:00 2001 From: aringenbach <80891108+aringenbach@users.noreply.github.com> Date: Tue, 14 Feb 2023 11:30:37 +0100 Subject: [PATCH 15/45] Update composer library version to 1.1.1 (#7370) * Update composer library version to 1.1.1 * Replace changelog file --- Riot.xcworkspace/xcshareddata/swiftpm/Package.resolved | 4 ++-- changelog.d/pr-7365.change | 1 - changelog.d/pr-7370.change | 1 + project.yml | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) delete mode 100644 changelog.d/pr-7365.change create mode 100644 changelog.d/pr-7370.change diff --git a/Riot.xcworkspace/xcshareddata/swiftpm/Package.resolved b/Riot.xcworkspace/xcshareddata/swiftpm/Package.resolved index 132150a17..b721d2300 100644 --- a/Riot.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/Riot.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -41,8 +41,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/matrix-org/matrix-wysiwyg-composer-swift", "state" : { - "revision" : "f25189fb9b37eb77fad016ee8f0b0346c5310644", - "version" : "1.1.0" + "revision" : "addf90f3e2a6ab46bd2b2febe117d9cddb646e7d", + "version" : "1.1.1" } }, { diff --git a/changelog.d/pr-7365.change b/changelog.d/pr-7365.change deleted file mode 100644 index 17c2965b6..000000000 --- a/changelog.d/pr-7365.change +++ /dev/null @@ -1 +0,0 @@ -Labs: Rich Text Editor: Update to version 1.1.0 diff --git a/changelog.d/pr-7370.change b/changelog.d/pr-7370.change new file mode 100644 index 000000000..6a53bd879 --- /dev/null +++ b/changelog.d/pr-7370.change @@ -0,0 +1 @@ +Labs: Rich Text Editor: Update to version 1.1.1 diff --git a/project.yml b/project.yml index 91ec35475..8d5de9110 100644 --- a/project.yml +++ b/project.yml @@ -53,7 +53,7 @@ packages: branch: main WysiwygComposer: url: https://github.com/matrix-org/matrix-wysiwyg-composer-swift - version: 1.1.0 + version: 1.1.1 DeviceKit: url: https://github.com/devicekit/DeviceKit majorVersion: 4.7.0 From dae7b2d35ac9b99c6c984d50154d1097815e79ab Mon Sep 17 00:00:00 2001 From: Johannes Marbach Date: Tue, 14 Feb 2023 11:38:47 +0100 Subject: [PATCH 16/45] Add EXF issues to the X-Plorer project --- .github/workflows/triage-move-labelled.yml | 24 ++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/.github/workflows/triage-move-labelled.yml b/.github/workflows/triage-move-labelled.yml index 122543abc..291360fd2 100644 --- a/.github/workflows/triage-move-labelled.yml +++ b/.github/workflows/triage-move-labelled.yml @@ -210,6 +210,30 @@ jobs: PROJECT_ID: "PVT_kwDOAM0swc4AArk0" GITHUB_TOKEN: ${{ secrets.ELEMENT_BOT_TOKEN }} + ex_plorers: + name: Add labelled issues to X-Plorer project + runs-on: ubuntu-latest + if: > + contains(github.event.issue.labels.*.name, 'Team: Element X Feature') + steps: + - uses: octokit/graphql-action@v2.x + id: add_to_project + with: + headers: '{"GraphQL-Features": "projects_next_graphql"}' + query: | + mutation add_to_project($projectid:ID!,$contentid:ID!) { + addProjectV2ItemById(input: {projectId: $projectid contentId: $contentid}) { + item { + id + } + } + } + projectid: ${{ env.PROJECT_ID }} + contentid: ${{ github.event.issue.node_id }} + env: + PROJECT_ID: "PVT_kwDOAM0swc4ALoFY" + GITHUB_TOKEN: ${{ secrets.ELEMENT_BOT_TOKEN }} + ps_features1: name: Add labelled issues to PS features team 1 runs-on: ubuntu-latest From a2a3205aceef15e7961781ae299f9c3babfe49ea Mon Sep 17 00:00:00 2001 From: Hugh Nimmo-Smith Date: Wed, 15 Feb 2023 14:02:33 +0000 Subject: [PATCH 17/45] Support for v2 of MSC3903 (#7372) --- Riot/Assets/en.lproj/Vector.strings | 1 + Riot/Generated/Strings.swift | 4 ++ .../Rendezvous/RendezvousService.swift | 46 ++++++++++++------ .../Service/MatrixSDK/QRLoginService.swift | 16 +++++-- .../Service/QRLoginServiceProtocol.swift | 1 + ...uthenticationQRLoginFailureViewModel.swift | 3 ++ ...henticationQRLoginFailureScreenState.swift | 5 +- .../AuthenticationQRLoginFailureUITests.swift | 14 ++++++ RiotTests/RendezvousServiceTests.swift | 48 +++++++++++++++++-- changelog.d/pr-7372.change | 1 + 10 files changed, 117 insertions(+), 22 deletions(-) create mode 100644 changelog.d/pr-7372.change diff --git a/Riot/Assets/en.lproj/Vector.strings b/Riot/Assets/en.lproj/Vector.strings index abdc8a6e0..7494bbfb4 100644 --- a/Riot/Assets/en.lproj/Vector.strings +++ b/Riot/Assets/en.lproj/Vector.strings @@ -239,6 +239,7 @@ "authentication_qr_login_loading_signed_in" = "You are now signed in on your other device."; "authentication_qr_login_failure_title" = "Linking failed"; +"authentication_qr_login_failure_device_not_supported" = "Linking with this device is not supported."; "authentication_qr_login_failure_invalid_qr" = "QR code is invalid."; "authentication_qr_login_failure_request_denied" = "The request was denied on the other device."; "authentication_qr_login_failure_request_timed_out" = "The linking wasn’t completed in the required time."; diff --git a/Riot/Generated/Strings.swift b/Riot/Generated/Strings.swift index de1b5a22e..96f5ba8fb 100644 --- a/Riot/Generated/Strings.swift +++ b/Riot/Generated/Strings.swift @@ -743,6 +743,10 @@ public class VectorL10n: NSObject { public static var authenticationQrLoginDisplayTitle: String { return VectorL10n.tr("Vector", "authentication_qr_login_display_title") } + /// Linking with this device is not supported. + public static var authenticationQrLoginFailureDeviceNotSupported: String { + return VectorL10n.tr("Vector", "authentication_qr_login_failure_device_not_supported") + } /// QR code is invalid. public static var authenticationQrLoginFailureInvalidQr: String { return VectorL10n.tr("Vector", "authentication_qr_login_failure_invalid_qr") diff --git a/Riot/Modules/Rendezvous/RendezvousService.swift b/Riot/Modules/Rendezvous/RendezvousService.swift index 49c3486bb..ac9a890e1 100644 --- a/Riot/Modules/Rendezvous/RendezvousService.swift +++ b/Riot/Modules/Rendezvous/RendezvousService.swift @@ -30,6 +30,7 @@ enum RendezvousServiceError: Error { /// Algorithm name as per MSC3903 enum RendezvousChannelAlgorithm: String { case ECDH_V1 = "org.matrix.msc3903.rendezvous.v1.curve25519-aes-sha256" + case ECDH_V2 = "org.matrix.msc3903.rendezvous.v2.curve25519-aes-sha256" } /// Allows communication through a secure channel. Based on MSC3886 and MSC3903 @@ -40,17 +41,20 @@ class RendezvousService { private var privateKey: Curve25519.KeyAgreement.PrivateKey! private var interlocutorPublicKey: Curve25519.KeyAgreement.PublicKey? private var symmetricKey: SymmetricKey? + private var algorithm: RendezvousChannelAlgorithm - init(transport: RendezvousTransportProtocol) { + init(transport: RendezvousTransportProtocol, algorithm: RendezvousChannelAlgorithm) { self.transport = transport + self.algorithm = algorithm } /// Creates a new rendezvous endpoint and publishes the creator's public key func createRendezvous() async -> Result { privateKey = Curve25519.KeyAgreement.PrivateKey() + let algorithm = RendezvousChannelAlgorithm.ECDH_V2 - let publicKeyString = privateKey.publicKey.rawRepresentation.base64EncodedString() - let details = RendezvousDetails(algorithm: RendezvousChannelAlgorithm.ECDH_V1.rawValue) + let publicKeyString = encodeBase64(data: privateKey.publicKey.rawRepresentation) + let details = RendezvousDetails(algorithm: algorithm.rawValue) switch await transport.create(body: details) { case .failure(let transportError): @@ -60,7 +64,7 @@ class RendezvousService { return .failure(.transportError(.rendezvousURLInvalid)) } - let fullDetails = RendezvousDetails(algorithm: RendezvousChannelAlgorithm.ECDH_V1.rawValue, + let fullDetails = RendezvousDetails(algorithm: algorithm.rawValue, transport: RendezvousTransportDetails(type: "org.matrix.msc3886.http.v1", uri: rendezvousURL.absoluteString), key: publicKeyString) @@ -80,7 +84,7 @@ class RendezvousService { } guard let key = response.key, - let interlocutorPublicKeyData = Data(base64Encoded: key), + let interlocutorPublicKeyData = decodeBase64(input: key), let interlocutorPublicKey = try? Curve25519.KeyAgreement.PublicKey(rawRepresentation: interlocutorPublicKeyData) else { return .failure(.invalidInterlocutorKey) } @@ -107,7 +111,7 @@ class RendezvousService { /// Joins an existing rendezvous and publishes the joiner's public key /// At the end of this a symmetric key will be available for encryption func joinRendezvous(withPublicKey publicKey: String) async -> Result { - guard let interlocutorPublicKeyData = Data(base64Encoded: publicKey), + guard let interlocutorPublicKeyData = decodeBase64(input: publicKey), let interlocutorPublicKey = try? Curve25519.KeyAgreement.PublicKey(rawRepresentation: interlocutorPublicKeyData) else { MXLog.debug("[RendezvousService] Invalid interlocutor data") return .failure(.invalidInterlocutorKey) @@ -115,8 +119,8 @@ class RendezvousService { privateKey = Curve25519.KeyAgreement.PrivateKey() - let publicKeyString = privateKey.publicKey.rawRepresentation.base64EncodedString() - let payload = RendezvousDetails(algorithm: RendezvousChannelAlgorithm.ECDH_V1.rawValue, + let publicKeyString = encodeBase64(data: privateKey.publicKey.rawRepresentation) + let payload = RendezvousDetails(algorithm: algorithm.rawValue, key: publicKeyString) guard case .success = await transport.send(body: payload) else { @@ -142,6 +146,18 @@ class RendezvousService { return .success(validationCode) } + private func encodeBase64(data: Data) -> String { + if algorithm == .ECDH_V2 { + return MXBase64Tools.unpaddedBase64(from: data) + } + return MXBase64Tools.base64(from: data) + } + + private func decodeBase64(input: String) -> Data? { + // MXBase64Tools will decode both padded and unpadded data so we don't need to take account of algorithm here + return MXBase64Tools.data(fromBase64: input) + } + /// Send arbitrary data over the secure channel /// This will use the previously generated symmetric key to AES encrypt the payload /// - Parameter data: the data to be encrypted and sent @@ -162,8 +178,8 @@ class RendezvousService { var ciphertext = sealedBox.ciphertext ciphertext.append(contentsOf: sealedBox.tag) - let body = RendezvousMessage(iv: Data(nonce).base64EncodedString(), - ciphertext: ciphertext.base64EncodedString()) + let body = RendezvousMessage(iv: encodeBase64(data: Data(nonce)), + ciphertext: encodeBase64(data: ciphertext)) switch await transport.send(body: body) { case .failure(let transportError): @@ -191,8 +207,8 @@ class RendezvousService { MXLog.debug("Received rendezvous response: \(response)") - guard let ciphertextData = Data(base64Encoded: response.ciphertext), - let nonceData = Data(base64Encoded: response.iv), + guard let ciphertextData = decodeBase64(input: response.ciphertext), + let nonceData = decodeBase64(input: response.iv), let nonce = try? AES.GCM.Nonce(data: nonceData) else { return .failure(.decodingError) } @@ -243,9 +259,9 @@ class RendezvousService { initiatorPublicKey: Curve25519.KeyAgreement.PublicKey, recipientPublicKey: Curve25519.KeyAgreement.PublicKey, byteCount: Int = SHA256Digest.byteCount) -> SymmetricKey { - guard let sharedInfoData = [RendezvousChannelAlgorithm.ECDH_V1.rawValue, - initiatorPublicKey.rawRepresentation.base64EncodedString(), - recipientPublicKey.rawRepresentation.base64EncodedString()] + guard let sharedInfoData = [algorithm.rawValue, + encodeBase64(data: initiatorPublicKey.rawRepresentation), + encodeBase64(data: recipientPublicKey.rawRepresentation)] .joined(separator: "|") .data(using: .utf8) else { fatalError("[RendezvousService] Failed creating symmetric key shared data") diff --git a/RiotSwiftUI/Modules/Authentication/QRLogin/Common/Service/MatrixSDK/QRLoginService.swift b/RiotSwiftUI/Modules/Authentication/QRLogin/Common/Service/MatrixSDK/QRLoginService.swift index fa743fd2b..05506b4bd 100644 --- a/RiotSwiftUI/Modules/Authentication/QRLogin/Common/Service/MatrixSDK/QRLoginService.swift +++ b/RiotSwiftUI/Modules/Authentication/QRLogin/Common/Service/MatrixSDK/QRLoginService.swift @@ -171,8 +171,16 @@ class QRLoginService: NSObject, QRLoginServiceProtocol { @MainActor private func processQRLoginCode(_ code: QRLoginCode) async { MXLog.debug("[QRLoginService] processQRLoginCode: \(code)") - state = .connectingToDevice - + + // we check these first so that we can show a more specific error message + guard code.rendezvous.transport?.type == "org.matrix.msc3886.http.v1", + let algorithm = RendezvousChannelAlgorithm(rawValue: code.rendezvous.algorithm) else { + MXLog.error("[QRLoginService] Unsupported algorithm or transport") + state = .failed(error: .deviceNotSupported) + return + } + + // so, this is of an expected algorithm so any bad data can be considered an invalid QR code guard code.intent == QRLoginRendezvousPayload.Intent.loginReciprocate.rawValue, let uri = code.rendezvous.transport?.uri, let rendezvousURL = URL(string: uri), @@ -182,9 +190,11 @@ class QRLoginService: NSObject, QRLoginServiceProtocol { return } + state = .connectingToDevice + let transport = RendezvousTransport(baseURL: BuildSettings.rendezvousServerBaseURL, rendezvousURL: rendezvousURL) - let rendezvousService = RendezvousService(transport: transport) + let rendezvousService = RendezvousService(transport: transport, algorithm: algorithm) self.rendezvousService = rendezvousService MXLog.debug("[QRLoginService] Joining the rendezvous at \(rendezvousURL)") diff --git a/RiotSwiftUI/Modules/Authentication/QRLogin/Common/Service/QRLoginServiceProtocol.swift b/RiotSwiftUI/Modules/Authentication/QRLogin/Common/Service/QRLoginServiceProtocol.swift index 823a4983c..c5b48a74f 100644 --- a/RiotSwiftUI/Modules/Authentication/QRLogin/Common/Service/QRLoginServiceProtocol.swift +++ b/RiotSwiftUI/Modules/Authentication/QRLogin/Common/Service/QRLoginServiceProtocol.swift @@ -30,6 +30,7 @@ enum QRLoginServiceMode { enum QRLoginServiceError: Error, Equatable { case noCameraAccess case noCameraAvailable + case deviceNotSupported case invalidQR case requestDenied case requestTimedOut diff --git a/RiotSwiftUI/Modules/Authentication/QRLogin/Failure/AuthenticationQRLoginFailureViewModel.swift b/RiotSwiftUI/Modules/Authentication/QRLogin/Failure/AuthenticationQRLoginFailureViewModel.swift index 0e363e549..656ab5bb4 100644 --- a/RiotSwiftUI/Modules/Authentication/QRLogin/Failure/AuthenticationQRLoginFailureViewModel.swift +++ b/RiotSwiftUI/Modules/Authentication/QRLogin/Failure/AuthenticationQRLoginFailureViewModel.swift @@ -55,6 +55,9 @@ class AuthenticationQRLoginFailureViewModel: AuthenticationQRLoginFailureViewMod case .invalidQR: self.state.failureText = VectorL10n.authenticationQrLoginFailureInvalidQr self.state.retryButtonVisible = true + case .deviceNotSupported: + self.state.failureText = VectorL10n.authenticationQrLoginFailureDeviceNotSupported + self.state.retryButtonVisible = true case .requestDenied: self.state.failureText = VectorL10n.authenticationQrLoginFailureRequestDenied self.state.retryButtonVisible = false diff --git a/RiotSwiftUI/Modules/Authentication/QRLogin/Failure/MockAuthenticationQRLoginFailureScreenState.swift b/RiotSwiftUI/Modules/Authentication/QRLogin/Failure/MockAuthenticationQRLoginFailureScreenState.swift index 5747c86bb..f55bf6d96 100644 --- a/RiotSwiftUI/Modules/Authentication/QRLogin/Failure/MockAuthenticationQRLoginFailureScreenState.swift +++ b/RiotSwiftUI/Modules/Authentication/QRLogin/Failure/MockAuthenticationQRLoginFailureScreenState.swift @@ -24,6 +24,7 @@ enum MockAuthenticationQRLoginFailureScreenState: MockScreenState, CaseIterable // with specific, minimal associated data that will allow you // mock that screen. case invalidQR + case deviceNotSupported case requestDenied case requestTimedOut @@ -35,7 +36,7 @@ enum MockAuthenticationQRLoginFailureScreenState: MockScreenState, CaseIterable /// A list of screen state definitions static var allCases: [MockAuthenticationQRLoginFailureScreenState] { // Each of the presence statuses - [.invalidQR, .requestDenied, .requestTimedOut] + [.invalidQR, .deviceNotSupported, .requestDenied, .requestTimedOut] } /// Generate the view struct for the screen state. @@ -45,6 +46,8 @@ enum MockAuthenticationQRLoginFailureScreenState: MockScreenState, CaseIterable switch self { case .invalidQR: viewModel = .init(qrLoginService: MockQRLoginService(withState: .failed(error: .invalidQR))) + case .deviceNotSupported: + viewModel = .init(qrLoginService: MockQRLoginService(withState: .failed(error: .deviceNotSupported))) case .requestDenied: viewModel = .init(qrLoginService: MockQRLoginService(withState: .failed(error: .requestDenied))) case .requestTimedOut: diff --git a/RiotSwiftUI/Modules/Authentication/QRLogin/Failure/Test/UI/AuthenticationQRLoginFailureUITests.swift b/RiotSwiftUI/Modules/Authentication/QRLogin/Failure/Test/UI/AuthenticationQRLoginFailureUITests.swift index 829349d78..33c82c949 100644 --- a/RiotSwiftUI/Modules/Authentication/QRLogin/Failure/Test/UI/AuthenticationQRLoginFailureUITests.swift +++ b/RiotSwiftUI/Modules/Authentication/QRLogin/Failure/Test/UI/AuthenticationQRLoginFailureUITests.swift @@ -32,6 +32,20 @@ class AuthenticationQRLoginFailureUITests: MockScreenTestCase { XCTAssertTrue(cancelButton.isEnabled) } + func testDeviceNotSupported() { + app.goToScreenWithIdentifier(MockAuthenticationQRLoginFailureScreenState.deviceNotSupported.title) + + XCTAssertTrue(app.staticTexts["failureLabel"].exists) + + let retryButton = app.buttons["retryButton"] + XCTAssertTrue(retryButton.exists) + XCTAssertTrue(retryButton.isEnabled) + + let cancelButton = app.buttons["cancelButton"] + XCTAssertTrue(cancelButton.exists) + XCTAssertTrue(cancelButton.isEnabled) + } + func testRequestDenied() { app.goToScreenWithIdentifier(MockAuthenticationQRLoginFailureScreenState.requestDenied.title) diff --git a/RiotTests/RendezvousServiceTests.swift b/RiotTests/RendezvousServiceTests.swift index 36297f778..2ffe58f06 100644 --- a/RiotTests/RendezvousServiceTests.swift +++ b/RiotTests/RendezvousServiceTests.swift @@ -19,10 +19,10 @@ import XCTest @MainActor class RendezvousServiceTests: XCTestCase { - func testEnd2End() async { + func testEnd2EndV1() async { let mockTransport = MockRendezvousTransport() - let aliceService = RendezvousService(transport: mockTransport) + let aliceService = RendezvousService(transport: mockTransport, algorithm: .ECDH_V1) guard case .success(let rendezvousDetails) = await aliceService.createRendezvous(), let alicePublicKey = rendezvousDetails.key else { @@ -32,7 +32,49 @@ class RendezvousServiceTests: XCTestCase { XCTAssertNotNil(mockTransport.rendezvousURL) - let bobService = RendezvousService(transport: mockTransport) + let bobService = RendezvousService(transport: mockTransport, algorithm: .ECDH_V1) + + guard case .success = await bobService.joinRendezvous(withPublicKey: alicePublicKey) else { + XCTFail("Bob failed to join") + return + } + + guard case .success = await aliceService.waitForInterlocutor() else { + XCTFail("Alice failed to establish connection") + return + } + + guard let messageData = "Hello from alice".data(using: .utf8) else { + fatalError() + } + + guard case .success = await aliceService.send(data: messageData) else { + XCTFail("Alice failed to send message") + return + } + + guard case .success(let data) = await bobService.receive() else { + XCTFail("Bob failed to receive message") + return + } + + XCTAssertEqual(messageData, data) + } + + func testEnd2EndV2() async { + let mockTransport = MockRendezvousTransport() + + let aliceService = RendezvousService(transport: mockTransport, algorithm: .ECDH_V2) + + guard case .success(let rendezvousDetails) = await aliceService.createRendezvous(), + let alicePublicKey = rendezvousDetails.key else { + XCTFail("Rendezvous creation failed") + return + } + + XCTAssertNotNil(mockTransport.rendezvousURL) + + let bobService = RendezvousService(transport: mockTransport, algorithm: .ECDH_V2) guard case .success = await bobService.joinRendezvous(withPublicKey: alicePublicKey) else { XCTFail("Bob failed to join") diff --git a/changelog.d/pr-7372.change b/changelog.d/pr-7372.change new file mode 100644 index 000000000..66791b73f --- /dev/null +++ b/changelog.d/pr-7372.change @@ -0,0 +1 @@ +Updates to protocol used for Sign in with QR code. From ae2d01be22c620044c08cc02793f8b9ae396ee45 Mon Sep 17 00:00:00 2001 From: Flescio Date: Mon, 20 Feb 2023 18:07:03 +0100 Subject: [PATCH 18/45] Show own location in map views (#7375) * show own location on static sharing * show own location on live sharing * add tests and changelog * check location authorisation, fix center to current user location button * it moves request to LocationManager in proper service * add static location viewer service --- .../LocationSharing/LocationManager.swift | 16 +++++ .../LiveLocationSharingViewerModels.swift | 7 ++ .../LiveLocationSharingViewerViewModel.swift | 10 +++ ...LocationSharingViewerServiceProtocol.swift | 2 + .../LiveLocationSharingViewerService.swift | 6 ++ ...MockLiveLocationSharingViewerService.swift | 15 ++++- ...eLocationSharingViewerViewModelTests.swift | 14 ++++ .../View/LiveLocationSharingViewer.swift | 64 +++++++++++++++---- .../MapViewErrorAlertInfoBuilder.swift | 2 +- .../MapView/View/LocationSharingMapView.swift | 13 ++-- .../View/UserLocationAnnotationView.swift | 16 ++++- .../StaticLocationViewingCoordinator.swift | 3 +- ...MockStaticLocationViewingScreenState.swift | 3 +- .../StaticLocationSharingViewerService.swift | 32 ++++++++++ ...ckStaticLocationSharingViewerService.swift | 25 ++++++++ ...LocationSharingViewerServiceProtocol.swift | 22 +++++++ .../StaticLocationViewingModels.swift | 3 + .../StaticLocationViewingViewModel.swift | 16 ++++- .../StaticLocationViewingViewModelTests.swift | 10 ++- .../View/StaticLocationView.swift | 23 ++++--- changelog.d/pr-7361.change | 1 + 21 files changed, 267 insertions(+), 36 deletions(-) create mode 100644 RiotSwiftUI/Modules/LocationSharing/StaticLocationSharingViewer/Service/MatrixSDK/StaticLocationSharingViewerService.swift create mode 100644 RiotSwiftUI/Modules/LocationSharing/StaticLocationSharingViewer/Service/Mock/MockStaticLocationSharingViewerService.swift create mode 100644 RiotSwiftUI/Modules/LocationSharing/StaticLocationSharingViewer/Service/StaticLocationSharingViewerServiceProtocol.swift create mode 100644 changelog.d/pr-7361.change diff --git a/Riot/Modules/LocationSharing/LocationManager.swift b/Riot/Modules/LocationSharing/LocationManager.swift index 857a59597..d51bdcac4 100644 --- a/Riot/Modules/LocationSharing/LocationManager.swift +++ b/Riot/Modules/LocationSharing/LocationManager.swift @@ -226,3 +226,19 @@ extension LocationManager: CLLocationManagerDelegate { MXLog.error("[LocationManager] Did failed", context: error) } } + +extension CLLocationManager { + func requestAuthorizationIfNeeded() -> Bool { + switch authorizationStatus { + case .notDetermined: + requestWhenInUseAuthorization() + return false + case .restricted, .denied: + return false + case .authorizedAlways, .authorizedWhenInUse, .authorized: + return true + @unknown default: + return false + } + } +} diff --git a/RiotSwiftUI/Modules/LocationSharing/LiveLocationSharingViewer/LiveLocationSharingViewerModels.swift b/RiotSwiftUI/Modules/LocationSharing/LiveLocationSharingViewer/LiveLocationSharingViewerModels.swift index 5c4f8a841..e6e93ccca 100644 --- a/RiotSwiftUI/Modules/LocationSharing/LiveLocationSharingViewer/LiveLocationSharingViewerModels.swift +++ b/RiotSwiftUI/Modules/LocationSharing/LiveLocationSharingViewer/LiveLocationSharingViewerModels.swift @@ -42,6 +42,12 @@ struct LiveLocationSharingViewerViewState: BindableState { /// Live location list items var listItemsViewData: [LiveLocationListItemViewData] + var showsUserLocation = false + + var isCurrentUserShared: Bool { + listItemsViewData.contains { $0.isCurrentUser } + } + var showLoadingIndicator = false var shareButtonEnabled: Bool { @@ -75,4 +81,5 @@ enum LiveLocationSharingViewerViewAction { case tapListItem(_ userId: String) case share(_ annotation: UserLocationAnnotation) case mapCreditsDidTap + case showUserLocation } diff --git a/RiotSwiftUI/Modules/LocationSharing/LiveLocationSharingViewer/LiveLocationSharingViewerViewModel.swift b/RiotSwiftUI/Modules/LocationSharing/LiveLocationSharingViewer/LiveLocationSharingViewerViewModel.swift index ebfd06220..6cc99e5a0 100644 --- a/RiotSwiftUI/Modules/LocationSharing/LiveLocationSharingViewer/LiveLocationSharingViewerViewModel.swift +++ b/RiotSwiftUI/Modules/LocationSharing/LiveLocationSharingViewer/LiveLocationSharingViewerViewModel.swift @@ -72,6 +72,8 @@ class LiveLocationSharingViewerViewModel: LiveLocationSharingViewerViewModelType completion?(.share(userLocationAnnotation.coordinate)) case .mapCreditsDidTap: state.bindings.showMapCreditsSheet.toggle() + case .showUserLocation: + showsCurrentUserLocation() } } @@ -229,4 +231,12 @@ class LiveLocationSharingViewerViewModel: LiveLocationSharingViewerViewModelType } } } + + private func showsCurrentUserLocation() { + if liveLocationSharingViewerService.requestAuthorizationIfNeeded() { + state.showsUserLocation = true + } else { + state.errorSubject.send(.invalidLocationAuthorization) + } + } } diff --git a/RiotSwiftUI/Modules/LocationSharing/LiveLocationSharingViewer/Service/LiveLocationSharingViewerServiceProtocol.swift b/RiotSwiftUI/Modules/LocationSharing/LiveLocationSharingViewer/Service/LiveLocationSharingViewerServiceProtocol.swift index cb9e107a5..864fa080f 100644 --- a/RiotSwiftUI/Modules/LocationSharing/LiveLocationSharingViewer/Service/LiveLocationSharingViewerServiceProtocol.swift +++ b/RiotSwiftUI/Modules/LocationSharing/LiveLocationSharingViewer/Service/LiveLocationSharingViewerServiceProtocol.swift @@ -33,4 +33,6 @@ protocol LiveLocationSharingViewerServiceProtocol { /// Stop current user location sharing func stopUserLiveLocationSharing(completion: @escaping (Result) -> Void) + + func requestAuthorizationIfNeeded() -> Bool } diff --git a/RiotSwiftUI/Modules/LocationSharing/LiveLocationSharingViewer/Service/MatrixSDK/LiveLocationSharingViewerService.swift b/RiotSwiftUI/Modules/LocationSharing/LiveLocationSharingViewer/Service/MatrixSDK/LiveLocationSharingViewerService.swift index 9b646a1bf..58c07c942 100644 --- a/RiotSwiftUI/Modules/LocationSharing/LiveLocationSharingViewer/Service/MatrixSDK/LiveLocationSharingViewerService.swift +++ b/RiotSwiftUI/Modules/LocationSharing/LiveLocationSharingViewer/Service/MatrixSDK/LiveLocationSharingViewerService.swift @@ -19,11 +19,13 @@ import Foundation import MatrixSDK class LiveLocationSharingViewerService: LiveLocationSharingViewerServiceProtocol { + // MARK: - Properties private(set) var usersLiveLocation: [UserLiveLocation] = [] private let roomId: String private var beaconInfoSummaryListener: Any? + private let locationManager = CLLocationManager() // MARK: Private @@ -74,6 +76,10 @@ class LiveLocationSharingViewerService: LiveLocationSharingViewerServiceProtocol } } + func requestAuthorizationIfNeeded() -> Bool { + locationManager.requestAuthorizationIfNeeded() + } + // MARK: - Private private func updateUsersLiveLocation(notifyUpdate: Bool) { diff --git a/RiotSwiftUI/Modules/LocationSharing/LiveLocationSharingViewer/Service/Mock/MockLiveLocationSharingViewerService.swift b/RiotSwiftUI/Modules/LocationSharing/LiveLocationSharingViewer/Service/Mock/MockLiveLocationSharingViewerService.swift index cfb34ef04..64b32dfb2 100644 --- a/RiotSwiftUI/Modules/LocationSharing/LiveLocationSharingViewer/Service/Mock/MockLiveLocationSharingViewerService.swift +++ b/RiotSwiftUI/Modules/LocationSharing/LiveLocationSharingViewer/Service/Mock/MockLiveLocationSharingViewerService.swift @@ -27,12 +27,17 @@ class MockLiveLocationSharingViewerService: LiveLocationSharingViewerServiceProt // MARK: Setup - init(generateRandomUsers: Bool = false) { - let firstUserLiveLocation = createFirstUserLiveLocation() + init(generateRandomUsers: Bool = false, currentUserSharingLocation: Bool = true) { + let firstUserLiveLocation: UserLiveLocation? + if currentUserSharingLocation { + firstUserLiveLocation = createFirstUserLiveLocation() + } else { + firstUserLiveLocation = nil + } let secondUserLiveLocation = createSecondUserLiveLocation() - var usersLiveLocation: [UserLiveLocation] = [firstUserLiveLocation, secondUserLiveLocation] + var usersLiveLocation: [UserLiveLocation] = [firstUserLiveLocation, secondUserLiveLocation].compactMap { $0 } if generateRandomUsers { for _ in 1...20 { @@ -56,6 +61,10 @@ class MockLiveLocationSharingViewerService: LiveLocationSharingViewerServiceProt func stopUserLiveLocationSharing(completion: @escaping (Result) -> Void) { } + func requestAuthorizationIfNeeded() -> Bool { + return true + } + // MARK: Private private func createFirstUserLiveLocation() -> UserLiveLocation { diff --git a/RiotSwiftUI/Modules/LocationSharing/LiveLocationSharingViewer/Test/Unit/LiveLocationSharingViewerViewModelTests.swift b/RiotSwiftUI/Modules/LocationSharing/LiveLocationSharingViewer/Test/Unit/LiveLocationSharingViewerViewModelTests.swift index cefe1c2ed..37ee71a61 100644 --- a/RiotSwiftUI/Modules/LocationSharing/LiveLocationSharingViewer/Test/Unit/LiveLocationSharingViewerViewModelTests.swift +++ b/RiotSwiftUI/Modules/LocationSharing/LiveLocationSharingViewer/Test/Unit/LiveLocationSharingViewerViewModelTests.swift @@ -15,6 +15,7 @@ // import Combine +import CoreLocation import XCTest @testable import RiotSwiftUI @@ -30,4 +31,17 @@ class LiveLocationSharingViewerViewModelTests: XCTestCase { viewModel = LiveLocationSharingViewerViewModel(mapStyleURL: BuildSettings.defaultTileServerMapStyleURL, service: service) context = viewModel.context } + + func testIsUserBeingShared() { + XCTAssertTrue(context.viewState.isCurrentUserShared) + } + + func testToggleShowUserLocation() { + let service = MockLiveLocationSharingViewerService(currentUserSharingLocation: false) + let viewModel = LiveLocationSharingViewerViewModel(mapStyleURL: BuildSettings.defaultTileServerMapStyleURL, service: service) + XCTAssertFalse(viewModel.context.viewState.isCurrentUserShared) + XCTAssertFalse(viewModel.context.viewState.showsUserLocation) + viewModel.context.send(viewAction: .showUserLocation) + XCTAssertTrue(viewModel.context.viewState.showsUserLocation) + } } diff --git a/RiotSwiftUI/Modules/LocationSharing/LiveLocationSharingViewer/View/LiveLocationSharingViewer.swift b/RiotSwiftUI/Modules/LocationSharing/LiveLocationSharingViewer/View/LiveLocationSharingViewer.swift index 9678028b8..2e1e1c012 100644 --- a/RiotSwiftUI/Modules/LocationSharing/LiveLocationSharingViewer/View/LiveLocationSharingViewer.swift +++ b/RiotSwiftUI/Modules/LocationSharing/LiveLocationSharingViewer/View/LiveLocationSharingViewer.swift @@ -34,23 +34,35 @@ struct LiveLocationSharingViewer: View { @ObservedObject var viewModel: LiveLocationSharingViewerViewModel.Context + var mapView: LocationSharingMapView { + LocationSharingMapView(tileServerMapURL: viewModel.viewState.mapStyleURL, + annotations: viewModel.viewState.annotations, + highlightedAnnotation: viewModel.viewState.highlightedAnnotation, + userAvatarData: nil, + showsUserLocation: viewModel.viewState.showsUserLocation, + userAnnotationCanShowCallout: true, + userLocation: Binding.constant(nil), + mapCenterCoordinate: Binding.constant(nil), + onCalloutTap: { annotation in + if let userLocationAnnotation = annotation as? UserLocationAnnotation { + viewModel.send(viewAction: .share(userLocationAnnotation)) + } + }, + errorSubject: viewModel.viewState.errorSubject) + } + var body: some View { ZStack(alignment: .bottom) { if !viewModel.viewState.showMapLoadingError { - LocationSharingMapView(tileServerMapURL: viewModel.viewState.mapStyleURL, - annotations: viewModel.viewState.annotations, - highlightedAnnotation: viewModel.viewState.highlightedAnnotation, - userAvatarData: nil, - showsUserLocation: false, - userAnnotationCanShowCallout: true, - userLocation: Binding.constant(nil), - mapCenterCoordinate: Binding.constant(nil), - onCalloutTap: { annotation in - if let userLocationAnnotation = annotation as? UserLocationAnnotation { - viewModel.send(viewAction: .share(userLocationAnnotation)) - } - }, - errorSubject: viewModel.viewState.errorSubject) + + if !viewModel.viewState.isCurrentUserShared { + mapView + .overlay(CenterToUserLocationButton(action: { + viewModel.send(viewAction: .showUserLocation) + }).offset(x: -11.0, y: 52), alignment: .topTrailing) + } else { + mapView + } // Show map credits above collapsed bottom sheet height if bottom sheet is visible if viewModel.viewState.isBottomSheetVisible { @@ -178,3 +190,27 @@ struct LiveLocationSharingViewer_Previews: PreviewProvider { } } } + +struct CenterToUserLocationButton: View { + + // MARK: Private + + @Environment(\.theme) private var theme: ThemeSwiftUI + + // MARK: Public + + var action: () -> Void + + var body: some View { + Button { + action() + } label: { + Image(uiImage: Asset.Images.locationCenterMapIcon.image) + .foregroundColor(theme.colors.accent) + } + .padding(8.0) + .background(theme.colors.background) + .clipShape(Circle()) + .shadow(radius: 2.0) + } +} diff --git a/RiotSwiftUI/Modules/LocationSharing/MapError/MapViewErrorAlertInfoBuilder.swift b/RiotSwiftUI/Modules/LocationSharing/MapError/MapViewErrorAlertInfoBuilder.swift index fa02eef50..fe7ad1521 100644 --- a/RiotSwiftUI/Modules/LocationSharing/MapError/MapViewErrorAlertInfoBuilder.swift +++ b/RiotSwiftUI/Modules/LocationSharing/MapError/MapViewErrorAlertInfoBuilder.swift @@ -32,7 +32,7 @@ struct MapViewErrorAlertInfoBuilder { case .invalidLocationAuthorization: alertInfo = AlertInfo(id: .authorizationError, title: VectorL10n.locationSharingInvalidAuthorizationErrorTitle(AppInfo.current.displayName), - primaryButton: (VectorL10n.locationSharingInvalidAuthorizationNotNow, primaryButtonCompletion), + primaryButton: (VectorL10n.locationSharingInvalidAuthorizationNotNow, {}), secondaryButton: (VectorL10n.locationSharingInvalidAuthorizationSettings, primaryButtonCompletion)) default: alertInfo = nil diff --git a/RiotSwiftUI/Modules/LocationSharing/MapView/View/LocationSharingMapView.swift b/RiotSwiftUI/Modules/LocationSharing/MapView/View/LocationSharingMapView.swift index 9b5073b33..31165396d 100644 --- a/RiotSwiftUI/Modules/LocationSharing/MapView/View/LocationSharingMapView.swift +++ b/RiotSwiftUI/Modules/LocationSharing/MapView/View/LocationSharingMapView.swift @@ -75,7 +75,7 @@ struct LocationSharingMapView: UIViewRepresentable { mapView.vc_removeAllAnnotations() mapView.addAnnotations(annotations) - if let highlightedAnnotation = highlightedAnnotation { + if let highlightedAnnotation = highlightedAnnotation, !showsUserLocation { mapView.setCenter(highlightedAnnotation.coordinate, zoomLevel: Constants.mapZoomLevel, animated: false) } @@ -125,11 +125,14 @@ extension LocationSharingMapView { return LocationAnnotationView(userLocationAnnotation: userLocationAnnotation) } else if let pinLocationAnnotation = annotation as? PinLocationAnnotation { return LocationAnnotationView(pinLocationAnnotation: pinLocationAnnotation) - } else if annotation is MGLUserLocation, let currentUserAvatarData = locationSharingMapView.userAvatarData { - // Replace default current location annotation view with a UserLocationAnnotatonView when the map is center on user location - return LocationAnnotationView(avatarData: currentUserAvatarData) + } else if annotation is MGLUserLocation { + if let currentUserAvatarData = locationSharingMapView.userAvatarData { + // Replace default current location annotation view with a UserLocationAnnotatonView when the map is center on user location + return LocationAnnotationView(avatarData: currentUserAvatarData) + } else { + return LocationAnnotationView(userPinLocationAnnotation: annotation) + } } - return nil } diff --git a/RiotSwiftUI/Modules/LocationSharing/MapView/View/UserLocationAnnotationView.swift b/RiotSwiftUI/Modules/LocationSharing/MapView/View/UserLocationAnnotationView.swift index 25a5b9848..0770f9b2c 100644 --- a/RiotSwiftUI/Modules/LocationSharing/MapView/View/UserLocationAnnotationView.swift +++ b/RiotSwiftUI/Modules/LocationSharing/MapView/View/UserLocationAnnotationView.swift @@ -48,7 +48,11 @@ class LocationAnnotationView: MGLUserLocationAnnotationView { addUserMarkerView(with: userLocationAnnotation.avatarData) } - + convenience init(userPinLocationAnnotation: MGLAnnotation) { + self.init(annotation: userPinLocationAnnotation, reuseIdentifier: "userPinLocation") + + addPinView() + } convenience init(pinLocationAnnotation: PinLocationAnnotation) { // TODO: Use a reuseIdentifier self.init(annotation: pinLocationAnnotation, reuseIdentifier: nil) @@ -74,6 +78,16 @@ class LocationAnnotationView: MGLUserLocationAnnotationView { addMarkerView(avatarMarkerView) } + private func addPinView() { + guard let pinView = UIHostingController(rootView: Image(uiImage: Asset.Images.locationMarkerIcon.image) + .resizable() + .foregroundColor(theme.colors.accent)).view else { + return + } + + addMarkerView(pinView) + } + private func addPinMarkerView() { guard let pinMarkerView = UIHostingController(rootView: LocationSharingMarkerView(backgroundColor: theme.colors.accent) { Image(uiImage: Asset.Images.locationPinIcon.image) diff --git a/RiotSwiftUI/Modules/LocationSharing/StaticLocationSharingViewer/Coordinator/StaticLocationViewingCoordinator.swift b/RiotSwiftUI/Modules/LocationSharing/StaticLocationSharingViewer/Coordinator/StaticLocationViewingCoordinator.swift index bf938dac8..09ed605df 100644 --- a/RiotSwiftUI/Modules/LocationSharing/StaticLocationSharingViewer/Coordinator/StaticLocationViewingCoordinator.swift +++ b/RiotSwiftUI/Modules/LocationSharing/StaticLocationSharingViewer/Coordinator/StaticLocationViewingCoordinator.swift @@ -53,7 +53,8 @@ final class StaticLocationViewingCoordinator: Coordinator, Presentable { mapStyleURL: parameters.session.vc_homeserverConfiguration().tileServer.mapStyleURL, avatarData: parameters.avatarData, location: parameters.location, - coordinateType: parameters.coordinateType + coordinateType: parameters.coordinateType, + service: StaticLocationSharingViewerService() ) let view = StaticLocationView(viewModel: viewModel.context) .environmentObject(AvatarViewModel(avatarService: AvatarService(mediaManager: parameters.mediaManager))) diff --git a/RiotSwiftUI/Modules/LocationSharing/StaticLocationSharingViewer/MockStaticLocationViewingScreenState.swift b/RiotSwiftUI/Modules/LocationSharing/StaticLocationSharingViewer/MockStaticLocationViewingScreenState.swift index 4430c36c2..d2d1ecdf7 100644 --- a/RiotSwiftUI/Modules/LocationSharing/StaticLocationSharingViewer/MockStaticLocationViewingScreenState.swift +++ b/RiotSwiftUI/Modules/LocationSharing/StaticLocationSharingViewer/MockStaticLocationViewingScreenState.swift @@ -46,7 +46,8 @@ enum MockStaticLocationViewingScreenState: MockScreenState, CaseIterable { let viewModel = StaticLocationViewingViewModel(mapStyleURL: mapStyleURL, avatarData: AvatarInput(mxContentUri: "", matrixItemId: "alice:matrix.org", displayName: "Alice"), location: location, - coordinateType: coordinateType) + coordinateType: coordinateType, + service: MockStaticLocationSharingViewerService()) return ([viewModel], AnyView(StaticLocationView(viewModel: viewModel.context) diff --git a/RiotSwiftUI/Modules/LocationSharing/StaticLocationSharingViewer/Service/MatrixSDK/StaticLocationSharingViewerService.swift b/RiotSwiftUI/Modules/LocationSharing/StaticLocationSharingViewer/Service/MatrixSDK/StaticLocationSharingViewerService.swift new file mode 100644 index 000000000..62be10936 --- /dev/null +++ b/RiotSwiftUI/Modules/LocationSharing/StaticLocationSharingViewer/Service/MatrixSDK/StaticLocationSharingViewerService.swift @@ -0,0 +1,32 @@ +// +// Copyright 2023 New Vector Ltd +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import CoreLocation +import Foundation + +class StaticLocationSharingViewerService: StaticLocationSharingViewerServiceProtocol { + + // MARK: Private + + private let locationManager = CLLocationManager() + + // MARK: Public + + func requestAuthorizationIfNeeded() -> Bool { + locationManager.requestAuthorizationIfNeeded() + } +} + diff --git a/RiotSwiftUI/Modules/LocationSharing/StaticLocationSharingViewer/Service/Mock/MockStaticLocationSharingViewerService.swift b/RiotSwiftUI/Modules/LocationSharing/StaticLocationSharingViewer/Service/Mock/MockStaticLocationSharingViewerService.swift new file mode 100644 index 000000000..e792b8a73 --- /dev/null +++ b/RiotSwiftUI/Modules/LocationSharing/StaticLocationSharingViewer/Service/Mock/MockStaticLocationSharingViewerService.swift @@ -0,0 +1,25 @@ +// +// Copyright 2023 New Vector Ltd +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import Foundation + +class MockStaticLocationSharingViewerService: StaticLocationSharingViewerServiceProtocol { + + func requestAuthorizationIfNeeded() -> Bool { + return true + } + +} diff --git a/RiotSwiftUI/Modules/LocationSharing/StaticLocationSharingViewer/Service/StaticLocationSharingViewerServiceProtocol.swift b/RiotSwiftUI/Modules/LocationSharing/StaticLocationSharingViewer/Service/StaticLocationSharingViewerServiceProtocol.swift new file mode 100644 index 000000000..06a99e1bf --- /dev/null +++ b/RiotSwiftUI/Modules/LocationSharing/StaticLocationSharingViewer/Service/StaticLocationSharingViewerServiceProtocol.swift @@ -0,0 +1,22 @@ +// +// Copyright 2023 New Vector Ltd +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import Foundation + +protocol StaticLocationSharingViewerServiceProtocol { + + func requestAuthorizationIfNeeded() -> Bool +} diff --git a/RiotSwiftUI/Modules/LocationSharing/StaticLocationSharingViewer/StaticLocationViewingModels.swift b/RiotSwiftUI/Modules/LocationSharing/StaticLocationSharingViewer/StaticLocationViewingModels.swift index f25b16e48..0789b3ede 100644 --- a/RiotSwiftUI/Modules/LocationSharing/StaticLocationSharingViewer/StaticLocationViewingModels.swift +++ b/RiotSwiftUI/Modules/LocationSharing/StaticLocationSharingViewer/StaticLocationViewingModels.swift @@ -23,6 +23,7 @@ import Foundation enum StaticLocationViewingViewAction { case close case share + case showUserLocation } enum StaticLocationViewingViewModelResult { @@ -42,6 +43,8 @@ struct StaticLocationViewingViewState: BindableState { /// Shared annotation to display existing location let sharedAnnotation: LocationAnnotation + var showsUserLocation = false + var showLoadingIndicator = false var shareButtonEnabled: Bool { diff --git a/RiotSwiftUI/Modules/LocationSharing/StaticLocationSharingViewer/StaticLocationViewingViewModel.swift b/RiotSwiftUI/Modules/LocationSharing/StaticLocationSharingViewer/StaticLocationViewingViewModel.swift index 83bdb0110..d597771bc 100644 --- a/RiotSwiftUI/Modules/LocationSharing/StaticLocationSharingViewer/StaticLocationViewingViewModel.swift +++ b/RiotSwiftUI/Modules/LocationSharing/StaticLocationSharingViewer/StaticLocationViewingViewModel.swift @@ -24,6 +24,7 @@ class StaticLocationViewingViewModel: StaticLocationViewingViewModelType, Static // MARK: Private + private var staticLocationSharingViewerService: StaticLocationSharingViewerServiceProtocol private var mapViewErrorAlertInfoBuilder: MapViewErrorAlertInfoBuilder // MARK: Public @@ -32,7 +33,10 @@ class StaticLocationViewingViewModel: StaticLocationViewingViewModelType, Static // MARK: - Setup - init(mapStyleURL: URL, avatarData: AvatarInputProtocol, location: CLLocationCoordinate2D, coordinateType: LocationSharingCoordinateType) { + init(mapStyleURL: URL, avatarData: AvatarInputProtocol, location: CLLocationCoordinate2D, coordinateType: LocationSharingCoordinateType, service: StaticLocationSharingViewerServiceProtocol) { + + staticLocationSharingViewerService = service + let sharedAnnotation: LocationAnnotation switch coordinateType { case .user: @@ -63,6 +67,8 @@ class StaticLocationViewingViewModel: StaticLocationViewingViewModelType, Static completion?(.close) case .share: completion?(.share(state.sharedAnnotation.coordinate)) + case .showUserLocation: + showsCurrentUserLocation() } } @@ -89,4 +95,12 @@ class StaticLocationViewingViewModel: StaticLocationViewingViewModelType, Static state.bindings.alertInfo = alertInfo } + + private func showsCurrentUserLocation() { + if staticLocationSharingViewerService.requestAuthorizationIfNeeded() { + state.showsUserLocation = true + } else { + state.errorSubject.send(.invalidLocationAuthorization) + } + } } diff --git a/RiotSwiftUI/Modules/LocationSharing/StaticLocationSharingViewer/Test/Unit/StaticLocationViewingViewModelTests.swift b/RiotSwiftUI/Modules/LocationSharing/StaticLocationSharingViewer/Test/Unit/StaticLocationViewingViewModelTests.swift index f9aa24324..8350975ef 100644 --- a/RiotSwiftUI/Modules/LocationSharing/StaticLocationSharingViewer/Test/Unit/StaticLocationViewingViewModelTests.swift +++ b/RiotSwiftUI/Modules/LocationSharing/StaticLocationSharingViewer/Test/Unit/StaticLocationViewingViewModelTests.swift @@ -79,10 +79,18 @@ class StaticLocationViewingViewModelTests: XCTestCase { waitForExpectations(timeout: 3) } + func testToggleShowUserLocation() { + let viewModel = buildViewModel() + XCTAssertFalse(viewModel.context.viewState.showsUserLocation) + viewModel.context.send(viewAction: .showUserLocation) + XCTAssertTrue(viewModel.context.viewState.showsUserLocation) + } + private func buildViewModel() -> StaticLocationViewingViewModel { StaticLocationViewingViewModel(mapStyleURL: URL(string: "http://empty.com")!, avatarData: AvatarInput(mxContentUri: "", matrixItemId: "", displayName: ""), location: CLLocationCoordinate2D(latitude: 51.4932641, longitude: -0.257096), - coordinateType: .user) + coordinateType: .user, + service: MockStaticLocationSharingViewerService()) } } diff --git a/RiotSwiftUI/Modules/LocationSharing/StaticLocationSharingViewer/View/StaticLocationView.swift b/RiotSwiftUI/Modules/LocationSharing/StaticLocationSharingViewer/View/StaticLocationView.swift index 3b12fa00c..30144268e 100644 --- a/RiotSwiftUI/Modules/LocationSharing/StaticLocationSharingViewer/View/StaticLocationView.swift +++ b/RiotSwiftUI/Modules/LocationSharing/StaticLocationSharingViewer/View/StaticLocationView.swift @@ -29,19 +29,26 @@ struct StaticLocationView: View { // MARK: Views + var mapView: LocationSharingMapView { + LocationSharingMapView(tileServerMapURL: viewModel.viewState.mapStyleURL, + annotations: [viewModel.viewState.sharedAnnotation], + highlightedAnnotation: viewModel.viewState.sharedAnnotation, + userAvatarData: nil, + showsUserLocation: viewModel.viewState.showsUserLocation, + userLocation: Binding.constant(nil), + mapCenterCoordinate: Binding.constant(nil), + errorSubject: viewModel.viewState.errorSubject) + } + var body: some View { NavigationView { ZStack(alignment: .bottom) { - LocationSharingMapView(tileServerMapURL: viewModel.viewState.mapStyleURL, - annotations: [viewModel.viewState.sharedAnnotation], - highlightedAnnotation: viewModel.viewState.sharedAnnotation, - userAvatarData: viewModel.viewState.userAvatarData, - showsUserLocation: false, - userLocation: Binding.constant(nil), - mapCenterCoordinate: Binding.constant(nil), - errorSubject: viewModel.viewState.errorSubject) + mapView MapCreditsView() } + .overlay(CenterToUserLocationButton(action: { + viewModel.send(viewAction: .showUserLocation) + }).offset(x: -11.0, y: 52), alignment: .topTrailing) .ignoresSafeArea(.all, edges: [.bottom]) .toolbar { ToolbarItem(placement: .navigationBarLeading) { diff --git a/changelog.d/pr-7361.change b/changelog.d/pr-7361.change new file mode 100644 index 000000000..d5927663d --- /dev/null +++ b/changelog.d/pr-7361.change @@ -0,0 +1 @@ +Map Views: Show own location in map views \ No newline at end of file From 9d8e40c34edf0ebae364a2a2e182500b9de1b256 Mon Sep 17 00:00:00 2001 From: Shahab Eskandary Date: Wed, 15 Feb 2023 05:39:59 +0000 Subject: [PATCH 19/45] Translated using Weblate (Persian) Currently translated at 100.0% (8 of 8 strings) Translation: Element iOS/Element iOS (Dialogs) Translate-URL: https://translate.element.io/projects/riot-ios/riot-ios-dialogs/fa/ --- Riot/Assets/fa.lproj/InfoPlist.strings | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/Riot/Assets/fa.lproj/InfoPlist.strings b/Riot/Assets/fa.lproj/InfoPlist.strings index 8b1378917..c53594b36 100644 --- a/Riot/Assets/fa.lproj/InfoPlist.strings +++ b/Riot/Assets/fa.lproj/InfoPlist.strings @@ -1 +1,11 @@ + +"NSLocationAlwaysAndWhenInUseUsageDescription" = "زمانی که شما مکان خود را با دیگران به اشتراک میگذارید، المنت برای نمایش مکانتان به آنها، به نقشه نیاز دارد."; +"NSLocationWhenInUseUsageDescription" = "زمانی که شما مکان خود را با دیگران به اشتراک میگذارید، المنت برای نمایش مکانتان به آنها، به نقشه نیاز دارد."; +"NSFaceIDUsageDescription" = "برای دسترسی به برنامه تان، از face Id استفاده میشود."; +"NSCalendarsUsageDescription" = "ملاقات های برنامه ریزی شده خود را در برنامه ببینید."; +"NSContactsUsageDescription" = "برای یافتن مخاطبانتان در ماتریکس، اینها را با سرور هویت شما به اشتراک خواهیم گذاشت."; +"NSMicrophoneUsageDescription" = "المنت برای ضبط صدا، فیلم برداری و ارسال پیام صوتی، دسترسی به میکروفون را نیاز دارد."; +"NSPhotoLibraryUsageDescription" = "برای انتخاب و آپلود تصاویر و ویدیو ها از گالری خود، اجازه دسترسی به گالری را بدهید."; +// Permissions usage explanations +"NSCameraUsageDescription" = "دوربین برای فیلم و تصویر برداری و آپلود آنها استفاده میشود."; From c7fc0fdda4ecb38472a68535f0007176d151e248 Mon Sep 17 00:00:00 2001 From: Szimszon Date: Tue, 7 Feb 2023 12:19:22 +0000 Subject: [PATCH 20/45] Translated using Weblate (Hungarian) Currently translated at 100.0% (2378 of 2378 strings) Translation: Element iOS/Element iOS Translate-URL: https://translate.element.io/projects/riot-ios/riot-ios/hu/ --- Riot/Assets/hu.lproj/Vector.strings | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/Riot/Assets/hu.lproj/Vector.strings b/Riot/Assets/hu.lproj/Vector.strings index a10998eb7..3b706c205 100644 --- a/Riot/Assets/hu.lproj/Vector.strings +++ b/Riot/Assets/hu.lproj/Vector.strings @@ -2710,10 +2710,14 @@ // MARK: - Launch loading "launch_loading_migrating_data" = "Adatok migrálása\n%@ %%"; -"settings_labs_disable_crypto_sdk" = "Végpontok közötti titkosítás 2.0 (kikapcsoláshoz kijelentkezés szükséges)"; -"settings_labs_confirm_crypto_sdk" = "Ezzel az opcióval egy gyorsabb és megbízhatóbb végponttól végponting titkosító motor kerül engedélyezésre ami Rustban lett megírva. Bekapcsolás után a kikapcsolásához ki kell jelentkezni. Folytatod?"; -"settings_labs_enable_crypto_sdk" = "Az új Rust alapú Titkosítási SDK engedélyezése"; +"settings_labs_disable_crypto_sdk" = "Rust végpontok közötti titkosítás (kikapcsoláshoz kijelentkezés szükséges)"; +"settings_labs_confirm_crypto_sdk" = "Ez a funkció még kísérleti fázisban van. Lehet, hogy nem az elvártnak megfelelően fog működni és előre nem látható következménye lehet. A funkció kikapcsolásához egyszerű ki-, és bejelentkezés szükséges. Használata csak saját felelősségre."; +"settings_labs_enable_crypto_sdk" = "Rust végpontok közötti titkosítás"; "home_context_menu_mark_as_unread" = "Olvasatlannak jelöl"; "poll_history_fetching_error" = "Szavazás betöltési hiba."; "voice_broadcast_playback_unable_to_decrypt" = "A hang közvetítés nem fejthető vissza."; "key_backup_recover_from_private_key_progress" = "%@%% kész"; +"wysiwyg_composer_format_action_un_indent" = "Behúzás csökkentése"; +"wysiwyg_composer_format_action_indent" = "Behúzás növelése"; +"poll_history_detail_view_in_timeline" = "Szavazás megjelenítése az idővonalon"; +"settings_push_rules_error" = "Hiba történt az értesítések beállításának frissítésekor. Próbáld meg az beállítást újra átkapcsolni."; From 86a8ddb689447c0b9e5f4f83cfceb02125af7511 Mon Sep 17 00:00:00 2001 From: Suguru Hirahara Date: Thu, 9 Feb 2023 10:51:24 +0000 Subject: [PATCH 21/45] Translated using Weblate (Japanese) Currently translated at 100.0% (2378 of 2378 strings) Translation: Element iOS/Element iOS Translate-URL: https://translate.element.io/projects/riot-ios/riot-ios/ja/ --- Riot/Assets/ja.lproj/Vector.strings | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/Riot/Assets/ja.lproj/Vector.strings b/Riot/Assets/ja.lproj/Vector.strings index 30c415fd8..a20a3b716 100644 --- a/Riot/Assets/ja.lproj/Vector.strings +++ b/Riot/Assets/ja.lproj/Vector.strings @@ -287,7 +287,7 @@ "settings_global_settings_info" = "全体の通知設定は %@ webクライアントで行えます"; "settings_pin_rooms_with_missed_notif" = "逃した通知があるルームをピン止め"; "settings_ui_language" = "言語"; -"settings_ui_theme" = "外観"; +"settings_ui_theme" = "テーマ"; "settings_ui_theme_auto" = "自動"; "settings_ui_theme_light" = "ライト"; "settings_ui_theme_dark" = "ダーク"; @@ -444,9 +444,9 @@ "widget_integration_need_to_be_able_to_invite" = "それを行うにはユーザーを招待する権限が必要です。"; "widget_integration_unable_to_create" = "ウィジェットを作成できません。"; "widget_integration_failed_to_send_request" = "リクエストの送信に失敗しました。"; -"widget_integration_room_not_recognised" = "このルームでは認められません。"; -"widget_integration_positive_power_level" = "権限の数値は正の整数で入力してください。"; -"widget_integration_must_be_in_room" = "あなたはこのルームに所属していません。"; +"widget_integration_room_not_recognised" = "このルームは認識されていません。"; +"widget_integration_positive_power_level" = "権限レベルは正の整数でなければなりません。"; +"widget_integration_must_be_in_room" = "あなたはこのルームのメンバーではありません。"; "widget_integration_no_permission_in_room" = "このルームでそれを行う権限がありません。"; "widget_integration_missing_room_id" = "リクエストにroom_idがありません。"; "widget_integration_missing_user_id" = "リクエストにuser_idがありません。"; @@ -641,7 +641,7 @@ "e2e_key_backup_wrong_version_title" = "新しい鍵のバックアップ"; "call_no_stun_server_error_use_fallback_button" = "%@を使ってみてください"; "call_actions_unhold" = "再開"; -"call_no_stun_server_error_message_2" = "または %@ の公開サーバーを使用することもできますが、信頼性が低く、また、あなたのIPアドレスがそのサーバーと共有されてしまいます。これは設定画面からも管理できます"; +"call_no_stun_server_error_message_2" = "公開サーバー %@ を使用することもできますが、信頼性は低く、また、サーバーとIPアドレスが共有されます。これは設定画面からも管理できます"; "call_no_stun_server_error_message_1" = "安定した通話のために、ホームサーバー %@ の管理者にTURNサーバーの設定を依頼してください。"; "call_no_stun_server_error_title" = "サーバーの不正な設定のため通話に失敗しました"; "room_does_not_exist" = "%@は存在しません"; @@ -1188,7 +1188,7 @@ "login_error_resource_limit_exceeded_title" = "リソース制限を超えました"; "login_error_resource_limit_exceeded_message_default" = "このホームサーバーはリソースの上限に達しました。"; "login_error_resource_limit_exceeded_message_monthly_active_user" = "このホームサーバーは月間アクティブユーザー数の上限に達しました 。"; -"login_error_resource_limit_exceeded_message_contact" = "\n\nこのサービスを続行するには、サービス管理者に連絡してください。"; +"login_error_resource_limit_exceeded_message_contact" = "\n\nこのサービスを引き続き使用するには、サービス管理者にお問い合わせください。"; "login_error_resource_limit_exceeded_contact_button" = "管理者に連絡"; "abort" = "中断"; "discard" = "破棄"; @@ -1758,7 +1758,7 @@ "room_details_notifs" = "通知"; "location_sharing_invalid_power_level_title" = "位置情報(ライブ)の共有に必要な権限がありません"; "settings_labs_enable_live_location_sharing" = "位置情報(ライブ)の共有 - 現在の位置情報を共有(開発中の機能。位置情報が一時的にルームの履歴に残ります)"; -"event_formatter_message_deleted" = "削除済みのメッセージ"; +"event_formatter_message_deleted" = "メッセージが削除されました"; "home_context_menu_unfavourite" = "お気に入りから削除"; "home_context_menu_favourite" = "お気に入り"; "all_chats_user_menu_settings" = "ユーザー設定"; @@ -1852,7 +1852,7 @@ "settings_labs_enable_new_app_layout" = "アプリケーションの新しいレイアウト"; "settings_labs_enable_new_client_info_feature" = "クライアントの名称、バージョン、URLを記録し、セッションマネージャーでより容易にセッションを認識できるよう設定"; "settings_labs_enable_new_session_manager" = "新しいセッションマネージャー"; -"settings_labs_use_only_latest_user_avatar_and_name" = "ユーザーのアバターと名前をメッセージの履歴に表示"; +"settings_labs_use_only_latest_user_avatar_and_name" = "ユーザーの最新のアバターと名前をメッセージの履歴に表示"; "settings_labs_enable_threads" = "メッセージのスレッド機能"; "settings_labs_enabled_polls" = "アンケート"; "settings_ui_show_redactions_in_room_history" = "削除されたメッセージに関する通知を表示"; From dd83d35fc1919e0461b006b23335a417b183f716 Mon Sep 17 00:00:00 2001 From: Suguru Hirahara Date: Fri, 10 Feb 2023 09:11:04 +0000 Subject: [PATCH 22/45] Translated using Weblate (Japanese) Currently translated at 100.0% (2378 of 2378 strings) Translation: Element iOS/Element iOS Translate-URL: https://translate.element.io/projects/riot-ios/riot-ios/ja/ --- Riot/Assets/ja.lproj/Vector.strings | 30 ++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/Riot/Assets/ja.lproj/Vector.strings b/Riot/Assets/ja.lproj/Vector.strings index a20a3b716..d6ad03ba9 100644 --- a/Riot/Assets/ja.lproj/Vector.strings +++ b/Riot/Assets/ja.lproj/Vector.strings @@ -147,8 +147,8 @@ // Chat participants "room_participants_title" = "参加者"; "room_participants_add_participant" = "参加者を追加"; -"room_participants_one_participant" = "参加者1名"; -"room_participants_multi_participants" = "参加者%d名"; +"room_participants_one_participant" = "1人の参加者"; +"room_participants_multi_participants" = "%d人の参加者"; "room_participants_leave_prompt_title" = "ルームから退出"; "room_participants_leave_prompt_msg" = "ルームから退出してよろしいですか?"; "room_participants_remove_prompt_title" = "確認"; @@ -164,7 +164,7 @@ "room_participants_online" = "オンライン"; "room_participants_offline" = "オフライン"; "room_participants_unknown" = "不明"; -"room_participants_idle" = "アイドル"; +"room_participants_idle" = "待機中"; "room_participants_now" = "現在"; "room_participants_ago" = "前"; "room_participants_action_section_admin_tools" = "管理者ツール"; @@ -530,7 +530,7 @@ // GDPR "gdpr_consent_not_given_alert_message" = "%@のホームサーバーを引き続き使用するには、利用規約を確認して同意する必要があります。"; "gdpr_consent_not_given_alert_review_now_action" = "確認"; -"deactivate_account_title" = "アカウントを無効化"; +"deactivate_account_title" = "アカウントの無効化"; "deactivate_account_informations_part1" = "この操作により、あなたのアカウントは永久に使えなくなります。ログインしたり同じユーザーIDを再登録したりすることはできなくなります。あなたのアカウントは参加している全てのルームから退出し、あなたのIDサーバーからアカウントの詳細が削除されます。 "; "deactivate_account_informations_part2_emphasize" = "この操作は取り消せません。"; "deactivate_account_informations_part3" = "\n\nアカウントを無効化しても、 "; @@ -540,7 +540,7 @@ "deactivate_account_forget_messages_information_part2_emphasize" = "警告"; "deactivate_account_forget_messages_information_part3" = ":今後のユーザーには、不完全な会話が表示されます)"; "deactivate_account_validate_action" = "アカウントを無効化"; -"deactivate_account_password_alert_title" = "アカウントを無効化"; +"deactivate_account_password_alert_title" = "アカウントの無効化"; "deactivate_account_password_alert_message" = "続行するには、Matrixのアカウントのパスワードを入力してください"; // Re-request confirmation dialog "rerequest_keys_alert_title" = "要求を送信しました"; @@ -683,14 +683,14 @@ "identity_server_settings_alert_error_terms_not_accepted" = "IDサーバーに設定するには、%@の利用規約を承諾する必要があります。"; "identity_server_settings_alert_disconnect_still_sharing_3pid_button" = "無視して接続解除"; "identity_server_settings_alert_disconnect_still_sharing_3pid" = "あなたはまだIDサーバー %@ で個人データを共有しています。\n\n接続を解除する前に、メールアドレスと電話番号をIDサーバーから削除することをお勧めします。"; -"identity_server_settings_alert_disconnect_button" = "接続を解除"; +"identity_server_settings_alert_disconnect_button" = "切断"; "identity_server_settings_alert_disconnect" = "IDサーバー %@ から切断しますか?"; -"identity_server_settings_alert_disconnect_title" = "IDサーバーから接続を解除"; -"identity_server_settings_alert_change" = "IDサーバー %1$@ を切断し、代わりに %2$@ に接続しますか?"; +"identity_server_settings_alert_disconnect_title" = "IDサーバーから切断"; +"identity_server_settings_alert_change" = "IDサーバー %1$@ から切断して %2$@ に接続しますか?"; "identity_server_settings_alert_change_title" = "IDサーバーを変更"; "identity_server_settings_alert_no_terms" = "選択したIDサーバーには利用規約がありません。そのサーバーの所有者を信頼できる場合にのみ続行してください。"; "identity_server_settings_alert_no_terms_title" = "IDサーバーには利用規約がありません"; -"identity_server_settings_disconnect" = "接続を解除"; +"identity_server_settings_disconnect" = "切断"; "identity_server_settings_disconnect_info" = "IDサーバーとの接続を解除すると、他のユーザーによって見つけられなくなり、また、メールアドレスや電話で他のユーザーを招待することもできなくなります。"; "identity_server_settings_change" = "変更"; "identity_server_settings_add" = "追加"; @@ -705,7 +705,7 @@ "settings_key_backup_info_valid" = "このセッションは鍵をバックアップしています。"; "settings_key_backup_info_algorithm" = "アルゴリズム:%@"; "settings_key_backup_info_version" = "鍵のバックアップのバージョン:%@"; -"settings_key_backup_info_none" = "あなたの鍵は、このセッションからバックアップされていません。"; +"settings_key_backup_info_none" = "このセッションでは鍵がバックアップされていません。"; "settings_key_backup_info_checking" = "確認しています…"; "settings_add_3pid_password_message" = "続行するには、Matrixのアカウントのパスワードを入力してください"; "settings_add_3pid_invalid_password_message" = "認証情報が正しくありません"; @@ -844,7 +844,7 @@ "settings_discovery_three_pids_management_information_part3" = "。"; "settings_discovery_three_pids_management_information_part2" = "ユーザー設定"; "settings_discovery_three_pids_management_information_part1" = "他のユーザーがあなたを発見したり、ルームに招待する際に使用するメールアドレスや電話番号を管理できます。このリストに、メールアドレスや電話番号を追加したり、削除したりすることができます。 "; -"settings_discovery_terms_not_signed" = "メールアドレスか電話番号でアカウントを見つけてもらえるようにするには、IDサーバー %@ の利用規約への同意が必要です。"; +"settings_discovery_terms_not_signed" = "メールアドレスか電話番号でアカウントを検出可能にするには、IDサーバー %@ の利用規約への同意が必要です。"; "settings_discovery_no_identity_server" = "現在、IDサーバーを使用していません。連絡先から見つけてもらうようにするには、IDサーバーを追加してください。"; "settings_key_backup_delete_confirmation_prompt_msg" = "よろしいですか?鍵が適切にバックアップされていないと、暗号化されたメッセージを読み取れなくなってしまいます。"; "settings_key_backup_button_connect" = "このセッションを鍵のバックアップに接続"; @@ -1553,7 +1553,7 @@ "location_sharing_title" = "位置情報"; "poll_timeline_not_closed_subtitle" = "もう一度やり直してください"; "poll_timeline_not_closed_title" = "アンケートの終了に失敗しました"; -"poll_timeline_total_no_votes" = "まだ誰も投票していません"; +"poll_timeline_total_no_votes" = "投票がありません"; "poll_timeline_votes_count" = "%lu票"; "poll_timeline_one_vote" = "1票"; "poll_edit_form_poll_type_closed_description" = "結果はアンケートを終了した後でのみ明らかにされます"; @@ -1577,7 +1577,7 @@ "poll_edit_form_create_poll" = "アンケートを作成"; "poll_timeline_vote_not_registered_subtitle" = "投票できませんでした。もう一度やり直してください"; "poll_timeline_vote_not_registered_title" = "投票できませんでした"; -"poll_timeline_total_final_results" = "合計%lu票の投票に基づく最終結果"; +"poll_timeline_total_final_results" = "合計%lu票に基づく最終結果"; "poll_timeline_total_final_results_one_vote" = "合計1票の投票に基づく最終結果"; "poll_timeline_total_votes_not_voted" = "合計%lu票。投票すると結果を確認できます"; "poll_timeline_total_one_vote_not_voted" = "合計1票。投票すると結果を確認できます"; @@ -2094,7 +2094,7 @@ "user_session_verified_session_title" = "認証済のセッション"; "user_session_unverified_session_title" = "未認証のセッション"; "user_session_inactive_session_title" = "非アクティブなセッション"; -"user_session_rename_session_title" = "セッション名を変更"; +"user_session_rename_session_title" = "セッション名の変更"; "user_other_session_security_recommendation_title" = "その他のセッション"; "user_other_session_unverified_sessions_header_subtitle" = "セッションを認証すると、より安全なメッセージのやりとりが可能になります。見覚えのない、または使用していないセッションがあれば、サインアウトしましょう。"; "user_other_session_current_session_details" = "現在のセッション"; @@ -2505,7 +2505,7 @@ "spaces_explore_rooms_room_number" = "%@個のルーム"; "leave_space_and_all_rooms_action" = "全てのルームとスペースから退出"; "leave_space_only_action" = "どのルームからも退出しない"; -"threads_discourage_information_2" = "\n\nスレッド機能を有効にしてよろしいですか?"; +"threads_discourage_information_2" = "\n\nスレッド機能を有効にしますか?"; "room_no_privileges_to_create_group_call" = "通話を開始するには管理者あるいはモデレーターである必要があります。"; "contacts_address_book_permission_denied_alert_message" = "連絡先を有効にするには、端末の設定画面を開いてください。"; "contacts_address_book_permission_denied_alert_title" = "連絡先が無効です"; From 7b7686ff0c10704090640375f2e71df7306fd6a2 Mon Sep 17 00:00:00 2001 From: Suguru Hirahara Date: Fri, 10 Feb 2023 11:13:50 +0000 Subject: [PATCH 23/45] Translated using Weblate (Japanese) Currently translated at 100.0% (2378 of 2378 strings) Translation: Element iOS/Element iOS Translate-URL: https://translate.element.io/projects/riot-ios/riot-ios/ja/ --- Riot/Assets/ja.lproj/Vector.strings | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/Riot/Assets/ja.lproj/Vector.strings b/Riot/Assets/ja.lproj/Vector.strings index d6ad03ba9..5a0f7158e 100644 --- a/Riot/Assets/ja.lproj/Vector.strings +++ b/Riot/Assets/ja.lproj/Vector.strings @@ -1556,7 +1556,7 @@ "poll_timeline_total_no_votes" = "投票がありません"; "poll_timeline_votes_count" = "%lu票"; "poll_timeline_one_vote" = "1票"; -"poll_edit_form_poll_type_closed_description" = "結果はアンケートを終了した後でのみ明らかにされます"; +"poll_edit_form_poll_type_closed_description" = "結果はアンケートが終了した後で表示されます"; "poll_edit_form_poll_type_closed" = "アンケートの終了後に結果を公開"; "poll_edit_form_poll_type_open_description" = "投票した人には、投票の際に即座に結果が表示されます"; "poll_edit_form_poll_type_open" = "投票の際に結果を公開"; @@ -1656,7 +1656,7 @@ "space_topic" = "詳細"; "spaces_creation_cancel_message" = "これまでの設定は失われます。"; "spaces_creation_cancel_title" = "スペースの作成を中止しますか?"; -"create_room_section_footer_type_private" = "招待した人のみが検索・参加できます。"; +"create_room_section_footer_type_private" = "招待した人のみが検索し、参加できます。"; // MARK: - Searchable Directory View Controller @@ -1705,9 +1705,9 @@ "leave_space_action" = "スペースから退出"; "leave_space_selection_title" = "ルームを選択"; -"create_room_section_footer_type_restricted" = "誰でもスペース名で検索・参加できます。"; +"create_room_section_footer_type_restricted" = "誰でもスペース名で検索し、参加できます。"; "create_room_suggest_room" = "スペースのメンバーへのおすすめ"; -"create_room_show_in_directory_footer" = "他の人が検索・参加できるようになります。"; +"create_room_show_in_directory_footer" = "他の人が検索し、参加できるようになります。"; "create_room_promotion_header" = "プロモート"; "searchable_directory_search_placeholder" = "名前または ID"; "room_suggestion_settings_screen_title" = "スペースにおすすめのルームを作成"; @@ -1745,9 +1745,9 @@ "room_access_settings_screen_edit_spaces" = "スペースを編集"; "room_access_settings_screen_upgrade_required" = "アップグレードが必要"; "room_access_settings_screen_upgrade_alert_title" = "ルームをアップグレード"; -"room_access_settings_screen_public_message" = "誰でも検索・参加できます。"; -"room_access_settings_screen_private_message" = "招待された人のみ検索・参加できます。"; -"room_access_settings_screen_message" = "誰が%@を検索・参加できるか選択してください。"; +"room_access_settings_screen_public_message" = "誰でも検索し、参加できます。"; +"room_access_settings_screen_private_message" = "招待された人のみ検索し、参加できます。"; +"room_access_settings_screen_message" = "誰が%@を検索し、参加できるか選択してください。"; "space_settings_access_section" = "このスペースにアクセスできる人は?"; "room_access_settings_screen_title" = "このルームにアクセスできる人は?"; "room_notifs_settings_none" = "なし"; @@ -2338,8 +2338,8 @@ // MARK: Start "device_verification_start_title" = "短い文字列を比較して認証"; -"device_verification_incoming_description_2" = "このセッションを認証すると、信頼済としてマークされ、あなたのセッションも相手に信頼済としてマークされます。"; -"device_verification_incoming_description_1" = "このセッションを認証すると、信頼済としてマークされます。相手のセッションを信頼すると、より一層安心してエンドツーエンド暗号化を使用することができます。"; +"device_verification_incoming_description_2" = "このセッションを認証すると、信頼済として表示し、あなたのセッションも相手に信頼済として表示されます。"; +"device_verification_incoming_description_1" = "このセッションを認証すると、信頼済として表示します。相手のセッションを信頼すると、より一層安心してエンドツーエンド暗号化を使用することができます。"; // MARK: Incoming "device_verification_incoming_title" = "認証のリクエストが届いています"; @@ -2626,7 +2626,7 @@ "event_formatter_call_active_voice" = "実施中の音声通話"; "launch_loading_server_syncing_nth_attempt" = "サーバーと同期しています\n(%@回試行)"; "create_room_suggest_room_footer" = "おすすめのルームは、スペースのメンバーに対して参加候補として表示されます。"; -"create_room_section_footer_type_public" = "スペースの名前だけでなく、招待された人だけが検索・参加できます。"; +"create_room_section_footer_type_public" = "スペースの名前だけでなく、招待された人だけが検索し、参加できます。"; "searchable_directory_x_network" = "%@ネットワーク"; "pin_protection_explanatory" = "PINコードを設定すると、メッセージや連絡先などのデータを保護できます。アプリの開始時にPINコードを入力するよう要求します。"; "secrets_recovery_with_key_information_default" = "セキュリティーキーを入力すると、保護されたメッセージの履歴と、他のセッションの認証用のクロス署名IDにアクセスできます。"; From 21b9ff79a74c6d582f670618e38b8903c12fe313 Mon Sep 17 00:00:00 2001 From: Suguru Hirahara Date: Sun, 12 Feb 2023 05:23:50 +0000 Subject: [PATCH 24/45] Translated using Weblate (Japanese) Currently translated at 100.0% (2378 of 2378 strings) Translation: Element iOS/Element iOS Translate-URL: https://translate.element.io/projects/riot-ios/riot-ios/ja/ --- Riot/Assets/ja.lproj/Vector.strings | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Riot/Assets/ja.lproj/Vector.strings b/Riot/Assets/ja.lproj/Vector.strings index 5a0f7158e..9b421d3b9 100644 --- a/Riot/Assets/ja.lproj/Vector.strings +++ b/Riot/Assets/ja.lproj/Vector.strings @@ -769,7 +769,7 @@ "callbar_active_and_multiple_paused" = "1件のアクティブな通話(%@)・%@件の一時停止された通話"; "callbar_only_multiple_paused" = "一時停止した%@件の通話"; "callbar_only_single_paused" = "一時停止した通話"; -"store_promotional_text" = "オープンなネットワーク上でプライバシーを保護したチャット・コラボレーションアプリ。あなた自身でコントロールできるように非中央集権化(分散化)されています。データマイニング、バックドア、第三者によるアクセスはありません。"; +"store_promotional_text" = "オープンなネットワーク上でプライバシーを保護したチャット・コラボレーションアプリ。あなた自身でコントロールできるように非中央集権化(分散化)されています。データマイニング、バックドア、第三者が勝手にデータにアクセスすることはありません。"; "auth_softlogout_clear_data" = "個人データを消去"; "auth_softlogout_recover_encryption_keys" = "暗号鍵はこの端末にのみ保存されています。保護されたメッセージをどの端末でも読むには、その暗号鍵が必要になります。サインインして暗号鍵を復元してください。"; "auth_softlogout_reason" = "あなたのホームサーバー(%1$@)の管理者が、あなたをアカウント %2$@ (%3$@)からサインアウトさせました。"; @@ -866,7 +866,7 @@ "settings_security" = "セキュリティー"; "settings_three_pids_management_information_part3" = "で設定しましょう。"; "settings_three_pids_management_information_part2" = "ディスカバリー(発見)"; -"store_full_description" = "Elementはまったく新しいメッセンジャーアプリです。\n\n1. あなた自身がプライバシーをコントロールできます。\n2. Matrixネットワークにいる誰とでもコミュニケーションできるだけでなく、Slackなどのアプリと連携すれば、他のネットワークともコミュニケーションを行うことができます。\n3. 広告、データマイニング、バックドア、ユーザーの囲い込みから、あなたを守ります。\n4. エンドツーエンド暗号化と、クロス署名による認証で、あなたを保護します。\n\nElementは分散型(非中央集権型)でオープンソースであるため、他のメッセンジャーアプリと完全に異なっています。\n\nElementでは、あなた自身がサーバーを運営することも、サーバーを選ぶこともできます。あなたのデータと会話に関するプライバシーや所有権は、あなた自身で管理できます。さらに、Elementは開かれたネットワークにアクセスするので、Elementのユーザー以外とも話すことができます。しかもきわめて安全です。\n\nElementはMatrix――オープンな分散型通信の標準規格――で動作するため、これら全てを実現することができています。\n\nElementでは、どのサーバーを使用するかを、ご自身でElementのアプリから決めることができます。\n\n1. 開発者がホストする matrix.org のパブリックサーバーで無料アカウントを取得する。\n2. あなた自身がサーバーを運営し、アカウントを管理する。\n3. Element Matrix Servicesのホスティングプラットフォームに加入し、カスタムサーバー上でアカウントを作る。\n\nなぜElementを選ぶべきなのか?\n\n自分のデータを、自分で所有:データやメッセージを保管する場所を自分で決めることができます。データを所有しコントロールするのは、あなた自身です。データを解析したり第三者にデータを渡したりする巨大IT企業ではありません。\n\nオープンなメッセージングとコラボレーション:Matrixネットワーク上の誰とでも、相手がElementや他のMatrixアプリを使っているか、さらにはSlack、IRC、XMPPのような他のメッセージングシステムを使っているかに関わらず、チャットをすることができます。\n\n非常に安全:本物のエンド・ツー・エンドの暗号化(会話に参加している人だけがメッセージを復号化できます)と、会話参加者の端末を認証するためのクロス署名を行います。\n\n包括的なコミュニケーション:メッセージング、音声およびビデオ通話、ファイル共有、画面共有、その他多くの機能統合、ボット、ウィジェットを提供します。ルームやコミュニティーを立ち上げて連絡を取り合い、物事をスムーズに成し遂げましょう。\n\nいつでも、どこにいても:全ての端末とウェブ https://app.element.io でメッセージの履歴が同期されるため、どこにいても連絡を取ることができます。"; +"store_full_description" = "Elementは画期的なメッセンジャーアプリです。\n\n1. あなた自身が、プライバシーをコントロールできます。\n2. Matrixネットワークにいる誰とでもコミュニケーションできます。Slackなどのアプリと連携すれば、他のネットワークのユーザーともコミュニケーションを行うことができます。\n3. 広告やデータマイニング、バックドア、ユーザーの囲い込みから、あなたを守ります。\n4. エンドツーエンド暗号化と、クロス署名による認証で、コミュニケーションの安全性を確保します。\n\nElementは分散型(非中央集権型)でオープンソースのメッセンジャーアプリです。他のメッセンジャーアプリとは全く性質が異なります。\n\nElementでは、あなた自身がサーバーを運営することも、サーバーを選ぶこともできます。あなたのデータや会話に関するプライバシーや、誰があなたのデータを所有するかは、あなた自身で定められます。さらに、Elementがアクセスするネットワークは、誰でも参加できるオープンなネットワークなので、Elementのユーザー以外ともコミュニケーションを行うことができます。しかもきわめて安全です。\n\nこれら全ては、ElementがMatrix――オープンな分散型通信の標準規格――で動作するために可能になっています。\n\nElementでは、どのサーバーを使用するかを、ご自身でElementのアプリから決めることができます。\n\n1. 開発者がホストする matrix.org のパブリックサーバーで無料アカウントを取得。\n2. あなた自身がサーバーを運営し、アカウントを管理。\n3. Element Matrix Servicesのホスティングプラットフォームに加入し、カスタムサーバー上でアカウントを作成。\n\nElementを選ぶべき理由:\n\n自分のデータを、自分で所有:データやメッセージを保管する場所を自分で決めることができます。データを所有しコントロールするのは、あなた自身です。データを解析したり、第三者にデータを渡したりする巨大IT企業ではありません。\n\nオープンなメッセージングと、コラボレーション:Matrixネットワーク上の誰とでも、メッセージのやり取りを行うことができます。Elementや他のMatrixアプリだけでなく、Slack、IRC、XMPPのような他のメッセージングシステムのユーザーとも、チャットをすることができます。\n\n非常に安全:本物のエンド・ツー・エンドの暗号化(会話に参加している人だけがメッセージを読み取ることができます)を備えています。また、クロス署名を行えば、会話に参加しているユーザーの端末が、本当にそのユーザーのものであるかを認証することができます。\n\n包括的なコミュニケーション:メッセージのやり取り、音声・ビデオ通話、ファイル共有、画面共有、その他多くの機能、ボット、ウィジェットを提供します。ルームやコミュニティーを立ち上げて連絡を取り合い、タスクをスムーズに成し遂げましょう。\n\nいつでも、どこにいても:アプリをインストールしている全ての端末とウェブ https://app.element.io でメッセージの履歴が同期されるため、どこにいても連絡を取ることができます。"; "user_verification_session_details_additional_information_untrusted_other_user" = "ユーザーがこのセッションを信頼するまでは、セッションとの間で送受信されるメッセージには警告が表示されます。また、手動で認証することもできます。"; "user_verification_session_details_information_untrusted_other_user" = " が新しいセッションを使ってサインインしました:"; "user_verification_session_details_information_untrusted_current_user" = "このセッションを認証して信頼済としてマークし、暗号化されたメッセージへのアクセスを許可。"; @@ -1326,7 +1326,7 @@ "room_error_topic_edition_not_authorized" = "このルームのトピックを編集する権限がありません"; "room_error_cannot_load_timeline" = "タイムラインの読み込みに失敗しました"; "room_error_timeline_event_not_found_title" = "タイムラインの位置を読み込めませんでした"; -"room_error_timeline_event_not_found" = "このルームのタイムラインに特定のポイントを読み込もうとしましたが、見つけられませんでした"; +"room_error_timeline_event_not_found" = "このルームのタイムラインの特定の地点を読み込もうとしましたが、見つけられませんでした"; "room_left" = "ルームから退出しました"; "room_no_power_to_create_conference_call" = "このルームで会議を開始するには、招待するための権限が必要です"; "room_no_conference_call_in_encrypted_rooms" = "暗号化されたルームでは、グループ通話はサポートされません"; @@ -1373,7 +1373,7 @@ "e2e_export_prompt" = "このプロセスでは、暗号化されたルームで受信したメッセージの鍵をローカルファイルにエクスポートできます。 そのファイルを別のMatrixのクライアントにインポートすると、クライアントはこれらのメッセージを復号化することができます。\nエクスポートしたファイルを使うと、誰でも暗号化されたメッセージを復号化できるため、ファイルを安全に保つように注意する必要があります。"; "e2e_export" = "エクスポート"; "e2e_passphrase_confirm" = "パスフレーズを確認"; -"e2e_passphrase_empty" = "パスフレーズは空であってはいけません"; +"e2e_passphrase_empty" = "パスフレーズには1文字以上が必要です"; "e2e_passphrase_not_match" = "パスフレーズが一致していません"; "e2e_passphrase_create" = "パスフレーズの作成"; // Others From d0f098416067273ed3d9069ccc319ae25e459bff Mon Sep 17 00:00:00 2001 From: Yoan Pintas Date: Mon, 13 Feb 2023 16:11:00 +0000 Subject: [PATCH 25/45] Translated using Weblate (French) Currently translated at 100.0% (2378 of 2378 strings) Translation: Element iOS/Element iOS Translate-URL: https://translate.element.io/projects/riot-ios/riot-ios/fr/ --- Riot/Assets/fr.lproj/Vector.strings | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/Riot/Assets/fr.lproj/Vector.strings b/Riot/Assets/fr.lproj/Vector.strings index 18612cab1..ea1e3070e 100644 --- a/Riot/Assets/fr.lproj/Vector.strings +++ b/Riot/Assets/fr.lproj/Vector.strings @@ -113,7 +113,7 @@ "people_conversation_section" = "DISCUSSIONS"; "people_no_conversation" = "Aucune discussion"; // Rooms tab -"room_directory_no_public_room" = "Aucun forum disponible"; +"room_directory_no_public_room" = "Aucun salon public disponible"; // Groups tab "group_invite_section" = "INVITATIONS"; "group_section" = "COMMUNAUTÉS"; @@ -399,7 +399,7 @@ "directory_server_picker_title" = "Sélectionner un répertoire"; "directory_server_all_rooms" = "Tous les salons sur le serveur %@"; "directory_server_all_native_rooms" = "Tous les salons Matrix natifs"; -"directory_server_type_homeserver" = "Saisir un serveur d’accueil pour lister ses forums"; +"directory_server_type_homeserver" = "Saisir un serveur d’accueil pour lister ses salons publics"; "directory_server_placeholder" = "matrix.org"; // Others "or" = "ou"; @@ -407,7 +407,7 @@ "today" = "Aujourd’hui"; "yesterday" = "Hier"; "network_offline_prompt" = "La connexion Internet semble être hors-ligne."; -"public_room_section_title" = "Forums (sur %@) :"; +"public_room_section_title" = "Salons publics (sur %@) :"; "bug_report_prompt" = "L’application s’est arrêtée brusquement la dernière fois. Voulez-vous envoyer un rapport d’anomalie ?"; "rage_shake_prompt" = "Vous semblez secouer le téléphone avec frustration. Souhaitez-vous soumettre un rapport d’anomalie ?"; "do_not_ask_again" = "Ne plus demander"; @@ -1211,8 +1211,8 @@ "create_room_section_header_address" = "ADRESSE"; "create_room_show_in_directory" = "Afficher le salon dans le répertoire"; "create_room_section_footer_type" = "Les personnes ne rejoignent un salon privé que sur invitation."; -"create_room_type_public" = "Forum (tout le monde)"; -"create_room_type_private" = "Salon (seulement sur invitation)"; +"create_room_type_public" = "Salon public (tout le monde)"; +"create_room_type_private" = "Salon privé (seulement sur invitation)"; "create_room_section_header_type" = "QUI PEUT Y ACCÉDER"; "create_room_section_footer_encryption" = "Le chiffrement ne peut pas être désactivé ensuite."; "create_room_enable_encryption" = "Activer le chiffrement"; @@ -1317,7 +1317,7 @@ "room_details_room_name_for_dm" = "Nom"; "room_details_photo_for_dm" = "Photo"; "room_details_title_for_dm" = "Détails"; -"settings_show_NSFW_public_rooms" = "Afficher les forums au contenu choquant"; +"settings_show_NSFW_public_rooms" = "Afficher les salons publics au contenu choquant"; "external_link_confirmation_message" = "Le lien %@ vous emmène vers un autre site : %@\n\nÊtes vous sûr de vouloir poursuivre ?"; "external_link_confirmation_title" = "Inspectez ce lien"; "room_open_dialpad" = "Pavé de numérotation"; @@ -1494,7 +1494,7 @@ "spaces_empty_space_title" = "Cet espace n’a pas (encore) de salon"; "space_tag" = "espace"; "spaces_suggested_room" = "Recommandé"; -"spaces_explore_rooms" = "Rejoindre un forum"; +"spaces_explore_rooms" = "Parcourir les salons"; "leave_space_and_all_rooms_action" = "Quitter tous les salons et espaces"; "leave_space_only_action" = "Ne quitter aucun salon"; "leave_space_message_admin_warning" = "Vous êtes administrateur de cet espace. Assurez-vous d’avoir transmis les droits d’administration à un autre membre avant de partir."; From 6ce11564acebb6114cd7b13f05d798a04d6c4c28 Mon Sep 17 00:00:00 2001 From: Suguru Hirahara Date: Wed, 15 Feb 2023 09:40:33 +0000 Subject: [PATCH 26/45] Translated using Weblate (Japanese) Currently translated at 100.0% (2378 of 2378 strings) Translation: Element iOS/Element iOS Translate-URL: https://translate.element.io/projects/riot-ios/riot-ios/ja/ --- Riot/Assets/ja.lproj/Vector.strings | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Riot/Assets/ja.lproj/Vector.strings b/Riot/Assets/ja.lproj/Vector.strings index 9b421d3b9..ba9da55fe 100644 --- a/Riot/Assets/ja.lproj/Vector.strings +++ b/Riot/Assets/ja.lproj/Vector.strings @@ -1348,7 +1348,7 @@ "attachment_cancel_download" = "ダウンロードをキャンセルしますか?"; "attachment_cancel_upload" = "アップロードをキャンセルしますか?"; "attachment_multiselection_size_prompt" = "画像を次のように送信しますか:"; -"attachment_multiselection_original" = "実際のサイズ"; +"attachment_multiselection_original" = "等倍"; "attachment_e2e_keys_file_prompt" = "このファイルには、Matrixのクライアントからエクスポートされた暗号鍵が含まれています。\nファイルの内容を表示するか、ファイル内の鍵をインポートしますか?"; "attachment_e2e_keys_import" = "インポート…"; // Contacts From 1c286d6b111c6db22ad560c37fcb5044f92c9b42 Mon Sep 17 00:00:00 2001 From: Shahab Eskandary Date: Wed, 15 Feb 2023 05:56:19 +0000 Subject: [PATCH 27/45] Translated using Weblate (Persian) Currently translated at 45.9% (1093 of 2378 strings) Translation: Element iOS/Element iOS Translate-URL: https://translate.element.io/projects/riot-ios/riot-ios/fa/ --- Riot/Assets/fa.lproj/Vector.strings | 56 +++++++++++++++++++++++++++++ 1 file changed, 56 insertions(+) diff --git a/Riot/Assets/fa.lproj/Vector.strings b/Riot/Assets/fa.lproj/Vector.strings index fe51dcd78..a7f7b720c 100644 --- a/Riot/Assets/fa.lproj/Vector.strings +++ b/Riot/Assets/fa.lproj/Vector.strings @@ -1290,3 +1290,59 @@ "stop" = "توقف"; "joining" = "پیوستن"; "enable" = "فعال"; +"authentication_server_selection_generic_error" = "در این آدرس سروری نیست! لطفا صحت آن را بررسی کنید."; +"authentication_server_selection_server_url" = "آدرس هوم سرور"; +"authentication_server_selection_register_message" = "آدرس سرورتان چیست؟ این آدرس ذخیره سازی اطلاعات شماست"; +"authentication_server_selection_register_title" = "هوم سرور خود را انتخاب کنید"; +"authentication_server_selection_login_message" = "آدرس سرورتان چیست؟"; +"authentication_server_selection_login_title" = "اتصال به هوم سرور"; +"authentication_login_with_qr" = "ورود با QR کد"; +"authentication_server_info_title_login" = "جایی که مکالماتتان قرار میگیرند"; +"authentication_login_forgot_password" = "فراموشی رمز عبور"; +"authentication_login_username" = "نام کاربری، ایمیل، یا شماره تلفن"; +"authentication_login_title" = "خوش برگشتید!"; +"authentication_server_info_title" = "جایی که مکالماتتان قرار میگیرند"; +"authentication_registration_password_footer" = "باید 8 حرف یا بیشتر باشد"; +/* The placeholder will show the full Matrix ID that has been entered. */ +"authentication_registration_username_footer_available" = "بقیه میتوانند شما را پیدا کنند %@"; +"authentication_registration_username_footer" = "نمیتوانید بعدا تغییرش دهید"; +"authentication_registration_username" = "نام کاربری"; + +// MARK: Authentication +"authentication_registration_title" = "حسابتان را بسازید"; +"onboarding_celebration_button" = "بزن بریم"; +"onboarding_celebration_message" = "برای ویرایش پروفایلتان، به تنظیمات بروید"; +"onboarding_celebration_title" = "خوب به نظر میرسد!"; +"onboarding_avatar_accessibility_label" = "تصویر پروفایل"; +"onboarding_avatar_message" = "زمان آن رسیده که به نامتان، تصویر اضافه کنید"; +"onboarding_avatar_title" = "یک عکس پروفایل اضافه کنید"; +"onboarding_display_name_max_length" = "نام نمایشی شما باید کمتر از 256 حرف باشد"; +"onboarding_display_name_hint" = "میتواند بعدا آن را تغییر دهید"; +"onboarding_display_name_placeholder" = "نام نمایشی"; +"onboarding_display_name_message" = "این نام هنگام ارسال پیام ها نمایش داده میشود."; +"onboarding_display_name_title" = "یک نام نمایشی انتخاب کنید"; +"onboarding_personalization_skip" = "این مرحله را رد کن"; +"onboarding_personalization_save" = "ذخیره و ادامه"; +"onboarding_congratulations_home_button" = "مرا به خانه ببر"; +"onboarding_congratulations_personalize_button" = "شخصی سازی پروفایل"; +/* The placeholder string contains the user's matrix ID */ +"onboarding_congratulations_message" = "حسابتان %@ ایجاد شد"; +"onboarding_congratulations_title" = "تبریک!"; +"onboarding_use_case_existing_server_button" = "اتصال به سرور"; +"onboarding_use_case_existing_server_message" = "دنبال اتصال به یک سرور موجود هستید؟"; +"onboarding_use_case_skip_button" = "این سوال را رد کن"; +/* The placeholder string contains onboarding_use_case_skip_button as a tappable action */ +"onboarding_use_case_not_sure_yet" = "هنوز مطمئن نیستید؟ %@"; +"onboarding_use_case_community_messaging" = "اجتماعات"; +"onboarding_use_case_work_messaging" = "تیم ها"; +"onboarding_use_case_personal_messaging" = "خانواده و دوستان"; +"onboarding_use_case_message" = "ما به شما کمک میکنیم که متصل شوید"; +"onboarding_use_case_title" = "با چه کسانی بیشتر چت میکنید؟"; +"onboarding_splash_page_4_message" = "المنت برای محیط های شغلی عالی است چرا که توسط امن ترین سازمان های جهانی، استفاده میشود."; +"onboarding_splash_page_4_title_no_pun" = "ارسال پیام بین اعضای تیمتان."; +"onboarding_splash_page_3_message" = "رمزنگاری کامل بدون نیاز به شماره تلفن، بدون وجود تبلیغات و دیتاکاوی."; +"onboarding_splash_page_3_title" = "پیام رسانی امن."; +"onboarding_splash_page_2_message" = "انتخاب مکان ذخیره سازی پیام هایتان، برایتان کنترل و استقلال را از طریق اتصال به ماتریکس به ارمغان می‌آورد."; +"onboarding_splash_page_2_title" = "تحت کنترل شماست."; +"onboarding_splash_page_1_message" = "یک ارتباط امن و مستقل که سطح حریم شخصی آن دقیقا مشابه ارتباط رو در رو در منزل شماست."; +"accessibility_selected" = "انتخاب شده"; From 0c7bbbc3c4370221c18a8a3ccca7a6640cc6c66d Mon Sep 17 00:00:00 2001 From: Vri Date: Wed, 15 Feb 2023 15:18:20 +0000 Subject: [PATCH 28/45] Translated using Weblate (German) Currently translated at 100.0% (2379 of 2379 strings) Translation: Element iOS/Element iOS Translate-URL: https://translate.element.io/projects/riot-ios/riot-ios/de/ --- Riot/Assets/de.lproj/Vector.strings | 1 + 1 file changed, 1 insertion(+) diff --git a/Riot/Assets/de.lproj/Vector.strings b/Riot/Assets/de.lproj/Vector.strings index dc6a2b333..c1b0796ae 100644 --- a/Riot/Assets/de.lproj/Vector.strings +++ b/Riot/Assets/de.lproj/Vector.strings @@ -2735,3 +2735,4 @@ "wysiwyg_composer_format_action_indent" = "Einrückung erhöhen"; "settings_push_rules_error" = "Ein Fehler ist während der Aktualisierung deiner Benachrichtigungseinstellungen aufgetreten. Bitte versuche die Option erneut umzuschalten."; "poll_history_detail_view_in_timeline" = "Umfrage in Verlauf anzeigen"; +"authentication_qr_login_failure_device_not_supported" = "Die Verbindung mit diesem Gerät wird nicht unterstützt."; From 9a2200ae04e7b489957eca4cf7045d62599a8876 Mon Sep 17 00:00:00 2001 From: Yoan Pintas Date: Wed, 15 Feb 2023 17:21:55 +0000 Subject: [PATCH 29/45] Translated using Weblate (French) Currently translated at 99.9% (2378 of 2379 strings) Translation: Element iOS/Element iOS Translate-URL: https://translate.element.io/projects/riot-ios/riot-ios/fr/ --- Riot/Assets/fr.lproj/Vector.strings | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Riot/Assets/fr.lproj/Vector.strings b/Riot/Assets/fr.lproj/Vector.strings index ea1e3070e..4767e596c 100644 --- a/Riot/Assets/fr.lproj/Vector.strings +++ b/Riot/Assets/fr.lproj/Vector.strings @@ -80,7 +80,7 @@ "auth_reset_password_success_message" = "Le mot de passe de votre compte Matrix a été réinitialisé.\n\nVous avez été déconnecté de toutes vos sessions et ne recevrez plus de notifications. Pour réactiver les notifications, reconnectez-vous sur chaque appareil."; "auth_add_email_and_phone_warning" = "L’inscription avec un e-mail et un numéro de téléphone à la fois n’est pas prise en charge tant que l’API n'existe pas. Seul votre numéro de téléphone sera pris en compte. Vous pourrez ajouter l’adresse e-mail dans vos options de profil."; // Chat creation -"room_creation_title" = "Nouvelle discussion"; +"room_creation_title" = "Nouveau message direct"; "room_creation_account" = "Compte"; "room_creation_appearance" = "Apparence"; "room_creation_appearance_name" = "Nom"; @@ -111,7 +111,7 @@ // People tab "people_invites_section" = "INVITATIONS"; "people_conversation_section" = "DISCUSSIONS"; -"people_no_conversation" = "Aucune discussion"; +"people_no_conversation" = "Aucun message direct"; // Rooms tab "room_directory_no_public_room" = "Aucun salon public disponible"; // Groups tab @@ -314,7 +314,7 @@ "room_details_favourite_tag" = "Favoris"; "room_details_low_priority_tag" = "Priorité basse"; "room_details_mute_notifs" = "Désactiver les notifications"; -"room_details_direct_chat" = "Discussion directe"; +"room_details_direct_chat" = "Message direct"; "room_details_access_section" = "Qui peut accéder à ce salon ?"; "room_details_access_section_invited_only" = "Seules les personnes qui ont été invitées"; "room_details_access_section_anyone_apart_from_guest" = "Tous ceux qui connaissent le lien du salon, à part les visiteurs"; From 8d603f2768c39d3fcbcc380aceed186223d8088f Mon Sep 17 00:00:00 2001 From: Suguru Hirahara Date: Thu, 16 Feb 2023 05:35:35 +0000 Subject: [PATCH 30/45] Translated using Weblate (Japanese) Currently translated at 100.0% (2379 of 2379 strings) Translation: Element iOS/Element iOS Translate-URL: https://translate.element.io/projects/riot-ios/riot-ios/ja/ --- Riot/Assets/ja.lproj/Vector.strings | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/Riot/Assets/ja.lproj/Vector.strings b/Riot/Assets/ja.lproj/Vector.strings index ba9da55fe..22a4db8e7 100644 --- a/Riot/Assets/ja.lproj/Vector.strings +++ b/Riot/Assets/ja.lproj/Vector.strings @@ -724,9 +724,9 @@ "room_message_replying_to" = "%@に返信しています"; "room_message_editing" = "編集中"; "room_accessiblity_scroll_to_bottom" = "いちばん下までスクロール"; -"room_member_power_level_short_custom" = "カスタム"; +"room_member_power_level_short_custom" = "ユーザー定義"; "room_member_power_level_short_moderator" = "モデレーター"; -"room_member_power_level_custom_in" = "カスタム(%@):%@"; +"room_member_power_level_custom_in" = "ユーザー定義(%@):%@"; "room_member_power_level_short_admin" = "管理者"; "room_member_power_level_moderator_in" = "%@のモデレーター"; "room_member_power_level_admin_in" = "%@の管理者"; @@ -1467,7 +1467,7 @@ "notification_settings_never_notify" = "通知しない"; "notification_settings_word_to_match" = "一致する単語"; "notification_settings_highlight" = "ハイライト"; -"notification_settings_custom_sound" = "カスタムサウンド"; +"notification_settings_custom_sound" = "カスタム音"; "notification_settings_per_room_notifications" = "ルーム単位の通知"; "notification_settings_per_sender_notifications" = "送信者単位の通知"; "notification_settings_sender_hint" = "@user:domain.com"; @@ -2824,3 +2824,4 @@ "event_formatter_call_missed_voice" = "不在着信(音声)"; "settings_push_rules_error" = "通知の設定をアップデートする際にエラーが発生しました。もう一度オプションを切り替えてみてください。"; "settings_presence" = "プレゼンス(ステータス表示)"; +"authentication_qr_login_failure_device_not_supported" = "この端末とのリンクはサポートしていません。"; From 09cf1cfee325a26c7524414557d735469c4af832 Mon Sep 17 00:00:00 2001 From: random Date: Thu, 16 Feb 2023 10:04:21 +0000 Subject: [PATCH 31/45] Translated using Weblate (Italian) Currently translated at 100.0% (2379 of 2379 strings) Translation: Element iOS/Element iOS Translate-URL: https://translate.element.io/projects/riot-ios/riot-ios/it/ --- Riot/Assets/it.lproj/Vector.strings | 1 + 1 file changed, 1 insertion(+) diff --git a/Riot/Assets/it.lproj/Vector.strings b/Riot/Assets/it.lproj/Vector.strings index 8d1cc83f0..6d2cbe086 100644 --- a/Riot/Assets/it.lproj/Vector.strings +++ b/Riot/Assets/it.lproj/Vector.strings @@ -2701,3 +2701,4 @@ "key_backup_recover_from_private_key_progress" = "%@%% Completato"; "poll_history_detail_view_in_timeline" = "Vedi sondaggio nella linea temporale"; "settings_push_rules_error" = "Si è verificato un errore aggiornando le tue preferenze di notifica. Prova ad attivare/disattivare di nuovo l'opzione."; +"authentication_qr_login_failure_device_not_supported" = "Il collegamento con questo dispositivo non è supportato."; From d7bd5b7d3aa6411f929728f0de25ec47b404a64c Mon Sep 17 00:00:00 2001 From: Ihor Hordiichuk Date: Wed, 15 Feb 2023 20:56:30 +0000 Subject: [PATCH 32/45] Translated using Weblate (Ukrainian) Currently translated at 100.0% (2379 of 2379 strings) Translation: Element iOS/Element iOS Translate-URL: https://translate.element.io/projects/riot-ios/riot-ios/uk/ --- Riot/Assets/uk.lproj/Vector.strings | 1 + 1 file changed, 1 insertion(+) diff --git a/Riot/Assets/uk.lproj/Vector.strings b/Riot/Assets/uk.lproj/Vector.strings index cc3bd8b93..143575185 100644 --- a/Riot/Assets/uk.lproj/Vector.strings +++ b/Riot/Assets/uk.lproj/Vector.strings @@ -2926,3 +2926,4 @@ "wysiwyg_composer_format_action_indent" = "Збільшити відступ"; "settings_push_rules_error" = "Сталася помилка під час оновлення налаштувань сповіщень. Спробуйте змінити налаштування ще раз."; "poll_history_detail_view_in_timeline" = "Переглянути опитування у стрічці"; +"authentication_qr_login_failure_device_not_supported" = "Пов'язування з цим пристроєм не підтримується."; From 45c688e406dfb7606d17ed0a796a9005415f9d3a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Priit=20J=C3=B5er=C3=BC=C3=BCt?= Date: Wed, 15 Feb 2023 16:32:26 +0000 Subject: [PATCH 33/45] Translated using Weblate (Estonian) Currently translated at 100.0% (2379 of 2379 strings) Translation: Element iOS/Element iOS Translate-URL: https://translate.element.io/projects/riot-ios/riot-ios/et/ --- Riot/Assets/et.lproj/Vector.strings | 1 + 1 file changed, 1 insertion(+) diff --git a/Riot/Assets/et.lproj/Vector.strings b/Riot/Assets/et.lproj/Vector.strings index 04a4197cc..f9eb1b318 100644 --- a/Riot/Assets/et.lproj/Vector.strings +++ b/Riot/Assets/et.lproj/Vector.strings @@ -2673,3 +2673,4 @@ "wysiwyg_composer_format_action_indent" = "Suurenda taandrida"; "settings_push_rules_error" = "Teavituste eelistuste muutmisel tekkis viga. Palun proovi sama valikut uuesti sisse/välja lülitada."; "poll_history_detail_view_in_timeline" = "Näita küsitlust ajajoonel"; +"authentication_qr_login_failure_device_not_supported" = "Sidumine selle seadmega ei ole toetatud."; From 9b1adde1002d514664b8a96ab2b0c0cfd50aebdb Mon Sep 17 00:00:00 2001 From: Linerly Date: Thu, 16 Feb 2023 11:59:57 +0000 Subject: [PATCH 34/45] Translated using Weblate (Indonesian) Currently translated at 100.0% (2379 of 2379 strings) Translation: Element iOS/Element iOS Translate-URL: https://translate.element.io/projects/riot-ios/riot-ios/id/ --- Riot/Assets/id.lproj/Vector.strings | 1 + 1 file changed, 1 insertion(+) diff --git a/Riot/Assets/id.lproj/Vector.strings b/Riot/Assets/id.lproj/Vector.strings index 0120b2b5a..b453818aa 100644 --- a/Riot/Assets/id.lproj/Vector.strings +++ b/Riot/Assets/id.lproj/Vector.strings @@ -2928,3 +2928,4 @@ "wysiwyg_composer_format_action_indent" = "Tambahkan indentasi"; "poll_history_detail_view_in_timeline" = "Tampilkan pemungutan suara dalam lini masa"; "settings_push_rules_error" = "Sebuah kesalahan terjadi ketika memperbarui preferensi notifikasi Anda. Silakan alih ulang opsi Anda."; +"authentication_qr_login_failure_device_not_supported" = "Penautan dengan perangkat ini tidak didukung."; From aaf0e0e6e5da67f53961229e9d6632f15422c20f Mon Sep 17 00:00:00 2001 From: Jozef Gaal Date: Wed, 15 Feb 2023 15:54:59 +0000 Subject: [PATCH 35/45] Translated using Weblate (Slovak) Currently translated at 100.0% (2379 of 2379 strings) Translation: Element iOS/Element iOS Translate-URL: https://translate.element.io/projects/riot-ios/riot-ios/sk/ --- Riot/Assets/sk.lproj/Vector.strings | 1 + 1 file changed, 1 insertion(+) diff --git a/Riot/Assets/sk.lproj/Vector.strings b/Riot/Assets/sk.lproj/Vector.strings index 0aace81cc..49be11fc5 100644 --- a/Riot/Assets/sk.lproj/Vector.strings +++ b/Riot/Assets/sk.lproj/Vector.strings @@ -2924,3 +2924,4 @@ "wysiwyg_composer_format_action_un_indent" = "Zmenšenie odsadenia"; "poll_history_detail_view_in_timeline" = "Zobraziť anketu na časovej osi"; "settings_push_rules_error" = "Pri aktualizácii vašich predvolieb oznámení došlo k chybe. Skúste prosím prepnúť možnosť znova."; +"authentication_qr_login_failure_device_not_supported" = "Prepojenie s týmto zariadením nie je podporované."; From 818154260886dfff79b8f7839fabe6d56c476156 Mon Sep 17 00:00:00 2001 From: Vri Date: Sun, 19 Feb 2023 07:30:54 +0000 Subject: [PATCH 36/45] Translated using Weblate (German) Currently translated at 100.0% (2379 of 2379 strings) Translation: Element iOS/Element iOS Translate-URL: https://translate.element.io/projects/riot-ios/riot-ios/de/ --- Riot/Assets/de.lproj/Vector.strings | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Riot/Assets/de.lproj/Vector.strings b/Riot/Assets/de.lproj/Vector.strings index c1b0796ae..f63349cd7 100644 --- a/Riot/Assets/de.lproj/Vector.strings +++ b/Riot/Assets/de.lproj/Vector.strings @@ -2734,5 +2734,5 @@ "wysiwyg_composer_format_action_un_indent" = "Einrückung verringern"; "wysiwyg_composer_format_action_indent" = "Einrückung erhöhen"; "settings_push_rules_error" = "Ein Fehler ist während der Aktualisierung deiner Benachrichtigungseinstellungen aufgetreten. Bitte versuche die Option erneut umzuschalten."; -"poll_history_detail_view_in_timeline" = "Umfrage in Verlauf anzeigen"; +"poll_history_detail_view_in_timeline" = "Umfrage im Verlauf anzeigen"; "authentication_qr_login_failure_device_not_supported" = "Die Verbindung mit diesem Gerät wird nicht unterstützt."; From b1de45e274b33c97a33e3111f1b6f728e001e9ad Mon Sep 17 00:00:00 2001 From: Meow Date: Sat, 18 Feb 2023 10:23:10 +0000 Subject: [PATCH 37/45] Translated using Weblate (Chinese (Traditional)) Currently translated at 46.8% (1114 of 2379 strings) Translation: Element iOS/Element iOS Translate-URL: https://translate.element.io/projects/riot-ios/riot-ios/zh_Hant/ --- Riot/Assets/zh_Hant.lproj/Vector.strings | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/Riot/Assets/zh_Hant.lproj/Vector.strings b/Riot/Assets/zh_Hant.lproj/Vector.strings index 25c64fe45..ef3116270 100644 --- a/Riot/Assets/zh_Hant.lproj/Vector.strings +++ b/Riot/Assets/zh_Hant.lproj/Vector.strings @@ -1,6 +1,6 @@ // Titles "title_home" = "首頁"; -"title_favourites" = "收藏夾"; +"title_favourites" = "喜好項目"; "title_people" = "聯絡人"; "title_rooms" = "聊天室"; "title_groups" = "社群"; @@ -8,7 +8,7 @@ // Actions "view" = "檢視"; "next" = "下一步"; -"back" = "返回"; +"back" = "上一步"; "continue" = "繼續"; "create" = "建立"; "start" = "開始"; @@ -618,10 +618,10 @@ "joined" = "已加入"; "skip" = "跳過"; "close" = "關閉"; -"store_promotional_text" = "開放網絡上的隱私保護聊天和協作應用程序。 去中心化管理。 沒有數據挖掘,沒有後門,也沒有第三方存取。"; +"store_promotional_text" = "開放網路上的隱私保護聊天和協作應用程式。去中心化管理。沒有資料探勘,沒有後門,也沒有第三方存取。"; "store_full_description" = "Element是一種新型的通訊和協作應用程式,它可以使你:\n\n1.掌控您的隱私\n2.可以與Matrix網絡中的任何人進行通信,甚至可以與Slack等應用程式整合\n3.保護您免受廣告,數據挖掘,後門和封閉平台的侵害\n4.通過端到端加密和交互簽名來驗證他人,從而保護您的安全\n\nElement是去中心化的開源軟件,因此與其他通訊和協作應用程式完全不同。\n\nElement允許您自行架設(或選擇託管)伺服器,使您擁有隱私權,所有權以及對數據和會話的控制權。自行架設的伺服器可以使您訪問開放的網絡;因此,您不僅可以只與其他 Element 用戶聊天。而且非常安全。\n\nElement之所以能夠達至所有這些目標,是因為它在Matrix(開放,去中心化通信的標準)上運行。\n\nElement通過讓您選擇託管對話的伺服器來控制您的訊息和資料。在Element應用程式中,您可以選擇以不同方式託管你的訊息:\n\n1.在matrix.org公共伺服器上獲得一個免費帳戶\n2.通過在自己的硬件上架設伺服器來託管帳戶\n3.訂閱Element Matrix Services託管平台,即可在自定伺服器上註冊帳戶\n\n為什麼選擇Element?\n\n擁有您的數據:您可以決定將數據和訊息保留在何處。您擁有並控制它,而不是某些超大型企業一樣,會挖掘您的數據或把數據提供給第三方。\n\n開放的通訊和協作:您可以與Matrix網絡中的任何人聊天,無論他們使用的是Element還是其他Matrix應用程式,甚至他們使用的是Slack,IRC或XMPP之類的其他通訊系統。\n\n超級安全:真正的端到端加密(只有對話中的人才能解密消息),並進行交互簽名以驗證對話參與者的設備。\n\n完整的通信:文字通訊,語音和視像通話,文件共享,屏幕共享以及大量整合,機器人和小部件。建立房間、社群,保持聯繫並完成工作。\n\n無論您身在何處都可保持聯繫:無論您身在何處,都可以通過 https://element.io/app 在所有設備和網絡上完全同步訊息歷史記錄來保持聯繫。"; // String for App Store -"store_short_description" = "去中心化的安全通訊軟件"; +"store_short_description" = "去中心化的安全通訊軟體"; "settings_three_pids_management_information_part1" = "在此管理你可以用作登入或回復帳戶的電郵或電話號碼。你也可控制誰可以用這些資料找到你。 "; "external_link_confirmation_message" = "此鏈結 %@ 將帶你到另一網頁: %@\n\n確定要前往?"; "external_link_confirmation_title" = "按此鏈結"; @@ -1141,7 +1141,7 @@ "notice_room_history_visible_to_members" = "%@ 讓所有聊天室成員都能看到未來的房間歷史記錄。"; "stop" = "停止"; "joining" = "正在加入"; -"enable" = "啓用"; +"enable" = "啟用"; "service_terms_modal_policy_checkbox_accessibility_hint" = "確認接受 %@"; /* The placeholder will show the homeserver's domain */ "authentication_terms_message" = "請閱讀 %@ 的條款與政策"; From 5e8de57e0540522fb6fcc6d6f3c4525eaf2d7008 Mon Sep 17 00:00:00 2001 From: Suguru Hirahara Date: Sun, 19 Feb 2023 16:36:46 +0000 Subject: [PATCH 38/45] Translated using Weblate (Japanese) Currently translated at 100.0% (2379 of 2379 strings) Translation: Element iOS/Element iOS Translate-URL: https://translate.element.io/projects/riot-ios/riot-ios/ja/ --- Riot/Assets/ja.lproj/Vector.strings | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/Riot/Assets/ja.lproj/Vector.strings b/Riot/Assets/ja.lproj/Vector.strings index 22a4db8e7..676a02c9a 100644 --- a/Riot/Assets/ja.lproj/Vector.strings +++ b/Riot/Assets/ja.lproj/Vector.strings @@ -473,7 +473,7 @@ "room_replacement_information" = "このルームは置き換えられており、アクティブではありません。"; "room_replacement_link" = "こちらから継続中の会話を確認。"; "room_predecessor_information" = "このルームは別の会話の続きです。"; -"room_predecessor_link" = "以前のメッセージを表示するには、ここをタップしてください。"; +"room_predecessor_link" = "ここをタップすると、以前のメッセージを表示します。"; "room_resource_limit_exceeded_message_contact_2_link" = "サービス管理者に連絡してください"; "room_resource_limit_exceeded_message_contact_3" = " このサービスの使用を継続するには。"; "room_resource_usage_limit_reached_message_1_default" = "このホームサーバーはリソースの上限に達しました "; @@ -646,7 +646,7 @@ "call_no_stun_server_error_title" = "サーバーの不正な設定のため通話に失敗しました"; "room_does_not_exist" = "%@は存在しません"; "photo_library_access_not_granted" = "%@にはフォトライブラリーにアクセスする権限がありません。プライバシー設定を変更してください"; -"camera_unavailable" = "この端末ではカメラを利用できません"; +"camera_unavailable" = "この端末ではカメラを使用できません"; "event_formatter_widget_removed_by_you" = "ウィジェットを削除しました:%@"; "event_formatter_jitsi_widget_removed_by_you" = "VoIP会議を削除しました"; "event_formatter_jitsi_widget_added_by_you" = "VoIP会議を追加しました"; @@ -705,7 +705,7 @@ "settings_key_backup_info_valid" = "このセッションは鍵をバックアップしています。"; "settings_key_backup_info_algorithm" = "アルゴリズム:%@"; "settings_key_backup_info_version" = "鍵のバックアップのバージョン:%@"; -"settings_key_backup_info_none" = "このセッションでは鍵がバックアップされていません。"; +"settings_key_backup_info_none" = "鍵はこのセッションからバックアップされていません。"; "settings_key_backup_info_checking" = "確認しています…"; "settings_add_3pid_password_message" = "続行するには、Matrixのアカウントのパスワードを入力してください"; "settings_add_3pid_invalid_password_message" = "認証情報が正しくありません"; @@ -2067,9 +2067,9 @@ "user_sessions_overview_security_recommendations_section_info" = "以下の勧告に従い、アカウントのセキュリティーを改善しましょう。"; "user_sessions_overview_security_recommendations_unverified_title" = "未認証のセッション"; "user_sessions_overview_security_recommendations_inactive_title" = "非アクティブなセッション"; -"user_sessions_overview_security_recommendations_inactive_info" = "使用していない古いセッション(90日以上使用されていません)からサインアウトすることを検討してください。"; +"user_sessions_overview_security_recommendations_inactive_info" = "使用していない古いセッション(90日以上使用されていません)からのサインアウトを検討してください。"; "user_sessions_overview_other_sessions_section_title" = "その他のセッション"; -"user_sessions_overview_other_sessions_section_info" = "セキュリティーを最大限に高めるには、セッションを認証し、不明なセッションや利用していないセッションからサインアウトしてください。"; +"user_sessions_overview_other_sessions_section_info" = "セキュリティーを最大限に高めるには、セッションを認証し、不明なセッションや使用していないセッションからサインアウトしてください。"; "user_sessions_show_location_info" = "IPアドレスを表示"; "user_sessions_hide_location_info" = "IPアドレスを表示しない"; "user_sessions_overview_current_session_section_title" = "現在のセッション"; @@ -2098,7 +2098,7 @@ "user_other_session_security_recommendation_title" = "その他のセッション"; "user_other_session_unverified_sessions_header_subtitle" = "セッションを認証すると、より安全なメッセージのやりとりが可能になります。見覚えのない、または使用していないセッションがあれば、サインアウトしましょう。"; "user_other_session_current_session_details" = "現在のセッション"; -"user_other_session_verified_sessions_header_subtitle" = "セキュリティーを最大限に高めるには、不明なセッションや利用していないセッションからサインアウトしてください。"; +"user_other_session_verified_sessions_header_subtitle" = "セキュリティーを最大限に高めるには、不明なセッションや使用していないセッションからサインアウトしてください。"; "user_other_session_filter" = "絞り込む"; "user_other_session_filter_menu_all" = "全てのセッション"; "user_other_session_filter_menu_verified" = "認証済"; @@ -2680,7 +2680,7 @@ "biometrics_desetup_disable_button_title_x" = "%@を無効にする"; "biometrics_desetup_title_x" = "%@を無効にする"; "pin_protection_kick_user_alert_message" = "多数のエラーが発生したため、ログアウトしました"; -"pin_protection_not_allowed_pin" = "セキュリティー上の理由で、このPINコードは利用できません。他のPINコードを試してください"; +"pin_protection_not_allowed_pin" = "セキュリティー上の理由で、このPINコードは使用できません。他のPINコードを試してください"; "pin_protection_settings_change_pin" = "PINコードを変更"; "pin_protection_settings_enabled_forced" = "PINコードが有効です"; "pin_protection_settings_section_footer" = "PINコードを再設定するには、再ログインして新しいコードを作成してください。"; From eeadfd06e88f9f52a56990be32e4346d7fa50f7b Mon Sep 17 00:00:00 2001 From: Szimszon Date: Mon, 20 Feb 2023 07:19:15 +0000 Subject: [PATCH 39/45] Translated using Weblate (Hungarian) Currently translated at 100.0% (2379 of 2379 strings) Translation: Element iOS/Element iOS Translate-URL: https://translate.element.io/projects/riot-ios/riot-ios/hu/ --- Riot/Assets/hu.lproj/Vector.strings | 1 + 1 file changed, 1 insertion(+) diff --git a/Riot/Assets/hu.lproj/Vector.strings b/Riot/Assets/hu.lproj/Vector.strings index 3b706c205..10761d695 100644 --- a/Riot/Assets/hu.lproj/Vector.strings +++ b/Riot/Assets/hu.lproj/Vector.strings @@ -2721,3 +2721,4 @@ "wysiwyg_composer_format_action_indent" = "Behúzás növelése"; "poll_history_detail_view_in_timeline" = "Szavazás megjelenítése az idővonalon"; "settings_push_rules_error" = "Hiba történt az értesítések beállításának frissítésekor. Próbáld meg az beállítást újra átkapcsolni."; +"authentication_qr_login_failure_device_not_supported" = "Ezzel az eszközzel való összeköttetés nem támogatott."; From d062041bef06b694796bc69c1b634ab0f97e6e8b Mon Sep 17 00:00:00 2001 From: Besnik Bleta Date: Sat, 18 Feb 2023 09:53:10 +0000 Subject: [PATCH 40/45] Translated using Weblate (Albanian) Currently translated at 99.6% (2370 of 2379 strings) Translation: Element iOS/Element iOS Translate-URL: https://translate.element.io/projects/riot-ios/riot-ios/sq/ --- Riot/Assets/sq.lproj/Vector.strings | 1 + 1 file changed, 1 insertion(+) diff --git a/Riot/Assets/sq.lproj/Vector.strings b/Riot/Assets/sq.lproj/Vector.strings index 4e6a4269e..0bf63c8d7 100644 --- a/Riot/Assets/sq.lproj/Vector.strings +++ b/Riot/Assets/sq.lproj/Vector.strings @@ -2711,3 +2711,4 @@ "wysiwyg_composer_format_action_un_indent" = "Zvogëlo shmangie kryeradhë"; "wysiwyg_composer_format_action_indent" = "Rrit shmangie kryeradhe"; "poll_history_detail_view_in_timeline" = "Shiheni pyetësorin në rrjedhë kohore"; +"authentication_qr_login_failure_device_not_supported" = "Nuk mbulohet lidhja me këtë pajisje."; From 201d7200cf96bef4b6a39b556b6bae9593c8b675 Mon Sep 17 00:00:00 2001 From: keda82 Date: Sat, 18 Feb 2023 11:24:46 +0000 Subject: [PATCH 41/45] Translated using Weblate (Swedish) Currently translated at 100.0% (2379 of 2379 strings) Translation: Element iOS/Element iOS Translate-URL: https://translate.element.io/projects/riot-ios/riot-ios/sv/ --- Riot/Assets/sv.lproj/Vector.strings | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Riot/Assets/sv.lproj/Vector.strings b/Riot/Assets/sv.lproj/Vector.strings index a8fa6cf8b..3720e301d 100644 --- a/Riot/Assets/sv.lproj/Vector.strings +++ b/Riot/Assets/sv.lproj/Vector.strings @@ -2661,7 +2661,7 @@ "settings_labs_confirm_crypto_sdk" = "Vänligen observera att den här funktionen fortfarande ska anses vara experimentell, den kanske inte fungerar som förväntat eller kan leda till okända konsekvenser. För att återgå, logga ut och logga sedan in igen. Använd på egen risk."; "settings_labs_enable_crypto_sdk" = "Totalsträckskryptering i Rust"; "accessibility_selected" = "vald"; -"settings_push_rules_error" = "Ett fel uppstod vid uppdatering av dina aviseringsinställningar. Vänligen försök att växla dina alternativ igen."; +"settings_push_rules_error" = "Ett fel uppstod vid uppdatering av dina aviseringsinställningar. Vänligen försök igen."; "wysiwyg_composer_format_action_un_indent" = "Minska indrag"; "wysiwyg_composer_format_action_indent" = "Öka indrag"; "poll_history_detail_view_in_timeline" = "Visa omröstning i tidslinje"; From 85a294e232542e5fea29463468284038cb568f43 Mon Sep 17 00:00:00 2001 From: Jon Arvid Date: Sat, 18 Feb 2023 10:04:40 +0000 Subject: [PATCH 42/45] Translated using Weblate (Swedish) Currently translated at 100.0% (2379 of 2379 strings) Translation: Element iOS/Element iOS Translate-URL: https://translate.element.io/projects/riot-ios/riot-ios/sv/ --- Riot/Assets/sv.lproj/Vector.strings | 1 + 1 file changed, 1 insertion(+) diff --git a/Riot/Assets/sv.lproj/Vector.strings b/Riot/Assets/sv.lproj/Vector.strings index 3720e301d..343877c48 100644 --- a/Riot/Assets/sv.lproj/Vector.strings +++ b/Riot/Assets/sv.lproj/Vector.strings @@ -2668,3 +2668,4 @@ "voice_broadcast_playback_unable_to_decrypt" = "Kunde inte avkryptera denna röstsändning."; "home_context_menu_mark_as_unread" = "Markera som oläst"; "key_backup_recover_from_private_key_progress" = "%@%% Färdig"; +"authentication_qr_login_failure_device_not_supported" = "Det finns inget stöd för att länka denna enhet."; From f0d91279291f747bbb34dd661009984fcd297acd Mon Sep 17 00:00:00 2001 From: Edgars Andersons Date: Mon, 20 Feb 2023 08:43:08 +0000 Subject: [PATCH 43/45] Translated using Weblate (Latvian) Currently translated at 44.0% (22 of 50 strings) Translation: Element iOS/Element iOS (Push) Translate-URL: https://translate.element.io/projects/riot-ios/riot-ios-push/lv/ --- Riot/Assets/lv.lproj/Localizable.strings | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Riot/Assets/lv.lproj/Localizable.strings b/Riot/Assets/lv.lproj/Localizable.strings index d1f7880f6..abd6219ad 100644 --- a/Riot/Assets/lv.lproj/Localizable.strings +++ b/Riot/Assets/lv.lproj/Localizable.strings @@ -44,3 +44,6 @@ "VOICE_CONF_NAMED_FROM_USER" = "Grupas zvans no %@: '%@'"; /* Incoming named video conference invite from a specific person */ "VIDEO_CONF_NAMED_FROM_USER" = "Grupas video zvans no %@: '%@'"; +/** General **/ + +"Notification" = "Paziņojums"; From f39b921f6515b65433bb5a2ec3d64bd3dc8fc7d1 Mon Sep 17 00:00:00 2001 From: Mauro Romito Date: Tue, 21 Feb 2023 14:54:24 +0100 Subject: [PATCH 44/45] changelog.d: Upgrade MatrixSDK version ([v0.25.2](https://github.com/matrix-org/matrix-ios-sdk/releases/tag/v0.25.2)). --- Podfile | 2 +- changelog.d/x-nolink-0.change | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) create mode 100644 changelog.d/x-nolink-0.change diff --git a/Podfile b/Podfile index d910bb9e6..50655ecd5 100644 --- a/Podfile +++ b/Podfile @@ -16,7 +16,7 @@ use_frameworks! # - `{ :specHash => {sdk spec hash}` to depend on specific pod options (:git => …, :podspec => …) for MatrixSDK repo. Used by Fastfile during CI # # Warning: our internal tooling depends on the name of this variable name, so be sure not to change it -$matrixSDKVersion = '= 0.25.1' +$matrixSDKVersion = '= 0.25.2' # $matrixSDKVersion = :local # $matrixSDKVersion = { :branch => 'develop'} # $matrixSDKVersion = { :specHash => { git: 'https://git.io/fork123', branch: 'fix' } } diff --git a/changelog.d/x-nolink-0.change b/changelog.d/x-nolink-0.change new file mode 100644 index 000000000..d449490b1 --- /dev/null +++ b/changelog.d/x-nolink-0.change @@ -0,0 +1 @@ +Upgrade MatrixSDK version ([v0.25.2](https://github.com/matrix-org/matrix-ios-sdk/releases/tag/v0.25.2)). \ No newline at end of file From 967d95d719296eaf03284a5c67eefec9357f7614 Mon Sep 17 00:00:00 2001 From: Mauro Romito Date: Tue, 21 Feb 2023 14:54:24 +0100 Subject: [PATCH 45/45] version++ --- CHANGES.md | 19 +++++++++++++++++++ changelog.d/7217.bugfix | 1 - changelog.d/7325.bugfix | 1 - changelog.d/7326.bugfix | 1 - changelog.d/pr-7353.change | 1 - changelog.d/pr-7356.change | 1 - changelog.d/pr-7361.change | 1 - changelog.d/pr-7369.change | 1 - changelog.d/pr-7370.change | 1 - changelog.d/pr-7372.change | 1 - changelog.d/x-nolink-0.change | 1 - 11 files changed, 19 insertions(+), 10 deletions(-) delete mode 100644 changelog.d/7217.bugfix delete mode 100644 changelog.d/7325.bugfix delete mode 100644 changelog.d/7326.bugfix delete mode 100644 changelog.d/pr-7353.change delete mode 100644 changelog.d/pr-7356.change delete mode 100644 changelog.d/pr-7361.change delete mode 100644 changelog.d/pr-7369.change delete mode 100644 changelog.d/pr-7370.change delete mode 100644 changelog.d/pr-7372.change delete mode 100644 changelog.d/x-nolink-0.change diff --git a/CHANGES.md b/CHANGES.md index 35fea8834..7e1ebc95d 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,3 +1,22 @@ +## Changes in 1.10.3 (2023-02-21) + +🙌 Improvements + +- Polls: add fallback text for poll ended events. ([#7353](https://github.com/vector-im/element-ios/pull/7353)) +- Push Rules: Apply push rules client side for encrypted rooms, hiding in case of dont_notify action ([#7356](https://github.com/vector-im/element-ios/pull/7356)) +- Map Views: Show own location in map views ([#7361](https://github.com/vector-im/element-ios/pull/7361)) +- Do not reset device keys if migrating to CryptoSDK ([#7369](https://github.com/vector-im/element-ios/pull/7369)) +- Labs: Rich Text Editor: Update to version 1.1.1 ([#7370](https://github.com/vector-im/element-ios/pull/7370)) +- Updates to protocol used for Sign in with QR code. ([#7372](https://github.com/vector-im/element-ios/pull/7372)) +- Upgrade MatrixSDK version ([v0.25.2](https://github.com/matrix-org/matrix-ios-sdk/releases/tag/v0.25.2)). + +🐛 Bugfixes + +- A voice message is now replayable. ([#7217](https://github.com/vector-im/element-ios/issues/7217)) +- Fix an issue where a voice message recording was failing. ([#7325](https://github.com/vector-im/element-ios/issues/7325)) +- Fix an issue where a voice message disappears after being sent. ([#7326](https://github.com/vector-im/element-ios/issues/7326)) + + ## Changes in 1.10.2 (2023-02-10) 🐛 Bugfixes diff --git a/changelog.d/7217.bugfix b/changelog.d/7217.bugfix deleted file mode 100644 index aaa993008..000000000 --- a/changelog.d/7217.bugfix +++ /dev/null @@ -1 +0,0 @@ -A voice message is now replayable. diff --git a/changelog.d/7325.bugfix b/changelog.d/7325.bugfix deleted file mode 100644 index 5edaaba0d..000000000 --- a/changelog.d/7325.bugfix +++ /dev/null @@ -1 +0,0 @@ -Fix an issue where a voice message recording was failing. diff --git a/changelog.d/7326.bugfix b/changelog.d/7326.bugfix deleted file mode 100644 index f9a6891c8..000000000 --- a/changelog.d/7326.bugfix +++ /dev/null @@ -1 +0,0 @@ -Fix an issue where a voice message disappears after being sent. diff --git a/changelog.d/pr-7353.change b/changelog.d/pr-7353.change deleted file mode 100644 index ac7539cc7..000000000 --- a/changelog.d/pr-7353.change +++ /dev/null @@ -1 +0,0 @@ -Polls: add fallback text for poll ended events. \ No newline at end of file diff --git a/changelog.d/pr-7356.change b/changelog.d/pr-7356.change deleted file mode 100644 index d0f12bb92..000000000 --- a/changelog.d/pr-7356.change +++ /dev/null @@ -1 +0,0 @@ -Push Rules: Apply push rules client side for encrypted rooms, hiding in case of dont_notify action \ No newline at end of file diff --git a/changelog.d/pr-7361.change b/changelog.d/pr-7361.change deleted file mode 100644 index d5927663d..000000000 --- a/changelog.d/pr-7361.change +++ /dev/null @@ -1 +0,0 @@ -Map Views: Show own location in map views \ No newline at end of file diff --git a/changelog.d/pr-7369.change b/changelog.d/pr-7369.change deleted file mode 100644 index a38028df9..000000000 --- a/changelog.d/pr-7369.change +++ /dev/null @@ -1 +0,0 @@ -Do not reset device keys if migrating to CryptoSDK diff --git a/changelog.d/pr-7370.change b/changelog.d/pr-7370.change deleted file mode 100644 index 6a53bd879..000000000 --- a/changelog.d/pr-7370.change +++ /dev/null @@ -1 +0,0 @@ -Labs: Rich Text Editor: Update to version 1.1.1 diff --git a/changelog.d/pr-7372.change b/changelog.d/pr-7372.change deleted file mode 100644 index 66791b73f..000000000 --- a/changelog.d/pr-7372.change +++ /dev/null @@ -1 +0,0 @@ -Updates to protocol used for Sign in with QR code. diff --git a/changelog.d/x-nolink-0.change b/changelog.d/x-nolink-0.change deleted file mode 100644 index d449490b1..000000000 --- a/changelog.d/x-nolink-0.change +++ /dev/null @@ -1 +0,0 @@ -Upgrade MatrixSDK version ([v0.25.2](https://github.com/matrix-org/matrix-ios-sdk/releases/tag/v0.25.2)). \ No newline at end of file