Skip to content

Commit

Permalink
Pinned events banner loading state (#3118)
Browse files Browse the repository at this point in the history
  • Loading branch information
Velin92 authored Aug 6, 2024
1 parent 0c9da2c commit 08347e1
Show file tree
Hide file tree
Showing 26 changed files with 464 additions and 109 deletions.
48 changes: 24 additions & 24 deletions ElementX.xcodeproj/project.pbxproj

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -149,8 +149,8 @@
"kind" : "remoteSourceControl",
"location" : "https://github.com/element-hq/matrix-rust-components-swift",
"state" : {
"revision" : "8e2b4049fb492dcf5b0c796784b7aa7a3c099943",
"version" : "1.0.31"
"revision" : "9a9d116e5f00b31ad4f727d0875fed13a230806b",
"version" : "1.0.32"
}
},
{
Expand Down
7 changes: 7 additions & 0 deletions ElementX/Resources/Localizations/en.lproj/Localizable.strings
Original file line number Diff line number Diff line change
Expand Up @@ -318,6 +318,7 @@
"screen_room_mentions_at_room_subtitle" = "Notify the whole room";
"screen_room_pinned_banner_indicator" = "%1$@ of %2$@";
"screen_room_pinned_banner_indicator_description" = "%1$@ Pinned messages";
"screen_room_pinned_banner_loading_description" = "Loading message…";
"screen_room_pinned_banner_view_all_button_title" = "View All";
"screen_account_provider_change" = "Change account provider";
"screen_account_provider_form_hint" = "Homeserver address";
Expand Down Expand Up @@ -851,6 +852,12 @@
"state_event_room_name_removed_by_you" = "You removed the room name";
"state_event_room_none" = "%1$@ made no changes";
"state_event_room_none_by_you" = "You made no changes";
"state_event_room_pinned_events_changed" = "%1$@ changed the pinned messages";
"state_event_room_pinned_events_changed_by_you" = "You changed the pinned messages";
"state_event_room_pinned_events_pinned" = "%1$@ pinned a message";
"state_event_room_pinned_events_pinned_by_you" = "You pinned a message";
"state_event_room_pinned_events_unpinned" = "%1$@ unpinned a message";
"state_event_room_pinned_events_unpinned_by_you" = "You unpinned a message";
"state_event_room_reject" = "%1$@ rejected the invitation";
"state_event_room_reject_by_you" = "You rejected the invitation";
"state_event_room_remove" = "%1$@ removed %2$@";
Expand Down
20 changes: 20 additions & 0 deletions ElementX/Sources/Generated/Strings.swift
Original file line number Diff line number Diff line change
Expand Up @@ -1675,6 +1675,8 @@ internal enum L10n {
internal static func screenRoomPinnedBannerIndicatorDescription(_ p1: Any) -> String {
return L10n.tr("Localizable", "screen_room_pinned_banner_indicator_description", String(describing: p1))
}
/// Loading message…
internal static var screenRoomPinnedBannerLoadingDescription: String { return L10n.tr("Localizable", "screen_room_pinned_banner_loading_description") }
/// View All
internal static var screenRoomPinnedBannerViewAllButtonTitle: String { return L10n.tr("Localizable", "screen_room_pinned_banner_view_all_button_title") }
/// Send again
Expand Down Expand Up @@ -2111,6 +2113,24 @@ internal enum L10n {
}
/// You made no changes
internal static var stateEventRoomNoneByYou: String { return L10n.tr("Localizable", "state_event_room_none_by_you") }
/// %1$@ changed the pinned messages
internal static func stateEventRoomPinnedEventsChanged(_ p1: Any) -> String {
return L10n.tr("Localizable", "state_event_room_pinned_events_changed", String(describing: p1))
}
/// You changed the pinned messages
internal static var stateEventRoomPinnedEventsChangedByYou: String { return L10n.tr("Localizable", "state_event_room_pinned_events_changed_by_you") }
/// %1$@ pinned a message
internal static func stateEventRoomPinnedEventsPinned(_ p1: Any) -> String {
return L10n.tr("Localizable", "state_event_room_pinned_events_pinned", String(describing: p1))
}
/// You pinned a message
internal static var stateEventRoomPinnedEventsPinnedByYou: String { return L10n.tr("Localizable", "state_event_room_pinned_events_pinned_by_you") }
/// %1$@ unpinned a message
internal static func stateEventRoomPinnedEventsUnpinned(_ p1: Any) -> String {
return L10n.tr("Localizable", "state_event_room_pinned_events_unpinned", String(describing: p1))
}
/// You unpinned a message
internal static var stateEventRoomPinnedEventsUnpinnedByYou: String { return L10n.tr("Localizable", "state_event_room_pinned_events_unpinned_by_you") }
/// %1$@ rejected the invitation
internal static func stateEventRoomReject(_ p1: Any) -> String {
return L10n.tr("Localizable", "state_event_room_reject", String(describing: p1))
Expand Down
26 changes: 26 additions & 0 deletions ElementX/Sources/Mocks/NetworkMonitorMock.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
//
// Copyright 2024 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 Combine
import Foundation

extension NetworkMonitorMock {
static var `default`: NetworkMonitorMock {
let mock = NetworkMonitorMock()
mock.underlyingReachabilityPublisher = .init(.init(.reachable))
return mock
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ final class RoomScreenCoordinator: CoordinatorProtocol {
mediaPlayerProvider: parameters.mediaPlayerProvider,
voiceMessageMediaManager: parameters.voiceMessageMediaManager,
userIndicatorController: ServiceLocator.shared.userIndicatorController,
networkMonitor: ServiceLocator.shared.networkMonitor,
appMediator: parameters.appMediator,
appSettings: parameters.appSettings,
analyticsService: ServiceLocator.shared.analytics)
Expand Down
107 changes: 95 additions & 12 deletions ElementX/Sources/Screens/RoomScreen/RoomScreenModels.swift
Original file line number Diff line number Diff line change
Expand Up @@ -177,10 +177,10 @@ struct RoomScreenViewState: BindableState {
// It's updated from the room info, so it's faster than using the timeline
var pinnedEventIDs: Set<String> = []
// This is used to control the banner
var pinnedEventsState = PinnedEventsState()
var pinnedEventsBannerState: PinnedEventsBannerState = .loading(numbersOfEvents: 0)

var shouldShowPinnedEventsBanner: Bool {
isPinningEnabled && !pinnedEventsState.pinnedEventContents.isEmpty && lastScrollDirection != .top
isPinningEnabled && !pinnedEventsBannerState.isEmpty && lastScrollDirection != .top
}

var canJoinCall = false
Expand Down Expand Up @@ -306,11 +306,12 @@ struct PinnedEventsState: Equatable {
var pinnedEventContents: OrderedDictionary<String, AttributedString> = [:] {
didSet {
if selectedPinEventID == nil, !pinnedEventContents.keys.isEmpty {
// The default selected event should always be the last one.
selectedPinEventID = pinnedEventContents.keys.last
} else if pinnedEventContents.isEmpty {
selectedPinEventID = nil
} else if let selectedPinEventID, !pinnedEventContents.keys.set.contains(selectedPinEventID) {
self.selectedPinEventID = pinnedEventContents.firstNonNil { $0.key }
self.selectedPinEventID = pinnedEventContents.keys.last
}
}
}
Expand All @@ -326,30 +327,112 @@ struct PinnedEventsState: Equatable {
}

var selectedPinContent: AttributedString {
guard let selectedPinEventID,
var content = pinnedEventContents[selectedPinEventID] else {
return AttributedString()
var content = AttributedString(" ")
if let selectedPinEventID,
var pinnedEventContent = pinnedEventContents[selectedPinEventID] {
content = pinnedEventContent
}
content.font = .compound.bodyMD
return content
}

mutating func nextPin() {
guard !pinnedEventContents.isEmpty else {
return
}
let currentIndex = selectedPinIndex
let nextIndex = (currentIndex + 1) % pinnedEventContents.count
selectedPinEventID = pinnedEventContents.keys[nextIndex]
}
}

enum PinnedEventsBannerState: Equatable {
case loading(numbersOfEvents: Int)
case loaded(state: PinnedEventsState)

var isEmpty: Bool {
switch self {
case .loaded(let state):
return state.pinnedEventContents.isEmpty
case .loading(let numberOfEvents):
return numberOfEvents == 0
}
}

var isLoading: Bool {
switch self {
case .loading:
return true
default:
return false
}
}

var selectedPinEventID: String? {
switch self {
case .loaded(let state):
return state.selectedPinEventID
default:
return nil
}
}

var count: Int {
switch self {
case .loaded(let state):
return state.pinnedEventContents.count
case .loading(let numberOfEvents):
return numberOfEvents
}
}

var selectedPinIndex: Int {
switch self {
case .loaded(let state):
return state.selectedPinIndex
case .loading(let numbersOfEvents):
// We always want the index to be the last one when loading, since is the default one.
return numbersOfEvents - 1
}
}

var displayedMessage: AttributedString {
switch self {
case .loading:
return AttributedString(L10n.screenRoomPinnedBannerLoadingDescription)
case .loaded(let state):
return state.selectedPinContent
}
}

var bannerIndicatorDescription: AttributedString {
let index = selectedPinIndex + 1
let boldPlaceholder = "{bold}"
var finalString = AttributedString(L10n.screenRoomPinnedBannerIndicatorDescription(boldPlaceholder))
var boldString = AttributedString(L10n.screenRoomPinnedBannerIndicator(index, pinnedEventContents.count))
var boldString = AttributedString(L10n.screenRoomPinnedBannerIndicator(index, count))
boldString.bold()
finalString.replace(boldPlaceholder, with: boldString)
return finalString
}

mutating func nextPin() {
guard !pinnedEventContents.isEmpty else {
return
switch self {
case .loaded(var state):
state.nextPin()
self = .loaded(state: state)
default:
break
}
}

mutating func setPinnedEventContents(_ pinnedEventContents: OrderedDictionary<String, AttributedString>) {
switch self {
case .loading:
// The default selected event should always be the last one.
self = .loaded(state: .init(pinnedEventContents: pinnedEventContents, selectedPinEventID: pinnedEventContents.keys.last))
case .loaded(var state):
state.pinnedEventContents = pinnedEventContents
self = .loaded(state: state)
}
let currentIndex = selectedPinIndex
let nextIndex = (currentIndex + 1) % pinnedEventContents.count
selectedPinEventID = pinnedEventContents.keys[nextIndex]
}
}
Loading

0 comments on commit 08347e1

Please sign in to comment.