mirror of
https://gitlab.opencode.de/bwi/bundesmessenger/clients/bundesmessenger-ios.git
synced 2026-05-02 14:16:59 +02:00
Release 1.9.12 (#7081)
* Update voice broadcast tiles UI (#6965) * Translated using Weblate (German) Currently translated at 100.0% (2307 of 2307 strings) Translation: Element iOS/Element iOS Translate-URL: https://translate.element.io/projects/riot-ios/riot-ios/de/ * speeding the animation a bit * tests and identifier improvements * fix * changelog * removed unused code * Avoid unnecessary send state request (#6970) * comment * Curate MXCrypto protocol methods * Add voice broadcast initial state in bubble data (#6972) - Add voice broadcast initial state in bubble data - Remove the local record after sending * Voice Broadcast: log and block unexpected state change * Sing out bottom bar * new line * Enable WYSIWYG plain text support * Remove change on Apple swift-collections revision * removed RiotSettings a non RiotSwiftUI reference from the ViewState code * fixed a test * Complete MXCryptoV2 implementation * Multi session logut * Switch the CI to code 14 and the iOS 14 simulator, fix UI tests * Fixes #6987 - Prevent ZXing from unnecessarily requesting camera access * Fixes #6988 - Prevent actor switching when tearing down the rendezvous * Separator fix * Removed warnings * add Z-Labs tag or rich text editor and update to the new label naming * changelog * Hide old sessions list when the new dm is enabled * Add changelog.d file * Sing out filtering * Avoid simultaneous state changes (#6986) * Improve kebab menu in UserSessionOverview * Add UI tests * Add changelog.d file * No customization for emptycell (#7000) * PSG-976 Exclude current session from security recommendations and other sessions * Padding fix * Fixed unit tests * Add empty onLearnMoreAction closure * Add InfoView skeleton * Add UserSessionOverviewViewBindings * Style info view * Add bottom sheet modifier * Localise content * Add inactive sessions copy * Fix bug in InlineTextButton * Improve UserSessionCardView * Add “learn more” button in UserOtherSessions * Show bottom sheet in user other sessions * Show rename info alert * Refine UX * Add iOS 15- fallback * Refine InfoView * Add UI tests * Improve UserOtherSessionsUITests * Improve InlineTextButton API * Add changelod.d file * Fix failing UTs * Hide keyboard in UserSessionName * Add .viewSessionInfo view action * Voice Broadcast - BugFix - send the last chunk (#7002) * Voice Broadcast - BugFix - send the last chunk with the right sequence number - we reset now and teardown the service only after the last chunk is sent * updated package + tests * change log * Bug Fix : Crash if the room has avatar and voice broadcast tiles * Add MVVM-C for InfoSheet * improving UI tests for slow CI * removing comment * test improvements for slow ci * Show bottom sheet in other sessions screen * Show bottom sheet in rename session screen * Delete bottom sheet modifier * Show rename sheet * UI and unit tests * Refresh fix * Changelog * Add InfoSheet SwiftUI preview * simplify the test to make it pass on the CI * Fix memory leak * Cleanup UI tests * improving tests for the CI * Fixed IRC-style message and commands support in Rich text editor * tests updated for the CI * test improvements * removing a test that can't pass on the CI due to its speed * Changelog * CryptoV2 changes * Display crypto version * Voice broadcast - Disable the sleep mode during the recording until we are able to handle it Currently go to "sleep mode" pauses the voice broadcast recording * Add issue automation for the VoIP team * Renamed sign out to logout * Renamed sign out to logout * Renamed sign out to logout * Sign out of all other sessions * Fix typo in issue automation * Fixed unit tests * Translations update from Weblate (#7017) * Translated using Weblate (Portuguese (Brazil)) Currently translated at 100.0% (2311 of 2311 strings) Translation: Element iOS/Element iOS Translate-URL: https://translate.element.io/projects/riot-ios/riot-ios/pt_BR/ * Translated using Weblate (Estonian) Currently translated at 100.0% (2311 of 2311 strings) Translation: Element iOS/Element iOS Translate-URL: https://translate.element.io/projects/riot-ios/riot-ios/et/ * Translated using Weblate (German) Currently translated at 100.0% (2311 of 2311 strings) Translation: Element iOS/Element iOS Translate-URL: https://translate.element.io/projects/riot-ios/riot-ios/de/ * Translated using Weblate (German) Currently translated at 100.0% (2311 of 2311 strings) Translation: Element iOS/Element iOS Translate-URL: https://translate.element.io/projects/riot-ios/riot-ios/de/ * Translated using Weblate (Dutch) Currently translated at 100.0% (2311 of 2311 strings) Translation: Element iOS/Element iOS Translate-URL: https://translate.element.io/projects/riot-ios/riot-ios/nl/ Co-authored-by: lvre <7uu3qrbvm@relay.firefox.com> Co-authored-by: Priit Jõerüüt <riot@joeruut.com> Co-authored-by: Vri <element@vrifox.cc> Co-authored-by: Johan Smits <johan@smitsmail.net> * Prepare for new sprint * Prepare for new sprint * Threads: added support to read receipts (MSC3771) - Update after review * Threads: added support to notifications count (MSC3773) * Update RiotSwiftUI/Modules/UserSessions/UserOtherSessions/Test/UI/UserOtherSessionsUITests.swift Co-authored-by: aringenbach <80891108+aringenbach@users.noreply.github.com> * Update RiotSwiftUI/Modules/UserSessions/UserOtherSessions/Test/UI/UserOtherSessionsUITests.swift Co-authored-by: aringenbach <80891108+aringenbach@users.noreply.github.com> * Comment fix * the test may fail on CI without blocking the task/check * tests may fail on CI * test improvement * test may fail on CI * Hide push toggles for http pushers when there is no server support * changelog * Code review fixes * Threads: added support to read receipts (MSC3771) - Update after review * Synchronise composer and toolbar resizing animation duration * Add kResizeComposerAnimationDuration constant description * fix for 6946 * Threads: add support to labs flag for read receipts * Cleanup * Code review fixes, created DestructiveButton * Update issue automation Stop using deprecated ProjectNext API in favour of the new ProjectV2 one * Update PR automation Stop using deprecated ProjectNext API in favour of the new ProjectV2 one * Code review fixes * Map location info * Map location info * Add location feature in UserSessionsOverview * Add “show location” feature in other sessions list * Add “show location“ feature in session overview * Fix Package.resolved * Cleanup merge leftovers * Cleanup code * Cleanup * Add show/hide ip persistency * Add location info in UserOtherSessions * Refine settings logic * Mock settings in UserSessionsOverviewViewModel * Add settings service in UserOtherSessionsViewModel * Inject setting service in UserSessionOverviewViewModel * Add changelog.d file * Fix UTs * Cleanup merge leftovers * Add animations * Fix failing test * Amend title font * Amend copies * Device Manager: Session list item is not tappable everywhere * changelog * Threads notification count in main timeline including un participated threads * Changed title and body * Removed "Do not ask again" button * Remove indication about plain text mode coming soon * Prevent `Unable to activate constraint with anchors .. because they have no common ancestor.` crashes. Only link toasts to the top safe area instead of the navigation controller * Revert "Replace attributed string height calculation with a more reliable implementation" This reverts commit 81773cd1e515cc391c1f21b499f61141cb03c810. * Revert "Fix timeline items text height calculation" This reverts commit 8f9eddee501702de84192316bd5b2ff9512d681a. * Revert "Fixes vector-im/element-ios/issues/6441 - Incorrect timeline item text height calculation (#6679)" This reverts commit 405c2d8e324c08c1a40e037aeb3c54e93f30bc9f. * Fixes vector-im/element-ios/issues/6441 - Incorrect timeline item text height calculation * Prepare for new sprint * Refine bottom sheet layout * updated pod * changelog * Switch to using an API key for interactions with AppStoreConnect while on CI; update fastlane and dependencies * Rich-text editor: Fix text formatting enabled inconsistent state * Labs: Rich-text editor - Fix text formatting switch losing the current content of the composer * Re-order View computed properties and move to private mark * Add intrinsic sized bottom sheet * Snooze controller * Changelog * Fix composer view model tests * Rich-text editor: enable translations between Markdown and HTML when toggling text formatting * Force a layout on the room bubble cell messageTextView to get a correct frame * Move Move UserAgentParserTests * Add UserSessionDetailsUITests * Improve UserSessionNameUITests * Cleanup tests * Improve UserSessionNameViewModelTests * Test empty state for UserOtherSessions * Fix typo * Cleanup unused code * Add changelog.d file * Threads: removed "unread_thread_notifications" from sync filters for server that doesn't support MSC3773 * Remove 10s wait on failed initial sync * Threads: removed "unread_thread_notifications" from sync filters for server that doesn't support MSC3773 - Update after review * Revert "Device Manager: Session list item is not tappable everywhere" This reverts commit e6367cba4c8f0cb2cdfe5e3381dcbb7bc0f94c52. * Fixup session list item is not tappable everywhere * Fix accessibility id in UserOtherSessions * Poll not usable after logging out and back in * Changelog * Removed init * voice dictation now works * plain text * Add voice broadcast slider (#7010) * Display number of unread messages above threads button * Translations update from Weblate (#7080) * Translated using Weblate (Dutch) Currently translated at 100.0% (2311 of 2311 strings) Translation: Element iOS/Element iOS Translate-URL: https://translate.element.io/projects/riot-ios/riot-ios/nl/ * Translated using Weblate (German) Currently translated at 100.0% (2311 of 2311 strings) Translation: Element iOS/Element iOS Translate-URL: https://translate.element.io/projects/riot-ios/riot-ios/de/ * Translated using Weblate (Dutch) Currently translated at 100.0% (2312 of 2312 strings) Translation: Element iOS/Element iOS Translate-URL: https://translate.element.io/projects/riot-ios/riot-ios/nl/ * Translated using Weblate (German) Currently translated at 100.0% (2312 of 2312 strings) Translation: Element iOS/Element iOS Translate-URL: https://translate.element.io/projects/riot-ios/riot-ios/de/ * Translated using Weblate (Albanian) Currently translated at 99.6% (2303 of 2312 strings) Translation: Element iOS/Element iOS Translate-URL: https://translate.element.io/projects/riot-ios/riot-ios/sq/ * Translated using Weblate (Portuguese (Brazil)) Currently translated at 100.0% (2312 of 2312 strings) Translation: Element iOS/Element iOS Translate-URL: https://translate.element.io/projects/riot-ios/riot-ios/pt_BR/ * Translated using Weblate (Ukrainian) Currently translated at 100.0% (2312 of 2312 strings) Translation: Element iOS/Element iOS Translate-URL: https://translate.element.io/projects/riot-ios/riot-ios/uk/ * Translated using Weblate (Estonian) Currently translated at 100.0% (2312 of 2312 strings) Translation: Element iOS/Element iOS Translate-URL: https://translate.element.io/projects/riot-ios/riot-ios/et/ * Translated using Weblate (Indonesian) Currently translated at 100.0% (2312 of 2312 strings) Translation: Element iOS/Element iOS Translate-URL: https://translate.element.io/projects/riot-ios/riot-ios/id/ * Translated using Weblate (Slovak) Currently translated at 100.0% (2312 of 2312 strings) Translation: Element iOS/Element iOS Translate-URL: https://translate.element.io/projects/riot-ios/riot-ios/sk/ * Translated using Weblate (Italian) Currently translated at 100.0% (2312 of 2312 strings) Translation: Element iOS/Element iOS Translate-URL: https://translate.element.io/projects/riot-ios/riot-ios/it/ * Translated using Weblate (German) Currently translated at 100.0% (2313 of 2313 strings) Translation: Element iOS/Element iOS Translate-URL: https://translate.element.io/projects/riot-ios/riot-ios/de/ * Translated using Weblate (Ukrainian) Currently translated at 100.0% (2313 of 2313 strings) Translation: Element iOS/Element iOS Translate-URL: https://translate.element.io/projects/riot-ios/riot-ios/uk/ * Translated using Weblate (Indonesian) Currently translated at 100.0% (2313 of 2313 strings) Translation: Element iOS/Element iOS Translate-URL: https://translate.element.io/projects/riot-ios/riot-ios/id/ * Translated using Weblate (Dutch) Currently translated at 100.0% (2315 of 2315 strings) Translation: Element iOS/Element iOS Translate-URL: https://translate.element.io/projects/riot-ios/riot-ios/nl/ * Translated using Weblate (German) Currently translated at 100.0% (2315 of 2315 strings) Translation: Element iOS/Element iOS Translate-URL: https://translate.element.io/projects/riot-ios/riot-ios/de/ * Translated using Weblate (Hungarian) Currently translated at 100.0% (2315 of 2315 strings) Translation: Element iOS/Element iOS Translate-URL: https://translate.element.io/projects/riot-ios/riot-ios/hu/ * Translated using Weblate (Italian) Currently translated at 100.0% (2315 of 2315 strings) Translation: Element iOS/Element iOS Translate-URL: https://translate.element.io/projects/riot-ios/riot-ios/it/ * Translated using Weblate (Ukrainian) Currently translated at 100.0% (2315 of 2315 strings) Translation: Element iOS/Element iOS Translate-URL: https://translate.element.io/projects/riot-ios/riot-ios/uk/ * Translated using Weblate (Estonian) Currently translated at 100.0% (2315 of 2315 strings) Translation: Element iOS/Element iOS Translate-URL: https://translate.element.io/projects/riot-ios/riot-ios/et/ * Translated using Weblate (Indonesian) Currently translated at 100.0% (2315 of 2315 strings) Translation: Element iOS/Element iOS Translate-URL: https://translate.element.io/projects/riot-ios/riot-ios/id/ * Translated using Weblate (Slovak) Currently translated at 100.0% (2315 of 2315 strings) Translation: Element iOS/Element iOS Translate-URL: https://translate.element.io/projects/riot-ios/riot-ios/sk/ * Translated using Weblate (Portuguese (Brazil)) Currently translated at 100.0% (2315 of 2315 strings) Translation: Element iOS/Element iOS Translate-URL: https://translate.element.io/projects/riot-ios/riot-ios/pt_BR/ * Translated using Weblate (Albanian) Currently translated at 99.5% (2305 of 2315 strings) Translation: Element iOS/Element iOS Translate-URL: https://translate.element.io/projects/riot-ios/riot-ios/sq/ * Translated using Weblate (German) Currently translated at 100.0% (2317 of 2317 strings) Translation: Element iOS/Element iOS Translate-URL: https://translate.element.io/projects/riot-ios/riot-ios/de/ * Translated using Weblate (Japanese) Currently translated at 66.2% (1534 of 2317 strings) Translation: Element iOS/Element iOS Translate-URL: https://translate.element.io/projects/riot-ios/riot-ios/ja/ * Translated using Weblate (Portuguese (Brazil)) Currently translated at 100.0% (2317 of 2317 strings) Translation: Element iOS/Element iOS Translate-URL: https://translate.element.io/projects/riot-ios/riot-ios/pt_BR/ * Translated using Weblate (Ukrainian) Currently translated at 100.0% (2317 of 2317 strings) Translation: Element iOS/Element iOS Translate-URL: https://translate.element.io/projects/riot-ios/riot-ios/uk/ * Translated using Weblate (Estonian) Currently translated at 100.0% (2317 of 2317 strings) Translation: Element iOS/Element iOS Translate-URL: https://translate.element.io/projects/riot-ios/riot-ios/et/ * Translated using Weblate (Indonesian) Currently translated at 100.0% (2317 of 2317 strings) Translation: Element iOS/Element iOS Translate-URL: https://translate.element.io/projects/riot-ios/riot-ios/id/ * Translated using Weblate (Slovak) Currently translated at 100.0% (2317 of 2317 strings) Translation: Element iOS/Element iOS Translate-URL: https://translate.element.io/projects/riot-ios/riot-ios/sk/ * Translated using Weblate (Japanese) Currently translated at 66.2% (1534 of 2317 strings) Translation: Element iOS/Element iOS Translate-URL: https://translate.element.io/projects/riot-ios/riot-ios/ja/ * Translated using Weblate (German) Currently translated at 100.0% (2326 of 2326 strings) Translation: Element iOS/Element iOS Translate-URL: https://translate.element.io/projects/riot-ios/riot-ios/de/ * Translated using Weblate (Ukrainian) Currently translated at 100.0% (2326 of 2326 strings) Translation: Element iOS/Element iOS Translate-URL: https://translate.element.io/projects/riot-ios/riot-ios/uk/ * Translated using Weblate (Estonian) Currently translated at 100.0% (2326 of 2326 strings) Translation: Element iOS/Element iOS Translate-URL: https://translate.element.io/projects/riot-ios/riot-ios/et/ * Translated using Weblate (Ukrainian) Currently translated at 100.0% (2326 of 2326 strings) Translation: Element iOS/Element iOS Translate-URL: https://translate.element.io/projects/riot-ios/riot-ios/uk/ * Translated using Weblate (Estonian) Currently translated at 100.0% (2326 of 2326 strings) Translation: Element iOS/Element iOS Translate-URL: https://translate.element.io/projects/riot-ios/riot-ios/et/ * Translated using Weblate (German) Currently translated at 100.0% (2326 of 2326 strings) Translation: Element iOS/Element iOS Translate-URL: https://translate.element.io/projects/riot-ios/riot-ios/de/ * Translated using Weblate (Ukrainian) Currently translated at 100.0% (2326 of 2326 strings) Translation: Element iOS/Element iOS Translate-URL: https://translate.element.io/projects/riot-ios/riot-ios/uk/ * Translated using Weblate (Estonian) Currently translated at 100.0% (2326 of 2326 strings) Translation: Element iOS/Element iOS Translate-URL: https://translate.element.io/projects/riot-ios/riot-ios/et/ * Translated using Weblate (Slovak) Currently translated at 100.0% (2326 of 2326 strings) Translation: Element iOS/Element iOS Translate-URL: https://translate.element.io/projects/riot-ios/riot-ios/sk/ * Translated using Weblate (Italian) Currently translated at 100.0% (2326 of 2326 strings) Translation: Element iOS/Element iOS Translate-URL: https://translate.element.io/projects/riot-ios/riot-ios/it/ * Translated using Weblate (Hungarian) Currently translated at 100.0% (2326 of 2326 strings) Translation: Element iOS/Element iOS Translate-URL: https://translate.element.io/projects/riot-ios/riot-ios/hu/ * Translated using Weblate (Indonesian) Currently translated at 100.0% (2326 of 2326 strings) Translation: Element iOS/Element iOS Translate-URL: https://translate.element.io/projects/riot-ios/riot-ios/id/ * Translated using Weblate (French) Currently translated at 97.6% (2272 of 2326 strings) Translation: Element iOS/Element iOS Translate-URL: https://translate.element.io/projects/riot-ios/riot-ios/fr/ * Translated using Weblate (Russian) Currently translated at 80.5% (1873 of 2326 strings) Translation: Element iOS/Element iOS Translate-URL: https://translate.element.io/projects/riot-ios/riot-ios/ru/ * Translated using Weblate (Russian) Currently translated at 80.7% (1879 of 2326 strings) Translation: Element iOS/Element iOS Translate-URL: https://translate.element.io/projects/riot-ios/riot-ios/ru/ Co-authored-by: Roel ter Maat <roel.termaat@nedap.com> Co-authored-by: Vri <element@vrifox.cc> Co-authored-by: Besnik Bleta <besnik@programeshqip.org> Co-authored-by: lvre <7uu3qrbvm@relay.firefox.com> Co-authored-by: Ihor Hordiichuk <igor_ck@outlook.com> Co-authored-by: Priit Jõerüüt <riot@joeruut.com> Co-authored-by: Linerly <linerly@protonmail.com> Co-authored-by: Jozef Gaal <preklady@mayday.sk> Co-authored-by: random <dictionary@tutamail.com> Co-authored-by: Szimszon <github@oregpreshaz.eu> Co-authored-by: Suguru Hirahara <ovestekona@protonmail.com> Co-authored-by: Thibault Martin <mail@thibaultmart.in> Co-authored-by: Platon Terekhov <ockenfels_vevent@aleeas.com> * changelog.d: Upgrade MatrixSDK version ([v0.24.3](https://github.com/matrix-org/matrix-ios-sdk/releases/tag/v0.24.3)). * version++ Co-authored-by: giomfo <gforet@matrix.org> Co-authored-by: Yoan Pintas <y.pintas@gmail.com> Co-authored-by: Vri <element@vrifox.cc> Co-authored-by: Anderas <andyuhnak@gmail.com> Co-authored-by: Mauro Romito <mauro.romito@element.io> Co-authored-by: Velin92 <34335419+Velin92@users.noreply.github.com> Co-authored-by: Giom Foret <giom@matrix.org> Co-authored-by: Aleksandrs Proskurins <paleksandrs@gmail.com> Co-authored-by: aringenbach <arnaudr@element.io> Co-authored-by: manuroe <manuroe@users.noreply.github.com> Co-authored-by: David Langley <langley.dave@gmail.com> Co-authored-by: Stefan Ceriu <stefanc@matrix.org> Co-authored-by: Alfonso Grillo <alfogrillo@gmail.com> Co-authored-by: Alfonso Grillo <alfogrillo@element.io> Co-authored-by: Kat Gerasimova <ekaterinag@element.io> Co-authored-by: Element Translate Bot <admin@riot.im> Co-authored-by: lvre <7uu3qrbvm@relay.firefox.com> Co-authored-by: Priit Jõerüüt <riot@joeruut.com> Co-authored-by: Johan Smits <johan@smitsmail.net> Co-authored-by: gulekismail <ismailgulek0@gmail.com> Co-authored-by: Gil Eluard <gile@element.io> Co-authored-by: Aleksandrs Proskurins <aleksandrsp@element.io> Co-authored-by: aringenbach <80891108+aringenbach@users.noreply.github.com> Co-authored-by: Stefan Ceriu <stefan.ceriu@gmail.com> Co-authored-by: Roel ter Maat <roel.termaat@nedap.com> Co-authored-by: Besnik Bleta <besnik@programeshqip.org> Co-authored-by: Ihor Hordiichuk <igor_ck@outlook.com> Co-authored-by: Linerly <linerly@protonmail.com> Co-authored-by: Jozef Gaal <preklady@mayday.sk> Co-authored-by: random <dictionary@tutamail.com> Co-authored-by: Szimszon <github@oregpreshaz.eu> Co-authored-by: Suguru Hirahara <ovestekona@protonmail.com> Co-authored-by: Thibault Martin <mail@thibaultmart.in> Co-authored-by: Platon Terekhov <ockenfels_vevent@aleeas.com>
This commit is contained in:
+7
-1
@@ -39,7 +39,7 @@ final class UserSessionsOverviewCoordinator: Coordinator, Presentable {
|
||||
self.parameters = parameters
|
||||
service = parameters.service
|
||||
|
||||
viewModel = UserSessionsOverviewViewModel(userSessionsOverviewService: parameters.service)
|
||||
viewModel = UserSessionsOverviewViewModel(userSessionsOverviewService: parameters.service, settingsService: RiotSettings.shared)
|
||||
|
||||
hostingViewController = VectorHostingController(rootView: UserSessionsOverview(viewModel: viewModel.context))
|
||||
hostingViewController.vc_setLargeTitleDisplayMode(.never)
|
||||
@@ -71,6 +71,8 @@ final class UserSessionsOverviewCoordinator: Coordinator, Presentable {
|
||||
self.showUserSessionOverview(sessionInfo: sessionInfo)
|
||||
case .linkDevice:
|
||||
self.completion?(.linkDevice)
|
||||
case let .logoutFromUserSessions(sessionInfos: sessionInfos):
|
||||
self.completion?(.logoutFromUserSessions(sessionInfos: sessionInfos))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -79,6 +81,10 @@ final class UserSessionsOverviewCoordinator: Coordinator, Presentable {
|
||||
hostingViewController
|
||||
}
|
||||
|
||||
func refreshData() {
|
||||
viewModel.context.send(viewAction: .viewAppeared)
|
||||
}
|
||||
|
||||
// MARK: - Private
|
||||
|
||||
/// Show an activity indicator whilst loading.
|
||||
|
||||
+1
-1
@@ -51,7 +51,7 @@ enum MockUserSessionsOverviewScreenState: MockScreenState, CaseIterable {
|
||||
fatalError()
|
||||
}
|
||||
|
||||
let viewModel = UserSessionsOverviewViewModel(userSessionsOverviewService: service)
|
||||
let viewModel = UserSessionsOverviewViewModel(userSessionsOverviewService: service, settingsService: MockUserSessionSettings())
|
||||
|
||||
return (
|
||||
[service, viewModel],
|
||||
|
||||
+1
-1
@@ -25,7 +25,7 @@ class UserSessionsOverviewService: UserSessionsOverviewServiceProtocol {
|
||||
private var cancellables: Set<AnyCancellable> = []
|
||||
|
||||
private(set) var overviewDataPublisher: CurrentValueSubject<UserSessionsOverviewData, Never>
|
||||
private(set) var sessionInfos: [UserSessionInfo]
|
||||
private var sessionInfos: [UserSessionInfo]
|
||||
|
||||
init(dataProvider: UserSessionsDataProviderProtocol) {
|
||||
self.dataProvider = dataProvider
|
||||
|
||||
-8
@@ -26,22 +26,14 @@ struct UserSessionsOverviewData {
|
||||
|
||||
protocol UserSessionsOverviewServiceProtocol {
|
||||
var overviewDataPublisher: CurrentValueSubject<UserSessionsOverviewData, Never> { get }
|
||||
var sessionInfos: [UserSessionInfo] { get }
|
||||
|
||||
func updateOverviewData(completion: @escaping (Result<UserSessionsOverviewData, Error>) -> Void) -> Void
|
||||
|
||||
func sessionForIdentifier(_ sessionId: String) -> UserSessionInfo?
|
||||
}
|
||||
|
||||
extension UserSessionsOverviewServiceProtocol {
|
||||
/// The user's current session.
|
||||
var currentSession: UserSessionInfo? { overviewDataPublisher.value.currentSession }
|
||||
/// Any unverified sessions on the user's account.
|
||||
var unverifiedSessions: [UserSessionInfo] { overviewDataPublisher.value.unverifiedSessions }
|
||||
/// Any inactive sessions on the user's account (not seen for a while).
|
||||
var inactiveSessions: [UserSessionInfo] { overviewDataPublisher.value.inactiveSessions }
|
||||
/// Any sessions that are verified and have been seen recently.
|
||||
var otherSessions: [UserSessionInfo] { overviewDataPublisher.value.otherSessions }
|
||||
/// Whether it is possible to link a new device via a QR code.
|
||||
var linkDeviceEnabled: Bool { overviewDataPublisher.value.linkDeviceEnabled }
|
||||
}
|
||||
|
||||
+4
-22
@@ -23,17 +23,14 @@ class UserSessionsOverviewUITests: MockScreenTestCase {
|
||||
|
||||
XCTAssertTrue(app.buttons["userSessionCardVerifyButton"].exists)
|
||||
XCTAssertTrue(app.staticTexts["userSessionCardViewDetails"].exists)
|
||||
|
||||
verifyLinkDeviceButtonStatus(true)
|
||||
}
|
||||
|
||||
func testCurrentSessionVerified() {
|
||||
app.goToScreenWithIdentifier(MockUserSessionsOverviewScreenState.currentSessionVerified.title)
|
||||
|
||||
XCTAssertFalse(app.buttons["userSessionCardVerifyButton"].exists)
|
||||
XCTAssertTrue(app.staticTexts["userSessionCardViewDetails"].exists)
|
||||
|
||||
verifyLinkDeviceButtonStatus(true)
|
||||
app.buttons["MoreOptionsMenu"].tap()
|
||||
XCTAssertTrue(app.buttons["Sign out of all other sessions"].exists)
|
||||
}
|
||||
|
||||
func testOnlyUnverifiedSessions() {
|
||||
@@ -41,8 +38,6 @@ class UserSessionsOverviewUITests: MockScreenTestCase {
|
||||
|
||||
XCTAssertTrue(app.staticTexts["userSessionsOverviewSecurityRecommendationsSection"].exists)
|
||||
XCTAssertTrue(app.staticTexts["userSessionsOverviewOtherSection"].exists)
|
||||
|
||||
verifyLinkDeviceButtonStatus(false)
|
||||
}
|
||||
|
||||
func testOnlyInactiveSessions() {
|
||||
@@ -50,8 +45,6 @@ class UserSessionsOverviewUITests: MockScreenTestCase {
|
||||
|
||||
XCTAssertTrue(app.staticTexts["userSessionsOverviewSecurityRecommendationsSection"].exists)
|
||||
XCTAssertTrue(app.staticTexts["userSessionsOverviewOtherSection"].exists)
|
||||
|
||||
verifyLinkDeviceButtonStatus(false)
|
||||
}
|
||||
|
||||
func testNoOtherSessions() {
|
||||
@@ -59,19 +52,8 @@ class UserSessionsOverviewUITests: MockScreenTestCase {
|
||||
|
||||
XCTAssertFalse(app.staticTexts["userSessionsOverviewSecurityRecommendationsSection"].exists)
|
||||
XCTAssertFalse(app.staticTexts["userSessionsOverviewOtherSection"].exists)
|
||||
|
||||
verifyLinkDeviceButtonStatus(false)
|
||||
}
|
||||
|
||||
func verifyLinkDeviceButtonStatus(_ enabled: Bool) {
|
||||
// if enabled {
|
||||
// let linkDeviceButton = app.buttons["linkDeviceButton"]
|
||||
// XCTAssertTrue(linkDeviceButton.exists)
|
||||
// XCTAssertTrue(linkDeviceButton.isEnabled)
|
||||
// } else {
|
||||
// let linkDeviceButton = app.buttons["linkDeviceButton"]
|
||||
// XCTAssertFalse(linkDeviceButton.exists)
|
||||
// }
|
||||
app.buttons["MoreOptionsMenu"].tap()
|
||||
XCTAssertFalse(app.buttons["Sign out of all other sessions"].exists)
|
||||
}
|
||||
|
||||
func testWhenMoreThan5OtherSessionsThenViewAllButtonVisible() {
|
||||
|
||||
-46
@@ -65,50 +65,4 @@ class UserSessionListItemViewDataFactoryTests: XCTestCase {
|
||||
XCTAssertEqual(sessionDetailsUnknown, VectorL10n.userSessionVerificationUnknownShort,
|
||||
"The details should only show the verification state when no timestamp exists.")
|
||||
}
|
||||
|
||||
func testCurrentSessionDetailsWithTimestamp() {
|
||||
// Given other devices in each of the verification states.
|
||||
let sessionInfoVerified = UserSessionInfo.mockPhone(verificationState: .verified, isCurrent: true)
|
||||
let sessionInfoUnverified = UserSessionInfo.mockPhone(verificationState: .unverified, isCurrent: true)
|
||||
let sessionInfoUnknown = UserSessionInfo.mockPhone(verificationState: .unknown, isCurrent: true)
|
||||
|
||||
// When getting session details for each of them.
|
||||
let sessionDetailsVerified = factory.create(from: sessionInfoVerified).sessionDetails
|
||||
let sessionDetailsUnverified = factory.create(from: sessionInfoUnverified).sessionDetails
|
||||
let sessionDetailsUnknown = factory.create(from: sessionInfoUnknown).sessionDetails
|
||||
|
||||
// Then the details should be formatted correctly.
|
||||
XCTAssertEqual(sessionDetailsVerified,
|
||||
VectorL10n.userSessionItemDetails(VectorL10n.userSessionVerifiedShort, VectorL10n.userOtherSessionCurrentSessionDetails),
|
||||
"The details should show as verified with a current session string when verified.")
|
||||
XCTAssertEqual(sessionDetailsUnverified,
|
||||
VectorL10n.userSessionItemDetails(VectorL10n.userSessionUnverifiedShort, VectorL10n.userOtherSessionCurrentSessionDetails),
|
||||
"The details should show as unverified with a current session string when unverified.")
|
||||
XCTAssertEqual(sessionDetailsUnknown,
|
||||
VectorL10n.userOtherSessionCurrentSessionDetails,
|
||||
"The details should only show the current session string when verification is unknown.")
|
||||
}
|
||||
|
||||
func testCurrentSessionDetailsVerifiedWithoutTimestamp() {
|
||||
// Given a verified other device
|
||||
let sessionInfoVerified = UserSessionInfo.mockPhone(hasTimestamp: false, isCurrent: true)
|
||||
let sessionInfoUnverified = UserSessionInfo.mockPhone(verificationState: .unverified, hasTimestamp: false, isCurrent: true)
|
||||
let sessionInfoUnknown = UserSessionInfo.mockPhone(verificationState: .unknown, hasTimestamp: false, isCurrent: true)
|
||||
|
||||
// When getting session details
|
||||
let sessionDetailsVerified = factory.create(from: sessionInfoVerified).sessionDetails
|
||||
let sessionDetailsUnverified = factory.create(from: sessionInfoUnverified).sessionDetails
|
||||
let sessionDetailsUnknown = factory.create(from: sessionInfoUnknown).sessionDetails
|
||||
|
||||
// Then the details should contain the verification state and a last seen date.
|
||||
XCTAssertEqual(sessionDetailsVerified,
|
||||
VectorL10n.userSessionItemDetails(VectorL10n.userSessionVerifiedShort, VectorL10n.userOtherSessionCurrentSessionDetails),
|
||||
"The details should show as verified with a current session string when verified.")
|
||||
XCTAssertEqual(sessionDetailsUnverified,
|
||||
VectorL10n.userSessionItemDetails(VectorL10n.userSessionUnverifiedShort, VectorL10n.userOtherSessionCurrentSessionDetails),
|
||||
"The details should show as unverified with a current session string when unverified.")
|
||||
XCTAssertEqual(sessionDetailsUnknown,
|
||||
VectorL10n.userOtherSessionCurrentSessionDetails,
|
||||
"The details should only show the current session string when verification is unknown.")
|
||||
}
|
||||
}
|
||||
|
||||
+4
-4
@@ -21,7 +21,7 @@ import XCTest
|
||||
|
||||
class UserSessionsOverviewViewModelTests: XCTestCase {
|
||||
func testInitialStateEmpty() {
|
||||
let viewModel = UserSessionsOverviewViewModel(userSessionsOverviewService: MockUserSessionsOverviewService())
|
||||
let viewModel = UserSessionsOverviewViewModel(userSessionsOverviewService: MockUserSessionsOverviewService(), settingsService: MockUserSessionSettings())
|
||||
|
||||
XCTAssertNil(viewModel.state.currentSessionViewData)
|
||||
XCTAssertTrue(viewModel.state.unverifiedSessionsViewData.isEmpty)
|
||||
@@ -31,7 +31,7 @@ class UserSessionsOverviewViewModelTests: XCTestCase {
|
||||
}
|
||||
|
||||
func testLoadOnDidAppear() {
|
||||
let viewModel = UserSessionsOverviewViewModel(userSessionsOverviewService: MockUserSessionsOverviewService())
|
||||
let viewModel = UserSessionsOverviewViewModel(userSessionsOverviewService: MockUserSessionsOverviewService(), settingsService: MockUserSessionSettings())
|
||||
viewModel.process(viewAction: .viewAppeared)
|
||||
|
||||
XCTAssertNotNil(viewModel.state.currentSessionViewData)
|
||||
@@ -42,7 +42,7 @@ class UserSessionsOverviewViewModelTests: XCTestCase {
|
||||
}
|
||||
|
||||
func testSimpleActionProcessing() {
|
||||
let viewModel = UserSessionsOverviewViewModel(userSessionsOverviewService: MockUserSessionsOverviewService())
|
||||
let viewModel = UserSessionsOverviewViewModel(userSessionsOverviewService: MockUserSessionsOverviewService(), settingsService: MockUserSessionSettings())
|
||||
|
||||
var result: UserSessionsOverviewViewModelResult?
|
||||
viewModel.completion = { action in
|
||||
@@ -69,7 +69,7 @@ class UserSessionsOverviewViewModelTests: XCTestCase {
|
||||
let service = MockUserSessionsOverviewService()
|
||||
service.updateOverviewData { _ in }
|
||||
|
||||
let viewModel = UserSessionsOverviewViewModel(userSessionsOverviewService: service)
|
||||
let viewModel = UserSessionsOverviewViewModel(userSessionsOverviewService: service, settingsService: MockUserSessionSettings())
|
||||
|
||||
var result: UserSessionsOverviewViewModelResult?
|
||||
viewModel.completion = { action in
|
||||
|
||||
+5
-5
@@ -25,6 +25,7 @@ enum UserSessionsOverviewCoordinatorResult {
|
||||
case openSessionOverview(sessionInfo: UserSessionInfo)
|
||||
case openOtherSessions(sessionInfos: [UserSessionInfo], filter: UserOtherSessionsFilter)
|
||||
case linkDevice
|
||||
case logoutFromUserSessions(sessionInfos: [UserSessionInfo])
|
||||
}
|
||||
|
||||
// MARK: View model
|
||||
@@ -37,22 +38,19 @@ enum UserSessionsOverviewViewModelResult: Equatable {
|
||||
case showCurrentSessionOverview(sessionInfo: UserSessionInfo)
|
||||
case showUserSessionOverview(sessionInfo: UserSessionInfo)
|
||||
case linkDevice
|
||||
case logoutFromUserSessions(sessionInfos: [UserSessionInfo])
|
||||
}
|
||||
|
||||
// MARK: View
|
||||
|
||||
struct UserSessionsOverviewViewState: BindableState {
|
||||
var currentSessionViewData: UserSessionCardViewData?
|
||||
|
||||
var unverifiedSessionsViewData = [UserSessionListItemViewData]()
|
||||
|
||||
var inactiveSessionsViewData = [UserSessionListItemViewData]()
|
||||
|
||||
var otherSessionsViewData = [UserSessionListItemViewData]()
|
||||
|
||||
var showLoadingIndicator = false
|
||||
|
||||
var linkDeviceButtonVisible = false
|
||||
var showLocationInfo: Bool
|
||||
}
|
||||
|
||||
enum UserSessionsOverviewViewAction {
|
||||
@@ -66,4 +64,6 @@ enum UserSessionsOverviewViewAction {
|
||||
case viewAllOtherSessions
|
||||
case tapUserSession(_ sessionId: String)
|
||||
case linkDevice
|
||||
case logoutOtherSessions
|
||||
case showLocationInfo
|
||||
}
|
||||
|
||||
+15
-3
@@ -20,19 +20,26 @@ typealias UserSessionsOverviewViewModelType = StateStoreViewModel<UserSessionsOv
|
||||
|
||||
class UserSessionsOverviewViewModel: UserSessionsOverviewViewModelType, UserSessionsOverviewViewModelProtocol {
|
||||
private let userSessionsOverviewService: UserSessionsOverviewServiceProtocol
|
||||
private let settingsService: UserSessionSettingsProtocol
|
||||
|
||||
var completion: ((UserSessionsOverviewViewModelResult) -> Void)?
|
||||
|
||||
init(userSessionsOverviewService: UserSessionsOverviewServiceProtocol) {
|
||||
init(userSessionsOverviewService: UserSessionsOverviewServiceProtocol, settingsService: UserSessionSettingsProtocol) {
|
||||
self.userSessionsOverviewService = userSessionsOverviewService
|
||||
self.settingsService = settingsService
|
||||
|
||||
super.init(initialViewState: .init())
|
||||
super.init(initialViewState: .init(showLocationInfo: settingsService.showIPAddressesInSessionsManager))
|
||||
|
||||
userSessionsOverviewService.overviewDataPublisher.sink { [weak self] overviewData in
|
||||
self?.updateViewState(with: overviewData)
|
||||
}
|
||||
.store(in: &cancellables)
|
||||
|
||||
self.settingsService
|
||||
.showIPAddressesInSessionsManagerPublisher
|
||||
.weakAssign(to: \.state.showLocationInfo, on: self)
|
||||
.store(in: &cancellables)
|
||||
|
||||
updateViewState(with: userSessionsOverviewService.overviewDataPublisher.value)
|
||||
}
|
||||
|
||||
@@ -76,6 +83,11 @@ class UserSessionsOverviewViewModel: UserSessionsOverviewViewModelType, UserSess
|
||||
completion?(.showUserSessionOverview(sessionInfo: session))
|
||||
case .linkDevice:
|
||||
completion?(.linkDevice)
|
||||
case .logoutOtherSessions:
|
||||
completion?(.logoutFromUserSessions(sessionInfos: userSessionsOverviewService.otherSessions))
|
||||
case .showLocationInfo:
|
||||
settingsService.showIPAddressesInSessionsManager.toggle()
|
||||
state.showLocationInfo = settingsService.showIPAddressesInSessionsManager
|
||||
}
|
||||
}
|
||||
|
||||
@@ -109,7 +121,7 @@ class UserSessionsOverviewViewModel: UserSessionsOverviewViewModelType, UserSess
|
||||
}
|
||||
|
||||
private func showSessions(filteredBy filter: UserOtherSessionsFilter) {
|
||||
completion?(.showOtherSessions(sessionInfos: userSessionsOverviewService.sessionInfos,
|
||||
completion?(.showOtherSessions(sessionInfos: userSessionsOverviewService.otherSessions,
|
||||
filter: filter))
|
||||
}
|
||||
}
|
||||
|
||||
+49
-40
@@ -17,19 +17,13 @@
|
||||
import SwiftUI
|
||||
|
||||
struct UserSessionListItem: View {
|
||||
private enum LayoutConstants {
|
||||
static let horizontalPadding: CGFloat = 15
|
||||
static let verticalPadding: CGFloat = 16
|
||||
static let avatarWidth: CGFloat = 40
|
||||
static let avatarRightMargin: CGFloat = 18
|
||||
}
|
||||
|
||||
@Environment(\.theme) private var theme: ThemeSwiftUI
|
||||
|
||||
let viewData: UserSessionListItemViewData
|
||||
let showsLocationInfo: Bool
|
||||
|
||||
var isSeparatorHidden = false
|
||||
var isEditModeEnabled = false
|
||||
|
||||
var onBackgroundTap: ((String) -> Void)?
|
||||
var onBackgroundLongPress: ((String) -> Void)?
|
||||
|
||||
@@ -42,39 +36,47 @@ struct UserSessionListItem: View {
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
.padding(4)
|
||||
}
|
||||
VStack(alignment: .leading, spacing: LayoutConstants.verticalPadding) {
|
||||
HStack(spacing: LayoutConstants.avatarRightMargin) {
|
||||
if isEditModeEnabled {
|
||||
Image(viewData.isSelected ? Asset.Images.userSessionListItemSelected.name : Asset.Images.userSessionListItemNotSelected.name)
|
||||
}
|
||||
DeviceAvatarView(viewData: viewData.deviceAvatarViewData, isSelected: viewData.isSelected)
|
||||
VStack(alignment: .leading, spacing: 2) {
|
||||
Text(viewData.sessionName)
|
||||
.font(theme.fonts.bodySB)
|
||||
.foregroundColor(theme.colors.primaryContent)
|
||||
.multilineTextAlignment(.leading)
|
||||
HStack {
|
||||
if let sessionDetailsIcon = viewData.sessionDetailsIcon {
|
||||
Image(sessionDetailsIcon)
|
||||
.padding(.leading, 2)
|
||||
}
|
||||
Text(viewData.sessionDetails)
|
||||
.font(theme.fonts.caption1)
|
||||
.foregroundColor(viewData.highlightSessionDetails ? theme.colors.alert : theme.colors.secondaryContent)
|
||||
.multilineTextAlignment(.leading)
|
||||
}
|
||||
}
|
||||
HStack {
|
||||
if isEditModeEnabled {
|
||||
Image(viewData.isSelected ? Asset.Images.userSessionListItemSelected.name : Asset.Images.userSessionListItemNotSelected.name)
|
||||
}
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
.padding(.horizontal, LayoutConstants.horizontalPadding)
|
||||
|
||||
// Separator
|
||||
// Note: Separator leading is matching the text leading, we could use alignment guide in the future
|
||||
SeparatorLine()
|
||||
.padding(.leading, LayoutConstants.horizontalPadding + LayoutConstants.avatarRightMargin + LayoutConstants.avatarWidth)
|
||||
DeviceAvatarView(viewData: viewData.deviceAvatarViewData, isSelected: viewData.isSelected)
|
||||
VStack(alignment: .leading, spacing: 0) {
|
||||
Text(viewData.sessionName)
|
||||
.font(theme.fonts.bodySB)
|
||||
.foregroundColor(theme.colors.primaryContent)
|
||||
.multilineTextAlignment(.leading)
|
||||
.padding(.top, 16)
|
||||
.padding(.bottom, 2)
|
||||
.padding(.trailing, 16)
|
||||
HStack {
|
||||
if let sessionDetailsIcon = viewData.sessionDetailsIcon {
|
||||
Image(sessionDetailsIcon)
|
||||
.padding(.leading, 2)
|
||||
}
|
||||
VStack(alignment: .leading, spacing: 0) {
|
||||
Text(viewData.sessionDetails)
|
||||
|
||||
if showsLocationInfo, let ipText = ipText {
|
||||
Text(ipText)
|
||||
}
|
||||
}
|
||||
.font(theme.fonts.caption1)
|
||||
.foregroundColor(theme.colors.secondaryContent)
|
||||
.multilineTextAlignment(.leading)
|
||||
}
|
||||
.padding(.bottom, 16)
|
||||
.padding(.trailing, 16)
|
||||
SeparatorLine()
|
||||
.isHidden(isSeparatorHidden)
|
||||
}
|
||||
.padding(.leading, 7)
|
||||
}
|
||||
.padding(.top, LayoutConstants.verticalPadding)
|
||||
}.onTapGesture {
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
.padding(.leading, 16)
|
||||
}
|
||||
.contentShape(Rectangle())
|
||||
.onTapGesture {
|
||||
onBackgroundTap?(viewData.sessionId)
|
||||
}
|
||||
.onLongPressGesture {
|
||||
@@ -84,6 +86,13 @@ struct UserSessionListItem: View {
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
.accessibilityIdentifier("UserSessionListItem_\(viewData.sessionId)")
|
||||
}
|
||||
|
||||
private var ipText: String? {
|
||||
guard let lastSeenIp = viewData.lastSeenIP, !lastSeenIp.isEmpty else {
|
||||
return nil
|
||||
}
|
||||
return viewData.lastSeenIPLocation.map { "\(lastSeenIp) (\($0))" } ?? lastSeenIp
|
||||
}
|
||||
}
|
||||
|
||||
struct UserSessionListPreview: View {
|
||||
@@ -94,7 +103,7 @@ struct UserSessionListPreview: View {
|
||||
VStack(alignment: .leading, spacing: 0) {
|
||||
ForEach(userSessionsOverviewService.otherSessions) { userSessionInfo in
|
||||
let viewData = UserSessionListItemViewDataFactory().create(from: userSessionInfo)
|
||||
UserSessionListItem(viewData: viewData, isEditModeEnabled: isEditModeEnabled, onBackgroundTap: { _ in
|
||||
UserSessionListItem(viewData: viewData, showsLocationInfo: true, isEditModeEnabled: isEditModeEnabled, onBackgroundTap: { _ in
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
+2
-7
@@ -25,16 +25,11 @@ struct UserSessionListItemViewData: Identifiable, Hashable {
|
||||
}
|
||||
|
||||
let sessionId: SessionId
|
||||
|
||||
let sessionName: String
|
||||
|
||||
let sessionDetails: String
|
||||
|
||||
let highlightSessionDetails: Bool
|
||||
|
||||
let deviceAvatarViewData: DeviceAvatarViewData
|
||||
|
||||
let sessionDetailsIcon: String?
|
||||
|
||||
let isSelected: Bool
|
||||
let lastSeenIP: String?
|
||||
let lastSeenIPLocation: String?
|
||||
}
|
||||
|
||||
+5
-8
@@ -18,7 +18,6 @@ import Foundation
|
||||
|
||||
struct UserSessionListItemViewDataFactory {
|
||||
func create(from sessionInfo: UserSessionInfo,
|
||||
highlightSessionDetails: Bool = false,
|
||||
isSelected: Bool = false) -> UserSessionListItemViewData {
|
||||
let sessionName = UserSessionNameFormatter.sessionName(deviceType: sessionInfo.deviceType,
|
||||
sessionDisplayName: sessionInfo.name)
|
||||
@@ -28,10 +27,11 @@ struct UserSessionListItemViewDataFactory {
|
||||
return UserSessionListItemViewData(sessionId: sessionInfo.id,
|
||||
sessionName: sessionName,
|
||||
sessionDetails: sessionDetails,
|
||||
highlightSessionDetails: highlightSessionDetails,
|
||||
deviceAvatarViewData: deviceAvatarViewData,
|
||||
sessionDetailsIcon: getSessionDetailsIcon(isActive: sessionInfo.isActive),
|
||||
isSelected: isSelected)
|
||||
isSelected: isSelected,
|
||||
lastSeenIP: sessionInfo.lastSeenIP,
|
||||
lastSeenIPLocation: sessionInfo.lastSeenIPLocation)
|
||||
}
|
||||
|
||||
private func buildSessionDetails(sessionInfo: UserSessionInfo) -> String {
|
||||
@@ -52,16 +52,13 @@ struct UserSessionListItemViewDataFactory {
|
||||
|
||||
private func activeSessionDetails(sessionInfo: UserSessionInfo) -> String {
|
||||
// Start by creating the main part of the details string.
|
||||
var sessionDetailsString = ""
|
||||
|
||||
var lastActivityDateString: String?
|
||||
if let lastActivityDate = sessionInfo.lastSeenTimestamp {
|
||||
lastActivityDateString = UserSessionLastActivityFormatter.lastActivityDateString(from: lastActivityDate)
|
||||
}
|
||||
|
||||
if sessionInfo.isCurrent {
|
||||
sessionDetailsString = VectorL10n.userOtherSessionCurrentSessionDetails
|
||||
} else if let lastActivityDateString = lastActivityDateString, lastActivityDateString.isEmpty == false {
|
||||
var sessionDetailsString = ""
|
||||
if let lastActivityDateString = lastActivityDateString, lastActivityDateString.isEmpty == false {
|
||||
sessionDetailsString = VectorL10n.userSessionItemDetailsLastActivity(lastActivityDateString)
|
||||
}
|
||||
|
||||
|
||||
+63
-38
@@ -24,26 +24,19 @@ struct UserSessionsOverview: View {
|
||||
private let maxOtherSessionsToDisplay = 5
|
||||
|
||||
var body: some View {
|
||||
GeometryReader { _ in
|
||||
VStack(alignment: .leading, spacing: 0) {
|
||||
ScrollView {
|
||||
if hasSecurityRecommendations {
|
||||
securityRecommendationsSection
|
||||
}
|
||||
|
||||
currentSessionsSection
|
||||
|
||||
if !viewModel.viewState.otherSessionsViewData.isEmpty {
|
||||
otherSessionsSection
|
||||
}
|
||||
VStack(alignment: .leading, spacing: 0) {
|
||||
ScrollView {
|
||||
if hasSecurityRecommendations {
|
||||
securityRecommendationsSection
|
||||
}
|
||||
|
||||
currentSessionsSection
|
||||
|
||||
if !viewModel.viewState.otherSessionsViewData.isEmpty {
|
||||
otherSessionsSection
|
||||
}
|
||||
.readableFrame()
|
||||
|
||||
// if viewModel.viewState.linkDeviceButtonVisible {
|
||||
// linkDeviceView
|
||||
// .padding(.bottom, geometry.safeAreaInsets.bottom > 0 ? 20 : 36)
|
||||
// }
|
||||
}
|
||||
.readableFrame()
|
||||
}
|
||||
.background(theme.colors.system.ignoresSafeArea())
|
||||
.frame(maxHeight: .infinity)
|
||||
@@ -103,7 +96,7 @@ struct UserSessionsOverview: View {
|
||||
viewModel.send(viewAction: .verifyCurrentSession)
|
||||
}, onViewDetailsAction: { _ in
|
||||
viewModel.send(viewAction: .viewCurrentSessionDetails)
|
||||
})
|
||||
}, showLocationInformations: viewModel.viewState.showLocationInfo)
|
||||
} header: {
|
||||
HStack(alignment: .firstTextBaseline) {
|
||||
Text(VectorL10n.userSessionsOverviewCurrentSessionSectionTitle)
|
||||
@@ -127,33 +120,61 @@ struct UserSessionsOverview: View {
|
||||
Button { viewModel.send(viewAction: .renameCurrentSession) } label: {
|
||||
Label(VectorL10n.manageSessionRename, systemImage: "pencil")
|
||||
}
|
||||
}
|
||||
|
||||
if #available(iOS 15, *) {
|
||||
Button(role: .destructive) { viewModel.send(viewAction: .logoutOfCurrentSession) } label: {
|
||||
DestructiveButton {
|
||||
viewModel.send(viewAction: .logoutOfCurrentSession)
|
||||
} label: {
|
||||
Label(VectorL10n.signOut, systemImage: "rectangle.portrait.and.arrow.right.fill")
|
||||
}
|
||||
} else {
|
||||
Button { viewModel.send(viewAction: .logoutOfCurrentSession) } label: {
|
||||
Label(VectorL10n.signOut, systemImage: "rectangle.righthalf.inset.fill.arrow.right")
|
||||
}
|
||||
if viewModel.viewState.otherSessionsViewData.count > 0 {
|
||||
DestructiveButton {
|
||||
viewModel.send(viewAction: .logoutOtherSessions)
|
||||
} label: {
|
||||
Label(VectorL10n.manageSessionSignOutOtherSessions, systemImage: "rectangle.portrait.and.arrow.forward.fill")
|
||||
}
|
||||
}
|
||||
} label: {
|
||||
Image(systemName: "ellipsis")
|
||||
.foregroundColor(theme.colors.secondaryContent)
|
||||
.padding(.horizontal, 8)
|
||||
.padding(.vertical, 12)
|
||||
menuImage
|
||||
}
|
||||
.accessibilityIdentifier("MoreOptionsMenu")
|
||||
.offset(x: 8) // Re-align the symbol after applying padding.
|
||||
}
|
||||
|
||||
private var otherSessionsMenu: some View {
|
||||
Menu {
|
||||
Button {
|
||||
withAnimation {
|
||||
viewModel.send(viewAction: .showLocationInfo)
|
||||
}
|
||||
} label: {
|
||||
Label(showLocationInfo: viewModel.viewState.showLocationInfo)
|
||||
}
|
||||
|
||||
DestructiveButton {
|
||||
viewModel.send(viewAction: .logoutOtherSessions)
|
||||
} label: {
|
||||
Label(VectorL10n.userOtherSessionMenuSignOutSessions(String(viewModel.viewState.otherSessionsViewData.count)), systemImage: "rectangle.portrait.and.arrow.forward.fill")
|
||||
}
|
||||
} label: {
|
||||
menuImage
|
||||
}
|
||||
}
|
||||
|
||||
private var menuImage: some View {
|
||||
Image(systemName: "ellipsis")
|
||||
.foregroundColor(theme.colors.secondaryContent)
|
||||
.padding(.horizontal, 8)
|
||||
.padding(.vertical, 12)
|
||||
}
|
||||
|
||||
private var otherSessionsSection: some View {
|
||||
SwiftUI.Section {
|
||||
LazyVStack(spacing: 0) {
|
||||
ForEach(viewModel.viewState.otherSessionsViewData.prefix(maxOtherSessionsToDisplay)) { viewData in
|
||||
UserSessionListItem(viewData: viewData, onBackgroundTap: { sessionId in
|
||||
viewModel.send(viewAction: .tapUserSession(sessionId))
|
||||
})
|
||||
UserSessionListItem(viewData: viewData,
|
||||
showsLocationInfo: viewModel.viewState.showLocationInfo,
|
||||
isSeparatorHidden: viewData == viewModel.viewState.otherSessionsViewData.last,
|
||||
onBackgroundTap: { sessionId in viewModel.send(viewAction: .tapUserSession(sessionId)) })
|
||||
}
|
||||
if viewModel.viewState.otherSessionsViewData.count > maxOtherSessionsToDisplay {
|
||||
UserSessionsListViewAllView(count: viewModel.viewState.otherSessionsViewData.count) {
|
||||
@@ -164,11 +185,15 @@ struct UserSessionsOverview: View {
|
||||
.background(theme.colors.background)
|
||||
} header: {
|
||||
VStack(alignment: .leading) {
|
||||
Text(VectorL10n.userSessionsOverviewOtherSessionsSectionTitle)
|
||||
.textCase(.uppercase)
|
||||
.font(theme.fonts.footnote)
|
||||
.foregroundColor(theme.colors.secondaryContent)
|
||||
.padding(.bottom, 8.0)
|
||||
HStack {
|
||||
Text(VectorL10n.userSessionsOverviewOtherSessionsSectionTitle)
|
||||
.textCase(.uppercase)
|
||||
.font(theme.fonts.footnote)
|
||||
.foregroundColor(theme.colors.secondaryContent)
|
||||
.padding(.bottom, 8.0)
|
||||
Spacer()
|
||||
otherSessionsMenu
|
||||
}
|
||||
|
||||
Text(VectorL10n.userSessionsOverviewOtherSessionsSectionInfo)
|
||||
.font(theme.fonts.footnote)
|
||||
|
||||
Reference in New Issue
Block a user