diff --git a/Riot/Assets/Images.xcassets/Room/Actions/action_formatting_disabled.imageset/Contents.json b/Riot/Assets/Images.xcassets/Room/Actions/action_formatting_disabled.imageset/Contents.json
new file mode 100644
index 000000000..dbee5479f
--- /dev/null
+++ b/Riot/Assets/Images.xcassets/Room/Actions/action_formatting_disabled.imageset/Contents.json
@@ -0,0 +1,23 @@
+{
+ "images" : [
+ {
+ "filename" : "action_formatting_disabled.png",
+ "idiom" : "universal",
+ "scale" : "1x"
+ },
+ {
+ "filename" : "action_formatting_disabled@2x.png",
+ "idiom" : "universal",
+ "scale" : "2x"
+ },
+ {
+ "filename" : "action_formatting_disabled@3x.png",
+ "idiom" : "universal",
+ "scale" : "3x"
+ }
+ ],
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ }
+}
diff --git a/Riot/Assets/Images.xcassets/Room/Actions/action_formatting_disabled.imageset/action_formatting_disabled.png b/Riot/Assets/Images.xcassets/Room/Actions/action_formatting_disabled.imageset/action_formatting_disabled.png
new file mode 100644
index 000000000..f7ef2b190
Binary files /dev/null and b/Riot/Assets/Images.xcassets/Room/Actions/action_formatting_disabled.imageset/action_formatting_disabled.png differ
diff --git a/Riot/Assets/Images.xcassets/Room/Actions/action_formatting_disabled.imageset/action_formatting_disabled@2x.png b/Riot/Assets/Images.xcassets/Room/Actions/action_formatting_disabled.imageset/action_formatting_disabled@2x.png
new file mode 100644
index 000000000..270dd75d2
Binary files /dev/null and b/Riot/Assets/Images.xcassets/Room/Actions/action_formatting_disabled.imageset/action_formatting_disabled@2x.png differ
diff --git a/Riot/Assets/Images.xcassets/Room/Actions/action_formatting_disabled.imageset/action_formatting_disabled@3x.png b/Riot/Assets/Images.xcassets/Room/Actions/action_formatting_disabled.imageset/action_formatting_disabled@3x.png
new file mode 100644
index 000000000..9394656ff
Binary files /dev/null and b/Riot/Assets/Images.xcassets/Room/Actions/action_formatting_disabled.imageset/action_formatting_disabled@3x.png differ
diff --git a/Riot/Assets/Images.xcassets/Room/Actions/action_formatting_enabled.imageset/Contents.json b/Riot/Assets/Images.xcassets/Room/Actions/action_formatting_enabled.imageset/Contents.json
new file mode 100644
index 000000000..198b65f6e
--- /dev/null
+++ b/Riot/Assets/Images.xcassets/Room/Actions/action_formatting_enabled.imageset/Contents.json
@@ -0,0 +1,23 @@
+{
+ "images" : [
+ {
+ "filename" : "action_formatting_enabled.png",
+ "idiom" : "universal",
+ "scale" : "1x"
+ },
+ {
+ "filename" : "action_formatting_enabled@2x.png",
+ "idiom" : "universal",
+ "scale" : "2x"
+ },
+ {
+ "filename" : "action_formatting_enabled@3x.png",
+ "idiom" : "universal",
+ "scale" : "3x"
+ }
+ ],
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ }
+}
diff --git a/Riot/Assets/Images.xcassets/Room/Actions/action_formatting_enabled.imageset/action_formatting_enabled.png b/Riot/Assets/Images.xcassets/Room/Actions/action_formatting_enabled.imageset/action_formatting_enabled.png
new file mode 100644
index 000000000..06798aaa7
Binary files /dev/null and b/Riot/Assets/Images.xcassets/Room/Actions/action_formatting_enabled.imageset/action_formatting_enabled.png differ
diff --git a/Riot/Assets/Images.xcassets/Room/Actions/action_formatting_enabled.imageset/action_formatting_enabled@2x.png b/Riot/Assets/Images.xcassets/Room/Actions/action_formatting_enabled.imageset/action_formatting_enabled@2x.png
new file mode 100644
index 000000000..b2aec071e
Binary files /dev/null and b/Riot/Assets/Images.xcassets/Room/Actions/action_formatting_enabled.imageset/action_formatting_enabled@2x.png differ
diff --git a/Riot/Assets/Images.xcassets/Room/Actions/action_formatting_enabled.imageset/action_formatting_enabled@3x.png b/Riot/Assets/Images.xcassets/Room/Actions/action_formatting_enabled.imageset/action_formatting_enabled@3x.png
new file mode 100644
index 000000000..414aa321f
Binary files /dev/null and b/Riot/Assets/Images.xcassets/Room/Actions/action_formatting_enabled.imageset/action_formatting_enabled@3x.png differ
diff --git a/Riot/Assets/Images.xcassets/VoiceBroadcast/voice_broadcast_tile_live.imageset/Contents.json b/Riot/Assets/Images.xcassets/VoiceBroadcast/voice_broadcast_tile_live.imageset/Contents.json
new file mode 100644
index 000000000..9a7c05104
--- /dev/null
+++ b/Riot/Assets/Images.xcassets/VoiceBroadcast/voice_broadcast_tile_live.imageset/Contents.json
@@ -0,0 +1,12 @@
+{
+ "images" : [
+ {
+ "filename" : "voice_broadcast_tile_live.svg",
+ "idiom" : "universal"
+ }
+ ],
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ }
+}
diff --git a/Riot/Assets/Images.xcassets/VoiceBroadcast/voice_broadcast_tile_live.imageset/voice_broadcast_tile_live.svg b/Riot/Assets/Images.xcassets/VoiceBroadcast/voice_broadcast_tile_live.imageset/voice_broadcast_tile_live.svg
new file mode 100644
index 000000000..5728fc8d4
--- /dev/null
+++ b/Riot/Assets/Images.xcassets/VoiceBroadcast/voice_broadcast_tile_live.imageset/voice_broadcast_tile_live.svg
@@ -0,0 +1,7 @@
+
diff --git a/Riot/Assets/Images.xcassets/VoiceBroadcast/voice_broadcast_tile_mic.imageset/Contents.json b/Riot/Assets/Images.xcassets/VoiceBroadcast/voice_broadcast_tile_mic.imageset/Contents.json
new file mode 100644
index 000000000..eeeba86ae
--- /dev/null
+++ b/Riot/Assets/Images.xcassets/VoiceBroadcast/voice_broadcast_tile_mic.imageset/Contents.json
@@ -0,0 +1,12 @@
+{
+ "images" : [
+ {
+ "filename" : "voice_broadcast_tile_mic.svg",
+ "idiom" : "universal"
+ }
+ ],
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ }
+}
diff --git a/Riot/Assets/Images.xcassets/VoiceBroadcast/voice_broadcast_tile_mic.imageset/voice_broadcast_tile_mic.svg b/Riot/Assets/Images.xcassets/VoiceBroadcast/voice_broadcast_tile_mic.imageset/voice_broadcast_tile_mic.svg
new file mode 100644
index 000000000..2f3deca13
--- /dev/null
+++ b/Riot/Assets/Images.xcassets/VoiceBroadcast/voice_broadcast_tile_mic.imageset/voice_broadcast_tile_mic.svg
@@ -0,0 +1,4 @@
+
diff --git a/Riot/Assets/en.lproj/Untranslated.strings b/Riot/Assets/en.lproj/Untranslated.strings
index 6d9320f4a..9ff00a53d 100644
--- a/Riot/Assets/en.lproj/Untranslated.strings
+++ b/Riot/Assets/en.lproj/Untranslated.strings
@@ -19,4 +19,3 @@
// MARK: Onboarding Personalization WIP
"image_picker_action_files" = "Choose from files";
-"voice_broadcast_in_timeline_title" = "Voice broadcast detected (under active development)";
diff --git a/Riot/Assets/en.lproj/Vector.strings b/Riot/Assets/en.lproj/Vector.strings
index 24b07b68a..a168812bd 100644
--- a/Riot/Assets/en.lproj/Vector.strings
+++ b/Riot/Assets/en.lproj/Vector.strings
@@ -797,7 +797,7 @@ Tap the + to start adding people.";
"settings_labs_enable_new_session_manager" = "New session manager";
"settings_labs_enable_new_client_info_feature" = "Record the client name, version, and url to recognise sessions more easily in session manager";
"settings_labs_enable_new_app_layout" = "New Application Layout";
-"settings_labs_enable_wysiwyg_composer" = "Try out the rich text editor (plain text mode coming soon)";
+"settings_labs_enable_wysiwyg_composer" = "Try out the rich text editor";
"settings_labs_enable_voice_broadcast" = "Voice broadcast (under active development)";
"settings_version" = "Version %@";
@@ -2195,6 +2195,8 @@ Tap the + to start adding people.";
"voice_broadcast_blocked_by_someone_else_message" = "Someone else is already recording a voice broadcast. Wait for their voice broadcast to end to start a new one.";
"voice_broadcast_already_in_progress_message" = "You are already recording a voice broadcast. Please end your current voice broadcast to start a new one.";
"voice_broadcast_playback_loading_error" = "Unable to play this voice broadcast.";
+"voice_broadcast_live" = "Live";
+"voice_broadcast_tile" = "Voice broadcast";
// Mark: - Version check
diff --git a/Riot/Generated/Images.swift b/Riot/Generated/Images.swift
index 922174427..94352b0be 100644
--- a/Riot/Generated/Images.swift
+++ b/Riot/Generated/Images.swift
@@ -197,6 +197,8 @@ internal class Asset: NSObject {
internal static let peopleFloatingAction = ImageAsset(name: "people_floating_action")
internal static let actionCamera = ImageAsset(name: "action_camera")
internal static let actionFile = ImageAsset(name: "action_file")
+ internal static let actionFormattingDisabled = ImageAsset(name: "action_formatting_disabled")
+ internal static let actionFormattingEnabled = ImageAsset(name: "action_formatting_enabled")
internal static let actionLive = ImageAsset(name: "action_live")
internal static let actionLocation = ImageAsset(name: "action_location")
internal static let actionMediaLibrary = ImageAsset(name: "action_media_library")
@@ -343,6 +345,8 @@ internal class Asset: NSObject {
internal static let voiceBroadcastRecord = ImageAsset(name: "voice_broadcast_record")
internal static let voiceBroadcastRecordPause = ImageAsset(name: "voice_broadcast_record_pause")
internal static let voiceBroadcastStop = ImageAsset(name: "voice_broadcast_stop")
+ internal static let voiceBroadcastTileLive = ImageAsset(name: "voice_broadcast_tile_live")
+ internal static let voiceBroadcastTileMic = ImageAsset(name: "voice_broadcast_tile_mic")
internal static let launchScreenLogo = ImageAsset(name: "launch_screen_logo")
}
@objcMembers
diff --git a/Riot/Generated/Strings.swift b/Riot/Generated/Strings.swift
index 00519d061..1ff68cf26 100644
--- a/Riot/Generated/Strings.swift
+++ b/Riot/Generated/Strings.swift
@@ -7547,7 +7547,7 @@ public class VectorL10n: NSObject {
public static var settingsLabsEnableVoiceBroadcast: String {
return VectorL10n.tr("Vector", "settings_labs_enable_voice_broadcast")
}
- /// Try out the rich text editor (plain text mode coming soon)
+ /// Try out the rich text editor
public static var settingsLabsEnableWysiwygComposer: String {
return VectorL10n.tr("Vector", "settings_labs_enable_wysiwyg_composer")
}
@@ -9087,6 +9087,10 @@ public class VectorL10n: NSObject {
public static var voiceBroadcastBlockedBySomeoneElseMessage: String {
return VectorL10n.tr("Vector", "voice_broadcast_blocked_by_someone_else_message")
}
+ /// Live
+ public static var voiceBroadcastLive: String {
+ return VectorL10n.tr("Vector", "voice_broadcast_live")
+ }
/// You don't have the required permissions to start a voice broadcast in this room. Contact a room administrator to upgrade your permissions.
public static var voiceBroadcastPermissionDeniedMessage: String {
return VectorL10n.tr("Vector", "voice_broadcast_permission_denied_message")
@@ -9095,6 +9099,10 @@ public class VectorL10n: NSObject {
public static var voiceBroadcastPlaybackLoadingError: String {
return VectorL10n.tr("Vector", "voice_broadcast_playback_loading_error")
}
+ /// Voice broadcast
+ public static var voiceBroadcastTile: String {
+ return VectorL10n.tr("Vector", "voice_broadcast_tile")
+ }
/// Can't start a new voice broadcast
public static var voiceBroadcastUnauthorizedTitle: String {
return VectorL10n.tr("Vector", "voice_broadcast_unauthorized_title")
diff --git a/Riot/Generated/UntranslatedStrings.swift b/Riot/Generated/UntranslatedStrings.swift
index 1f417c770..f273877eb 100644
--- a/Riot/Generated/UntranslatedStrings.swift
+++ b/Riot/Generated/UntranslatedStrings.swift
@@ -14,10 +14,6 @@ public extension VectorL10n {
static var imagePickerActionFiles: String {
return VectorL10n.tr("Untranslated", "image_picker_action_files")
}
- /// Voice broadcast detected (under active development)
- static var voiceBroadcastInTimelineTitle: String {
- return VectorL10n.tr("Untranslated", "voice_broadcast_in_timeline_title")
- }
}
// swiftlint:enable function_parameter_count identifier_name line_length type_body_length
diff --git a/Riot/Managers/Settings/RiotSettings.swift b/Riot/Managers/Settings/RiotSettings.swift
index f2024aa88..177fb8b03 100644
--- a/Riot/Managers/Settings/RiotSettings.swift
+++ b/Riot/Managers/Settings/RiotSettings.swift
@@ -176,6 +176,9 @@ final class RiotSettings: NSObject {
/// Flag indicating if the wysiwyg composer feature is enabled
@UserDefault(key: "enableWysiwygComposer", defaultValue: false, storage: defaults)
var enableWysiwygComposer
+
+ @UserDefault(key: "enableWysiwygTextFormatting", defaultValue: true, storage: defaults)
+ var enableWysiwygTextFormatting
/// Flag indicating if the IP addresses should be shown in the new device manager
@UserDefault(key: UserDefaultsKeys.showIPAddressesInSessionsManager, defaultValue: false, storage: defaults)
diff --git a/Riot/Modules/GlobalSearch/Messages/DataSources/HomeMessagesSearchDataSource.m b/Riot/Modules/GlobalSearch/Messages/DataSources/HomeMessagesSearchDataSource.m
index f2f2cd406..1488377f5 100644
--- a/Riot/Modules/GlobalSearch/Messages/DataSources/HomeMessagesSearchDataSource.m
+++ b/Riot/Modules/GlobalSearch/Messages/DataSources/HomeMessagesSearchDataSource.m
@@ -152,7 +152,7 @@
UITableViewCell *cell = [super tableView:tableView cellForRowAtIndexPath:indexPath];
// Finalize cell view customization here
- if ([cell isKindOfClass:MXKRoomBubbleTableViewCell.class])
+ if ([cell isKindOfClass:MXKRoomBubbleTableViewCell.class] && ![cell isKindOfClass:MXKRoomEmptyBubbleTableViewCell.class])
{
MXKRoomBubbleTableViewCell *bubbleCell = (MXKRoomBubbleTableViewCell*)cell;
diff --git a/Riot/Modules/Room/CellData/RoomBubbleCellData.h b/Riot/Modules/Room/CellData/RoomBubbleCellData.h
index 94f7346aa..8b3a49a5f 100644
--- a/Riot/Modules/Room/CellData/RoomBubbleCellData.h
+++ b/Riot/Modules/Room/CellData/RoomBubbleCellData.h
@@ -105,6 +105,8 @@ typedef NS_ENUM(NSInteger, RoomBubbleCellDataTag)
*/
@property(nonatomic) NSInteger componentIndexOfSentMessageTick;
+@property(nonatomic, strong) NSString *voiceBroadcastState;
+
/**
Indicate that both the text message layout and any additional content height are no longer
valid and should be recomputed before presentation in a bubble cell. This could be due
diff --git a/Riot/Modules/Room/CellData/RoomBubbleCellData.m b/Riot/Modules/Room/CellData/RoomBubbleCellData.m
index adcd6692e..712604203 100644
--- a/Riot/Modules/Room/CellData/RoomBubbleCellData.m
+++ b/Riot/Modules/Room/CellData/RoomBubbleCellData.m
@@ -186,23 +186,45 @@ NSString *const URLPreviewDidUpdateNotification = @"URLPreviewDidUpdateNotificat
else if ([event.type isEqualToString:VoiceBroadcastSettings.voiceBroadcastInfoContentKeyType])
{
VoiceBroadcastInfo *voiceBroadcastInfo = [VoiceBroadcastInfo modelFromJSON: event.content];
+
+ // Check if the state event corresponds to the beginning of a voice broadcast
if ([VoiceBroadcastInfo isStartedFor:voiceBroadcastInfo.state])
{
- // This state event corresponds to the beginning of a voice broadcast
- // Check whether this is a local live broadcast to display it with the recorder view or not
- // Note: Because of race condition, the voiceBroadcastService may be running without id here (the sync response may be received before
- // the success of the event sending), in that case, we will display a recorder view by default to let the user be able to stop a potential record.
- if ([event.sender isEqualToString: self.mxSession.myUserId] &&
- [voiceBroadcastInfo.deviceId isEqualToString:self.mxSession.myDeviceId] &&
- self.mxSession.voiceBroadcastService != nil &&
- ([event.eventId isEqualToString: self.mxSession.voiceBroadcastService.voiceBroadcastInfoEventId] ||
- self.mxSession.voiceBroadcastService.voiceBroadcastInfoEventId == nil))
+ // Retrieve the most recent voice broadcast info.
+ MXEvent *lastVoiceBroadcastInfoEvent = [roomDataSource.roomState stateEventsWithType:VoiceBroadcastSettings.voiceBroadcastInfoContentKeyType].lastObject;
+ if (event.originServerTs > lastVoiceBroadcastInfoEvent.originServerTs)
{
- self.tag = RoomBubbleCellDataTagVoiceBroadcastRecord;
+ lastVoiceBroadcastInfoEvent = event;
+ }
+
+ VoiceBroadcastInfo *lastVoiceBroadcastInfo = [VoiceBroadcastInfo modelFromJSON: lastVoiceBroadcastInfoEvent.content];
+
+ // Handle the specific case where the state event is a started voice broadcast (the voiceBroadcastId is the event id itself).
+ if (!lastVoiceBroadcastInfo.voiceBroadcastId)
+ {
+ lastVoiceBroadcastInfo.voiceBroadcastId = lastVoiceBroadcastInfoEvent.eventId;
+ }
+
+ // Check if the voice broadcast is still alive.
+ if ([lastVoiceBroadcastInfo.voiceBroadcastId isEqualToString:event.eventId] && ![VoiceBroadcastInfo isStoppedFor:lastVoiceBroadcastInfo.state])
+ {
+ // Check whether this broadcast is sent from the currrent session to display it with the recorder view or not.
+ if ([event.stateKey isEqualToString:self.mxSession.myUserId] &&
+ [voiceBroadcastInfo.deviceId isEqualToString:self.mxSession.myDeviceId])
+ {
+ self.tag = RoomBubbleCellDataTagVoiceBroadcastRecord;
+ }
+ else
+ {
+ self.tag = RoomBubbleCellDataTagVoiceBroadcastPlayback;
+ }
+
+ self.voiceBroadcastState = lastVoiceBroadcastInfo.state;
}
else
{
self.tag = RoomBubbleCellDataTagVoiceBroadcastPlayback;
+ self.voiceBroadcastState = VoiceBroadcastInfo.stoppedValue;
}
}
else
@@ -213,8 +235,9 @@ NSString *const URLPreviewDidUpdateNotification = @"URLPreviewDidUpdateNotificat
{
// This state event corresponds to the end of a voice broadcast
// Force the tag of the potential cellData which corresponds to the started event to switch the display from recorder to listener
- id bubbleData = [roomDataSource cellDataOfEventWithEventId:voiceBroadcastInfo.eventId];
+ RoomBubbleCellData *bubbleData = [roomDataSource cellDataOfEventWithEventId:voiceBroadcastInfo.voiceBroadcastId];
bubbleData.tag = RoomBubbleCellDataTagVoiceBroadcastPlayback;
+ bubbleData.voiceBroadcastState = VoiceBroadcastInfo.stoppedValue;
}
}
self.collapsable = NO;
diff --git a/Riot/Modules/Room/DataSources/RoomDataSource.m b/Riot/Modules/Room/DataSources/RoomDataSource.m
index 842623818..5d76bd5f6 100644
--- a/Riot/Modules/Room/DataSources/RoomDataSource.m
+++ b/Riot/Modules/Room/DataSources/RoomDataSource.m
@@ -395,7 +395,7 @@ const CGFloat kTypingCellHeight = 24;
id cellDecorator = [RoomTimelineConfiguration shared].currentStyle.cellDecorator;
// Finalize cell view customization here
- if ([cell isKindOfClass:MXKRoomBubbleTableViewCell.class])
+ if ([cell isKindOfClass:MXKRoomBubbleTableViewCell.class] && ![cell isKindOfClass:MXKRoomEmptyBubbleTableViewCell.class])
{
MXKRoomBubbleTableViewCell *bubbleCell = (MXKRoomBubbleTableViewCell*)cell;
[self resetAccessibilityForCell:bubbleCell];
diff --git a/Riot/Modules/Room/MXKRoomViewController.m b/Riot/Modules/Room/MXKRoomViewController.m
index 8c1376bce..de6a691f4 100644
--- a/Riot/Modules/Room/MXKRoomViewController.m
+++ b/Riot/Modules/Room/MXKRoomViewController.m
@@ -1859,7 +1859,7 @@
CGFloat localPositionOfEvent = 0.0;
- if ([cell isKindOfClass:MXKRoomBubbleTableViewCell.class])
+ if ([cell isKindOfClass:MXKRoomBubbleTableViewCell.class] && ![cell isKindOfClass:MXKRoomEmptyBubbleTableViewCell.class])
{
MXKRoomBubbleTableViewCell *roomBubbleTableViewCell = (MXKRoomBubbleTableViewCell *)cell;
@@ -2303,7 +2303,7 @@
CGFloat eventBottomPosition = eventTopPosition + cell.frame.size.height;
// Compute accurate event positions in case of bubble with multiple components
- if ([cell isKindOfClass:MXKRoomBubbleTableViewCell.class])
+ if ([cell isKindOfClass:MXKRoomBubbleTableViewCell.class] && ![cell isKindOfClass:MXKRoomEmptyBubbleTableViewCell.class])
{
MXKRoomBubbleTableViewCell *roomBubbleTableViewCell = (MXKRoomBubbleTableViewCell *)cell;
NSArray *bubbleComponents = roomBubbleTableViewCell.bubbleData.bubbleComponents;
@@ -2604,11 +2604,11 @@
roomDataSource.showBubblesDateTime = !roomDataSource.showBubblesDateTime;
MXLogDebug(@" -> Turn %@ cells date", roomDataSource.showBubblesDateTime ? @"ON" : @"OFF");
}
- else if ([actionIdentifier isEqualToString:kMXKRoomBubbleCellTapOnAttachmentView] && [cell isKindOfClass:MXKRoomBubbleTableViewCell.class])
+ else if ([actionIdentifier isEqualToString:kMXKRoomBubbleCellTapOnAttachmentView] && [cell isKindOfClass:MXKRoomBubbleTableViewCell.class] && ![cell isKindOfClass:MXKRoomEmptyBubbleTableViewCell.class])
{
[self showAttachmentInCell:(MXKRoomBubbleTableViewCell *)cell];
}
- else if ([actionIdentifier isEqualToString:kMXKRoomBubbleCellLongPressOnProgressView] && [cell isKindOfClass:MXKRoomBubbleTableViewCell.class])
+ else if ([actionIdentifier isEqualToString:kMXKRoomBubbleCellLongPressOnProgressView] && [cell isKindOfClass:MXKRoomBubbleTableViewCell.class] && ![cell isKindOfClass:MXKRoomEmptyBubbleTableViewCell.class])
{
MXKRoomBubbleTableViewCell *roomBubbleTableViewCell = (MXKRoomBubbleTableViewCell *)cell;
@@ -2719,7 +2719,7 @@
}
}
}
- else if ([actionIdentifier isEqualToString:kMXKRoomBubbleCellLongPressOnEvent] && [cell isKindOfClass:MXKRoomBubbleTableViewCell.class])
+ else if ([actionIdentifier isEqualToString:kMXKRoomBubbleCellLongPressOnEvent] && [cell isKindOfClass:MXKRoomBubbleTableViewCell.class] && ![cell isKindOfClass:MXKRoomEmptyBubbleTableViewCell.class])
{
[self dismissKeyboard];
@@ -3089,7 +3089,7 @@
return;
}
- if ([cell isKindOfClass:MXKRoomBubbleTableViewCell.class])
+ if ([cell isKindOfClass:MXKRoomBubbleTableViewCell.class] && ![cell isKindOfClass:MXKRoomEmptyBubbleTableViewCell.class])
{
MXKRoomBubbleTableViewCell *roomBubbleTableViewCell = (MXKRoomBubbleTableViewCell *)cell;
selectedText = roomBubbleTableViewCell.bubbleData.textMessage;
@@ -3628,7 +3628,7 @@
// Keep here the image view used to display the attachment in the selected cell.
// Note: Only `MXKRoomBubbleTableViewCell` and `MXKSearchTableViewCell` are supported for the moment.
- if ([cell isKindOfClass:MXKRoomBubbleTableViewCell.class])
+ if ([cell isKindOfClass:MXKRoomBubbleTableViewCell.class] && ![cell isKindOfClass:MXKRoomEmptyBubbleTableViewCell.class])
{
self.openedAttachmentImageView = ((MXKRoomBubbleTableViewCell *)cell).attachmentView.imageView;
}
@@ -3806,7 +3806,7 @@
}];
- if ([cell isKindOfClass:MXKRoomBubbleTableViewCell.class])
+ if ([cell isKindOfClass:MXKRoomBubbleTableViewCell.class] && ![cell isKindOfClass:MXKRoomEmptyBubbleTableViewCell.class])
{
// Start animation in case of download
MXKRoomBubbleTableViewCell *roomBubbleTableViewCell = (MXKRoomBubbleTableViewCell *)cell;
diff --git a/Riot/Modules/Room/RoomViewController.m b/Riot/Modules/Room/RoomViewController.m
index 8929aeec2..10b002abf 100644
--- a/Riot/Modules/Room/RoomViewController.m
+++ b/Riot/Modules/Room/RoomViewController.m
@@ -3237,30 +3237,30 @@ static CGSize kThreadListBarButtonItemImageSize;
{
if (bubbleData.isPaginationFirstBubble)
{
- cellIdentifier = RoomTimelineCellIdentifierIncomingVoiceBroadcastWithPaginationTitle;
+ cellIdentifier = RoomTimelineCellIdentifierIncomingVoiceBroadcastPlaybackWithPaginationTitle;
}
else if (bubbleData.shouldHideSenderInformation)
{
- cellIdentifier = RoomTimelineCellIdentifierIncomingVoiceBroadcastWithoutSenderInfo;
+ cellIdentifier = RoomTimelineCellIdentifierIncomingVoiceBroadcastPlaybackWithoutSenderInfo;
}
else
{
- cellIdentifier = RoomTimelineCellIdentifierIncomingVoiceBroadcast;
+ cellIdentifier = RoomTimelineCellIdentifierIncomingVoiceBroadcastPlayback;
}
}
else
{
if (bubbleData.isPaginationFirstBubble)
{
- cellIdentifier = RoomTimelineCellIdentifierOutgoingVoiceBroadcastWithPaginationTitle;
+ cellIdentifier = RoomTimelineCellIdentifierOutgoingVoiceBroadcastPlaybackWithPaginationTitle;
}
else if (bubbleData.shouldHideSenderInformation)
{
- cellIdentifier = RoomTimelineCellIdentifierOutgoingVoiceBroadcastWithoutSenderInfo;
+ cellIdentifier = RoomTimelineCellIdentifierOutgoingVoiceBroadcastPlaybackWithoutSenderInfo;
}
else
{
- cellIdentifier = RoomTimelineCellIdentifierOutgoingVoiceBroadcast;
+ cellIdentifier = RoomTimelineCellIdentifierOutgoingVoiceBroadcastPlayback;
}
}
}
@@ -5115,7 +5115,9 @@ static CGSize kThreadListBarButtonItemImageSize;
[actionItems addObject:@(ComposerCreateActionCamera)];
}
- self.composerCreateActionListBridgePresenter = [[ComposerCreateActionListBridgePresenter alloc] initWithActions:actionItems];
+ self.composerCreateActionListBridgePresenter = [[ComposerCreateActionListBridgePresenter alloc] initWithActions:actionItems
+ wysiwygEnabled:RiotSettings.shared.enableWysiwygComposer
+ textFormattingEnabled:RiotSettings.shared.enableWysiwygTextFormatting];
self.composerCreateActionListBridgePresenter.delegate = self;
[self.composerCreateActionListBridgePresenter presentFrom:self animated:YES];
}
@@ -5268,7 +5270,7 @@ static CGSize kThreadListBarButtonItemImageSize;
}
}
- if ([cell isKindOfClass:MXKRoomBubbleTableViewCell.class])
+ if ([cell isKindOfClass:MXKRoomBubbleTableViewCell.class] && ![cell isKindOfClass:MXKRoomEmptyBubbleTableViewCell.class])
{
MXKRoomBubbleTableViewCell *roomBubbleTableViewCell = (MXKRoomBubbleTableViewCell*)cell;
if (roomBubbleTableViewCell.readMarkerView)
@@ -6522,7 +6524,7 @@ static CGSize kThreadListBarButtonItemImageSize;
if (self.roomDataSource.isLive && !self.roomDataSource.isPeeking && self.roomDataSource.showReadMarker && self.roomDataSource.room.accountData.readMarkerEventId)
{
UITableViewCell *cell = [self.bubblesTableView visibleCells].firstObject;
- if ([cell isKindOfClass:MXKRoomBubbleTableViewCell.class])
+ if ([cell isKindOfClass:MXKRoomBubbleTableViewCell.class] && ![cell isKindOfClass:MXKRoomEmptyBubbleTableViewCell.class])
{
MXKRoomBubbleTableViewCell *roomBubbleTableViewCell = (MXKRoomBubbleTableViewCell*)cell;
// Check whether the read marker is inside the first displayed cell.
@@ -8054,6 +8056,11 @@ static CGSize kThreadListBarButtonItemImageSize;
}];
}
+- (void)composerCreateActionListBridgePresenterDelegateDidToggleTextFormatting:(ComposerCreateActionListBridgePresenter *)coordinatorBridgePresenter enabled:(BOOL)enabled
+{
+ [self togglePlainTextMode];
+}
+
- (void)composerCreateActionListBridgePresenterDidDismissInteractively:(ComposerCreateActionListBridgePresenter *)coordinatorBridgePresenter
{
self.composerCreateActionListBridgePresenter = nil;
diff --git a/Riot/Modules/Room/RoomViewController.swift b/Riot/Modules/Room/RoomViewController.swift
index 7bbc6812c..1acdf90f8 100644
--- a/Riot/Modules/Room/RoomViewController.swift
+++ b/Riot/Modules/Room/RoomViewController.swift
@@ -149,6 +149,11 @@ extension RoomViewController {
}
}
}
+
+ @objc func togglePlainTextMode() {
+ RiotSettings.shared.enableWysiwygTextFormatting.toggle()
+ wysiwygInputToolbar?.textFormattingEnabled.toggle()
+ }
}
// MARK: - Private Helpers
diff --git a/Riot/Modules/Room/Search/DataSources/RoomSearchDataSource.m b/Riot/Modules/Room/Search/DataSources/RoomSearchDataSource.m
index 9c0530a7b..2790df090 100644
--- a/Riot/Modules/Room/Search/DataSources/RoomSearchDataSource.m
+++ b/Riot/Modules/Room/Search/DataSources/RoomSearchDataSource.m
@@ -131,7 +131,7 @@
UITableViewCell *cell = [super tableView:tableView cellForRowAtIndexPath:indexPath];
// Finalize cell view customization here
- if ([cell isKindOfClass:MXKRoomBubbleTableViewCell.class])
+ if ([cell isKindOfClass:MXKRoomBubbleTableViewCell.class] && ![cell isKindOfClass:MXKRoomEmptyBubbleTableViewCell.class])
{
MXKRoomBubbleTableViewCell *bubbleCell = (MXKRoomBubbleTableViewCell*)cell;
diff --git a/Riot/Modules/Room/TimelineCells/RoomTimelineCellIdentifier.h b/Riot/Modules/Room/TimelineCells/RoomTimelineCellIdentifier.h
index 3348df0e6..5a91d01e3 100644
--- a/Riot/Modules/Room/TimelineCells/RoomTimelineCellIdentifier.h
+++ b/Riot/Modules/Room/TimelineCells/RoomTimelineCellIdentifier.h
@@ -170,13 +170,13 @@ typedef NS_ENUM(NSUInteger, RoomTimelineCellIdentifier) {
// - Voice broadcast
// -- Incoming
- RoomTimelineCellIdentifierIncomingVoiceBroadcast,
- RoomTimelineCellIdentifierIncomingVoiceBroadcastWithoutSenderInfo,
- RoomTimelineCellIdentifierIncomingVoiceBroadcastWithPaginationTitle,
+ RoomTimelineCellIdentifierIncomingVoiceBroadcastPlayback,
+ RoomTimelineCellIdentifierIncomingVoiceBroadcastPlaybackWithoutSenderInfo,
+ RoomTimelineCellIdentifierIncomingVoiceBroadcastPlaybackWithPaginationTitle,
// -- Outgoing
- RoomTimelineCellIdentifierOutgoingVoiceBroadcast,
- RoomTimelineCellIdentifierOutgoingVoiceBroadcastWithoutSenderInfo,
- RoomTimelineCellIdentifierOutgoingVoiceBroadcastWithPaginationTitle,
+ RoomTimelineCellIdentifierOutgoingVoiceBroadcastPlayback,
+ RoomTimelineCellIdentifierOutgoingVoiceBroadcastPlaybackWithoutSenderInfo,
+ RoomTimelineCellIdentifierOutgoingVoiceBroadcastPlaybackWithPaginationTitle,
// - Voice broadcast recorder
RoomTimelineCellIdentifierOutgoingVoiceBroadcastRecorder,
diff --git a/Riot/Modules/Room/TimelineCells/Styles/Bubble/BubbleRoomTimelineCellProvider.m b/Riot/Modules/Room/TimelineCells/Styles/Bubble/BubbleRoomTimelineCellProvider.m
index c747476ee..4cfb03de1 100644
--- a/Riot/Modules/Room/TimelineCells/Styles/Bubble/BubbleRoomTimelineCellProvider.m
+++ b/Riot/Modules/Room/TimelineCells/Styles/Bubble/BubbleRoomTimelineCellProvider.m
@@ -135,12 +135,12 @@
- (void)registerVoiceBroadcastCellsForTableView:(UITableView*)tableView
{
// Incoming
- [tableView registerClass:VoiceBroadcastIncomingBubbleCell.class forCellReuseIdentifier:VoiceBroadcastIncomingBubbleCell.defaultReuseIdentifier];
- [tableView registerClass:VoiceBroadcastIncomingWithoutSenderInfoBubbleCell.class forCellReuseIdentifier:VoiceBroadcastIncomingWithoutSenderInfoBubbleCell.defaultReuseIdentifier];
- [tableView registerClass:VoiceBroadcastIncomingWithPaginationTitleBubbleCell.class forCellReuseIdentifier:VoiceBroadcastIncomingWithPaginationTitleBubbleCell.defaultReuseIdentifier];
+ [tableView registerClass:VoiceBroadcastPlaybackIncomingBubbleCell.class forCellReuseIdentifier:VoiceBroadcastPlaybackIncomingBubbleCell.defaultReuseIdentifier];
+ [tableView registerClass:VoiceBroadcastPlaybackIncomingWithoutSenderInfoBubbleCell.class forCellReuseIdentifier:VoiceBroadcastPlaybackIncomingWithoutSenderInfoBubbleCell.defaultReuseIdentifier];
+ [tableView registerClass:VoiceBroadcastPlaybackIncomingWithPaginationTitleBubbleCell.class forCellReuseIdentifier:VoiceBroadcastPlaybackIncomingWithPaginationTitleBubbleCell.defaultReuseIdentifier];
// Outgoing
- [tableView registerClass:VoiceBroadcastOutgoingWithoutSenderInfoBubbleCell.class forCellReuseIdentifier:VoiceBroadcastOutgoingWithoutSenderInfoBubbleCell.defaultReuseIdentifier];
- [tableView registerClass:VoiceBroadcastOutgoingWithPaginationTitleBubbleCell.class forCellReuseIdentifier:VoiceBroadcastOutgoingWithPaginationTitleBubbleCell.defaultReuseIdentifier];
+ [tableView registerClass:VoiceBroadcastPlaybackOutgoingWithoutSenderInfoBubbleCell.class forCellReuseIdentifier:VoiceBroadcastPlaybackOutgoingWithoutSenderInfoBubbleCell.defaultReuseIdentifier];
+ [tableView registerClass:VoiceBroadcastPlaybackOutgoingWithPaginationTitleBubbleCell.class forCellReuseIdentifier:VoiceBroadcastPlaybackOutgoingWithPaginationTitleBubbleCell.defaultReuseIdentifier];
}
- (void)registerVoiceBroadcastRecorderCellsForTableView:(UITableView*)tableView
@@ -311,17 +311,17 @@
};
}
-- (NSDictionary*)voiceBroadcastCellsMapping
+- (NSDictionary*)voiceBroadcastPlaybackCellsMapping
{
return @{
// Incoming
- @(RoomTimelineCellIdentifierIncomingVoiceBroadcast) : VoiceBroadcastIncomingBubbleCell.class,
- @(RoomTimelineCellIdentifierIncomingVoiceBroadcastWithoutSenderInfo) : VoiceBroadcastIncomingWithoutSenderInfoBubbleCell.class,
- @(RoomTimelineCellIdentifierIncomingVoiceBroadcastWithPaginationTitle) : VoiceBroadcastIncomingWithPaginationTitleBubbleCell.class,
+ @(RoomTimelineCellIdentifierIncomingVoiceBroadcastPlayback) : VoiceBroadcastPlaybackIncomingBubbleCell.class,
+ @(RoomTimelineCellIdentifierIncomingVoiceBroadcastPlaybackWithoutSenderInfo) : VoiceBroadcastPlaybackIncomingWithoutSenderInfoBubbleCell.class,
+ @(RoomTimelineCellIdentifierIncomingVoiceBroadcastPlaybackWithPaginationTitle) : VoiceBroadcastPlaybackIncomingWithPaginationTitleBubbleCell.class,
// Outgoing
- @(RoomTimelineCellIdentifierOutgoingVoiceBroadcast) : VoiceBroadcastOutgoingWithoutSenderInfoBubbleCell.class,
- @(RoomTimelineCellIdentifierOutgoingVoiceBroadcastWithoutSenderInfo) : VoiceBroadcastOutgoingWithoutSenderInfoBubbleCell.class,
- @(RoomTimelineCellIdentifierOutgoingVoiceBroadcastWithPaginationTitle) : VoiceBroadcastOutgoingWithPaginationTitleBubbleCell.class,
+ @(RoomTimelineCellIdentifierOutgoingVoiceBroadcastPlayback) : VoiceBroadcastPlaybackOutgoingWithoutSenderInfoBubbleCell.class,
+ @(RoomTimelineCellIdentifierOutgoingVoiceBroadcastPlaybackWithoutSenderInfo) : VoiceBroadcastPlaybackOutgoingWithoutSenderInfoBubbleCell.class,
+ @(RoomTimelineCellIdentifierOutgoingVoiceBroadcastPlaybackWithPaginationTitle) : VoiceBroadcastPlaybackOutgoingWithPaginationTitleBubbleCell.class,
};
}
diff --git a/Riot/Modules/Room/TimelineCells/Styles/Bubble/Cells/VoiceBroadcast/Incoming/VoiceBroadcastIncomingBubbleCell.swift b/Riot/Modules/Room/TimelineCells/Styles/Bubble/Cells/VoiceBroadcast/Playback/Incoming/VoiceBroadcastPlaybackIncomingBubbleCell.swift
similarity index 92%
rename from Riot/Modules/Room/TimelineCells/Styles/Bubble/Cells/VoiceBroadcast/Incoming/VoiceBroadcastIncomingBubbleCell.swift
rename to Riot/Modules/Room/TimelineCells/Styles/Bubble/Cells/VoiceBroadcast/Playback/Incoming/VoiceBroadcastPlaybackIncomingBubbleCell.swift
index f46acbae1..fda3dbd6d 100644
--- a/Riot/Modules/Room/TimelineCells/Styles/Bubble/Cells/VoiceBroadcast/Incoming/VoiceBroadcastIncomingBubbleCell.swift
+++ b/Riot/Modules/Room/TimelineCells/Styles/Bubble/Cells/VoiceBroadcast/Playback/Incoming/VoiceBroadcastPlaybackIncomingBubbleCell.swift
@@ -16,7 +16,7 @@
import Foundation
-class VoiceBroadcastIncomingBubbleCell: VoiceBroadcastBubbleCell, BubbleIncomingRoomCellProtocol {
+class VoiceBroadcastPlaybackIncomingBubbleCell: VoiceBroadcastPlaybackBubbleCell, BubbleIncomingRoomCellProtocol {
override func setupViews() {
super.setupViews()
diff --git a/Riot/Modules/Room/TimelineCells/Styles/Bubble/Cells/VoiceBroadcast/Incoming/VoiceBroadcastIncomingWithPaginationTitleBubbleCell.swift b/Riot/Modules/Room/TimelineCells/Styles/Bubble/Cells/VoiceBroadcast/Playback/Incoming/VoiceBroadcastPlaybackIncomingWithPaginationTitleBubbleCell.swift
similarity index 87%
rename from Riot/Modules/Room/TimelineCells/Styles/Bubble/Cells/VoiceBroadcast/Incoming/VoiceBroadcastIncomingWithPaginationTitleBubbleCell.swift
rename to Riot/Modules/Room/TimelineCells/Styles/Bubble/Cells/VoiceBroadcast/Playback/Incoming/VoiceBroadcastPlaybackIncomingWithPaginationTitleBubbleCell.swift
index 6bbb10d9a..979ccd27f 100644
--- a/Riot/Modules/Room/TimelineCells/Styles/Bubble/Cells/VoiceBroadcast/Incoming/VoiceBroadcastIncomingWithPaginationTitleBubbleCell.swift
+++ b/Riot/Modules/Room/TimelineCells/Styles/Bubble/Cells/VoiceBroadcast/Playback/Incoming/VoiceBroadcastPlaybackIncomingWithPaginationTitleBubbleCell.swift
@@ -16,7 +16,7 @@
import Foundation
-class VoiceBroadcastIncomingWithPaginationTitleBubbleCell: VoiceBroadcastIncomingBubbleCell {
+class VoiceBroadcastPlaybackIncomingWithPaginationTitleBubbleCell: VoiceBroadcastPlaybackIncomingBubbleCell {
override func setupViews() {
super.setupViews()
diff --git a/Riot/Modules/Room/TimelineCells/Styles/Plain/Cells/VoiceBroadcast/VoiceBroadcastWithoutSenderInfoPlainCell.swift b/Riot/Modules/Room/TimelineCells/Styles/Bubble/Cells/VoiceBroadcast/Playback/Incoming/VoiceBroadcastPlaybackIncomingWithoutSenderInfoBubbleCell.swift
similarity index 87%
rename from Riot/Modules/Room/TimelineCells/Styles/Plain/Cells/VoiceBroadcast/VoiceBroadcastWithoutSenderInfoPlainCell.swift
rename to Riot/Modules/Room/TimelineCells/Styles/Bubble/Cells/VoiceBroadcast/Playback/Incoming/VoiceBroadcastPlaybackIncomingWithoutSenderInfoBubbleCell.swift
index 6f3ec9110..7a99e2ecb 100644
--- a/Riot/Modules/Room/TimelineCells/Styles/Plain/Cells/VoiceBroadcast/VoiceBroadcastWithoutSenderInfoPlainCell.swift
+++ b/Riot/Modules/Room/TimelineCells/Styles/Bubble/Cells/VoiceBroadcast/Playback/Incoming/VoiceBroadcastPlaybackIncomingWithoutSenderInfoBubbleCell.swift
@@ -16,7 +16,7 @@
import Foundation
-class VoiceBroadcastWithoutSenderInfoPlainCell: VoiceBroadcastPlainCell {
+class VoiceBroadcastPlaybackIncomingWithoutSenderInfoBubbleCell: VoiceBroadcastPlaybackIncomingBubbleCell {
override func setupViews() {
super.setupViews()
diff --git a/Riot/Modules/Room/TimelineCells/Styles/Bubble/Cells/VoiceBroadcast/Outgoing/VoiceBroadcastOutgoingWithPaginationTitleBubbleCell.swift b/Riot/Modules/Room/TimelineCells/Styles/Bubble/Cells/VoiceBroadcast/Playback/Outgoing/VoiceBroadcastPlaybackOutgoingWithPaginationTitleBubbleCell.swift
similarity index 85%
rename from Riot/Modules/Room/TimelineCells/Styles/Bubble/Cells/VoiceBroadcast/Outgoing/VoiceBroadcastOutgoingWithPaginationTitleBubbleCell.swift
rename to Riot/Modules/Room/TimelineCells/Styles/Bubble/Cells/VoiceBroadcast/Playback/Outgoing/VoiceBroadcastPlaybackOutgoingWithPaginationTitleBubbleCell.swift
index 72f69e4d7..34bf80670 100644
--- a/Riot/Modules/Room/TimelineCells/Styles/Bubble/Cells/VoiceBroadcast/Outgoing/VoiceBroadcastOutgoingWithPaginationTitleBubbleCell.swift
+++ b/Riot/Modules/Room/TimelineCells/Styles/Bubble/Cells/VoiceBroadcast/Playback/Outgoing/VoiceBroadcastPlaybackOutgoingWithPaginationTitleBubbleCell.swift
@@ -16,7 +16,7 @@
import Foundation
-class VoiceBroadcastOutgoingWithPaginationTitleBubbleCell: VoiceBroadcastOutgoingWithoutSenderInfoBubbleCell {
+class VoiceBroadcastPlaybackOutgoingWithPaginationTitleBubbleCell: VoiceBroadcastPlaybackOutgoingWithoutSenderInfoBubbleCell {
override func setupViews() {
super.setupViews()
diff --git a/Riot/Modules/Room/TimelineCells/Styles/Bubble/Cells/VoiceBroadcast/Outgoing/VoiceBroadcastOutgoingWithoutSenderInfoBubbleCell.swift b/Riot/Modules/Room/TimelineCells/Styles/Bubble/Cells/VoiceBroadcast/Playback/Outgoing/VoiceBroadcastPlaybackOutgoingWithoutSenderInfoBubbleCell.swift
similarity index 92%
rename from Riot/Modules/Room/TimelineCells/Styles/Bubble/Cells/VoiceBroadcast/Outgoing/VoiceBroadcastOutgoingWithoutSenderInfoBubbleCell.swift
rename to Riot/Modules/Room/TimelineCells/Styles/Bubble/Cells/VoiceBroadcast/Playback/Outgoing/VoiceBroadcastPlaybackOutgoingWithoutSenderInfoBubbleCell.swift
index b149647b6..3616469e9 100644
--- a/Riot/Modules/Room/TimelineCells/Styles/Bubble/Cells/VoiceBroadcast/Outgoing/VoiceBroadcastOutgoingWithoutSenderInfoBubbleCell.swift
+++ b/Riot/Modules/Room/TimelineCells/Styles/Bubble/Cells/VoiceBroadcast/Playback/Outgoing/VoiceBroadcastPlaybackOutgoingWithoutSenderInfoBubbleCell.swift
@@ -16,7 +16,7 @@
import Foundation
-class VoiceBroadcastOutgoingWithoutSenderInfoBubbleCell: VoiceBroadcastBubbleCell, BubbleOutgoingRoomCellProtocol {
+class VoiceBroadcastPlaybackOutgoingWithoutSenderInfoBubbleCell: VoiceBroadcastPlaybackBubbleCell, BubbleOutgoingRoomCellProtocol {
override func setupViews() {
super.setupViews()
diff --git a/Riot/Modules/Room/TimelineCells/Styles/Bubble/Cells/VoiceBroadcast/VoiceBroadcastBubbleCell.swift b/Riot/Modules/Room/TimelineCells/Styles/Bubble/Cells/VoiceBroadcast/Playback/VoiceBroadcastPlaybackBubbleCell.swift
similarity index 96%
rename from Riot/Modules/Room/TimelineCells/Styles/Bubble/Cells/VoiceBroadcast/VoiceBroadcastBubbleCell.swift
rename to Riot/Modules/Room/TimelineCells/Styles/Bubble/Cells/VoiceBroadcast/Playback/VoiceBroadcastPlaybackBubbleCell.swift
index 67db62e88..2de4341db 100644
--- a/Riot/Modules/Room/TimelineCells/Styles/Bubble/Cells/VoiceBroadcast/VoiceBroadcastBubbleCell.swift
+++ b/Riot/Modules/Room/TimelineCells/Styles/Bubble/Cells/VoiceBroadcast/Playback/VoiceBroadcastPlaybackBubbleCell.swift
@@ -16,7 +16,7 @@
import UIKit
-class VoiceBroadcastBubbleCell: VoiceBroadcastPlainCell {
+class VoiceBroadcastPlaybackBubbleCell: VoiceBroadcastPlaybackPlainCell {
// MARK: - Properties
@@ -95,7 +95,7 @@ class VoiceBroadcastBubbleCell: VoiceBroadcastPlainCell {
}
// MARK: - RoomCellTimestampDisplayable
-extension VoiceBroadcastBubbleCell: TimestampDisplayable {
+extension VoiceBroadcastPlaybackBubbleCell: TimestampDisplayable {
func addTimestampView(_ timestampView: UIView) {
guard let messageBubbleBackgroundView = self.getBubbleBackgroundView() else {
diff --git a/Riot/Modules/Room/TimelineCells/Styles/Bubble/Cells/VoiceBroadcast/Playback/VoiceBroadcastPlaybackPlainBubbleCell.swift b/Riot/Modules/Room/TimelineCells/Styles/Bubble/Cells/VoiceBroadcast/Playback/VoiceBroadcastPlaybackPlainBubbleCell.swift
new file mode 100644
index 000000000..365a15956
--- /dev/null
+++ b/Riot/Modules/Room/TimelineCells/Styles/Bubble/Cells/VoiceBroadcast/Playback/VoiceBroadcastPlaybackPlainBubbleCell.swift
@@ -0,0 +1,37 @@
+//
+// Copyright 2022 New Vector Ltd
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+import Foundation
+
+class VoiceBroadcastPlaybackPlainBubbleCell: VoiceBroadcastPlaybackBubbleCell {
+
+ override func setupViews() {
+ super.setupViews()
+
+ // TODO: VB update margins attributes
+ let leftMargin: CGFloat = BubbleRoomCellLayoutConstants.incomingBubbleBackgroundMargins.left + BubbleRoomCellLayoutConstants.pollBubbleBackgroundInsets.left
+ let rightMargin: CGFloat = 15 + BubbleRoomCellLayoutConstants.pollBubbleBackgroundInsets.right
+
+ roomCellContentView?.innerContentViewLeadingConstraint.constant = leftMargin
+ roomCellContentView?.innerContentViewTrailingConstraint.constant = rightMargin
+ }
+
+ override func update(theme: Theme) {
+ super.update(theme: theme)
+
+ self.bubbleBackgroundColor = theme.roomCellIncomingBubbleBackgroundColor
+ }
+}
diff --git a/Riot/Modules/Room/TimelineCells/Styles/Bubble/Cells/VoiceBroadcast/Recorder/VoiceBroadcastRecorderPlainBubbleCell.swift b/Riot/Modules/Room/TimelineCells/Styles/Bubble/Cells/VoiceBroadcast/Recorder/VoiceBroadcastRecorderPlainBubbleCell.swift
new file mode 100644
index 000000000..69f94e8fa
--- /dev/null
+++ b/Riot/Modules/Room/TimelineCells/Styles/Bubble/Cells/VoiceBroadcast/Recorder/VoiceBroadcastRecorderPlainBubbleCell.swift
@@ -0,0 +1,37 @@
+//
+// Copyright 2022 New Vector Ltd
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+import Foundation
+
+class VoiceBroadcastRecorderPlainBubbleCell: VoiceBroadcastRecorderBubbleCell {
+
+ override func setupViews() {
+ super.setupViews()
+
+ // TODO: VB update margins attributes
+ let leftMargin: CGFloat = BubbleRoomCellLayoutConstants.incomingBubbleBackgroundMargins.left + BubbleRoomCellLayoutConstants.pollBubbleBackgroundInsets.left
+ let rightMargin: CGFloat = 15 + BubbleRoomCellLayoutConstants.pollBubbleBackgroundInsets.right
+
+ roomCellContentView?.innerContentViewLeadingConstraint.constant = leftMargin
+ roomCellContentView?.innerContentViewTrailingConstraint.constant = rightMargin
+ }
+
+ override func update(theme: Theme) {
+ super.update(theme: theme)
+
+ self.bubbleBackgroundColor = theme.roomCellIncomingBubbleBackgroundColor
+ }
+}
diff --git a/Riot/Modules/Room/TimelineCells/Styles/Plain/Cells/VoiceBroadcast/VoiceBroadcastPlainCell.swift b/Riot/Modules/Room/TimelineCells/Styles/Plain/Cells/VoiceBroadcast/Playback/VoiceBroadcastPlaybackPlainCell.swift
similarity index 76%
rename from Riot/Modules/Room/TimelineCells/Styles/Plain/Cells/VoiceBroadcast/VoiceBroadcastPlainCell.swift
rename to Riot/Modules/Room/TimelineCells/Styles/Plain/Cells/VoiceBroadcast/Playback/VoiceBroadcastPlaybackPlainCell.swift
index 14c602c4c..8987cb1de 100644
--- a/Riot/Modules/Room/TimelineCells/Styles/Plain/Cells/VoiceBroadcast/VoiceBroadcastPlainCell.swift
+++ b/Riot/Modules/Room/TimelineCells/Styles/Plain/Cells/VoiceBroadcast/Playback/VoiceBroadcastPlaybackPlainCell.swift
@@ -16,19 +16,22 @@
import Foundation
-class VoiceBroadcastPlainCell: SizableBaseRoomCell, RoomCellReactionsDisplayable, RoomCellReadMarkerDisplayable {
+class VoiceBroadcastPlaybackPlainCell: SizableBaseRoomCell, RoomCellReactionsDisplayable, RoomCellReadMarkerDisplayable {
private var event: MXEvent?
override func render(_ cellData: MXKCellData!) {
super.render(cellData)
-
+
guard let contentView = roomCellContentView?.innerContentView,
let bubbleData = cellData as? RoomBubbleCellData,
let event = bubbleData.events.last,
let voiceBroadcastContent = VoiceBroadcastInfo(fromJSON: event.content),
voiceBroadcastContent.state == VoiceBroadcastInfo.State.started.rawValue,
- let controller = VoiceBroadcastPlaybackProvider.shared.buildVoiceBroadcastPlaybackVCForEvent(event, senderDisplayName: bubbleData.senderDisplayName) else {
+ let controller = VoiceBroadcastPlaybackProvider.shared.buildVoiceBroadcastPlaybackVCForEvent(event,
+ senderDisplayName: bubbleData.senderDisplayName,
+ voiceBroadcastState: bubbleData.voiceBroadcastState)
+ else {
return
}
@@ -54,4 +57,4 @@ class VoiceBroadcastPlainCell: SizableBaseRoomCell, RoomCellReactionsDisplayable
}
}
-extension VoiceBroadcastPlainCell: RoomCellThreadSummaryDisplayable {}
+extension VoiceBroadcastPlaybackPlainCell: RoomCellThreadSummaryDisplayable {}
diff --git a/Riot/Modules/Room/TimelineCells/Styles/Plain/Cells/VoiceBroadcast/VoiceBroadcastWithPaginationTitlePlainCell.swift b/Riot/Modules/Room/TimelineCells/Styles/Plain/Cells/VoiceBroadcast/Playback/VoiceBroadcastPlaybackWithPaginationTitlePlainCell.swift
similarity index 88%
rename from Riot/Modules/Room/TimelineCells/Styles/Plain/Cells/VoiceBroadcast/VoiceBroadcastWithPaginationTitlePlainCell.swift
rename to Riot/Modules/Room/TimelineCells/Styles/Plain/Cells/VoiceBroadcast/Playback/VoiceBroadcastPlaybackWithPaginationTitlePlainCell.swift
index fa3c3bc50..09f0bcff5 100644
--- a/Riot/Modules/Room/TimelineCells/Styles/Plain/Cells/VoiceBroadcast/VoiceBroadcastWithPaginationTitlePlainCell.swift
+++ b/Riot/Modules/Room/TimelineCells/Styles/Plain/Cells/VoiceBroadcast/Playback/VoiceBroadcastPlaybackWithPaginationTitlePlainCell.swift
@@ -16,7 +16,7 @@
import Foundation
-class VoiceBroadcastWithPaginationTitlePlainCell: VoiceBroadcastPlainCell {
+class VoiceBroadcastPlaybackWithPaginationTitlePlainCell: VoiceBroadcastPlaybackPlainBubbleCell {
override func setupViews() {
super.setupViews()
diff --git a/Riot/Modules/Room/TimelineCells/Styles/Bubble/Cells/VoiceBroadcast/Incoming/VoiceBroadcastIncomingWithoutSenderInfoBubbleCell.swift b/Riot/Modules/Room/TimelineCells/Styles/Plain/Cells/VoiceBroadcast/Playback/VoiceBroadcastPlaybackWithoutSenderInfoPlainCell.swift
similarity index 88%
rename from Riot/Modules/Room/TimelineCells/Styles/Bubble/Cells/VoiceBroadcast/Incoming/VoiceBroadcastIncomingWithoutSenderInfoBubbleCell.swift
rename to Riot/Modules/Room/TimelineCells/Styles/Plain/Cells/VoiceBroadcast/Playback/VoiceBroadcastPlaybackWithoutSenderInfoPlainCell.swift
index 4f123da7d..41f98f81a 100644
--- a/Riot/Modules/Room/TimelineCells/Styles/Bubble/Cells/VoiceBroadcast/Incoming/VoiceBroadcastIncomingWithoutSenderInfoBubbleCell.swift
+++ b/Riot/Modules/Room/TimelineCells/Styles/Plain/Cells/VoiceBroadcast/Playback/VoiceBroadcastPlaybackWithoutSenderInfoPlainCell.swift
@@ -16,7 +16,7 @@
import Foundation
-class VoiceBroadcastIncomingWithoutSenderInfoBubbleCell: VoiceBroadcastIncomingBubbleCell {
+class VoiceBroadcastPlaybackWithoutSenderInfoPlainCell: VoiceBroadcastPlaybackPlainBubbleCell {
override func setupViews() {
super.setupViews()
diff --git a/Riot/Modules/Room/TimelineCells/Styles/Plain/Cells/VoiceBroadcast/Recorder/VoiceBroadcastRecorderWithPaginationTitlePlainCell.swift b/Riot/Modules/Room/TimelineCells/Styles/Plain/Cells/VoiceBroadcast/Recorder/VoiceBroadcastRecorderWithPaginationTitlePlainCell.swift
index 4247f306c..5c0bb9143 100644
--- a/Riot/Modules/Room/TimelineCells/Styles/Plain/Cells/VoiceBroadcast/Recorder/VoiceBroadcastRecorderWithPaginationTitlePlainCell.swift
+++ b/Riot/Modules/Room/TimelineCells/Styles/Plain/Cells/VoiceBroadcast/Recorder/VoiceBroadcastRecorderWithPaginationTitlePlainCell.swift
@@ -16,7 +16,7 @@
import Foundation
-class VoiceBroadcastRecorderWithPaginationTitlePlainCell: VoiceBroadcastRecorderPlainCell {
+class VoiceBroadcastRecorderWithPaginationTitlePlainCell: VoiceBroadcastRecorderPlainBubbleCell {
override func setupViews() {
super.setupViews()
diff --git a/Riot/Modules/Room/TimelineCells/Styles/Plain/Cells/VoiceBroadcast/Recorder/VoiceBroadcastRecorderWithoutSenderInfoPlainCell.swift b/Riot/Modules/Room/TimelineCells/Styles/Plain/Cells/VoiceBroadcast/Recorder/VoiceBroadcastRecorderWithoutSenderInfoPlainCell.swift
index 172b10aee..797ab8c57 100644
--- a/Riot/Modules/Room/TimelineCells/Styles/Plain/Cells/VoiceBroadcast/Recorder/VoiceBroadcastRecorderWithoutSenderInfoPlainCell.swift
+++ b/Riot/Modules/Room/TimelineCells/Styles/Plain/Cells/VoiceBroadcast/Recorder/VoiceBroadcastRecorderWithoutSenderInfoPlainCell.swift
@@ -16,7 +16,7 @@
import Foundation
-class VoiceBroadcastRecorderWithoutSenderInfoPlainCell: VoiceBroadcastRecorderPlainCell {
+class VoiceBroadcastRecorderWithoutSenderInfoPlainCell: VoiceBroadcastRecorderPlainBubbleCell {
override func setupViews() {
super.setupViews()
diff --git a/Riot/Modules/Room/TimelineCells/Styles/Plain/PlainRoomTimelineCellProvider.h b/Riot/Modules/Room/TimelineCells/Styles/Plain/PlainRoomTimelineCellProvider.h
index b1e85a621..21d8b11bc 100644
--- a/Riot/Modules/Room/TimelineCells/Styles/Plain/PlainRoomTimelineCellProvider.h
+++ b/Riot/Modules/Room/TimelineCells/Styles/Plain/PlainRoomTimelineCellProvider.h
@@ -56,7 +56,7 @@ NS_ASSUME_NONNULL_BEGIN
- (NSDictionary*)locationCellsMapping;
-- (NSDictionary*)voiceBroadcastCellsMapping;
+- (NSDictionary*)voiceBroadcastPlaybackCellsMapping;
- (NSDictionary*)voiceBroadcastRecorderCellsMapping;
diff --git a/Riot/Modules/Room/TimelineCells/Styles/Plain/PlainRoomTimelineCellProvider.m b/Riot/Modules/Room/TimelineCells/Styles/Plain/PlainRoomTimelineCellProvider.m
index 4813b539d..83b835579 100644
--- a/Riot/Modules/Room/TimelineCells/Styles/Plain/PlainRoomTimelineCellProvider.m
+++ b/Riot/Modules/Room/TimelineCells/Styles/Plain/PlainRoomTimelineCellProvider.m
@@ -276,14 +276,14 @@
- (void)registerVoiceBroadcastCellsForTableView:(UITableView*)tableView
{
- [tableView registerClass:VoiceBroadcastPlainCell.class forCellReuseIdentifier:VoiceBroadcastPlainCell.defaultReuseIdentifier];
- [tableView registerClass:VoiceBroadcastWithoutSenderInfoPlainCell.class forCellReuseIdentifier:VoiceBroadcastWithoutSenderInfoPlainCell.defaultReuseIdentifier];
- [tableView registerClass:VoiceBroadcastWithPaginationTitlePlainCell.class forCellReuseIdentifier:VoiceBroadcastWithPaginationTitlePlainCell.defaultReuseIdentifier];
+ [tableView registerClass:VoiceBroadcastPlaybackPlainBubbleCell.class forCellReuseIdentifier:VoiceBroadcastPlaybackPlainBubbleCell.defaultReuseIdentifier];
+ [tableView registerClass:VoiceBroadcastPlaybackWithoutSenderInfoPlainCell.class forCellReuseIdentifier:VoiceBroadcastPlaybackWithoutSenderInfoPlainCell.defaultReuseIdentifier];
+ [tableView registerClass:VoiceBroadcastPlaybackWithPaginationTitlePlainCell.class forCellReuseIdentifier:VoiceBroadcastPlaybackWithPaginationTitlePlainCell.defaultReuseIdentifier];
}
- (void)registerVoiceBroadcastRecorderCellsForTableView:(UITableView*)tableView
{
- [tableView registerClass:VoiceBroadcastRecorderPlainCell.class forCellReuseIdentifier:VoiceBroadcastRecorderPlainCell.defaultReuseIdentifier];
+ [tableView registerClass:VoiceBroadcastRecorderPlainBubbleCell.class forCellReuseIdentifier:VoiceBroadcastRecorderPlainBubbleCell.defaultReuseIdentifier];
[tableView registerClass:VoiceBroadcastRecorderWithoutSenderInfoPlainCell.class forCellReuseIdentifier:VoiceBroadcastRecorderWithoutSenderInfoPlainCell.defaultReuseIdentifier];
[tableView registerClass:VoiceBroadcastRecorderWithPaginationTitlePlainCell.class forCellReuseIdentifier:VoiceBroadcastRecorderWithPaginationTitlePlainCell.defaultReuseIdentifier];
}
@@ -346,8 +346,8 @@
NSDictionary *locationCellsMapping = [self locationCellsMapping];
[cellClasses addEntriesFromDictionary:locationCellsMapping];
- NSDictionary *voiceBroadcastCellsMapping = [self voiceBroadcastCellsMapping];
- [cellClasses addEntriesFromDictionary:voiceBroadcastCellsMapping];
+ NSDictionary *voiceBroadcastPlaybackCellsMapping = [self voiceBroadcastPlaybackCellsMapping];
+ [cellClasses addEntriesFromDictionary:voiceBroadcastPlaybackCellsMapping];
NSDictionary *voiceBroadcastRecorderCellsMapping = [self voiceBroadcastRecorderCellsMapping];
[cellClasses addEntriesFromDictionary:voiceBroadcastRecorderCellsMapping];
@@ -574,17 +574,17 @@
};
}
-- (NSDictionary*)voiceBroadcastCellsMapping
+- (NSDictionary*)voiceBroadcastPlaybackCellsMapping
{
return @{
// Incoming
- @(RoomTimelineCellIdentifierIncomingVoiceBroadcast) : VoiceBroadcastPlainCell.class,
- @(RoomTimelineCellIdentifierIncomingVoiceBroadcastWithoutSenderInfo) : VoiceBroadcastWithoutSenderInfoPlainCell.class,
- @(RoomTimelineCellIdentifierIncomingVoiceBroadcastWithPaginationTitle) : VoiceBroadcastWithPaginationTitlePlainCell.class,
+ @(RoomTimelineCellIdentifierIncomingVoiceBroadcastPlayback) : VoiceBroadcastPlaybackPlainBubbleCell.class,
+ @(RoomTimelineCellIdentifierIncomingVoiceBroadcastPlaybackWithoutSenderInfo) : VoiceBroadcastPlaybackWithoutSenderInfoPlainCell.class,
+ @(RoomTimelineCellIdentifierIncomingVoiceBroadcastPlaybackWithPaginationTitle) : VoiceBroadcastPlaybackWithPaginationTitlePlainCell.class,
// Outoing
- @(RoomTimelineCellIdentifierOutgoingVoiceBroadcast) : VoiceBroadcastPlainCell.class,
- @(RoomTimelineCellIdentifierOutgoingVoiceBroadcastWithoutSenderInfo) : VoiceBroadcastWithoutSenderInfoPlainCell.class,
- @(RoomTimelineCellIdentifierOutgoingVoiceBroadcastWithPaginationTitle) : VoiceBroadcastWithPaginationTitlePlainCell.class
+ @(RoomTimelineCellIdentifierOutgoingVoiceBroadcastPlayback) : VoiceBroadcastPlaybackPlainBubbleCell.class,
+ @(RoomTimelineCellIdentifierOutgoingVoiceBroadcastPlaybackWithoutSenderInfo) : VoiceBroadcastPlaybackWithoutSenderInfoPlainCell.class,
+ @(RoomTimelineCellIdentifierOutgoingVoiceBroadcastPlaybackWithPaginationTitle) : VoiceBroadcastPlaybackWithPaginationTitlePlainCell.class
};
}
@@ -592,7 +592,7 @@
{
return @{
// Outoing
- @(RoomTimelineCellIdentifierOutgoingVoiceBroadcastRecorder) : VoiceBroadcastRecorderPlainCell.class,
+ @(RoomTimelineCellIdentifierOutgoingVoiceBroadcastRecorder) : VoiceBroadcastRecorderPlainBubbleCell.class,
@(RoomTimelineCellIdentifierOutgoingVoiceBroadcastRecorderWithoutSenderInfo) : VoiceBroadcastRecorderWithoutSenderInfoPlainCell.class,
@(RoomTimelineCellIdentifierOutgoingVoiceBroadcastRecorderWithPaginationTitle) : VoiceBroadcastRecorderWithPaginationTitlePlainCell.class
};
diff --git a/Riot/Modules/Room/Views/WYSIWYGInputToolbar/WysiwygInputToolbarView.swift b/Riot/Modules/Room/Views/WYSIWYGInputToolbar/WysiwygInputToolbarView.swift
index 368dfaf65..dfc53844b 100644
--- a/Riot/Modules/Room/Views/WYSIWYGInputToolbar/WysiwygInputToolbarView.swift
+++ b/Riot/Modules/Room/Views/WYSIWYGInputToolbar/WysiwygInputToolbarView.swift
@@ -66,6 +66,7 @@ class WysiwygInputToolbarView: MXKRoomInputToolbarView, NibLoadable, HtmlRoomInp
viewModel.callback = { [weak self] result in
self?.handleViewModelResult(result)
}
+ wysiwygViewModel.plainTextMode = !RiotSettings.shared.enableWysiwygTextFormatting
inputAccessoryViewForKeyboard = UIView(frame: .zero)
@@ -99,6 +100,7 @@ class WysiwygInputToolbarView: MXKRoomInputToolbarView, NibLoadable, HtmlRoomInp
subView.trailingAnchor.constraint(equalTo: self.trailingAnchor),
subView.bottomAnchor.constraint(equalTo: self.bottomAnchor)
])
+
cancellables = [
hostingViewController.heightPublisher
.removeDuplicates()
@@ -135,7 +137,8 @@ class WysiwygInputToolbarView: MXKRoomInputToolbarView, NibLoadable, HtmlRoomInp
}
private func sendWysiwygMessage(content: WysiwygComposerContent) {
- delegate?.roomInputToolbarView?(self, sendFormattedTextMessage: content.html, withRawText: content.plainText)
+ let html = content.html.isEmpty ? content.plainText : content.html
+ delegate?.roomInputToolbarView?(self, sendFormattedTextMessage: html, withRawText: content.plainText)
}
private func showSendMediaActions() {
@@ -212,6 +215,20 @@ class WysiwygInputToolbarView: MXKRoomInputToolbarView, NibLoadable, HtmlRoomInp
updatePlaceholderText()
}
}
+
+ /// Whether text formatting is currently enabled in the composer.
+ var textFormattingEnabled: Bool {
+ get {
+ self.viewModel.textFormattingEnabled
+ }
+ set {
+ self.viewModel.textFormattingEnabled = newValue
+ self.wysiwygViewModel.plainTextMode = !newValue
+ if !newValue {
+ self.wysiwygViewModel.maximised = false
+ }
+ }
+ }
/// Add the voice message toolbar to the composer
/// - Parameter voiceMessageToolbarView: the voice message toolbar UIView
diff --git a/Riot/Modules/VoiceBroadcast/VoiceBroadcastSDK/VoiceBroadcastAggregator.swift b/Riot/Modules/VoiceBroadcast/VoiceBroadcastSDK/VoiceBroadcastAggregator.swift
index 1de022904..fb90d834d 100644
--- a/Riot/Modules/VoiceBroadcast/VoiceBroadcastSDK/VoiceBroadcastAggregator.swift
+++ b/Riot/Modules/VoiceBroadcast/VoiceBroadcastSDK/VoiceBroadcastAggregator.swift
@@ -110,7 +110,7 @@ public class VoiceBroadcastAggregator {
guard let event = roomState?.stateEvents(with: .custom(VoiceBroadcastSettings.voiceBroadcastInfoContentKeyType))?.last,
event.stateKey == self.voiceBroadcastSenderId,
let voiceBroadcastInfo = VoiceBroadcastInfo(fromJSON: event.content),
- (event.eventId == self.voiceBroadcastStartEventId || voiceBroadcastInfo.eventId == self.voiceBroadcastStartEventId),
+ (event.eventId == self.voiceBroadcastStartEventId || voiceBroadcastInfo.voiceBroadcastId == self.voiceBroadcastStartEventId),
let state = VoiceBroadcastInfo.State(rawValue: voiceBroadcastInfo.state) else {
return
}
diff --git a/Riot/Modules/VoiceBroadcast/VoiceBroadcastSDK/VoiceBroadcastInfo.h b/Riot/Modules/VoiceBroadcast/VoiceBroadcastSDK/VoiceBroadcastInfo.h
index 36b963e47..71781d927 100644
--- a/Riot/Modules/VoiceBroadcast/VoiceBroadcastSDK/VoiceBroadcastInfo.h
+++ b/Riot/Modules/VoiceBroadcast/VoiceBroadcastSDK/VoiceBroadcastInfo.h
@@ -32,15 +32,12 @@ NS_ASSUME_NONNULL_BEGIN
@property (nonatomic) NSInteger chunkLength;
/// The event id of the started voice broadcast info state event.
-@property (nonatomic, strong, nullable) NSString* eventId;
-
-/// The event used to build the MXBeaconInfo.
-@property (nonatomic, readonly, nullable) MXEvent *originalEvent;
+@property (nonatomic, strong, nullable) NSString* voiceBroadcastId;
- (instancetype)initWithDeviceId:(NSString *)deviceId
state:(NSString *)state
chunkLength:(NSInteger)chunkLength
- eventId:(NSString *)eventId;
+ voiceBroadcastId:(NSString *)voiceBroadcastId;
@end
diff --git a/Riot/Modules/VoiceBroadcast/VoiceBroadcastSDK/VoiceBroadcastInfo.m b/Riot/Modules/VoiceBroadcast/VoiceBroadcastSDK/VoiceBroadcastInfo.m
index 51a50876c..eaaaa9047 100644
--- a/Riot/Modules/VoiceBroadcast/VoiceBroadcastSDK/VoiceBroadcastInfo.m
+++ b/Riot/Modules/VoiceBroadcast/VoiceBroadcastSDK/VoiceBroadcastInfo.m
@@ -22,14 +22,14 @@
- (instancetype)initWithDeviceId:(NSString *)deviceId
state:(NSString *)state
chunkLength:(NSInteger)chunkLength
- eventId:(NSString *)eventId
+ voiceBroadcastId:(NSString *)voiceBroadcastId
{
if (self = [super init])
{
_deviceId = deviceId;
_state = state;
_chunkLength = chunkLength;
- _eventId = eventId;
+ _voiceBroadcastId = voiceBroadcastId;
}
return self;
@@ -55,7 +55,7 @@
MXJSONModelSetInteger(chunkLength, JSONDictionary[VoiceBroadcastSettings.voiceBroadcastContentKeyChunkLength]);
}
- NSString *eventId;
+ NSString *voiceBroadcastId;
if (JSONDictionary[kMXEventRelationRelatesToKey]) {
MXEventContentRelatesTo *relatesTo;
@@ -63,11 +63,11 @@
if (relatesTo && [relatesTo.relationType isEqualToString:MXEventRelationTypeReference])
{
- eventId = relatesTo.eventId;
+ voiceBroadcastId = relatesTo.eventId;
}
}
- return [[VoiceBroadcastInfo alloc] initWithDeviceId:deviceId state:state chunkLength:chunkLength eventId:eventId];
+ return [[VoiceBroadcastInfo alloc] initWithDeviceId:deviceId state:state chunkLength:chunkLength voiceBroadcastId:voiceBroadcastId];
}
- (NSDictionary *)JSONDictionary
@@ -78,8 +78,8 @@
JSONDictionary[VoiceBroadcastSettings.voiceBroadcastContentKeyState] = self.state;
- if (_eventId) {
- MXEventContentRelatesTo *relatesTo = [[MXEventContentRelatesTo alloc] initWithRelationType:MXEventRelationTypeReference eventId:_eventId];
+ if (_voiceBroadcastId) {
+ MXEventContentRelatesTo *relatesTo = [[MXEventContentRelatesTo alloc] initWithRelationType:MXEventRelationTypeReference eventId:_voiceBroadcastId];
JSONDictionary[kMXEventRelationRelatesToKey] = relatesTo.JSONDictionary;
} else {
diff --git a/Riot/Modules/VoiceBroadcast/VoiceBroadcastSDK/VoiceBroadcastInfo.swift b/Riot/Modules/VoiceBroadcast/VoiceBroadcastSDK/VoiceBroadcastInfo.swift
index 3515a5b59..b2bc1afe4 100644
--- a/Riot/Modules/VoiceBroadcast/VoiceBroadcastSDK/VoiceBroadcastInfo.swift
+++ b/Riot/Modules/VoiceBroadcast/VoiceBroadcastSDK/VoiceBroadcastInfo.swift
@@ -35,4 +35,20 @@ extension VoiceBroadcastInfo {
@objc static func isStopped(for name: String) -> Bool {
return name == State.stopped.rawValue
}
+
+ @objc static func startedValue() -> String {
+ return State.started.rawValue
+ }
+
+ @objc static func pausedValue() -> String {
+ return State.paused.rawValue
+ }
+
+ @objc static func resumedValue() -> String {
+ return State.resumed.rawValue
+ }
+
+ @objc static func stoppedValue() -> String {
+ return State.stopped.rawValue
+ }
}
diff --git a/Riot/Modules/VoiceBroadcast/VoiceBroadcastSDK/VoiceBroadcastService.swift b/Riot/Modules/VoiceBroadcast/VoiceBroadcastSDK/VoiceBroadcastService.swift
index 81cbc51af..e6d6171a8 100644
--- a/Riot/Modules/VoiceBroadcast/VoiceBroadcastSDK/VoiceBroadcastService.swift
+++ b/Riot/Modules/VoiceBroadcast/VoiceBroadcastSDK/VoiceBroadcastService.swift
@@ -23,15 +23,18 @@ public class VoiceBroadcastService: NSObject {
// MARK: - Properties
- public private(set) var voiceBroadcastInfoEventId: String?
public let room: MXRoom
+ public private(set) var voiceBroadcastId: String?
public private(set) var state: VoiceBroadcastInfo.State
+ // Mechanism to process one call of sendVoiceBroadcastInfo() at a time
+ private let asyncTaskQueue: MXAsyncTaskQueue
// MARK: - Setup
public init(room: MXRoom, state: VoiceBroadcastInfo.State) {
self.room = room
self.state = state
+ self.asyncTaskQueue = MXAsyncTaskQueue(label: "VoiceBroadcastServiceQueueEventSerialQueue-" + MXTools.generateSecret())
}
// MARK: - Constants
@@ -43,14 +46,13 @@ public class VoiceBroadcastService: NSObject {
/// Start a voice broadcast.
/// - Parameters:
/// - completion: A closure called when the operation completes. Provides the event id of the event generated on the home server on success.
- /// - Returns: a `MXHTTPOperation` instance.
- func startVoiceBroadcast(completion: @escaping (MXResponse) -> Void) -> MXHTTPOperation? {
- return sendVoiceBroadcastInfo(state: VoiceBroadcastInfo.State.started) { [weak self] response in
+ func startVoiceBroadcast(completion: @escaping (MXResponse) -> Void) {
+ sendVoiceBroadcastInfo(state: VoiceBroadcastInfo.State.started) { [weak self] response in
guard let self = self else { return }
switch response {
case .success((let eventIdResponse)):
- self.voiceBroadcastInfoEventId = eventIdResponse
+ self.voiceBroadcastId = eventIdResponse
completion(.success(eventIdResponse))
case .failure(let error):
completion(.failure(error))
@@ -61,25 +63,22 @@ public class VoiceBroadcastService: NSObject {
/// Pause a voice broadcast.
/// - Parameters:
/// - completion: A closure called when the operation completes. Provides the event id of the event generated on the home server on success.
- /// - Returns: a `MXHTTPOperation` instance.
- func pauseVoiceBroadcast(completion: @escaping (MXResponse) -> Void) -> MXHTTPOperation? {
- return sendVoiceBroadcastInfo(state: VoiceBroadcastInfo.State.paused, completion: completion)
+ func pauseVoiceBroadcast(completion: @escaping (MXResponse) -> Void) {
+ sendVoiceBroadcastInfo(state: VoiceBroadcastInfo.State.paused, completion: completion)
}
/// resume a voice broadcast.
/// - Parameters:
/// - completion: A closure called when the operation completes. Provides the event id of the event generated on the home server on success.
- /// - Returns: a `MXHTTPOperation` instance.
- func resumeVoiceBroadcast(completion: @escaping (MXResponse) -> Void) -> MXHTTPOperation? {
- return sendVoiceBroadcastInfo(state: VoiceBroadcastInfo.State.resumed, completion: completion)
+ func resumeVoiceBroadcast(completion: @escaping (MXResponse) -> Void) {
+ sendVoiceBroadcastInfo(state: VoiceBroadcastInfo.State.resumed, completion: completion)
}
/// stop a voice broadcast info.
/// - Parameters:
/// - completion: A closure called when the operation completes. Provides the event id of the event generated on the home server on success.
- /// - Returns: a `MXHTTPOperation` instance.
- func stopVoiceBroadcast(completion: @escaping (MXResponse) -> Void) -> MXHTTPOperation? {
- return sendVoiceBroadcastInfo(state: VoiceBroadcastInfo.State.stopped, completion: completion)
+ func stopVoiceBroadcast(completion: @escaping (MXResponse) -> Void) {
+ sendVoiceBroadcastInfo(state: VoiceBroadcastInfo.State.stopped, completion: completion)
}
func getState() -> String {
@@ -104,19 +103,17 @@ public class VoiceBroadcastService: NSObject {
func sendChunkOfVoiceBroadcast(audioFileLocalURL: URL,
mimeType: String?,
duration: UInt,
- samples: [Float]?,
sequence: UInt,
success: @escaping ((String?) -> Void),
failure: @escaping ((Error?) -> Void)) {
- guard let voiceBroadcastInfoEventId = self.voiceBroadcastInfoEventId else {
+ guard let voiceBroadcastId = self.voiceBroadcastId else {
return failure(VoiceBroadcastServiceError.notStarted)
}
self.room.sendChunkOfVoiceBroadcast(localURL: audioFileLocalURL,
- voiceBroadcastInfoEventId: voiceBroadcastInfoEventId,
+ voiceBroadcastId: voiceBroadcastId,
mimeType: mimeType,
duration: duration,
- samples: samples,
sequence: sequence,
success: success,
failure: failure)
@@ -124,46 +121,71 @@ public class VoiceBroadcastService: NSObject {
// MARK: - Private
- private func sendVoiceBroadcastInfo(state: VoiceBroadcastInfo.State, completion: @escaping (MXResponse) -> Void) -> MXHTTPOperation? {
+ private func allowedStates(from state: VoiceBroadcastInfo.State) -> [VoiceBroadcastInfo.State] {
+ switch state {
+ case .started:
+ return [.paused, .stopped]
+ case .paused:
+ return [.resumed, .stopped]
+ case .resumed:
+ return [.paused, .stopped]
+ case .stopped:
+ return [.started]
+ }
+ }
+
+ private func sendVoiceBroadcastInfo(state: VoiceBroadcastInfo.State, completion: @escaping (MXResponse) -> Void) {
guard let userId = self.room.mxSession.myUserId else {
completion(.failure(VoiceBroadcastServiceError.missingUserId))
- return nil
+ return
}
- let stateKey = userId
-
- let voiceBroadcastInfo = VoiceBroadcastInfo()
-
- voiceBroadcastInfo.deviceId = self.room.mxSession.myDeviceId
-
- voiceBroadcastInfo.state = state.rawValue
-
- if state != VoiceBroadcastInfo.State.started {
- guard let voiceBroadcastInfoEventId = self.voiceBroadcastInfoEventId else {
- completion(.failure(VoiceBroadcastServiceError.notStarted))
- return nil
+ asyncTaskQueue.async { (taskCompleted) in
+ guard self.allowedStates(from: self.state).contains(state) else {
+ MXLog.warning("[VoiceBroadcastService] sendVoiceBroadcastInfo: unexpected state change \(self.state) -> \(state)")
+ completion(.failure(VoiceBroadcastServiceError.unexpectedState))
+ taskCompleted()
+ return
}
- voiceBroadcastInfo.eventId = voiceBroadcastInfoEventId
- } else {
- voiceBroadcastInfo.chunkLength = BuildSettings.voiceBroadcastChunkLength
- }
-
- guard let stateEventContent = voiceBroadcastInfo.jsonDictionary() as? [String: Any] else {
- completion(.failure(VoiceBroadcastServiceError.unknown))
- return nil
- }
-
- return self.room.sendStateEvent(.custom(VoiceBroadcastSettings.voiceBroadcastInfoContentKeyType),
- content: stateEventContent, stateKey: stateKey) { [weak self] response in
- guard let self = self else { return }
+ let stateKey = userId
- switch response {
- case .success(let object):
- self.state = state
- completion(.success(object))
- case .failure(let error):
- completion(.failure(error))
+ let voiceBroadcastInfo = VoiceBroadcastInfo()
+
+ voiceBroadcastInfo.deviceId = self.room.mxSession.myDeviceId
+
+ voiceBroadcastInfo.state = state.rawValue
+
+ if state != VoiceBroadcastInfo.State.started {
+ guard let voiceBroadcastId = self.voiceBroadcastId else {
+ completion(.failure(VoiceBroadcastServiceError.notStarted))
+ taskCompleted()
+ return
+ }
+
+ voiceBroadcastInfo.voiceBroadcastId = voiceBroadcastId
+ } else {
+ voiceBroadcastInfo.chunkLength = BuildSettings.voiceBroadcastChunkLength
+ }
+
+ guard let stateEventContent = voiceBroadcastInfo.jsonDictionary() as? [String: Any] else {
+ completion(.failure(VoiceBroadcastServiceError.unknown))
+ taskCompleted()
+ return
+ }
+
+ self.room.sendStateEvent(.custom(VoiceBroadcastSettings.voiceBroadcastInfoContentKeyType),
+ content: stateEventContent, stateKey: stateKey) { [weak self] response in
+ guard let self = self else { return }
+
+ switch response {
+ case .success(let object):
+ self.state = state
+ completion(.success(object))
+ case .failure(let error):
+ completion(.failure(error))
+ }
+ taskCompleted()
}
}
}
@@ -176,10 +198,8 @@ extension VoiceBroadcastService {
/// - Parameters:
/// - success: A closure called when the operation is complete.
/// - failure: A closure called when the operation fails.
- /// - Returns: a `MXHTTPOperation` instance.
- @discardableResult
- @objc public func startVoiceBroadcast(success: @escaping (String?) -> Void, failure: @escaping (Error) -> Void) -> MXHTTPOperation? {
- return self.startVoiceBroadcast { response in
+ @objc public func startVoiceBroadcast(success: @escaping (String?) -> Void, failure: @escaping (Error) -> Void) {
+ self.startVoiceBroadcast { response in
switch response {
case .success(let object):
success(object)
@@ -193,10 +213,8 @@ extension VoiceBroadcastService {
/// - Parameters:
/// - success: A closure called when the operation is complete.
/// - failure: A closure called when the operation fails.
- /// - Returns: a `MXHTTPOperation` instance.
- @discardableResult
- @objc public func pauseVoiceBroadcast(success: @escaping (String?) -> Void, failure: @escaping (Error) -> Void) -> MXHTTPOperation? {
- return self.pauseVoiceBroadcast { response in
+ @objc public func pauseVoiceBroadcast(success: @escaping (String?) -> Void, failure: @escaping (Error) -> Void) {
+ self.pauseVoiceBroadcast { response in
switch response {
case .success(let object):
success(object)
@@ -210,10 +228,8 @@ extension VoiceBroadcastService {
/// - Parameters:
/// - success: A closure called when the operation is complete.
/// - failure: A closure called when the operation fails.
- /// - Returns: a `MXHTTPOperation` instance.
- @discardableResult
- @objc public func resumeVoiceBroadcast(success: @escaping (String?) -> Void, failure: @escaping (Error) -> Void) -> MXHTTPOperation? {
- return self.resumeVoiceBroadcast { response in
+ @objc public func resumeVoiceBroadcast(success: @escaping (String?) -> Void, failure: @escaping (Error) -> Void) {
+ self.resumeVoiceBroadcast { response in
switch response {
case .success(let object):
success(object)
@@ -227,10 +243,8 @@ extension VoiceBroadcastService {
/// - Parameters:
/// - success: A closure called when the operation is complete.
/// - failure: A closure called when the operation fails.
- /// - Returns: a `MXHTTPOperation` instance.
- @discardableResult
- @objc public func stopVoiceBroadcast(success: @escaping (String?) -> Void, failure: @escaping (Error) -> Void) -> MXHTTPOperation? {
- return self.stopVoiceBroadcast { response in
+ @objc public func stopVoiceBroadcast(success: @escaping (String?) -> Void, failure: @escaping (Error) -> Void) {
+ self.stopVoiceBroadcast { response in
switch response {
case .success(let object):
success(object)
@@ -247,7 +261,7 @@ extension MXRoom {
/// Send a voice broadcast to the room.
/// - Parameters:
/// - localURL: the local filesystem path of the file to send.
- /// - voiceBroadcastInfoEventId: The id of the voice broadcast info event.
+ /// - voiceBroadcastId: The event id of the started voice broadcast info state event
/// - mimeType: (optional) the mime type of the file. Defaults to `audio/ogg`.
/// - duration: the length of the voice message in milliseconds
/// - samples: an array of floating point values normalized to [0, 1]
@@ -257,19 +271,15 @@ extension MXRoom {
/// - failure: A closure called when the operation fails.
/// - Returns: a `MXHTTPOperation` instance.
@nonobjc @discardableResult func sendChunkOfVoiceBroadcast(localURL: URL,
- voiceBroadcastInfoEventId: String,
+ voiceBroadcastId: String,
mimeType: String?,
duration: UInt,
- samples: [Float]?,
threadId: String? = nil,
sequence: UInt,
success: @escaping ((String?) -> Void),
failure: @escaping ((Error?) -> Void)) -> MXHTTPOperation? {
- let boxedSamples = samples?.compactMap { NSNumber(value: $0) }
-
-
guard let relatesTo = MXEventContentRelatesTo(relationType: MXEventRelationTypeReference,
- eventId: voiceBroadcastInfoEventId).jsonDictionary() as? [String: Any] else {
+ eventId: voiceBroadcastId).jsonDictionary() as? [String: Any] else {
failure(VoiceBroadcastServiceError.unknown)
return nil
}
@@ -281,7 +291,7 @@ extension MXRoom {
VoiceBroadcastSettings.voiceBroadcastContentKeyChunkType: sequenceValue],
mimeType: mimeType,
duration: duration,
- samples: boxedSamples,
+ samples: nil,
threadId: threadId,
localEcho: nil,
success: success,
diff --git a/Riot/Modules/VoiceBroadcast/VoiceBroadcastSDK/VoiceBroadcastServiceError.swift b/Riot/Modules/VoiceBroadcast/VoiceBroadcastSDK/VoiceBroadcastServiceError.swift
index 55d0820fa..70f3851e0 100644
--- a/Riot/Modules/VoiceBroadcast/VoiceBroadcastSDK/VoiceBroadcastServiceError.swift
+++ b/Riot/Modules/VoiceBroadcast/VoiceBroadcastSDK/VoiceBroadcastServiceError.swift
@@ -21,6 +21,7 @@ public enum VoiceBroadcastServiceError: Int, Error {
case missingUserId
case roomNotFound
case notStarted
+ case unexpectedState
case unknown
}
diff --git a/Riot/Utils/EventFormatter.m b/Riot/Utils/EventFormatter.m
index 80efe2f99..2a1fe4879 100644
--- a/Riot/Utils/EventFormatter.m
+++ b/Riot/Utils/EventFormatter.m
@@ -273,7 +273,8 @@ static NSString *const kEventFormatterTimeFormat = @"HH:mm";
return [self renderString:displayText forEvent:event];
}
} else if ([event.type isEqualToString:VoiceBroadcastSettings.voiceBroadcastInfoContentKeyType]) {
- MXLogDebug(@"VB incoming build string")
+ // do not show voice broadcast info in the timeline
+ return nil;
}
}
diff --git a/RiotSwiftUI/Modules/Room/Composer/CreateActionList/Coordinator/ComposerCreateActionListBridgePresenter.swift b/RiotSwiftUI/Modules/Room/Composer/CreateActionList/Coordinator/ComposerCreateActionListBridgePresenter.swift
index 41b79334d..6d2a08f0f 100644
--- a/RiotSwiftUI/Modules/Room/Composer/CreateActionList/Coordinator/ComposerCreateActionListBridgePresenter.swift
+++ b/RiotSwiftUI/Modules/Room/Composer/CreateActionList/Coordinator/ComposerCreateActionListBridgePresenter.swift
@@ -18,6 +18,7 @@ import Foundation
@objc protocol ComposerCreateActionListBridgePresenterDelegate {
func composerCreateActionListBridgePresenterDelegateDidComplete(_ coordinatorBridgePresenter: ComposerCreateActionListBridgePresenter, action: ComposerCreateAction)
+ func composerCreateActionListBridgePresenterDelegateDidToggleTextFormatting(_ coordinatorBridgePresenter: ComposerCreateActionListBridgePresenter, enabled: Bool)
func composerCreateActionListBridgePresenterDidDismissInteractively(_ coordinatorBridgePresenter: ComposerCreateActionListBridgePresenter)
}
@@ -34,6 +35,8 @@ final class ComposerCreateActionListBridgePresenter: NSObject {
// MARK: Private
private let actions: [ComposerCreateAction]
+ private let wysiwygEnabled: Bool
+ private let textFormattingEnabled: Bool
private var coordinator: ComposerCreateActionListCoordinator?
// MARK: Public
@@ -42,10 +45,12 @@ final class ComposerCreateActionListBridgePresenter: NSObject {
// MARK: - Setup
- init(actions: [Int]) {
+ init(actions: [Int], wysiwygEnabled: Bool, textFormattingEnabled: Bool) {
self.actions = actions.compactMap {
ComposerCreateAction(rawValue: $0)
}
+ self.wysiwygEnabled = wysiwygEnabled
+ self.textFormattingEnabled = textFormattingEnabled
super.init()
}
@@ -57,12 +62,16 @@ final class ComposerCreateActionListBridgePresenter: NSObject {
// }
func present(from viewController: UIViewController, animated: Bool) {
- let composerCreateActionListCoordinator = ComposerCreateActionListCoordinator(actions: actions)
+ let composerCreateActionListCoordinator = ComposerCreateActionListCoordinator(actions: actions,
+ wysiwygEnabled: wysiwygEnabled,
+ textFormattingEnabled: textFormattingEnabled)
composerCreateActionListCoordinator.callback = { [weak self] action in
guard let self = self else { return }
switch action {
case .done(let composeAction):
self.delegate?.composerCreateActionListBridgePresenterDelegateDidComplete(self, action: composeAction)
+ case .toggleTextFormatting(let enabled):
+ self.delegate?.composerCreateActionListBridgePresenterDelegateDidToggleTextFormatting(self, enabled: enabled)
case .cancel:
self.delegate?.composerCreateActionListBridgePresenterDidDismissInteractively(self)
}
diff --git a/RiotSwiftUI/Modules/Room/Composer/CreateActionList/Coordinator/ComposerCreateActionListCoordinator.swift b/RiotSwiftUI/Modules/Room/Composer/CreateActionList/Coordinator/ComposerCreateActionListCoordinator.swift
index fcc05c1f2..cb52281eb 100644
--- a/RiotSwiftUI/Modules/Room/Composer/CreateActionList/Coordinator/ComposerCreateActionListCoordinator.swift
+++ b/RiotSwiftUI/Modules/Room/Composer/CreateActionList/Coordinator/ComposerCreateActionListCoordinator.swift
@@ -19,6 +19,7 @@ import SwiftUI
/// Actions returned by the coordinator callback
enum ComposerCreateActionListCoordinatorAction {
case done(ComposerCreateAction)
+ case toggleTextFormatting(Bool)
case cancel
}
@@ -39,8 +40,11 @@ final class ComposerCreateActionListCoordinator: NSObject, Coordinator, Presenta
// MARK: - Setup
- init(actions: [ComposerCreateAction]) {
- viewModel = ComposerCreateActionListViewModel(initialViewState: ComposerCreateActionListViewState(actions: actions))
+ init(actions: [ComposerCreateAction], wysiwygEnabled: Bool, textFormattingEnabled: Bool) {
+ viewModel = ComposerCreateActionListViewModel(initialViewState: ComposerCreateActionListViewState(
+ actions: actions,
+ wysiwygEnabled: wysiwygEnabled,
+ bindings: ComposerCreateActionListBindings(textFormattingEnabled: textFormattingEnabled)))
view = ComposerCreateActionList(viewModel: viewModel.context)
let hostingVC = VectorHostingController(rootView: view)
hostingVC.bottomSheetPreferences = VectorHostingBottomSheetPreferences(
@@ -61,6 +65,8 @@ final class ComposerCreateActionListCoordinator: NSObject, Coordinator, Presenta
switch result {
case .done(let action):
self.callback?(.done(action))
+ case .toggleTextFormatting(let enabled):
+ self.callback?(.toggleTextFormatting(enabled))
}
}
}
diff --git a/RiotSwiftUI/Modules/Room/Composer/CreateActionList/MockComposerCreateActionListScreenState.swift b/RiotSwiftUI/Modules/Room/Composer/CreateActionList/MockComposerCreateActionListScreenState.swift
index 31d5b9487..cb1a53b88 100644
--- a/RiotSwiftUI/Modules/Room/Composer/CreateActionList/MockComposerCreateActionListScreenState.swift
+++ b/RiotSwiftUI/Modules/Room/Composer/CreateActionList/MockComposerCreateActionListScreenState.swift
@@ -33,7 +33,10 @@ enum MockComposerCreateActionListScreenState: MockScreenState, CaseIterable {
case .fullList:
actions = ComposerCreateAction.allCases
}
- let viewModel = ComposerCreateActionListViewModel(initialViewState: ComposerCreateActionListViewState(actions: actions))
+ let viewModel = ComposerCreateActionListViewModel(initialViewState: ComposerCreateActionListViewState(
+ actions: actions,
+ wysiwygEnabled: true,
+ bindings: ComposerCreateActionListBindings(textFormattingEnabled: true)))
return (
[viewModel],
diff --git a/RiotSwiftUI/Modules/Room/Composer/CreateActionList/Model/ComposerCreateActionListModels.swift b/RiotSwiftUI/Modules/Room/Composer/CreateActionList/Model/ComposerCreateActionListModels.swift
index 457cc612a..6c42041b7 100644
--- a/RiotSwiftUI/Modules/Room/Composer/CreateActionList/Model/ComposerCreateActionListModels.swift
+++ b/RiotSwiftUI/Modules/Room/Composer/CreateActionList/Model/ComposerCreateActionListModels.swift
@@ -21,11 +21,15 @@ import Foundation
enum ComposerCreateActionListViewAction {
// The user selected an action
case selectAction(ComposerCreateAction)
+ // The user toggled the text formatting action
+ case toggleTextFormatting(Bool)
}
enum ComposerCreateActionListViewModelResult: Equatable {
// The user selected an action and is done with the screen
case done(ComposerCreateAction)
+ // The user toggled the text formatting setting but might not be done with the screen
+ case toggleTextFormatting(Bool)
}
// MARK: View
@@ -33,6 +37,13 @@ enum ComposerCreateActionListViewModelResult: Equatable {
struct ComposerCreateActionListViewState: BindableState {
/// The list of composer create actions to display to the user
let actions: [ComposerCreateAction]
+ let wysiwygEnabled: Bool
+
+ var bindings: ComposerCreateActionListBindings
+}
+
+struct ComposerCreateActionListBindings {
+ var textFormattingEnabled: Bool
}
@objc enum ComposerCreateAction: Int {
diff --git a/RiotSwiftUI/Modules/Room/Composer/CreateActionList/Test/Unit/ComposerCreateActionListTests.swift b/RiotSwiftUI/Modules/Room/Composer/CreateActionList/Test/Unit/ComposerCreateActionListTests.swift
index 33258467b..35532a212 100644
--- a/RiotSwiftUI/Modules/Room/Composer/CreateActionList/Test/Unit/ComposerCreateActionListTests.swift
+++ b/RiotSwiftUI/Modules/Room/Composer/CreateActionList/Test/Unit/ComposerCreateActionListTests.swift
@@ -23,7 +23,13 @@ class ComposerCreateActionListTests: XCTestCase {
var context: ComposerCreateActionListViewModel.Context!
override func setUpWithError() throws {
- viewModel = ComposerCreateActionListViewModel(initialViewState: ComposerCreateActionListViewState(actions: ComposerCreateAction.allCases))
+ viewModel = ComposerCreateActionListViewModel(
+ initialViewState: ComposerCreateActionListViewState(
+ actions: ComposerCreateAction.allCases,
+ wysiwygEnabled: true,
+ bindings: ComposerCreateActionListBindings(textFormattingEnabled: true)
+ )
+ )
context = viewModel.context
}
diff --git a/RiotSwiftUI/Modules/Room/Composer/CreateActionList/View/ComposerCreateActionList.swift b/RiotSwiftUI/Modules/Room/Composer/CreateActionList/View/ComposerCreateActionList.swift
index dbc484372..7f2733e2b 100644
--- a/RiotSwiftUI/Modules/Room/Composer/CreateActionList/View/ComposerCreateActionList.swift
+++ b/RiotSwiftUI/Modules/Room/Composer/CreateActionList/View/ComposerCreateActionList.swift
@@ -22,11 +22,17 @@ struct ComposerCreateActionList: View {
// MARK: Private
@Environment(\.theme) private var theme: ThemeSwiftUI
+
+ private var textFormattingIcon: String {
+ viewModel.textFormattingEnabled
+ ? Asset.Images.actionFormattingEnabled.name
+ : Asset.Images.actionFormattingDisabled.name
+ }
// MARK: Public
@ObservedObject var viewModel: ComposerCreateActionListViewModel.Context
-
+
var body: some View {
VStack {
VStack(alignment: .leading) {
@@ -48,6 +54,29 @@ struct ComposerCreateActionList: View {
.padding(.horizontal, 16)
.padding(.vertical, 12)
}
+ if viewModel.viewState.wysiwygEnabled {
+ SeparatorLine()
+ HStack(spacing: 16) {
+ Image(textFormattingIcon)
+ .renderingMode(.template)
+ .foregroundColor(theme.colors.accent)
+ Text(VectorL10n.wysiwygComposerStartActionTextFormatting)
+ .foregroundColor(theme.colors.primaryContent)
+ .font(theme.fonts.body)
+ .accessibilityIdentifier("textFormatting")
+ Spacer()
+ Toggle("", isOn: $viewModel.textFormattingEnabled)
+ .toggleStyle(ComposerToggleActionStyle())
+ .labelsHidden()
+ .onChange(of: viewModel.textFormattingEnabled) { isOn in
+ viewModel.send(viewAction: .toggleTextFormatting(isOn))
+ }
+ }
+ .contentShape(Rectangle())
+ .padding(.horizontal, 16)
+ .padding(.vertical, 12)
+
+ }
}
.padding(.top, 8)
Spacer()
@@ -63,3 +92,35 @@ struct ComposerCreateActionList_Previews: PreviewProvider {
stateRenderer.screenGroup()
}
}
+
+struct ComposerToggleActionStyle: ToggleStyle {
+ @Environment(\.theme) private var theme
+
+ func makeBody(configuration: Configuration) -> some View {
+ HStack {
+ Rectangle()
+ .foregroundColor(.clear)
+ .frame(width: 50, height: 30, alignment: .center)
+ .overlay(
+ Rectangle()
+ .foregroundColor(configuration.isOn
+ ? theme.colors.accent.opacity(0.5)
+ : theme.colors.primaryContent.opacity(0.25))
+ .cornerRadius(7)
+ .padding(.all, 8)
+ )
+ .overlay(
+ Circle()
+ .foregroundColor(configuration.isOn
+ ? theme.colors.accent
+ : theme.colors.background)
+ .padding(.all, 3)
+ .offset(x: configuration.isOn ? 11 : -11, y: 0)
+ .shadow(radius: configuration.isOn ? 0.0 : 2.0)
+ .animation(Animation.linear(duration: 0.1))
+
+ ).cornerRadius(20)
+ .onTapGesture { configuration.isOn.toggle() }
+ }
+ }
+}
diff --git a/RiotSwiftUI/Modules/Room/Composer/CreateActionList/ViewModel/ComposerCreateActionListViewModel.swift b/RiotSwiftUI/Modules/Room/Composer/CreateActionList/ViewModel/ComposerCreateActionListViewModel.swift
index bd063b1b2..93fa9950b 100644
--- a/RiotSwiftUI/Modules/Room/Composer/CreateActionList/ViewModel/ComposerCreateActionListViewModel.swift
+++ b/RiotSwiftUI/Modules/Room/Composer/CreateActionList/ViewModel/ComposerCreateActionListViewModel.swift
@@ -35,6 +35,8 @@ class ComposerCreateActionListViewModel: ComposerCreateActionListViewModelType,
switch viewAction {
case .selectAction(let action):
callback?(.done(action))
+ case .toggleTextFormatting(let enabled):
+ callback?(.toggleTextFormatting(enabled))
}
}
}
diff --git a/RiotSwiftUI/Modules/Room/Composer/Model/ComposerViewState.swift b/RiotSwiftUI/Modules/Room/Composer/Model/ComposerViewState.swift
index 2e86750b5..38f1cf227 100644
--- a/RiotSwiftUI/Modules/Room/Composer/Model/ComposerViewState.swift
+++ b/RiotSwiftUI/Modules/Room/Composer/Model/ComposerViewState.swift
@@ -19,6 +19,7 @@ import Foundation
struct ComposerViewState: BindableState {
var eventSenderDisplayName: String?
var sendMode: ComposerSendMode = .send
+ var textFormattingEnabled = true
var placeholder: String?
var bindings: ComposerBindings
@@ -26,7 +27,7 @@ struct ComposerViewState: BindableState {
extension ComposerViewState {
var shouldDisplayContext: Bool {
- return sendMode == .edit || sendMode == .reply
+ sendMode == .edit || sendMode == .reply
}
var contextDescription: String? {
diff --git a/RiotSwiftUI/Modules/Room/Composer/View/Composer.swift b/RiotSwiftUI/Modules/Room/Composer/View/Composer.swift
index cab370558..2ed64b4e5 100644
--- a/RiotSwiftUI/Modules/Room/Composer/View/Composer.swift
+++ b/RiotSwiftUI/Modules/Room/Composer/View/Composer.swift
@@ -83,70 +83,10 @@ struct Composer: View {
var body: some View {
VStack(spacing: 8) {
- let rect = RoundedRectangle(cornerRadius: cornerRadius)
- VStack(spacing: 12) {
- if viewModel.viewState.shouldDisplayContext {
- HStack {
- if let imageName = viewModel.viewState.contextImageName {
- Image(imageName)
- .foregroundColor(theme.colors.tertiaryContent)
- }
- if let contextDescription = viewModel.viewState.contextDescription {
- Text(contextDescription)
- .accessibilityIdentifier("contextDescription")
- .font(.system(size: 12, weight: .medium))
- .foregroundColor(theme.colors.secondaryContent)
- }
- Spacer()
- Button {
- viewModel.send(viewAction: .cancel)
- } label: {
- Image(Asset.Images.inputCloseIcon.name)
- .foregroundColor(theme.colors.tertiaryContent)
- }
- .accessibilityIdentifier("cancelButton")
- }
- .padding(.top, 8)
- .padding(.horizontal, horizontalPadding)
- }
- HStack(alignment: .top, spacing: 0) {
- WysiwygComposerView(
- focused: $viewModel.focused,
- viewModel: wysiwygViewModel
- )
- .tintColor(theme.colors.accent)
- .placeholder(viewModel.viewState.placeholder, color: theme.colors.tertiaryContent)
- .frame(height: wysiwygViewModel.idealHeight)
- .onAppear {
- wysiwygViewModel.setup()
- }
- Button {
- wysiwygViewModel.maximised.toggle()
- } label: {
- Image(toggleButtonImageName)
- .resizable()
- .foregroundColor(theme.colors.tertiaryContent)
- .frame(width: 16, height: 16)
- }
- .accessibilityIdentifier(toggleButtonAcccessibilityIdentifier)
- .padding(.leading, 12)
- .padding(.trailing, 4)
- }
- .padding(.horizontal, horizontalPadding)
- .padding(.top, topPadding)
- .padding(.bottom, verticalPadding)
+ if viewModel.viewState.textFormattingEnabled {
+ composerContainer
}
- .clipShape(rect)
- .overlay(rect.stroke(borderColor, lineWidth: 1))
- .animation(.easeInOut(duration: resizeAnimationDuration), value: wysiwygViewModel.idealHeight)
- .padding(.horizontal, horizontalPadding)
- .padding(.top, 8)
- .onTapGesture {
- if viewModel.focused {
- viewModel.focused = true
- }
- }
- HStack(spacing: 0) {
+ HStack(alignment: .bottom, spacing: 0) {
Button {
showSendMediaActions()
} label: {
@@ -159,13 +99,21 @@ struct Composer: View {
.background(Circle().fill(theme.colors.system))
.padding(.trailing, 8)
.accessibilityLabel(VectorL10n.create)
- FormattingToolbar(formatItems: formatItems) { type in
- wysiwygViewModel.apply(type.action)
+ if viewModel.viewState.textFormattingEnabled {
+ FormattingToolbar(formatItems: formatItems) { type in
+ wysiwygViewModel.apply(type.action)
+ }
+ .frame(height: 44)
+ Spacer()
+ } else {
+ composerContainer
}
- .frame(height: 44)
- Spacer()
Button {
- sendMessageAction(wysiwygViewModel.content)
+ if wysiwygViewModel.plainTextMode {
+ sendMessageAction(wysiwygViewModel.plainTextModeContent)
+ } else {
+ sendMessageAction(wysiwygViewModel.content)
+ }
wysiwygViewModel.clearContent()
} label: {
if viewModel.viewState.sendMode == .edit {
@@ -190,6 +138,76 @@ struct Composer: View {
.padding(.bottom, 4)
}
}
+
+ private var composerContainer: some View {
+ let rect = RoundedRectangle(cornerRadius: cornerRadius)
+ return VStack(spacing: 12) {
+ if viewModel.viewState.shouldDisplayContext {
+ HStack {
+ if let imageName = viewModel.viewState.contextImageName {
+ Image(imageName)
+ .foregroundColor(theme.colors.tertiaryContent)
+ }
+ if let contextDescription = viewModel.viewState.contextDescription {
+ Text(contextDescription)
+ .accessibilityIdentifier("contextDescription")
+ .font(.system(size: 12, weight: .medium))
+ .foregroundColor(theme.colors.secondaryContent)
+ }
+ Spacer()
+ Button {
+ viewModel.send(viewAction: .cancel)
+ } label: {
+ Image(Asset.Images.inputCloseIcon.name)
+ .foregroundColor(theme.colors.tertiaryContent)
+ }
+ .accessibilityIdentifier("cancelButton")
+ }
+ .padding(.top, 8)
+ .padding(.horizontal, horizontalPadding)
+ }
+ HStack(alignment: .top, spacing: 0) {
+ WysiwygComposerView(
+ focused: $viewModel.focused,
+ viewModel: wysiwygViewModel
+ )
+ .tintColor(theme.colors.accent)
+ .placeholder(viewModel.viewState.placeholder, color: theme.colors.tertiaryContent)
+ .frame(height: wysiwygViewModel.idealHeight)
+ .onAppear {
+ if wysiwygViewModel.isContentEmpty {
+ wysiwygViewModel.setup()
+ }
+ }
+ if viewModel.viewState.textFormattingEnabled {
+ Button {
+ wysiwygViewModel.maximised.toggle()
+ } label: {
+ Image(toggleButtonImageName)
+ .resizable()
+ .foregroundColor(theme.colors.tertiaryContent)
+ .frame(width: 16, height: 16)
+ }
+ .accessibilityIdentifier(toggleButtonAcccessibilityIdentifier)
+ .padding(.leading, 12)
+ .padding(.trailing, 4)
+ }
+ }
+ .padding(.horizontal, horizontalPadding)
+ .padding(.top, topPadding)
+ .padding(.bottom, verticalPadding)
+ }
+ .clipShape(rect)
+ .overlay(rect.stroke(borderColor, lineWidth: 1))
+ .animation(.easeInOut(duration: resizeAnimationDuration), value: wysiwygViewModel.idealHeight)
+ .padding(.horizontal, horizontalPadding)
+ .padding(.top, 8)
+ .onTapGesture {
+ if viewModel.focused {
+ viewModel.focused = true
+ }
+ }
+ }
}
// MARK: Previews
diff --git a/RiotSwiftUI/Modules/Room/Composer/ViewModel/ComposerViewModel.swift b/RiotSwiftUI/Modules/Room/Composer/ViewModel/ComposerViewModel.swift
index 5d57e34b3..8ad3ebd27 100644
--- a/RiotSwiftUI/Modules/Room/Composer/ViewModel/ComposerViewModel.swift
+++ b/RiotSwiftUI/Modules/Room/Composer/ViewModel/ComposerViewModel.swift
@@ -35,6 +35,15 @@ final class ComposerViewModel: ComposerViewModelType, ComposerViewModelProtocol
state.sendMode = newValue
}
}
+
+ var textFormattingEnabled: Bool {
+ get {
+ state.textFormattingEnabled
+ }
+ set {
+ state.textFormattingEnabled = newValue
+ }
+ }
var eventSenderDisplayName: String? {
get {
diff --git a/RiotSwiftUI/Modules/Room/Composer/ViewModel/ComposerViewModelProtocol.swift b/RiotSwiftUI/Modules/Room/Composer/ViewModel/ComposerViewModelProtocol.swift
index 41005324b..a1674ff4d 100644
--- a/RiotSwiftUI/Modules/Room/Composer/ViewModel/ComposerViewModelProtocol.swift
+++ b/RiotSwiftUI/Modules/Room/Composer/ViewModel/ComposerViewModelProtocol.swift
@@ -20,6 +20,7 @@ protocol ComposerViewModelProtocol {
var context: ComposerViewModelType.Context { get }
var callback: ((ComposerViewModelResult) -> Void)? { get set }
var sendMode: ComposerSendMode { get set }
+ var textFormattingEnabled: Bool { get set }
var eventSenderDisplayName: String? { get set }
var placeholder: String? { get set }
diff --git a/RiotSwiftUI/Modules/Room/VoiceBroadcastPlayback/Coordinator/VoiceBroadcastPlaybackCoordinator.swift b/RiotSwiftUI/Modules/Room/VoiceBroadcastPlayback/Coordinator/VoiceBroadcastPlaybackCoordinator.swift
index 4184f0d63..ee7b51e4e 100644
--- a/RiotSwiftUI/Modules/Room/VoiceBroadcastPlayback/Coordinator/VoiceBroadcastPlaybackCoordinator.swift
+++ b/RiotSwiftUI/Modules/Room/VoiceBroadcastPlayback/Coordinator/VoiceBroadcastPlaybackCoordinator.swift
@@ -48,7 +48,7 @@ final class VoiceBroadcastPlaybackCoordinator: Coordinator, Presentable {
let voiceBroadcastAggregator = try VoiceBroadcastAggregator(session: parameters.session, room: parameters.room, voiceBroadcastStartEventId: parameters.voiceBroadcastStartEvent.eventId, voiceBroadcastState: parameters.voiceBroadcastState)
- let details = VoiceBroadcastPlaybackDetails(senderDisplayName: parameters.senderDisplayName)
+ let details = VoiceBroadcastPlaybackDetails(senderDisplayName: parameters.senderDisplayName, avatarData: parameters.room.avatarData)
viewModel = VoiceBroadcastPlaybackViewModel(details: details,
mediaServiceProvider: VoiceMessageMediaServiceProvider.sharedProvider,
cacheManager: VoiceMessageAttachmentCacheManager.sharedManager,
@@ -61,7 +61,9 @@ final class VoiceBroadcastPlaybackCoordinator: Coordinator, Presentable {
func start() { }
func toPresentable() -> UIViewController {
- VectorHostingController(rootView: VoiceBroadcastPlaybackView(viewModel: viewModel.context))
+ let view = VoiceBroadcastPlaybackView(viewModel: viewModel.context)
+ .addDependency(AvatarService.instantiate(mediaManager: parameters.session.mediaManager))
+ return VectorHostingController(rootView: view)
}
func canEndVoiceBroadcast() -> Bool {
diff --git a/RiotSwiftUI/Modules/Room/VoiceBroadcastPlayback/Coordinator/VoiceBroadcastPlaybackProvider.swift b/RiotSwiftUI/Modules/Room/VoiceBroadcastPlayback/Coordinator/VoiceBroadcastPlaybackProvider.swift
index 5167a2364..7ca72c413 100644
--- a/RiotSwiftUI/Modules/Room/VoiceBroadcastPlayback/Coordinator/VoiceBroadcastPlaybackProvider.swift
+++ b/RiotSwiftUI/Modules/Room/VoiceBroadcastPlayback/Coordinator/VoiceBroadcastPlaybackProvider.swift
@@ -26,7 +26,7 @@ class VoiceBroadcastPlaybackProvider {
/// Create or retrieve the voiceBroadcast timeline coordinator for this event and return
/// a view to be displayed in the timeline
- func buildVoiceBroadcastPlaybackVCForEvent(_ event: MXEvent, senderDisplayName: String?) -> UIViewController? {
+ func buildVoiceBroadcastPlaybackVCForEvent(_ event: MXEvent, senderDisplayName: String?, voiceBroadcastState: String) -> UIViewController? {
guard let session = session, let room = session.room(withRoomId: event.roomId) else {
return nil
}
@@ -35,26 +35,10 @@ class VoiceBroadcastPlaybackProvider {
return coordinator.toPresentable()
}
- let dispatchGroup = DispatchGroup()
- dispatchGroup.enter()
- var voiceBroadcastState = VoiceBroadcastInfo.State.stopped
-
- room.state { roomState in
- if let stateEvent = roomState?.stateEvents(with: .custom(VoiceBroadcastSettings.voiceBroadcastInfoContentKeyType))?.last,
- stateEvent.stateKey == event.stateKey,
- let voiceBroadcastInfo = VoiceBroadcastInfo(fromJSON: stateEvent.content),
- (stateEvent.eventId == event.eventId || voiceBroadcastInfo.eventId == event.eventId),
- let state = VoiceBroadcastInfo.State(rawValue: voiceBroadcastInfo.state) {
- voiceBroadcastState = state
- }
-
- dispatchGroup.leave()
- }
-
let parameters = VoiceBroadcastPlaybackCoordinatorParameters(session: session,
room: room,
voiceBroadcastStartEvent: event,
- voiceBroadcastState: voiceBroadcastState,
+ voiceBroadcastState: VoiceBroadcastInfo.State(rawValue: voiceBroadcastState) ?? VoiceBroadcastInfo.State.stopped,
senderDisplayName: senderDisplayName)
guard let coordinator = try? VoiceBroadcastPlaybackCoordinator(parameters: parameters) else {
return nil
diff --git a/RiotSwiftUI/Modules/Room/VoiceBroadcastPlayback/View/VoiceBroadcastPlaybackView.swift b/RiotSwiftUI/Modules/Room/VoiceBroadcastPlayback/View/VoiceBroadcastPlaybackView.swift
index 04ade8a77..a16c83471 100644
--- a/RiotSwiftUI/Modules/Room/VoiceBroadcastPlayback/View/VoiceBroadcastPlaybackView.swift
+++ b/RiotSwiftUI/Modules/Room/VoiceBroadcastPlayback/View/VoiceBroadcastPlaybackView.swift
@@ -45,33 +45,49 @@ struct VoiceBroadcastPlaybackView: View {
var body: some View {
let details = viewModel.viewState.details
- VStack(alignment: .center, spacing: 16.0) {
+ VStack(alignment: .center) {
- HStack {
- Text(details.senderDisplayName ?? "")
- //Text(VectorL10n.voiceBroadcastInTimelineTitle)
- .font(theme.fonts.bodySB)
- .foregroundColor(theme.colors.primaryContent)
+ HStack (alignment: .top) {
+ AvatarImage(avatarData: viewModel.viewState.details.avatarData, size: .xSmall)
+
+ VStack(alignment: .leading, spacing: 0) {
+ Text(details.avatarData.displayName ?? details.avatarData.matrixItemId)
+ .font(theme.fonts.bodySB)
+ .foregroundColor(theme.colors.primaryContent)
+ Label {
+ Text(details.senderDisplayName ?? details.avatarData.matrixItemId)
+ .foregroundColor(theme.colors.secondaryContent)
+ .font(theme.fonts.caption1)
+ } icon: {
+ Image(uiImage: Asset.Images.voiceBroadcastTileMic.image)
+ }
+ Label {
+ Text(VectorL10n.voiceBroadcastTile)
+ .foregroundColor(theme.colors.secondaryContent)
+ .font(theme.fonts.caption1)
+ } icon: {
+ Image(uiImage: Asset.Images.voiceBroadcastTileLive.image)
+ }
+ }.frame(maxWidth: .infinity, alignment: .leading)
if viewModel.viewState.broadcastState == .live {
Button { viewModel.send(viewAction: .playLive) } label:
{
- HStack {
- Image(uiImage: Asset.Images.voiceBroadcastLive.image)
- .renderingMode(.original)
- Text("Live")
- .font(theme.fonts.bodySB)
+ Label {
+ Text(VectorL10n.voiceBroadcastLive)
+ .font(theme.fonts.caption1SB)
.foregroundColor(Color.white)
+ } icon: {
+ Image(uiImage: Asset.Images.voiceBroadcastLive.image)
}
-
}
- .padding(5.0)
- .background(RoundedRectangle(cornerRadius: 4, style: .continuous)
- .fill(backgroundColor))
+ .padding(.horizontal, 5)
+ .background(RoundedRectangle(cornerRadius: 4, style: .continuous).fill(backgroundColor))
.accessibilityIdentifier("liveButton")
}
}
-
+ .frame(maxWidth: .infinity, alignment: .leading)
+
if viewModel.viewState.playbackState == .error {
VoiceBroadcastPlaybackErrorView()
} else {
@@ -101,13 +117,9 @@ struct VoiceBroadcastPlaybackView: View {
}
.activityIndicator(show: viewModel.viewState.playbackState == .buffering)
}
-
}
.padding([.horizontal, .top], 2.0)
.padding([.bottom])
- .alert(item: $viewModel.alertInfo) { info in
- info.alert
- }
}
}
diff --git a/RiotSwiftUI/Modules/Room/VoiceBroadcastPlayback/VoiceBroadcastPlaybackModels.swift b/RiotSwiftUI/Modules/Room/VoiceBroadcastPlayback/VoiceBroadcastPlaybackModels.swift
index 09a12b87d..3fed0075f 100644
--- a/RiotSwiftUI/Modules/Room/VoiceBroadcastPlayback/VoiceBroadcastPlaybackModels.swift
+++ b/RiotSwiftUI/Modules/Room/VoiceBroadcastPlayback/VoiceBroadcastPlaybackModels.swift
@@ -34,6 +34,7 @@ enum VoiceBroadcastPlaybackState {
struct VoiceBroadcastPlaybackDetails {
let senderDisplayName: String?
+ let avatarData: AvatarInputProtocol
}
enum VoiceBroadcastState {
@@ -51,12 +52,5 @@ struct VoiceBroadcastPlaybackViewState: BindableState {
}
struct VoiceBroadcastPlaybackViewStateBindings {
- // TODO: Neeeded?
- var alertInfo: AlertInfo?
-}
-
-enum VoiceBroadcastPlaybackAlertType {
- // TODO: What is it?
- case failedClosingVoiceBroadcast
}
diff --git a/RiotSwiftUI/Modules/Room/VoiceBroadcastPlayback/VoiceBroadcastPlaybackScreenState.swift b/RiotSwiftUI/Modules/Room/VoiceBroadcastPlayback/VoiceBroadcastPlaybackScreenState.swift
index 72a15185f..f4fabadb1 100644
--- a/RiotSwiftUI/Modules/Room/VoiceBroadcastPlayback/VoiceBroadcastPlaybackScreenState.swift
+++ b/RiotSwiftUI/Modules/Room/VoiceBroadcastPlayback/VoiceBroadcastPlaybackScreenState.swift
@@ -42,7 +42,7 @@ enum MockVoiceBroadcastPlaybackScreenState: MockScreenState, CaseIterable {
/// Generate the view struct for the screen state.
var screenView: ([Any], AnyView) {
- let details = VoiceBroadcastPlaybackDetails(senderDisplayName: "Alice")
+ let details = VoiceBroadcastPlaybackDetails(senderDisplayName: "Alice", avatarData: AvatarInput(mxContentUri: "", matrixItemId: "!fakeroomid:matrix.org", displayName: "The name of the room"))
let viewModel = MockVoiceBroadcastPlaybackViewModel(initialViewState: VoiceBroadcastPlaybackViewState(details: details, broadcastState: .live, playbackState: .stopped, bindings: VoiceBroadcastPlaybackViewStateBindings()))
return (
diff --git a/RiotSwiftUI/Modules/Room/VoiceBroadcastRecorder/Coordinator/VoiceBroadcastRecorderCoordinator.swift b/RiotSwiftUI/Modules/Room/VoiceBroadcastRecorder/Coordinator/VoiceBroadcastRecorderCoordinator.swift
index c13524e13..e5e0afe3c 100644
--- a/RiotSwiftUI/Modules/Room/VoiceBroadcastRecorder/Coordinator/VoiceBroadcastRecorderCoordinator.swift
+++ b/RiotSwiftUI/Modules/Room/VoiceBroadcastRecorder/Coordinator/VoiceBroadcastRecorderCoordinator.swift
@@ -45,7 +45,7 @@ final class VoiceBroadcastRecorderCoordinator: Coordinator, Presentable {
voiceBroadcastRecorderService = VoiceBroadcastRecorderService(session: parameters.session, roomId: parameters.room.matrixItemId)
- let details = VoiceBroadcastRecorderDetails(senderDisplayName: parameters.senderDisplayName)
+ let details = VoiceBroadcastRecorderDetails(senderDisplayName: parameters.senderDisplayName, avatarData: parameters.room.avatarData)
let viewModel = VoiceBroadcastRecorderViewModel(details: details,
recorderService: voiceBroadcastRecorderService)
voiceBroadcastRecorderViewModel = viewModel
@@ -56,7 +56,9 @@ final class VoiceBroadcastRecorderCoordinator: Coordinator, Presentable {
func start() { }
func toPresentable() -> UIViewController {
- VectorHostingController(rootView: VoiceBroadcastRecorderView(viewModel: voiceBroadcastRecorderViewModel.context))
+ let view = VoiceBroadcastRecorderView(viewModel: voiceBroadcastRecorderViewModel.context)
+ .addDependency(AvatarService.instantiate(mediaManager: parameters.session.mediaManager))
+ return VectorHostingController(rootView: view)
}
func pauseRecording() {
diff --git a/RiotSwiftUI/Modules/Room/VoiceBroadcastRecorder/Service/MatrixSDK/VoiceBroadcastRecorderService.swift b/RiotSwiftUI/Modules/Room/VoiceBroadcastRecorder/Service/MatrixSDK/VoiceBroadcastRecorderService.swift
index d75f69830..0ad1fa682 100644
--- a/RiotSwiftUI/Modules/Room/VoiceBroadcastRecorder/Service/MatrixSDK/VoiceBroadcastRecorderService.swift
+++ b/RiotSwiftUI/Modules/Room/VoiceBroadcastRecorder/Service/MatrixSDK/VoiceBroadcastRecorderService.swift
@@ -33,7 +33,7 @@ class VoiceBroadcastRecorderService: VoiceBroadcastRecorderServiceProtocol {
private var chunkFile: AVAudioFile! = nil
private var chunkFrames: AVAudioFrameCount = 0
- private var chunkFileNumber: Int = 1
+ private var chunkFileNumber: Int = 0
// MARK: Public
@@ -63,14 +63,16 @@ class VoiceBroadcastRecorderService: VoiceBroadcastRecorderServiceProtocol {
}
try? audioEngine.start()
+
+ // Disable the sleep mode during the recording until we are able to handle it
+ UIApplication.shared.isIdleTimerDisabled = true
}
func stopRecordingVoiceBroadcast() {
MXLog.debug("[VoiceBroadcastRecorderService] Stop recording voice broadcast")
audioEngine.stop()
audioEngine.inputNode.removeTap(onBus: audioNodeBus)
-
- resetValues()
+ UIApplication.shared.isIdleTimerDisabled = false
voiceBroadcastService?.stopVoiceBroadcast(success: { [weak self] _ in
MXLog.debug("[VoiceBroadcastRecorderService] Stopped")
@@ -82,25 +84,33 @@ class VoiceBroadcastRecorderService: VoiceBroadcastRecorderServiceProtocol {
// Send current chunk
if self.chunkFile != nil {
- self.sendChunkFile(at: self.chunkFile.url, sequence: self.chunkFileNumber)
+ self.sendChunkFile(at: self.chunkFile.url, sequence: self.chunkFileNumber) {
+ self.tearDownVoiceBroadcastService()
+ }
+ } else {
+ self.tearDownVoiceBroadcastService()
}
-
- self.session.tearDownVoiceBroadcastService()
}, failure: { error in
MXLog.error("[VoiceBroadcastRecorderService] Failed to stop voice broadcast", context: error)
+ // Discard the service on VoiceBroadcastService error. We keep the service in case of other error type
+ if error as? VoiceBroadcastServiceError != nil {
+ self.tearDownVoiceBroadcastService()
+ }
})
}
func pauseRecordingVoiceBroadcast() {
audioEngine.pause()
+ UIApplication.shared.isIdleTimerDisabled = false
voiceBroadcastService?.pauseVoiceBroadcast(success: { [weak self] _ in
guard let self = self else { return }
// Send current chunk
- self.sendChunkFile(at: self.chunkFile.url, sequence: self.chunkFileNumber)
- self.chunkFile = nil
-
+ if self.chunkFile != nil {
+ self.sendChunkFile(at: self.chunkFile.url, sequence: self.chunkFileNumber)
+ self.chunkFile = nil
+ }
}, failure: { error in
MXLog.error("[VoiceBroadcastRecorderService] Failed to pause voice broadcast", context: error)
})
@@ -113,7 +123,8 @@ class VoiceBroadcastRecorderService: VoiceBroadcastRecorderServiceProtocol {
guard let self = self else { return }
// Update state
- self.serviceDelegate?.voiceBroadcastRecorderService(self, didUpdateState: .started)
+ self.serviceDelegate?.voiceBroadcastRecorderService(self, didUpdateState: .resumed)
+ UIApplication.shared.isIdleTimerDisabled = true
}, failure: { error in
MXLog.error("[VoiceBroadcastRecorderService] Failed to resume voice broadcast", context: error)
})
@@ -123,7 +134,13 @@ class VoiceBroadcastRecorderService: VoiceBroadcastRecorderServiceProtocol {
/// Reset chunk values.
private func resetValues() {
chunkFrames = 0
- chunkFileNumber = 1
+ chunkFileNumber = 0
+ }
+
+ /// Release the service
+ private func tearDownVoiceBroadcastService() {
+ resetValues()
+ session.tearDownVoiceBroadcastService()
}
/// Write audio buffer to chunk file.
@@ -150,6 +167,7 @@ class VoiceBroadcastRecorderService: VoiceBroadcastRecorderServiceProtocol {
// FIXME: Manage error
return
}
+ chunkFileNumber += 1
let temporaryFileName = "VoiceBroadcastChunk-\(roomId)-\(chunkFileNumber)"
let fileUrl = directory
.appendingPathComponent(temporaryFileName)
@@ -165,18 +183,20 @@ class VoiceBroadcastRecorderService: VoiceBroadcastRecorderServiceProtocol {
chunkFile = try? AVAudioFile(forWriting: fileUrl, settings: settings)
if chunkFile != nil {
- chunkFileNumber += 1
chunkFrames = 0
} else {
+ chunkFileNumber -= 1
stopRecordingVoiceBroadcast()
// FIXME: Manage error ?
}
}
/// Send chunk file to the server.
- private func sendChunkFile(at url: URL, sequence: Int) {
- guard let voiceBroadcastService = voiceBroadcastService else {
+ private func sendChunkFile(at url: URL, sequence: Int, completion: (() -> Void)? = nil) {
+ guard voiceBroadcastService != nil else {
// FIXME: Manage error
+ MXLog.debug("[VoiceBroadcastRecorderService] sendChunkFile: service is not available")
+ completion?()
return
}
@@ -200,21 +220,29 @@ class VoiceBroadcastRecorderService: VoiceBroadcastRecorderServiceProtocol {
}
convertAACToM4A(at: url) { [weak self] convertedUrl in
- guard let self = self else { return }
+ guard let self = self else {
+ completion?()
+ return
+ }
+
+ // Delete the source file.
+ self.deleteRecording(at: url)
if let convertedUrl = convertedUrl {
dispatchGroup.notify(queue: .main) {
self.voiceBroadcastService?.sendChunkOfVoiceBroadcast(audioFileLocalURL: convertedUrl,
mimeType: "audio/mp4",
duration: UInt(duration * 1000),
- samples: nil,
sequence: UInt(sequence)) { eventId in
MXLog.debug("[VoiceBroadcastRecorderService] Send voice broadcast chunk with success.")
- if eventId != nil {
- self.deleteRecording(at: url)
- }
+ self.deleteRecording(at: convertedUrl)
+ completion?()
} failure: { error in
MXLog.error("[VoiceBroadcastRecorderService] Failed to send voice broadcast chunk.", context: error)
+ // Do not delete the file to be sent if request failed, the retry flow will need it
+ // There's no manual mechanism to clean it up afterwards but the tmp folder
+ // they live in will eventually be deleted by the system
+ completion?()
}
}
}
diff --git a/RiotSwiftUI/Modules/Room/VoiceBroadcastRecorder/View/VoiceBroadcastRecorderView.swift b/RiotSwiftUI/Modules/Room/VoiceBroadcastRecorder/View/VoiceBroadcastRecorderView.swift
index 71fb41cc1..411ce0333 100644
--- a/RiotSwiftUI/Modules/Room/VoiceBroadcastRecorder/View/VoiceBroadcastRecorderView.swift
+++ b/RiotSwiftUI/Modules/Room/VoiceBroadcastRecorder/View/VoiceBroadcastRecorderView.swift
@@ -23,6 +23,13 @@ struct VoiceBroadcastRecorderView: View {
@Environment(\.theme) private var theme: ThemeSwiftUI
+ private var backgroundColor: Color {
+ if viewModel.viewState.recordingState != .paused {
+ return theme.colors.alert
+ }
+ return theme.colors.quarterlyContent
+ }
+
// MARK: Public
@ObservedObject var viewModel: VoiceBroadcastRecorderViewModel.Context
@@ -30,10 +37,35 @@ struct VoiceBroadcastRecorderView: View {
var body: some View {
let details = viewModel.viewState.details
- VStack(alignment: .leading, spacing: 16.0) {
- Text(details.senderDisplayName ?? "")
- .font(theme.fonts.bodySB)
- .foregroundColor(theme.colors.primaryContent)
+ VStack(alignment: .center) {
+
+ HStack(alignment: .top) {
+ AvatarImage(avatarData: viewModel.viewState.details.avatarData, size: .xSmall)
+
+ VStack(alignment: .leading, spacing: 0) {
+ Text(details.avatarData.displayName ?? details.avatarData.matrixItemId)
+ .font(theme.fonts.bodySB)
+ .foregroundColor(theme.colors.primaryContent)
+ Label {
+ Text(VectorL10n.voiceBroadcastTile)
+ .foregroundColor(theme.colors.secondaryContent)
+ .font(theme.fonts.caption1)
+ } icon: {
+ Image(uiImage: Asset.Images.voiceBroadcastTileLive.image)
+ }
+ }.frame(maxWidth: .infinity, alignment: .leading)
+
+ Label {
+ Text(VectorL10n.voiceBroadcastLive)
+ .font(theme.fonts.caption1SB)
+ .foregroundColor(Color.white)
+ } icon: {
+ Image(uiImage: Asset.Images.voiceBroadcastLive.image)
+ }
+ .padding(.horizontal, 5)
+ .background(RoundedRectangle(cornerRadius: 4, style: .continuous).fill(backgroundColor))
+ .accessibilityIdentifier("liveButton")
+ }
HStack(alignment: .top, spacing: 16.0) {
Button {
diff --git a/RiotSwiftUI/Modules/Room/VoiceBroadcastRecorder/VoiceBroadcastRecorderModels.swift b/RiotSwiftUI/Modules/Room/VoiceBroadcastRecorder/VoiceBroadcastRecorderModels.swift
index b88021bfe..7a2566aad 100644
--- a/RiotSwiftUI/Modules/Room/VoiceBroadcastRecorder/VoiceBroadcastRecorderModels.swift
+++ b/RiotSwiftUI/Modules/Room/VoiceBroadcastRecorder/VoiceBroadcastRecorderModels.swift
@@ -32,6 +32,7 @@ enum VoiceBroadcastRecorderState {
struct VoiceBroadcastRecorderDetails {
let senderDisplayName: String?
+ let avatarData: AvatarInputProtocol
}
struct VoiceBroadcastRecorderViewState: BindableState {
diff --git a/RiotSwiftUI/Modules/Room/VoiceBroadcastRecorder/VoiceBroadcastRecorderScreenState.swift b/RiotSwiftUI/Modules/Room/VoiceBroadcastRecorder/VoiceBroadcastRecorderScreenState.swift
index baa9488f4..bc915d36a 100644
--- a/RiotSwiftUI/Modules/Room/VoiceBroadcastRecorder/VoiceBroadcastRecorderScreenState.swift
+++ b/RiotSwiftUI/Modules/Room/VoiceBroadcastRecorder/VoiceBroadcastRecorderScreenState.swift
@@ -31,7 +31,7 @@ enum MockVoiceBroadcastRecorderScreenState: MockScreenState, CaseIterable {
}
var screenView: ([Any], AnyView) {
- let details = VoiceBroadcastRecorderDetails(senderDisplayName: "")
+ let details = VoiceBroadcastRecorderDetails(senderDisplayName: "", avatarData: AvatarInput(mxContentUri: "", matrixItemId: "!fakeroomid:matrix.org", displayName: "The name of the room"))
let viewModel = MockVoiceBroadcastRecorderViewModel(initialViewState: VoiceBroadcastRecorderViewState(details: details, recordingState: .started, bindings: VoiceBroadcastRecorderViewStateBindings()))
return (
diff --git a/changelog.d/6980.change b/changelog.d/6980.change
new file mode 100644
index 000000000..88d3df0f9
--- /dev/null
+++ b/changelog.d/6980.change
@@ -0,0 +1 @@
+Labs: Rich text-editor - Add support for plain text mode