mirror of
https://github.com/Novaloop-AG/element-x-ios.git
synced 2026-11-04 10:33:45 +00:00
Pin/Unpin Logic (#3084)
This commit is contained in:
@@ -7501,7 +7501,7 @@
|
|||||||
repositoryURL = "https://github.com/element-hq/matrix-rust-components-swift";
|
repositoryURL = "https://github.com/element-hq/matrix-rust-components-swift";
|
||||||
requirement = {
|
requirement = {
|
||||||
kind = exactVersion;
|
kind = exactVersion;
|
||||||
version = 1.0.28;
|
version = 1.0.29;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
701C7BEF8F70F7A83E852DCC /* XCRemoteSwiftPackageReference "GZIP" */ = {
|
701C7BEF8F70F7A83E852DCC /* XCRemoteSwiftPackageReference "GZIP" */ = {
|
||||||
|
|||||||
@@ -149,8 +149,8 @@
|
|||||||
"kind" : "remoteSourceControl",
|
"kind" : "remoteSourceControl",
|
||||||
"location" : "https://github.com/element-hq/matrix-rust-components-swift",
|
"location" : "https://github.com/element-hq/matrix-rust-components-swift",
|
||||||
"state" : {
|
"state" : {
|
||||||
"revision" : "1a1cbc9d9d43a188d9b07fe00a141d02f7c43c7c",
|
"revision" : "d0226f669526e908d45bf9b5682f372d84cf9ffe",
|
||||||
"version" : "1.0.28"
|
"version" : "1.0.29"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -70,6 +70,7 @@
|
|||||||
"action_ok" = "OK";
|
"action_ok" = "OK";
|
||||||
"action_open_settings" = "Settings";
|
"action_open_settings" = "Settings";
|
||||||
"action_open_with" = "Open with";
|
"action_open_with" = "Open with";
|
||||||
|
"action_pin" = "Pin";
|
||||||
"action_quick_reply" = "Quick reply";
|
"action_quick_reply" = "Quick reply";
|
||||||
"action_quote" = "Quote";
|
"action_quote" = "Quote";
|
||||||
"action_react" = "React";
|
"action_react" = "React";
|
||||||
@@ -99,10 +100,10 @@
|
|||||||
"action_take_photo" = "Take photo";
|
"action_take_photo" = "Take photo";
|
||||||
"action_tap_for_options" = "Tap for options";
|
"action_tap_for_options" = "Tap for options";
|
||||||
"action_try_again" = "Try again";
|
"action_try_again" = "Try again";
|
||||||
|
"action_unpin" = "Unpin";
|
||||||
"action_view_source" = "View source";
|
"action_view_source" = "View source";
|
||||||
"action_yes" = "Yes";
|
"action_yes" = "Yes";
|
||||||
"action.load_more" = "Load more";
|
"action.load_more" = "Load more";
|
||||||
"action.pin" = "Pin";
|
|
||||||
"common_about" = "About";
|
"common_about" = "About";
|
||||||
"common_acceptable_use_policy" = "Acceptable use policy";
|
"common_acceptable_use_policy" = "Acceptable use policy";
|
||||||
"common_advanced_settings" = "Advanced settings";
|
"common_advanced_settings" = "Advanced settings";
|
||||||
@@ -313,9 +314,9 @@
|
|||||||
"screen_advanced_settings_element_call_base_url_description" = "Set a custom base URL for Element Call.";
|
"screen_advanced_settings_element_call_base_url_description" = "Set a custom base URL for Element Call.";
|
||||||
"screen_advanced_settings_element_call_base_url_validation_error" = "Invalid URL, please make sure you include the protocol (http/https) and the correct address.";
|
"screen_advanced_settings_element_call_base_url_validation_error" = "Invalid URL, please make sure you include the protocol (http/https) and the correct address.";
|
||||||
"screen_room_mentions_at_room_subtitle" = "Notify the whole room";
|
"screen_room_mentions_at_room_subtitle" = "Notify the whole room";
|
||||||
"screen.room.pinned_banner_indicator" = "%1$@ of %2$@";
|
"screen_room_pinned_banner_indicator" = "%1$@ of %2$@";
|
||||||
"screen.room.pinned_banner_indicator_description" = "%1$@ Pinned messages";
|
"screen_room_pinned_banner_indicator_description" = "%1$@ Pinned messages";
|
||||||
"screen.room.pinned_banner_view_all_button_title" = "View All";
|
"screen_room_pinned_banner_view_all_button_title" = "View All";
|
||||||
"screen_account_provider_change" = "Change account provider";
|
"screen_account_provider_change" = "Change account provider";
|
||||||
"screen_account_provider_form_hint" = "Homeserver address";
|
"screen_account_provider_form_hint" = "Homeserver address";
|
||||||
"screen_account_provider_form_notice" = "Enter a search term or a domain address.";
|
"screen_account_provider_form_notice" = "Enter a search term or a domain address.";
|
||||||
|
|||||||
@@ -174,6 +174,8 @@ internal enum L10n {
|
|||||||
internal static var actionOpenSettings: String { return L10n.tr("Localizable", "action_open_settings") }
|
internal static var actionOpenSettings: String { return L10n.tr("Localizable", "action_open_settings") }
|
||||||
/// Open with
|
/// Open with
|
||||||
internal static var actionOpenWith: String { return L10n.tr("Localizable", "action_open_with") }
|
internal static var actionOpenWith: String { return L10n.tr("Localizable", "action_open_with") }
|
||||||
|
/// Pin
|
||||||
|
internal static var actionPin: String { return L10n.tr("Localizable", "action_pin") }
|
||||||
/// Quick reply
|
/// Quick reply
|
||||||
internal static var actionQuickReply: String { return L10n.tr("Localizable", "action_quick_reply") }
|
internal static var actionQuickReply: String { return L10n.tr("Localizable", "action_quick_reply") }
|
||||||
/// Quote
|
/// Quote
|
||||||
@@ -232,6 +234,8 @@ internal enum L10n {
|
|||||||
internal static var actionTapForOptions: String { return L10n.tr("Localizable", "action_tap_for_options") }
|
internal static var actionTapForOptions: String { return L10n.tr("Localizable", "action_tap_for_options") }
|
||||||
/// Try again
|
/// Try again
|
||||||
internal static var actionTryAgain: String { return L10n.tr("Localizable", "action_try_again") }
|
internal static var actionTryAgain: String { return L10n.tr("Localizable", "action_try_again") }
|
||||||
|
/// Unpin
|
||||||
|
internal static var actionUnpin: String { return L10n.tr("Localizable", "action_unpin") }
|
||||||
/// View source
|
/// View source
|
||||||
internal static var actionViewSource: String { return L10n.tr("Localizable", "action_view_source") }
|
internal static var actionViewSource: String { return L10n.tr("Localizable", "action_view_source") }
|
||||||
/// Yes
|
/// Yes
|
||||||
@@ -1629,6 +1633,16 @@ internal enum L10n {
|
|||||||
internal static var screenRoomNotificationSettingsModeMentionsAndKeywords: String { return L10n.tr("Localizable", "screen_room_notification_settings_mode_mentions_and_keywords") }
|
internal static var screenRoomNotificationSettingsModeMentionsAndKeywords: String { return L10n.tr("Localizable", "screen_room_notification_settings_mode_mentions_and_keywords") }
|
||||||
/// In this room, notify me for
|
/// In this room, notify me for
|
||||||
internal static var screenRoomNotificationSettingsRoomCustomSettingsTitle: String { return L10n.tr("Localizable", "screen_room_notification_settings_room_custom_settings_title") }
|
internal static var screenRoomNotificationSettingsRoomCustomSettingsTitle: String { return L10n.tr("Localizable", "screen_room_notification_settings_room_custom_settings_title") }
|
||||||
|
/// %1$@ of %2$@
|
||||||
|
internal static func screenRoomPinnedBannerIndicator(_ p1: Any, _ p2: Any) -> String {
|
||||||
|
return L10n.tr("Localizable", "screen_room_pinned_banner_indicator", String(describing: p1), String(describing: p2))
|
||||||
|
}
|
||||||
|
/// %1$@ Pinned messages
|
||||||
|
internal static func screenRoomPinnedBannerIndicatorDescription(_ p1: Any) -> String {
|
||||||
|
return L10n.tr("Localizable", "screen_room_pinned_banner_indicator_description", String(describing: p1))
|
||||||
|
}
|
||||||
|
/// View All
|
||||||
|
internal static var screenRoomPinnedBannerViewAllButtonTitle: String { return L10n.tr("Localizable", "screen_room_pinned_banner_view_all_button_title") }
|
||||||
/// Send again
|
/// Send again
|
||||||
internal static var screenRoomRetrySendMenuSendAgainAction: String { return L10n.tr("Localizable", "screen_room_retry_send_menu_send_again_action") }
|
internal static var screenRoomRetrySendMenuSendAgainAction: String { return L10n.tr("Localizable", "screen_room_retry_send_menu_send_again_action") }
|
||||||
/// Your message failed to send
|
/// Your message failed to send
|
||||||
@@ -2229,8 +2243,6 @@ internal enum L10n {
|
|||||||
internal enum Action {
|
internal enum Action {
|
||||||
/// Load more
|
/// Load more
|
||||||
internal static var loadMore: String { return L10n.tr("Localizable", "action.load_more") }
|
internal static var loadMore: String { return L10n.tr("Localizable", "action.load_more") }
|
||||||
/// Pin
|
|
||||||
internal static var pin: String { return L10n.tr("Localizable", "action.pin") }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
internal enum Common {
|
internal enum Common {
|
||||||
@@ -2241,21 +2253,6 @@ internal enum L10n {
|
|||||||
/// Send to
|
/// Send to
|
||||||
internal static var sendTo: String { return L10n.tr("Localizable", "common.send_to") }
|
internal static var sendTo: String { return L10n.tr("Localizable", "common.send_to") }
|
||||||
}
|
}
|
||||||
|
|
||||||
internal enum Screen {
|
|
||||||
internal enum Room {
|
|
||||||
/// %1$@ of %2$@
|
|
||||||
internal static func pinnedBannerIndicator(_ p1: Any, _ p2: Any) -> String {
|
|
||||||
return L10n.tr("Localizable", "screen.room.pinned_banner_indicator", String(describing: p1), String(describing: p2))
|
|
||||||
}
|
|
||||||
/// %1$@ Pinned messages
|
|
||||||
internal static func pinnedBannerIndicatorDescription(_ p1: Any) -> String {
|
|
||||||
return L10n.tr("Localizable", "screen.room.pinned_banner_indicator_description", String(describing: p1))
|
|
||||||
}
|
|
||||||
/// View All
|
|
||||||
internal static var pinnedBannerViewAllButtonTitle: String { return L10n.tr("Localizable", "screen.room.pinned_banner_view_all_button_title") }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
// swiftlint:enable explicit_type_interface function_parameter_count identifier_name line_length
|
// swiftlint:enable explicit_type_interface function_parameter_count identifier_name line_length
|
||||||
// swiftlint:enable nesting type_body_length type_name vertical_whitespace_opening_braces
|
// swiftlint:enable nesting type_body_length type_name vertical_whitespace_opening_braces
|
||||||
|
|||||||
@@ -8251,6 +8251,23 @@ class RoomProxyMock: RoomProxyProtocol {
|
|||||||
}
|
}
|
||||||
var underlyingIsFavourite: Bool!
|
var underlyingIsFavourite: Bool!
|
||||||
var isFavouriteClosure: (() async -> Bool)?
|
var isFavouriteClosure: (() async -> Bool)?
|
||||||
|
var pinnedEventIDsCallsCount = 0
|
||||||
|
var pinnedEventIDsCalled: Bool {
|
||||||
|
return pinnedEventIDsCallsCount > 0
|
||||||
|
}
|
||||||
|
|
||||||
|
var pinnedEventIDs: [String] {
|
||||||
|
get async {
|
||||||
|
pinnedEventIDsCallsCount += 1
|
||||||
|
if let pinnedEventIDsClosure = pinnedEventIDsClosure {
|
||||||
|
return await pinnedEventIDsClosure()
|
||||||
|
} else {
|
||||||
|
return underlyingPinnedEventIDs
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var underlyingPinnedEventIDs: [String]!
|
||||||
|
var pinnedEventIDsClosure: (() async -> [String])?
|
||||||
var membership: Membership {
|
var membership: Membership {
|
||||||
get { return underlyingMembership }
|
get { return underlyingMembership }
|
||||||
set(value) { underlyingMembership = value }
|
set(value) { underlyingMembership = value }
|
||||||
@@ -10406,6 +10423,76 @@ class RoomProxyMock: RoomProxyProtocol {
|
|||||||
return canUserTriggerRoomNotificationUserIDReturnValue
|
return canUserTriggerRoomNotificationUserIDReturnValue
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
//MARK: - canUserPinOrUnpin
|
||||||
|
|
||||||
|
var canUserPinOrUnpinUserIDUnderlyingCallsCount = 0
|
||||||
|
var canUserPinOrUnpinUserIDCallsCount: Int {
|
||||||
|
get {
|
||||||
|
if Thread.isMainThread {
|
||||||
|
return canUserPinOrUnpinUserIDUnderlyingCallsCount
|
||||||
|
} else {
|
||||||
|
var returnValue: Int? = nil
|
||||||
|
DispatchQueue.main.sync {
|
||||||
|
returnValue = canUserPinOrUnpinUserIDUnderlyingCallsCount
|
||||||
|
}
|
||||||
|
|
||||||
|
return returnValue!
|
||||||
|
}
|
||||||
|
}
|
||||||
|
set {
|
||||||
|
if Thread.isMainThread {
|
||||||
|
canUserPinOrUnpinUserIDUnderlyingCallsCount = newValue
|
||||||
|
} else {
|
||||||
|
DispatchQueue.main.sync {
|
||||||
|
canUserPinOrUnpinUserIDUnderlyingCallsCount = newValue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var canUserPinOrUnpinUserIDCalled: Bool {
|
||||||
|
return canUserPinOrUnpinUserIDCallsCount > 0
|
||||||
|
}
|
||||||
|
var canUserPinOrUnpinUserIDReceivedUserID: String?
|
||||||
|
var canUserPinOrUnpinUserIDReceivedInvocations: [String] = []
|
||||||
|
|
||||||
|
var canUserPinOrUnpinUserIDUnderlyingReturnValue: Result<Bool, RoomProxyError>!
|
||||||
|
var canUserPinOrUnpinUserIDReturnValue: Result<Bool, RoomProxyError>! {
|
||||||
|
get {
|
||||||
|
if Thread.isMainThread {
|
||||||
|
return canUserPinOrUnpinUserIDUnderlyingReturnValue
|
||||||
|
} else {
|
||||||
|
var returnValue: Result<Bool, RoomProxyError>? = nil
|
||||||
|
DispatchQueue.main.sync {
|
||||||
|
returnValue = canUserPinOrUnpinUserIDUnderlyingReturnValue
|
||||||
|
}
|
||||||
|
|
||||||
|
return returnValue!
|
||||||
|
}
|
||||||
|
}
|
||||||
|
set {
|
||||||
|
if Thread.isMainThread {
|
||||||
|
canUserPinOrUnpinUserIDUnderlyingReturnValue = newValue
|
||||||
|
} else {
|
||||||
|
DispatchQueue.main.sync {
|
||||||
|
canUserPinOrUnpinUserIDUnderlyingReturnValue = newValue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var canUserPinOrUnpinUserIDClosure: ((String) async -> Result<Bool, RoomProxyError>)?
|
||||||
|
|
||||||
|
func canUserPinOrUnpin(userID: String) async -> Result<Bool, RoomProxyError> {
|
||||||
|
canUserPinOrUnpinUserIDCallsCount += 1
|
||||||
|
canUserPinOrUnpinUserIDReceivedUserID = userID
|
||||||
|
DispatchQueue.main.async {
|
||||||
|
self.canUserPinOrUnpinUserIDReceivedInvocations.append(userID)
|
||||||
|
}
|
||||||
|
if let canUserPinOrUnpinUserIDClosure = canUserPinOrUnpinUserIDClosure {
|
||||||
|
return await canUserPinOrUnpinUserIDClosure(userID)
|
||||||
|
} else {
|
||||||
|
return canUserPinOrUnpinUserIDReturnValue
|
||||||
|
}
|
||||||
|
}
|
||||||
//MARK: - kickUser
|
//MARK: - kickUser
|
||||||
|
|
||||||
var kickUserUnderlyingCallsCount = 0
|
var kickUserUnderlyingCallsCount = 0
|
||||||
@@ -12527,6 +12614,146 @@ class TimelineProxyMock: TimelineProxyProtocol {
|
|||||||
return redactReasonReturnValue
|
return redactReasonReturnValue
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
//MARK: - pin
|
||||||
|
|
||||||
|
var pinEventIDUnderlyingCallsCount = 0
|
||||||
|
var pinEventIDCallsCount: Int {
|
||||||
|
get {
|
||||||
|
if Thread.isMainThread {
|
||||||
|
return pinEventIDUnderlyingCallsCount
|
||||||
|
} else {
|
||||||
|
var returnValue: Int? = nil
|
||||||
|
DispatchQueue.main.sync {
|
||||||
|
returnValue = pinEventIDUnderlyingCallsCount
|
||||||
|
}
|
||||||
|
|
||||||
|
return returnValue!
|
||||||
|
}
|
||||||
|
}
|
||||||
|
set {
|
||||||
|
if Thread.isMainThread {
|
||||||
|
pinEventIDUnderlyingCallsCount = newValue
|
||||||
|
} else {
|
||||||
|
DispatchQueue.main.sync {
|
||||||
|
pinEventIDUnderlyingCallsCount = newValue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var pinEventIDCalled: Bool {
|
||||||
|
return pinEventIDCallsCount > 0
|
||||||
|
}
|
||||||
|
var pinEventIDReceivedEventID: String?
|
||||||
|
var pinEventIDReceivedInvocations: [String] = []
|
||||||
|
|
||||||
|
var pinEventIDUnderlyingReturnValue: Result<Bool, TimelineProxyError>!
|
||||||
|
var pinEventIDReturnValue: Result<Bool, TimelineProxyError>! {
|
||||||
|
get {
|
||||||
|
if Thread.isMainThread {
|
||||||
|
return pinEventIDUnderlyingReturnValue
|
||||||
|
} else {
|
||||||
|
var returnValue: Result<Bool, TimelineProxyError>? = nil
|
||||||
|
DispatchQueue.main.sync {
|
||||||
|
returnValue = pinEventIDUnderlyingReturnValue
|
||||||
|
}
|
||||||
|
|
||||||
|
return returnValue!
|
||||||
|
}
|
||||||
|
}
|
||||||
|
set {
|
||||||
|
if Thread.isMainThread {
|
||||||
|
pinEventIDUnderlyingReturnValue = newValue
|
||||||
|
} else {
|
||||||
|
DispatchQueue.main.sync {
|
||||||
|
pinEventIDUnderlyingReturnValue = newValue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var pinEventIDClosure: ((String) async -> Result<Bool, TimelineProxyError>)?
|
||||||
|
|
||||||
|
func pin(eventID: String) async -> Result<Bool, TimelineProxyError> {
|
||||||
|
pinEventIDCallsCount += 1
|
||||||
|
pinEventIDReceivedEventID = eventID
|
||||||
|
DispatchQueue.main.async {
|
||||||
|
self.pinEventIDReceivedInvocations.append(eventID)
|
||||||
|
}
|
||||||
|
if let pinEventIDClosure = pinEventIDClosure {
|
||||||
|
return await pinEventIDClosure(eventID)
|
||||||
|
} else {
|
||||||
|
return pinEventIDReturnValue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//MARK: - unpin
|
||||||
|
|
||||||
|
var unpinEventIDUnderlyingCallsCount = 0
|
||||||
|
var unpinEventIDCallsCount: Int {
|
||||||
|
get {
|
||||||
|
if Thread.isMainThread {
|
||||||
|
return unpinEventIDUnderlyingCallsCount
|
||||||
|
} else {
|
||||||
|
var returnValue: Int? = nil
|
||||||
|
DispatchQueue.main.sync {
|
||||||
|
returnValue = unpinEventIDUnderlyingCallsCount
|
||||||
|
}
|
||||||
|
|
||||||
|
return returnValue!
|
||||||
|
}
|
||||||
|
}
|
||||||
|
set {
|
||||||
|
if Thread.isMainThread {
|
||||||
|
unpinEventIDUnderlyingCallsCount = newValue
|
||||||
|
} else {
|
||||||
|
DispatchQueue.main.sync {
|
||||||
|
unpinEventIDUnderlyingCallsCount = newValue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var unpinEventIDCalled: Bool {
|
||||||
|
return unpinEventIDCallsCount > 0
|
||||||
|
}
|
||||||
|
var unpinEventIDReceivedEventID: String?
|
||||||
|
var unpinEventIDReceivedInvocations: [String] = []
|
||||||
|
|
||||||
|
var unpinEventIDUnderlyingReturnValue: Result<Bool, TimelineProxyError>!
|
||||||
|
var unpinEventIDReturnValue: Result<Bool, TimelineProxyError>! {
|
||||||
|
get {
|
||||||
|
if Thread.isMainThread {
|
||||||
|
return unpinEventIDUnderlyingReturnValue
|
||||||
|
} else {
|
||||||
|
var returnValue: Result<Bool, TimelineProxyError>? = nil
|
||||||
|
DispatchQueue.main.sync {
|
||||||
|
returnValue = unpinEventIDUnderlyingReturnValue
|
||||||
|
}
|
||||||
|
|
||||||
|
return returnValue!
|
||||||
|
}
|
||||||
|
}
|
||||||
|
set {
|
||||||
|
if Thread.isMainThread {
|
||||||
|
unpinEventIDUnderlyingReturnValue = newValue
|
||||||
|
} else {
|
||||||
|
DispatchQueue.main.sync {
|
||||||
|
unpinEventIDUnderlyingReturnValue = newValue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var unpinEventIDClosure: ((String) async -> Result<Bool, TimelineProxyError>)?
|
||||||
|
|
||||||
|
func unpin(eventID: String) async -> Result<Bool, TimelineProxyError> {
|
||||||
|
unpinEventIDCallsCount += 1
|
||||||
|
unpinEventIDReceivedEventID = eventID
|
||||||
|
DispatchQueue.main.async {
|
||||||
|
self.unpinEventIDReceivedInvocations.append(eventID)
|
||||||
|
}
|
||||||
|
if let unpinEventIDClosure = unpinEventIDClosure {
|
||||||
|
return await unpinEventIDClosure(eventID)
|
||||||
|
} else {
|
||||||
|
return unpinEventIDReturnValue
|
||||||
|
}
|
||||||
|
}
|
||||||
//MARK: - sendAudio
|
//MARK: - sendAudio
|
||||||
|
|
||||||
var sendAudioUrlAudioInfoProgressSubjectRequestHandleUnderlyingCallsCount = 0
|
var sendAudioUrlAudioInfoProgressSubjectRequestHandleUnderlyingCallsCount = 0
|
||||||
|
|||||||
@@ -10243,6 +10243,81 @@ open class RoomSDKMock: MatrixRustSDK.Room {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//MARK: - canUserPinUnpin
|
||||||
|
|
||||||
|
open var canUserPinUnpinUserIdThrowableError: Error?
|
||||||
|
var canUserPinUnpinUserIdUnderlyingCallsCount = 0
|
||||||
|
open var canUserPinUnpinUserIdCallsCount: Int {
|
||||||
|
get {
|
||||||
|
if Thread.isMainThread {
|
||||||
|
return canUserPinUnpinUserIdUnderlyingCallsCount
|
||||||
|
} else {
|
||||||
|
var returnValue: Int? = nil
|
||||||
|
DispatchQueue.main.sync {
|
||||||
|
returnValue = canUserPinUnpinUserIdUnderlyingCallsCount
|
||||||
|
}
|
||||||
|
|
||||||
|
return returnValue!
|
||||||
|
}
|
||||||
|
}
|
||||||
|
set {
|
||||||
|
if Thread.isMainThread {
|
||||||
|
canUserPinUnpinUserIdUnderlyingCallsCount = newValue
|
||||||
|
} else {
|
||||||
|
DispatchQueue.main.sync {
|
||||||
|
canUserPinUnpinUserIdUnderlyingCallsCount = newValue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
open var canUserPinUnpinUserIdCalled: Bool {
|
||||||
|
return canUserPinUnpinUserIdCallsCount > 0
|
||||||
|
}
|
||||||
|
open var canUserPinUnpinUserIdReceivedUserId: String?
|
||||||
|
open var canUserPinUnpinUserIdReceivedInvocations: [String] = []
|
||||||
|
|
||||||
|
var canUserPinUnpinUserIdUnderlyingReturnValue: Bool!
|
||||||
|
open var canUserPinUnpinUserIdReturnValue: Bool! {
|
||||||
|
get {
|
||||||
|
if Thread.isMainThread {
|
||||||
|
return canUserPinUnpinUserIdUnderlyingReturnValue
|
||||||
|
} else {
|
||||||
|
var returnValue: Bool? = nil
|
||||||
|
DispatchQueue.main.sync {
|
||||||
|
returnValue = canUserPinUnpinUserIdUnderlyingReturnValue
|
||||||
|
}
|
||||||
|
|
||||||
|
return returnValue!
|
||||||
|
}
|
||||||
|
}
|
||||||
|
set {
|
||||||
|
if Thread.isMainThread {
|
||||||
|
canUserPinUnpinUserIdUnderlyingReturnValue = newValue
|
||||||
|
} else {
|
||||||
|
DispatchQueue.main.sync {
|
||||||
|
canUserPinUnpinUserIdUnderlyingReturnValue = newValue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
open var canUserPinUnpinUserIdClosure: ((String) async throws -> Bool)?
|
||||||
|
|
||||||
|
open override func canUserPinUnpin(userId: String) async throws -> Bool {
|
||||||
|
if let error = canUserPinUnpinUserIdThrowableError {
|
||||||
|
throw error
|
||||||
|
}
|
||||||
|
canUserPinUnpinUserIdCallsCount += 1
|
||||||
|
canUserPinUnpinUserIdReceivedUserId = userId
|
||||||
|
DispatchQueue.main.async {
|
||||||
|
self.canUserPinUnpinUserIdReceivedInvocations.append(userId)
|
||||||
|
}
|
||||||
|
if let canUserPinUnpinUserIdClosure = canUserPinUnpinUserIdClosure {
|
||||||
|
return try await canUserPinUnpinUserIdClosure(userId)
|
||||||
|
} else {
|
||||||
|
return canUserPinUnpinUserIdReturnValue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
//MARK: - canUserRedactOther
|
//MARK: - canUserRedactOther
|
||||||
|
|
||||||
open var canUserRedactOtherUserIdThrowableError: Error?
|
open var canUserRedactOtherUserIdThrowableError: Error?
|
||||||
@@ -18491,6 +18566,81 @@ open class TimelineSDKMock: MatrixRustSDK.Timeline {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//MARK: - pinEvent
|
||||||
|
|
||||||
|
open var pinEventEventIdThrowableError: Error?
|
||||||
|
var pinEventEventIdUnderlyingCallsCount = 0
|
||||||
|
open var pinEventEventIdCallsCount: Int {
|
||||||
|
get {
|
||||||
|
if Thread.isMainThread {
|
||||||
|
return pinEventEventIdUnderlyingCallsCount
|
||||||
|
} else {
|
||||||
|
var returnValue: Int? = nil
|
||||||
|
DispatchQueue.main.sync {
|
||||||
|
returnValue = pinEventEventIdUnderlyingCallsCount
|
||||||
|
}
|
||||||
|
|
||||||
|
return returnValue!
|
||||||
|
}
|
||||||
|
}
|
||||||
|
set {
|
||||||
|
if Thread.isMainThread {
|
||||||
|
pinEventEventIdUnderlyingCallsCount = newValue
|
||||||
|
} else {
|
||||||
|
DispatchQueue.main.sync {
|
||||||
|
pinEventEventIdUnderlyingCallsCount = newValue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
open var pinEventEventIdCalled: Bool {
|
||||||
|
return pinEventEventIdCallsCount > 0
|
||||||
|
}
|
||||||
|
open var pinEventEventIdReceivedEventId: String?
|
||||||
|
open var pinEventEventIdReceivedInvocations: [String] = []
|
||||||
|
|
||||||
|
var pinEventEventIdUnderlyingReturnValue: Bool!
|
||||||
|
open var pinEventEventIdReturnValue: Bool! {
|
||||||
|
get {
|
||||||
|
if Thread.isMainThread {
|
||||||
|
return pinEventEventIdUnderlyingReturnValue
|
||||||
|
} else {
|
||||||
|
var returnValue: Bool? = nil
|
||||||
|
DispatchQueue.main.sync {
|
||||||
|
returnValue = pinEventEventIdUnderlyingReturnValue
|
||||||
|
}
|
||||||
|
|
||||||
|
return returnValue!
|
||||||
|
}
|
||||||
|
}
|
||||||
|
set {
|
||||||
|
if Thread.isMainThread {
|
||||||
|
pinEventEventIdUnderlyingReturnValue = newValue
|
||||||
|
} else {
|
||||||
|
DispatchQueue.main.sync {
|
||||||
|
pinEventEventIdUnderlyingReturnValue = newValue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
open var pinEventEventIdClosure: ((String) async throws -> Bool)?
|
||||||
|
|
||||||
|
open override func pinEvent(eventId: String) async throws -> Bool {
|
||||||
|
if let error = pinEventEventIdThrowableError {
|
||||||
|
throw error
|
||||||
|
}
|
||||||
|
pinEventEventIdCallsCount += 1
|
||||||
|
pinEventEventIdReceivedEventId = eventId
|
||||||
|
DispatchQueue.main.async {
|
||||||
|
self.pinEventEventIdReceivedInvocations.append(eventId)
|
||||||
|
}
|
||||||
|
if let pinEventEventIdClosure = pinEventEventIdClosure {
|
||||||
|
return try await pinEventEventIdClosure(eventId)
|
||||||
|
} else {
|
||||||
|
return pinEventEventIdReturnValue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
//MARK: - redactEvent
|
//MARK: - redactEvent
|
||||||
|
|
||||||
open var redactEventItemReasonThrowableError: Error?
|
open var redactEventItemReasonThrowableError: Error?
|
||||||
@@ -19338,6 +19488,81 @@ open class TimelineSDKMock: MatrixRustSDK.Timeline {
|
|||||||
}
|
}
|
||||||
try await toggleReactionEventIdKeyClosure?(eventId, key)
|
try await toggleReactionEventIdKeyClosure?(eventId, key)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//MARK: - unpinEvent
|
||||||
|
|
||||||
|
open var unpinEventEventIdThrowableError: Error?
|
||||||
|
var unpinEventEventIdUnderlyingCallsCount = 0
|
||||||
|
open var unpinEventEventIdCallsCount: Int {
|
||||||
|
get {
|
||||||
|
if Thread.isMainThread {
|
||||||
|
return unpinEventEventIdUnderlyingCallsCount
|
||||||
|
} else {
|
||||||
|
var returnValue: Int? = nil
|
||||||
|
DispatchQueue.main.sync {
|
||||||
|
returnValue = unpinEventEventIdUnderlyingCallsCount
|
||||||
|
}
|
||||||
|
|
||||||
|
return returnValue!
|
||||||
|
}
|
||||||
|
}
|
||||||
|
set {
|
||||||
|
if Thread.isMainThread {
|
||||||
|
unpinEventEventIdUnderlyingCallsCount = newValue
|
||||||
|
} else {
|
||||||
|
DispatchQueue.main.sync {
|
||||||
|
unpinEventEventIdUnderlyingCallsCount = newValue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
open var unpinEventEventIdCalled: Bool {
|
||||||
|
return unpinEventEventIdCallsCount > 0
|
||||||
|
}
|
||||||
|
open var unpinEventEventIdReceivedEventId: String?
|
||||||
|
open var unpinEventEventIdReceivedInvocations: [String] = []
|
||||||
|
|
||||||
|
var unpinEventEventIdUnderlyingReturnValue: Bool!
|
||||||
|
open var unpinEventEventIdReturnValue: Bool! {
|
||||||
|
get {
|
||||||
|
if Thread.isMainThread {
|
||||||
|
return unpinEventEventIdUnderlyingReturnValue
|
||||||
|
} else {
|
||||||
|
var returnValue: Bool? = nil
|
||||||
|
DispatchQueue.main.sync {
|
||||||
|
returnValue = unpinEventEventIdUnderlyingReturnValue
|
||||||
|
}
|
||||||
|
|
||||||
|
return returnValue!
|
||||||
|
}
|
||||||
|
}
|
||||||
|
set {
|
||||||
|
if Thread.isMainThread {
|
||||||
|
unpinEventEventIdUnderlyingReturnValue = newValue
|
||||||
|
} else {
|
||||||
|
DispatchQueue.main.sync {
|
||||||
|
unpinEventEventIdUnderlyingReturnValue = newValue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
open var unpinEventEventIdClosure: ((String) async throws -> Bool)?
|
||||||
|
|
||||||
|
open override func unpinEvent(eventId: String) async throws -> Bool {
|
||||||
|
if let error = unpinEventEventIdThrowableError {
|
||||||
|
throw error
|
||||||
|
}
|
||||||
|
unpinEventEventIdCallsCount += 1
|
||||||
|
unpinEventEventIdReceivedEventId = eventId
|
||||||
|
DispatchQueue.main.async {
|
||||||
|
self.unpinEventEventIdReceivedInvocations.append(eventId)
|
||||||
|
}
|
||||||
|
if let unpinEventEventIdClosure = unpinEventEventIdClosure {
|
||||||
|
return try await unpinEventEventIdClosure(eventId)
|
||||||
|
} else {
|
||||||
|
return unpinEventEventIdReturnValue
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
open class TimelineDiffSDKMock: MatrixRustSDK.TimelineDiff {
|
open class TimelineDiffSDKMock: MatrixRustSDK.TimelineDiff {
|
||||||
init() {
|
init() {
|
||||||
|
|||||||
@@ -29,6 +29,7 @@ struct RoomProxyMockConfiguration {
|
|||||||
var isEncrypted = true
|
var isEncrypted = true
|
||||||
var hasOngoingCall = true
|
var hasOngoingCall = true
|
||||||
var canonicalAlias: String?
|
var canonicalAlias: String?
|
||||||
|
var pinnedEventIDs: [String] = []
|
||||||
|
|
||||||
var timelineStartReached = false
|
var timelineStartReached = false
|
||||||
|
|
||||||
@@ -63,6 +64,8 @@ extension RoomProxyMock {
|
|||||||
hasOngoingCall = configuration.hasOngoingCall
|
hasOngoingCall = configuration.hasOngoingCall
|
||||||
canonicalAlias = configuration.canonicalAlias
|
canonicalAlias = configuration.canonicalAlias
|
||||||
|
|
||||||
|
underlyingPinnedEventIDs = configuration.pinnedEventIDs
|
||||||
|
|
||||||
let timeline = TimelineProxyMock()
|
let timeline = TimelineProxyMock()
|
||||||
timeline.sendMessageEventContentReturnValue = .success(())
|
timeline.sendMessageEventContentReturnValue = .success(())
|
||||||
timeline.paginateBackwardsRequestSizeReturnValue = .success(())
|
timeline.paginateBackwardsRequestSizeReturnValue = .success(())
|
||||||
|
|||||||
@@ -14,6 +14,7 @@
|
|||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
//
|
//
|
||||||
|
|
||||||
|
import Combine
|
||||||
import Foundation
|
import Foundation
|
||||||
|
|
||||||
public extension Task where Success == Never, Failure == Never {
|
public extension Task where Success == Never, Failure == Never {
|
||||||
@@ -61,3 +62,13 @@ public extension Task where Success == Never, Failure == Never {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
extension Task {
|
||||||
|
func store(in cancellables: inout Set<AnyCancellable>) {
|
||||||
|
asCancellable().store(in: &cancellables)
|
||||||
|
}
|
||||||
|
|
||||||
|
func asCancellable() -> AnyCancellable {
|
||||||
|
AnyCancellable(cancel)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -172,8 +172,11 @@ class RoomScreenInteractionHandler {
|
|||||||
case .endPoll(let pollStartID):
|
case .endPoll(let pollStartID):
|
||||||
endPoll(pollStartID: pollStartID)
|
endPoll(pollStartID: pollStartID)
|
||||||
case .pin:
|
case .pin:
|
||||||
// TODO: Implement the pin action
|
guard let eventID = itemID.eventID else { return }
|
||||||
break
|
Task { await timelineController.pin(eventID: eventID) }
|
||||||
|
case .unpin:
|
||||||
|
guard let eventID = itemID.eventID else { return }
|
||||||
|
Task { await timelineController.unpin(eventID: eventID) }
|
||||||
}
|
}
|
||||||
|
|
||||||
if action.switchToDefaultComposer {
|
if action.switchToDefaultComposer {
|
||||||
|
|||||||
@@ -15,9 +15,8 @@
|
|||||||
//
|
//
|
||||||
|
|
||||||
import Combine
|
import Combine
|
||||||
import SwiftUI
|
|
||||||
|
|
||||||
import OrderedCollections
|
import OrderedCollections
|
||||||
|
import SwiftUI
|
||||||
|
|
||||||
enum RoomScreenViewModelAction {
|
enum RoomScreenViewModelAction {
|
||||||
case displayRoomDetails
|
case displayRoomDetails
|
||||||
@@ -140,7 +139,7 @@ enum RoomScreenViewAction {
|
|||||||
case hasSwitchedTimeline
|
case hasSwitchedTimeline
|
||||||
|
|
||||||
case hasScrolled(direction: ScrollDirection)
|
case hasScrolled(direction: ScrollDirection)
|
||||||
case nextPin
|
case tappedPinBanner
|
||||||
case viewAllPins
|
case viewAllPins
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -172,20 +171,11 @@ struct RoomScreenViewState: BindableState {
|
|||||||
|
|
||||||
var isPinningEnabled = false
|
var isPinningEnabled = false
|
||||||
var lastScrollDirection: ScrollDirection?
|
var lastScrollDirection: ScrollDirection?
|
||||||
// These are just mocked items used for testing, their types might change
|
|
||||||
let pinnedItems = [
|
|
||||||
"Hello 1",
|
|
||||||
"How are you 2",
|
|
||||||
"I am fine 3",
|
|
||||||
"Thank you 4"
|
|
||||||
]
|
|
||||||
var currentPinIndex = 0
|
|
||||||
var shouldShowPinBanner: Bool {
|
|
||||||
isPinningEnabled && !pinnedItems.isEmpty && lastScrollDirection != .top
|
|
||||||
}
|
|
||||||
|
|
||||||
var selectedPinContent: AttributedString {
|
var pinnedEventsState = PinnedEventsState()
|
||||||
.init(pinnedItems[currentPinIndex])
|
|
||||||
|
var shouldShowPinBanner: Bool {
|
||||||
|
isPinningEnabled && !pinnedEventsState.pinnedEventIDs.isEmpty && lastScrollDirection != .top
|
||||||
}
|
}
|
||||||
|
|
||||||
var canJoinCall = false
|
var canJoinCall = false
|
||||||
@@ -304,3 +294,41 @@ enum ScrollDirection: Equatable {
|
|||||||
case top
|
case top
|
||||||
case bottom
|
case bottom
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct PinnedEventsState: Equatable {
|
||||||
|
// For now these will only contain and show the event IDs, but in the future they will also contain the content
|
||||||
|
var pinnedEventIDs: OrderedSet<String> = [] {
|
||||||
|
didSet {
|
||||||
|
if selectedPinEventID == nil, !pinnedEventIDs.isEmpty {
|
||||||
|
selectedPinEventID = pinnedEventIDs.first
|
||||||
|
} else if pinnedEventIDs.isEmpty {
|
||||||
|
selectedPinEventID = nil
|
||||||
|
} else if let selectedPinEventID, !pinnedEventIDs.contains(selectedPinEventID) {
|
||||||
|
self.selectedPinEventID = pinnedEventIDs.first
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var selectedPinEventID: String?
|
||||||
|
|
||||||
|
var selectedPinIndex: Int {
|
||||||
|
guard let selectedPinEventID else {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
return pinnedEventIDs.firstIndex(of: selectedPinEventID) ?? 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// For now we show the event ID as the content, but is just until we have a way to get the real content
|
||||||
|
var selectedPinContent: AttributedString {
|
||||||
|
.init(selectedPinEventID ?? "")
|
||||||
|
}
|
||||||
|
|
||||||
|
mutating func nextPin() {
|
||||||
|
guard !pinnedEventIDs.isEmpty else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
let currentIndex = selectedPinIndex
|
||||||
|
let nextIndex = (currentIndex + 1) % pinnedEventIDs.count
|
||||||
|
selectedPinEventID = pinnedEventIDs[nextIndex]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -196,8 +196,11 @@ class RoomScreenViewModel: RoomScreenViewModelType, RoomScreenViewModelProtocol
|
|||||||
Task { state.timelineViewState.isSwitchingTimelines = false }
|
Task { state.timelineViewState.isSwitchingTimelines = false }
|
||||||
case let .hasScrolled(direction):
|
case let .hasScrolled(direction):
|
||||||
state.lastScrollDirection = direction
|
state.lastScrollDirection = direction
|
||||||
case .nextPin:
|
case .tappedPinBanner:
|
||||||
state.currentPinIndex = (state.currentPinIndex + 1) % state.pinnedItems.count
|
if let eventID = state.pinnedEventsState.selectedPinEventID {
|
||||||
|
Task { await focusOnEvent(eventID: eventID) }
|
||||||
|
}
|
||||||
|
state.pinnedEventsState.nextPin()
|
||||||
case .viewAllPins:
|
case .viewAllPins:
|
||||||
// TODO: Implement
|
// TODO: Implement
|
||||||
break
|
break
|
||||||
@@ -368,7 +371,7 @@ class RoomScreenViewModel: RoomScreenViewModelType, RoomScreenViewModelProtocol
|
|||||||
}
|
}
|
||||||
|
|
||||||
if state.isPinningEnabled,
|
if state.isPinningEnabled,
|
||||||
case let .success(value) = await roomProxy.canUser(userID: roomProxy.ownUserID, sendStateEvent: .roomPinnedEvents) {
|
case let .success(value) = await roomProxy.canUserPinOrUnpin(userID: roomProxy.ownUserID) {
|
||||||
state.canCurrentUserPin = value
|
state.canCurrentUserPin = value
|
||||||
} else {
|
} else {
|
||||||
state.canCurrentUserPin = false
|
state.canCurrentUserPin = false
|
||||||
@@ -401,9 +404,11 @@ class RoomScreenViewModel: RoomScreenViewModelType, RoomScreenViewModelProtocol
|
|||||||
}
|
}
|
||||||
.store(in: &cancellables)
|
.store(in: &cancellables)
|
||||||
|
|
||||||
roomProxy
|
let roomInfoSubscription = roomProxy
|
||||||
.actionsPublisher
|
.actionsPublisher
|
||||||
.filter { $0 == .roomInfoUpdate }
|
.filter { $0 == .roomInfoUpdate }
|
||||||
|
|
||||||
|
roomInfoSubscription
|
||||||
.throttle(for: .seconds(1), scheduler: DispatchQueue.main, latest: true)
|
.throttle(for: .seconds(1), scheduler: DispatchQueue.main, latest: true)
|
||||||
.sink { [weak self] _ in
|
.sink { [weak self] _ in
|
||||||
guard let self else { return }
|
guard let self else { return }
|
||||||
@@ -413,6 +418,21 @@ class RoomScreenViewModel: RoomScreenViewModelType, RoomScreenViewModelProtocol
|
|||||||
}
|
}
|
||||||
.store(in: &cancellables)
|
.store(in: &cancellables)
|
||||||
|
|
||||||
|
Task { [weak self] in
|
||||||
|
guard let self else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// If the subscription has sent a value before the Task has started it might be lost, so before entering the loop we always do an update.
|
||||||
|
await state.pinnedEventsState.pinnedEventIDs = .init(roomProxy.pinnedEventIDs)
|
||||||
|
for await _ in roomInfoSubscription.receive(on: DispatchQueue.main).values {
|
||||||
|
guard !Task.isCancelled else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
await state.pinnedEventsState.pinnedEventIDs = .init(roomProxy.pinnedEventIDs)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.store(in: &cancellables)
|
||||||
|
|
||||||
appSettings.$sharePresence
|
appSettings.$sharePresence
|
||||||
.weakAssign(to: \.state.showReadReceipts, on: self)
|
.weakAssign(to: \.state.showReadReceipts, on: self)
|
||||||
.store(in: &cancellables)
|
.store(in: &cancellables)
|
||||||
|
|||||||
@@ -62,6 +62,7 @@ enum TimelineItemMenuAction: Identifiable, Hashable {
|
|||||||
case toggleReaction(key: String)
|
case toggleReaction(key: String)
|
||||||
case endPoll(pollStartID: String)
|
case endPoll(pollStartID: String)
|
||||||
case pin
|
case pin
|
||||||
|
case unpin
|
||||||
|
|
||||||
var id: Self { self }
|
var id: Self { self }
|
||||||
|
|
||||||
@@ -135,7 +136,9 @@ enum TimelineItemMenuAction: Identifiable, Hashable {
|
|||||||
case .endPoll:
|
case .endPoll:
|
||||||
Label(L10n.actionEndPoll, icon: \.pollsEnd)
|
Label(L10n.actionEndPoll, icon: \.pollsEnd)
|
||||||
case .pin:
|
case .pin:
|
||||||
Label(L10n.Action.pin, icon: \.pin)
|
Label(L10n.actionPin, icon: \.pin)
|
||||||
|
case .unpin:
|
||||||
|
Label(L10n.actionUnpin, icon: \.unpin)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+3
-3
@@ -21,6 +21,7 @@ struct TimelineItemMenuActionProvider {
|
|||||||
let canCurrentUserRedactSelf: Bool
|
let canCurrentUserRedactSelf: Bool
|
||||||
let canCurrentUserRedactOthers: Bool
|
let canCurrentUserRedactOthers: Bool
|
||||||
let canCurrentUserPin: Bool
|
let canCurrentUserPin: Bool
|
||||||
|
let pinnedEventIDs: Set<String>
|
||||||
let isDM: Bool
|
let isDM: Bool
|
||||||
let isViewSourceEnabled: Bool
|
let isViewSourceEnabled: Bool
|
||||||
|
|
||||||
@@ -66,9 +67,8 @@ struct TimelineItemMenuActionProvider {
|
|||||||
actions.append(.forward(itemID: item.id))
|
actions.append(.forward(itemID: item.id))
|
||||||
}
|
}
|
||||||
|
|
||||||
if canCurrentUserPin {
|
if canCurrentUserPin, let eventID = item.id.eventID {
|
||||||
// TODO: If the event is already pinned use the unpinned action
|
actions.append(pinnedEventIDs.contains(eventID) ? .unpin : .pin)
|
||||||
actions.append(.pin)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if item.isEditable {
|
if item.isEditable {
|
||||||
|
|||||||
+8
-12
@@ -18,18 +18,16 @@ import Compound
|
|||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
|
||||||
struct PinnedItemsBannerView: View {
|
struct PinnedItemsBannerView: View {
|
||||||
let pinIndex: Int
|
let pinnedEventsState: PinnedEventsState
|
||||||
let pinsCount: Int
|
|
||||||
let pinContent: AttributedString
|
|
||||||
|
|
||||||
let onMainButtonTap: () -> Void
|
let onMainButtonTap: () -> Void
|
||||||
let onViewAllButtonTap: () -> Void
|
let onViewAllButtonTap: () -> Void
|
||||||
|
|
||||||
private var bannerIndicatorDescription: AttributedString {
|
private var bannerIndicatorDescription: AttributedString {
|
||||||
let index = pinIndex + 1
|
let index = pinnedEventsState.selectedPinIndex + 1
|
||||||
let boldPlaceholder = "{bold}"
|
let boldPlaceholder = "{bold}"
|
||||||
var finalString = AttributedString(L10n.Screen.Room.pinnedBannerIndicatorDescription(boldPlaceholder))
|
var finalString = AttributedString(L10n.screenRoomPinnedBannerIndicatorDescription(boldPlaceholder))
|
||||||
var boldString = AttributedString(L10n.Screen.Room.pinnedBannerIndicator(index, pinsCount))
|
var boldString = AttributedString(L10n.screenRoomPinnedBannerIndicator(index, pinnedEventsState.pinnedEventIDs.count))
|
||||||
boldString.bold()
|
boldString.bold()
|
||||||
finalString.replace(boldPlaceholder, with: boldString)
|
finalString.replace(boldPlaceholder, with: boldString)
|
||||||
return finalString
|
return finalString
|
||||||
@@ -50,7 +48,7 @@ struct PinnedItemsBannerView: View {
|
|||||||
Button { onMainButtonTap() } label: {
|
Button { onMainButtonTap() } label: {
|
||||||
HStack(spacing: 0) {
|
HStack(spacing: 0) {
|
||||||
HStack(spacing: 10) {
|
HStack(spacing: 10) {
|
||||||
PinnedItemsIndicatorView(pinIndex: pinIndex, pinsCount: pinsCount)
|
PinnedItemsIndicatorView(pinIndex: pinnedEventsState.selectedPinIndex, pinsCount: pinnedEventsState.pinnedEventIDs.count)
|
||||||
.accessibilityHidden(true)
|
.accessibilityHidden(true)
|
||||||
CompoundIcon(\.pinSolid, size: .small, relativeTo: .compound.bodyMD)
|
CompoundIcon(\.pinSolid, size: .small, relativeTo: .compound.bodyMD)
|
||||||
.foregroundColor(Color.compound.iconSecondaryAlpha)
|
.foregroundColor(Color.compound.iconSecondaryAlpha)
|
||||||
@@ -65,7 +63,7 @@ struct PinnedItemsBannerView: View {
|
|||||||
|
|
||||||
private var viewAllButton: some View {
|
private var viewAllButton: some View {
|
||||||
Button { onViewAllButtonTap() } label: {
|
Button { onViewAllButtonTap() } label: {
|
||||||
Text(L10n.Screen.Room.pinnedBannerViewAllButtonTitle)
|
Text(L10n.screenRoomPinnedBannerViewAllButtonTitle)
|
||||||
.font(.compound.bodyMDSemibold)
|
.font(.compound.bodyMDSemibold)
|
||||||
.foregroundStyle(Color.compound.textPrimary)
|
.foregroundStyle(Color.compound.textPrimary)
|
||||||
.padding(.horizontal, 16)
|
.padding(.horizontal, 16)
|
||||||
@@ -79,7 +77,7 @@ struct PinnedItemsBannerView: View {
|
|||||||
.font(.compound.bodySM)
|
.font(.compound.bodySM)
|
||||||
.foregroundColor(.compound.textActionAccent)
|
.foregroundColor(.compound.textActionAccent)
|
||||||
.lineLimit(1)
|
.lineLimit(1)
|
||||||
Text(pinContent)
|
Text(pinnedEventsState.selectedPinContent)
|
||||||
.font(.compound.bodyMD)
|
.font(.compound.bodyMD)
|
||||||
.foregroundColor(.compound.textPrimary)
|
.foregroundColor(.compound.textPrimary)
|
||||||
.lineLimit(1)
|
.lineLimit(1)
|
||||||
@@ -89,9 +87,7 @@ struct PinnedItemsBannerView: View {
|
|||||||
|
|
||||||
struct PinnedItemsBannerView_Previews: PreviewProvider, TestablePreview {
|
struct PinnedItemsBannerView_Previews: PreviewProvider, TestablePreview {
|
||||||
static var previews: some View {
|
static var previews: some View {
|
||||||
PinnedItemsBannerView(pinIndex: 0,
|
PinnedItemsBannerView(pinnedEventsState: .init(pinnedEventIDs: ["Content", "NotShown1", "NotShown2"], selectedPinEventID: "Content"),
|
||||||
pinsCount: 3,
|
|
||||||
pinContent: .init(stringLiteral: "Content"),
|
|
||||||
onMainButtonTap: { },
|
onMainButtonTap: { },
|
||||||
onViewAllButtonTap: { })
|
onViewAllButtonTap: { })
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -69,6 +69,7 @@ struct RoomScreen: View {
|
|||||||
canCurrentUserRedactSelf: context.viewState.canCurrentUserRedactSelf,
|
canCurrentUserRedactSelf: context.viewState.canCurrentUserRedactSelf,
|
||||||
canCurrentUserRedactOthers: context.viewState.canCurrentUserRedactOthers,
|
canCurrentUserRedactOthers: context.viewState.canCurrentUserRedactOthers,
|
||||||
canCurrentUserPin: context.viewState.canCurrentUserPin,
|
canCurrentUserPin: context.viewState.canCurrentUserPin,
|
||||||
|
pinnedEventIDs: context.viewState.pinnedEventsState.pinnedEventIDs.set,
|
||||||
isDM: context.viewState.isEncryptedOneToOneRoom,
|
isDM: context.viewState.isEncryptedOneToOneRoom,
|
||||||
isViewSourceEnabled: context.viewState.isViewSourceEnabled).makeActions()
|
isViewSourceEnabled: context.viewState.isViewSourceEnabled).makeActions()
|
||||||
if let actions {
|
if let actions {
|
||||||
@@ -109,10 +110,8 @@ struct RoomScreen: View {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private var pinnedItemsBanner: some View {
|
private var pinnedItemsBanner: some View {
|
||||||
PinnedItemsBannerView(pinIndex: context.viewState.currentPinIndex,
|
PinnedItemsBannerView(pinnedEventsState: context.viewState.pinnedEventsState,
|
||||||
pinsCount: context.viewState.pinnedItems.count,
|
onMainButtonTap: { context.send(viewAction: .tappedPinBanner) },
|
||||||
pinContent: context.viewState.selectedPinContent,
|
|
||||||
onMainButtonTap: { context.send(viewAction: .nextPin) },
|
|
||||||
onViewAllButtonTap: { context.send(viewAction: .viewAllPins) })
|
onViewAllButtonTap: { context.send(viewAction: .viewAllPins) })
|
||||||
.transition(.move(edge: .top))
|
.transition(.move(edge: .top))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -147,6 +147,7 @@ struct TimelineItemBubbledStylerView<Content: View>: View {
|
|||||||
canCurrentUserRedactSelf: context.viewState.canCurrentUserRedactSelf,
|
canCurrentUserRedactSelf: context.viewState.canCurrentUserRedactSelf,
|
||||||
canCurrentUserRedactOthers: context.viewState.canCurrentUserRedactOthers,
|
canCurrentUserRedactOthers: context.viewState.canCurrentUserRedactOthers,
|
||||||
canCurrentUserPin: context.viewState.canCurrentUserPin,
|
canCurrentUserPin: context.viewState.canCurrentUserPin,
|
||||||
|
pinnedEventIDs: context.viewState.pinnedEventsState.pinnedEventIDs.set,
|
||||||
isDM: context.viewState.isEncryptedOneToOneRoom,
|
isDM: context.viewState.isEncryptedOneToOneRoom,
|
||||||
isViewSourceEnabled: context.viewState.isViewSourceEnabled)
|
isViewSourceEnabled: context.viewState.isViewSourceEnabled)
|
||||||
TimelineItemMacContextMenu(item: timelineItem, actionProvider: provider) { action in
|
TimelineItemMacContextMenu(item: timelineItem, actionProvider: provider) { action in
|
||||||
|
|||||||
@@ -16,9 +16,8 @@
|
|||||||
|
|
||||||
import Combine
|
import Combine
|
||||||
import Foundation
|
import Foundation
|
||||||
import UIKit
|
|
||||||
|
|
||||||
import MatrixRustSDK
|
import MatrixRustSDK
|
||||||
|
import UIKit
|
||||||
|
|
||||||
class RoomProxy: RoomProxyProtocol {
|
class RoomProxy: RoomProxyProtocol {
|
||||||
private let roomListItem: RoomListItemProtocol
|
private let roomListItem: RoomListItemProtocol
|
||||||
@@ -87,6 +86,12 @@ class RoomProxy: RoomProxyProtocol {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var pinnedEventIDs: [String] {
|
||||||
|
get async {
|
||||||
|
await (try? room.roomInfo().pinnedEventIds) ?? []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
var hasOngoingCall: Bool {
|
var hasOngoingCall: Bool {
|
||||||
room.hasActiveRoomCall()
|
room.hasActiveRoomCall()
|
||||||
}
|
}
|
||||||
@@ -493,6 +498,15 @@ class RoomProxy: RoomProxyProtocol {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func canUserPinOrUnpin(userID: String) async -> Result<Bool, RoomProxyError> {
|
||||||
|
do {
|
||||||
|
return try await .success(room.canUserPinUnpin(userId: userID))
|
||||||
|
} catch {
|
||||||
|
MXLog.error("Failed checking if the user can pin or unnpin: \(error)")
|
||||||
|
return .failure(.sdkError(error))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// MARK: - Moderation
|
// MARK: - Moderation
|
||||||
|
|
||||||
func kickUser(_ userID: String) async -> Result<Void, RoomProxyError> {
|
func kickUser(_ userID: String) async -> Result<Void, RoomProxyError> {
|
||||||
|
|||||||
@@ -38,6 +38,7 @@ protocol RoomProxyProtocol {
|
|||||||
var isSpace: Bool { get }
|
var isSpace: Bool { get }
|
||||||
var isEncrypted: Bool { get }
|
var isEncrypted: Bool { get }
|
||||||
var isFavourite: Bool { get async }
|
var isFavourite: Bool { get async }
|
||||||
|
var pinnedEventIDs: [String] { get async }
|
||||||
var membership: Membership { get }
|
var membership: Membership { get }
|
||||||
var hasOngoingCall: Bool { get }
|
var hasOngoingCall: Bool { get }
|
||||||
var canonicalAlias: String? { get }
|
var canonicalAlias: String? { get }
|
||||||
@@ -121,6 +122,7 @@ protocol RoomProxyProtocol {
|
|||||||
func canUserKick(userID: String) async -> Result<Bool, RoomProxyError>
|
func canUserKick(userID: String) async -> Result<Bool, RoomProxyError>
|
||||||
func canUserBan(userID: String) async -> Result<Bool, RoomProxyError>
|
func canUserBan(userID: String) async -> Result<Bool, RoomProxyError>
|
||||||
func canUserTriggerRoomNotification(userID: String) async -> Result<Bool, RoomProxyError>
|
func canUserTriggerRoomNotification(userID: String) async -> Result<Bool, RoomProxyError>
|
||||||
|
func canUserPinOrUnpin(userID: String) async -> Result<Bool, RoomProxyError>
|
||||||
|
|
||||||
// MARK: - Moderation
|
// MARK: - Moderation
|
||||||
|
|
||||||
|
|||||||
@@ -100,6 +100,10 @@ class MockRoomTimelineController: RoomTimelineControllerProtocol {
|
|||||||
|
|
||||||
func redact(_ itemID: TimelineItemIdentifier) async { }
|
func redact(_ itemID: TimelineItemIdentifier) async { }
|
||||||
|
|
||||||
|
func pin(eventID: String) async { }
|
||||||
|
|
||||||
|
func unpin(eventID: String) async { }
|
||||||
|
|
||||||
func messageEventContent(for itemID: TimelineItemIdentifier) -> RoomMessageEventContentWithoutRelation? {
|
func messageEventContent(for itemID: TimelineItemIdentifier) -> RoomMessageEventContentWithoutRelation? {
|
||||||
.init(noPointer: .init())
|
.init(noPointer: .init())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -237,6 +237,36 @@ class RoomTimelineController: RoomTimelineControllerProtocol {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func pin(eventID: String) async {
|
||||||
|
MXLog.info("Pinning event \(eventID) in \(roomID)")
|
||||||
|
|
||||||
|
switch await activeTimeline.pin(eventID: eventID) {
|
||||||
|
case .success(let value):
|
||||||
|
if value {
|
||||||
|
MXLog.info("Finished pinning event \(eventID)")
|
||||||
|
} else {
|
||||||
|
MXLog.error("Failed pinning event \(eventID) because is already pinned")
|
||||||
|
}
|
||||||
|
case .failure(let error):
|
||||||
|
MXLog.error("Failed pinning event \(eventID) with error: \(error)")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func unpin(eventID: String) async {
|
||||||
|
MXLog.info("Unpinning event \(eventID) in \(roomID)")
|
||||||
|
|
||||||
|
switch await activeTimeline.unpin(eventID: eventID) {
|
||||||
|
case .success(let value):
|
||||||
|
if value {
|
||||||
|
MXLog.info("Finished unpinning event \(eventID)")
|
||||||
|
} else {
|
||||||
|
MXLog.error("Failed unpinning event \(eventID) because is not pinned")
|
||||||
|
}
|
||||||
|
case .failure(let error):
|
||||||
|
MXLog.error("Failed unpinning event \(eventID) with error: \(error)")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func messageEventContent(for timelineItemID: TimelineItemIdentifier) async -> RoomMessageEventContentWithoutRelation? {
|
func messageEventContent(for timelineItemID: TimelineItemIdentifier) async -> RoomMessageEventContentWithoutRelation? {
|
||||||
await activeTimeline.messageEventContent(for: timelineItemID)
|
await activeTimeline.messageEventContent(for: timelineItemID)
|
||||||
}
|
}
|
||||||
|
|||||||
+4
@@ -68,6 +68,10 @@ protocol RoomTimelineControllerProtocol {
|
|||||||
|
|
||||||
func redact(_ itemID: TimelineItemIdentifier) async
|
func redact(_ itemID: TimelineItemIdentifier) async
|
||||||
|
|
||||||
|
func pin(eventID: String) async
|
||||||
|
|
||||||
|
func unpin(eventID: String) async
|
||||||
|
|
||||||
func messageEventContent(for itemID: TimelineItemIdentifier) async -> RoomMessageEventContentWithoutRelation?
|
func messageEventContent(for itemID: TimelineItemIdentifier) async -> RoomMessageEventContentWithoutRelation?
|
||||||
|
|
||||||
func debugInfo(for itemID: TimelineItemIdentifier) -> TimelineItemDebugInfo
|
func debugInfo(for itemID: TimelineItemIdentifier) -> TimelineItemDebugInfo
|
||||||
|
|||||||
@@ -202,6 +202,24 @@ final class TimelineProxy: TimelineProxyProtocol {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func pin(eventID: String) async -> Result<Bool, TimelineProxyError> {
|
||||||
|
do {
|
||||||
|
return try await .success(timeline.pinEvent(eventId: eventID))
|
||||||
|
} catch {
|
||||||
|
MXLog.error("Failed to pin the event \(eventID) with error: \(error)")
|
||||||
|
return .failure(.sdkError(error))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func unpin(eventID: String) async -> Result<Bool, TimelineProxyError> {
|
||||||
|
do {
|
||||||
|
return try await .success(timeline.unpinEvent(eventId: eventID))
|
||||||
|
} catch {
|
||||||
|
MXLog.error("Failed to unpin the event \(eventID) with error: \(error)")
|
||||||
|
return .failure(.sdkError(error))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// MARK: - Sending
|
// MARK: - Sending
|
||||||
|
|
||||||
func sendAudio(url: URL,
|
func sendAudio(url: URL,
|
||||||
|
|||||||
@@ -46,6 +46,10 @@ protocol TimelineProxyProtocol {
|
|||||||
func redact(_ timelineItemID: TimelineItemIdentifier,
|
func redact(_ timelineItemID: TimelineItemIdentifier,
|
||||||
reason: String?) async -> Result<Void, TimelineProxyError>
|
reason: String?) async -> Result<Void, TimelineProxyError>
|
||||||
|
|
||||||
|
func pin(eventID: String) async -> Result<Bool, TimelineProxyError>
|
||||||
|
|
||||||
|
func unpin(eventID: String) async -> Result<Bool, TimelineProxyError>
|
||||||
|
|
||||||
// MARK: - Sending
|
// MARK: - Sending
|
||||||
|
|
||||||
func sendAudio(url: URL,
|
func sendAudio(url: URL,
|
||||||
|
|||||||
+1
-1
@@ -60,7 +60,7 @@ packages:
|
|||||||
# Element/Matrix dependencies
|
# Element/Matrix dependencies
|
||||||
MatrixRustSDK:
|
MatrixRustSDK:
|
||||||
url: https://github.com/element-hq/matrix-rust-components-swift
|
url: https://github.com/element-hq/matrix-rust-components-swift
|
||||||
exactVersion: 1.0.28
|
exactVersion: 1.0.29
|
||||||
# path: ../matrix-rust-sdk
|
# path: ../matrix-rust-sdk
|
||||||
Compound:
|
Compound:
|
||||||
url: https://github.com/element-hq/compound-ios
|
url: https://github.com/element-hq/compound-ios
|
||||||
|
|||||||
Reference in New Issue
Block a user