diff --git a/Riot/Assets/Images.xcassets/VoiceBroadcast/Contents.json b/Riot/Assets/Images.xcassets/VoiceBroadcast/Contents.json new file mode 100644 index 000000000..73c00596a --- /dev/null +++ b/Riot/Assets/Images.xcassets/VoiceBroadcast/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Riot/Assets/Images.xcassets/VoiceBroadcast/voice_broadcast_pause.imageset/Contents.json b/Riot/Assets/Images.xcassets/VoiceBroadcast/voice_broadcast_pause.imageset/Contents.json new file mode 100644 index 000000000..4f275b2b0 --- /dev/null +++ b/Riot/Assets/Images.xcassets/VoiceBroadcast/voice_broadcast_pause.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "voice_broadcast_pause.svg", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Riot/Assets/Images.xcassets/VoiceBroadcast/voice_broadcast_pause.imageset/voice_broadcast_pause.svg b/Riot/Assets/Images.xcassets/VoiceBroadcast/voice_broadcast_pause.imageset/voice_broadcast_pause.svg new file mode 100644 index 000000000..babd78716 --- /dev/null +++ b/Riot/Assets/Images.xcassets/VoiceBroadcast/voice_broadcast_pause.imageset/voice_broadcast_pause.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/Riot/Assets/Images.xcassets/VoiceBroadcast/voice_broadcast_play.imageset/Contents.json b/Riot/Assets/Images.xcassets/VoiceBroadcast/voice_broadcast_play.imageset/Contents.json new file mode 100644 index 000000000..6302334b3 --- /dev/null +++ b/Riot/Assets/Images.xcassets/VoiceBroadcast/voice_broadcast_play.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "voice_broadcast_play.svg", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Riot/Assets/Images.xcassets/VoiceBroadcast/voice_broadcast_play.imageset/voice_broadcast_play.svg b/Riot/Assets/Images.xcassets/VoiceBroadcast/voice_broadcast_play.imageset/voice_broadcast_play.svg new file mode 100644 index 000000000..65849ae58 --- /dev/null +++ b/Riot/Assets/Images.xcassets/VoiceBroadcast/voice_broadcast_play.imageset/voice_broadcast_play.svg @@ -0,0 +1,4 @@ + + + + diff --git a/Riot/Generated/Images.swift b/Riot/Generated/Images.swift index 3cc10eb2e..72652d1c8 100644 --- a/Riot/Generated/Images.swift +++ b/Riot/Generated/Images.swift @@ -335,6 +335,8 @@ internal class Asset: NSObject { internal static let tabHome = ImageAsset(name: "tab_home") internal static let tabPeople = ImageAsset(name: "tab_people") internal static let tabRooms = ImageAsset(name: "tab_rooms") + internal static let voiceBroadcastPause = ImageAsset(name: "voice_broadcast_pause") + internal static let voiceBroadcastPlay = ImageAsset(name: "voice_broadcast_play") internal static let launchScreenLogo = ImageAsset(name: "launch_screen_logo") } @objcMembers diff --git a/RiotSwiftUI/Modules/Common/Mock/MockAppScreens.swift b/RiotSwiftUI/Modules/Common/Mock/MockAppScreens.swift index 8beba56a5..7c65792c3 100644 --- a/RiotSwiftUI/Modules/Common/Mock/MockAppScreens.swift +++ b/RiotSwiftUI/Modules/Common/Mock/MockAppScreens.swift @@ -70,6 +70,7 @@ enum MockAppScreens { MockTemplateRoomChatScreenState.self, MockSpaceSelectorScreenState.self, MockComposerScreenState.self, - MockComposerCreateActionListScreenState.self + MockComposerCreateActionListScreenState.self, + MockTimelineVoiceBroadcastScreenState.self ] } diff --git a/RiotSwiftUI/Modules/Room/TimelineVoiceBroadcast/Coordinator/TimelineVoiceBroadcastCoordinator.swift b/RiotSwiftUI/Modules/Room/TimelineVoiceBroadcast/Coordinator/TimelineVoiceBroadcastCoordinator.swift index 65c618860..30adcfd15 100644 --- a/RiotSwiftUI/Modules/Room/TimelineVoiceBroadcast/Coordinator/TimelineVoiceBroadcastCoordinator.swift +++ b/RiotSwiftUI/Modules/Room/TimelineVoiceBroadcast/Coordinator/TimelineVoiceBroadcastCoordinator.swift @@ -51,8 +51,16 @@ final class TimelineVoiceBroadcastCoordinator: Coordinator, Presentable, VoiceBr viewModel = TimelineVoiceBroadcastViewModel(timelineVoiceBroadcastDetails: buildTimelineVoiceBroadcastFrom(voiceBroadcastAggregator.voiceBroadcast)) - // TODO: manage voicebroacast chunks - viewModel.completion = { } + viewModel.completion = { [weak self] result in + guard let self = self else { return } + + switch result { + case .played: + MXLog.debug("click on play") + case .paused: + MXLog.debug("click on pause") + } + } } diff --git a/RiotSwiftUI/Modules/Room/TimelineVoiceBroadcast/Service/VoiceBroadcastChunk.swift b/RiotSwiftUI/Modules/Room/TimelineVoiceBroadcast/Service/VoiceBroadcastChunk.swift new file mode 100644 index 000000000..a464997d1 --- /dev/null +++ b/RiotSwiftUI/Modules/Room/TimelineVoiceBroadcast/Service/VoiceBroadcastChunk.swift @@ -0,0 +1,36 @@ +// +// 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 + +/// Represents user live location +struct VoiceBroadcastChunk { + var userId: String { + avatarData.matrixItemId + } + + var displayName: String { + avatarData.displayName ?? userId + } + + let avatarData: AvatarInputProtocol + + /// Chunk sequence number + let sequence: UInt + + // TODO: add attachment here + let attachment: NSObject +} diff --git a/RiotSwiftUI/Modules/Room/TimelineVoiceBroadcast/Service/VoiceBroadcastPlaybackServiceProtocol.swift b/RiotSwiftUI/Modules/Room/TimelineVoiceBroadcast/Service/VoiceBroadcastPlaybackServiceProtocol.swift new file mode 100644 index 000000000..6d4ef33e9 --- /dev/null +++ b/RiotSwiftUI/Modules/Room/TimelineVoiceBroadcast/Service/VoiceBroadcastPlaybackServiceProtocol.swift @@ -0,0 +1,31 @@ +// +// 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 Combine +import CoreLocation +import Foundation + +protocol VoiceBroadcastPlaybackServiceProtocol { + /// All shared voice broadcast chunks + var voiceBroadcastChunks: [VoiceBroadcastChunk] { get } + + /// Called when voice broadcast chunks are updated. + var didUpdateVoiceBroadcastChunks: (([VoiceBroadcastChunk]) -> Void)? { get set } + + func startPlayingVoiceBroadcast() + + func pausePlayingVoiceBroadcast() +} diff --git a/RiotSwiftUI/Modules/Room/TimelineVoiceBroadcast/TimelineVoiceBroadcastModels.swift b/RiotSwiftUI/Modules/Room/TimelineVoiceBroadcast/TimelineVoiceBroadcastModels.swift index f11cca32b..b29b1bc7c 100644 --- a/RiotSwiftUI/Modules/Room/TimelineVoiceBroadcast/TimelineVoiceBroadcastModels.swift +++ b/RiotSwiftUI/Modules/Room/TimelineVoiceBroadcast/TimelineVoiceBroadcastModels.swift @@ -17,10 +17,16 @@ import Foundation import SwiftUI -typealias TimelineVoiceBroadcastViewModelCallback = () -> Void - // TODO: add play pause cases -enum TimelineVoiceBroadcastViewAction { } +enum TimelineVoiceBroadcastViewAction { + case play + case pause +} + +enum TimelineVoiceBroadcastViewModelResult { + case played + case paused +} enum TimelineVoiceBroadcastType { case disclosed diff --git a/RiotSwiftUI/Modules/Room/TimelineVoiceBroadcast/TimelineVoiceBroadcastScreenState.swift b/RiotSwiftUI/Modules/Room/TimelineVoiceBroadcast/TimelineVoiceBroadcastScreenState.swift new file mode 100644 index 000000000..f2ddaf4b3 --- /dev/null +++ b/RiotSwiftUI/Modules/Room/TimelineVoiceBroadcast/TimelineVoiceBroadcastScreenState.swift @@ -0,0 +1,49 @@ +// +// 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 +import SwiftUI + +/// Using an enum for the screen allows you define the different state cases with +/// the relevant associated data for each case. +enum MockTimelineVoiceBroadcastScreenState: MockScreenState, CaseIterable { + // A case for each state you want to represent + // with specific, minimal associated data that will allow you + // mock that screen. + case animated + + /// The associated screen + var screenType: Any.Type { + TimelineVoiceBroadcastView.self + } + + /// A list of screen state definitions + static var allCases: [MockTimelineVoiceBroadcastScreenState] { + [.animated] + } + + /// Generate the view struct for the screen state. + var screenView: ([Any], AnyView) { + let voiceBroadcast = TimelineVoiceBroadcastDetails(closed: false, type: TimelineVoiceBroadcastType.disclosed) + + let viewModel = TimelineVoiceBroadcastViewModel(timelineVoiceBroadcastDetails: voiceBroadcast) + + return ( + [false, viewModel], + AnyView(TimelineVoiceBroadcastView(viewModel: viewModel.context)) + ) + } +} diff --git a/RiotSwiftUI/Modules/Room/TimelineVoiceBroadcast/TimelineVoiceBroadcastViewModel.swift b/RiotSwiftUI/Modules/Room/TimelineVoiceBroadcast/TimelineVoiceBroadcastViewModel.swift index dd546cfcc..129436fe6 100644 --- a/RiotSwiftUI/Modules/Room/TimelineVoiceBroadcast/TimelineVoiceBroadcastViewModel.swift +++ b/RiotSwiftUI/Modules/Room/TimelineVoiceBroadcast/TimelineVoiceBroadcastViewModel.swift @@ -20,13 +20,14 @@ import SwiftUI typealias TimelineVoiceBroadcastViewModelType = StateStoreViewModel class TimelineVoiceBroadcastViewModel: TimelineVoiceBroadcastViewModelType, TimelineVoiceBroadcastViewModelProtocol { + // MARK: - Properties // MARK: Private // MARK: Public - var completion: TimelineVoiceBroadcastViewModelCallback? + var completion: ((TimelineVoiceBroadcastViewModelResult) -> Void)? // MARK: - Setup @@ -37,7 +38,22 @@ class TimelineVoiceBroadcastViewModel: TimelineVoiceBroadcastViewModelType, Time // MARK: - Public override func process(viewAction: TimelineVoiceBroadcastViewAction) { - // TODO: add some actions as play pause + switch viewAction { + case .play: + play() + case .pause: + pause() + } + } + + /// Listen voice broadcast + private func play() { + completion?(.played) + } + + /// Stop voice broadcast + private func pause() { + completion?(.paused) } // MARK: - TimelineVoiceBroadcastViewModelProtocol diff --git a/RiotSwiftUI/Modules/Room/TimelineVoiceBroadcast/TimelineVoiceBroadcastViewModelProtocol.swift b/RiotSwiftUI/Modules/Room/TimelineVoiceBroadcast/TimelineVoiceBroadcastViewModelProtocol.swift index 80d44c211..014c7208c 100644 --- a/RiotSwiftUI/Modules/Room/TimelineVoiceBroadcast/TimelineVoiceBroadcastViewModelProtocol.swift +++ b/RiotSwiftUI/Modules/Room/TimelineVoiceBroadcast/TimelineVoiceBroadcastViewModelProtocol.swift @@ -18,7 +18,7 @@ import Foundation protocol TimelineVoiceBroadcastViewModelProtocol { var context: TimelineVoiceBroadcastViewModelType.Context { get } - var completion: (() -> Void)? { get set } + var completion: ((TimelineVoiceBroadcastViewModelResult) -> Void)? { get set } func updateWithVoiceBroadcastDetails(_ voiceBroadcastDetails: TimelineVoiceBroadcastDetails) } diff --git a/RiotSwiftUI/Modules/Room/TimelineVoiceBroadcast/View/TimelineVoiceBroadcastView.swift b/RiotSwiftUI/Modules/Room/TimelineVoiceBroadcast/View/TimelineVoiceBroadcastView.swift index 5235677dc..ececa70a6 100644 --- a/RiotSwiftUI/Modules/Room/TimelineVoiceBroadcast/View/TimelineVoiceBroadcastView.swift +++ b/RiotSwiftUI/Modules/Room/TimelineVoiceBroadcast/View/TimelineVoiceBroadcastView.swift @@ -34,9 +34,25 @@ struct TimelineVoiceBroadcastView: View { Text(VectorL10n.voiceBroadcastInTimelineTitle) .font(theme.fonts.bodySB) .foregroundColor(theme.colors.primaryContent) - Text(VectorL10n.voiceBroadcastInTimelineBody) - .font(theme.fonts.body) - .foregroundColor(theme.colors.primaryContent) +// Text(VectorL10n.voiceBroadcastInTimelineBody) +// .font(theme.fonts.body) +// .foregroundColor(theme.colors.primaryContent) + + HStack(alignment: .top, spacing: 16.0) { + Button { viewModel.send(viewAction: .play) } label: { + Image("voice_broadcast_play") + .renderingMode(.original) + } + .accessibilityIdentifier("playButton") + + Button { viewModel.send(viewAction: .pause) } label: { + Image("voice_broadcast_pause") + .renderingMode(.original) + } + .accessibilityIdentifier("pauseButton") + + } + } .padding([.horizontal, .top], 2.0) .padding([.bottom]) @@ -48,4 +64,9 @@ struct TimelineVoiceBroadcastView: View { // MARK: - Previews -// TODO: Add Voice broadcast preview +struct TimelineVoiceBroadcastView_Previews: PreviewProvider { + static let stateRenderer = MockTimelineVoiceBroadcastScreenState.stateRenderer + static var previews: some View { + stateRenderer.screenGroup() + } +}