From 524ac5e102c4eb3eeb6d4de0ff78dd40ee0c7298 Mon Sep 17 00:00:00 2001 From: Dennis Skokov Date: Wed, 8 Jan 2025 15:48:37 +0100 Subject: [PATCH] Media list view iOS --- .../android/src/main/res/values/strings.xml | 1 + example/ios/Demo.xcodeproj/project.pbxproj | 4 + .../ios/Demo/Base.lproj/Localizable.strings | 1 + example/ios/Demo/Base.lproj/Main.storyboard | 53 ++++++++++- .../FeaturesListViewController.swift | 2 +- .../Demo/Media/MediaListViewController.swift | 95 +++++++++++++++++++ .../viewmodel/media/MediaListViewModel.kt | 52 ++++++++++ .../shared/viewmodel/media/MediaViewModel.kt | 2 - 8 files changed, 205 insertions(+), 5 deletions(-) create mode 100644 example/ios/Demo/Media/MediaListViewController.swift create mode 100644 example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/media/MediaListViewModel.kt diff --git a/example/android/src/main/res/values/strings.xml b/example/android/src/main/res/values/strings.xml index e79193d7f..5399d1e14 100644 --- a/example/android/src/main/res/values/strings.xml +++ b/example/android/src/main/res/values/strings.xml @@ -11,6 +11,7 @@ Links Location Media + Sound Permissions System Beacons diff --git a/example/ios/Demo.xcodeproj/project.pbxproj b/example/ios/Demo.xcodeproj/project.pbxproj index d9ccc0084..576749c38 100644 --- a/example/ios/Demo.xcodeproj/project.pbxproj +++ b/example/ios/Demo.xcodeproj/project.pbxproj @@ -88,6 +88,7 @@ 93EC5B31E0ADDED3225B15C9 /* Color+Color.generated.swift in Sources */ = {isa = PBXBuildFile; fileRef = 12F49DA13243DC3767266475 /* Color+Color.generated.swift */; }; 9481F22DC3CFE170CE894584 /* KeyboardManager.generated.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2F34DFA21F534655B6FD3D03 /* KeyboardManager.generated.swift */; }; BC5100D52D2C3B2200095938 /* sound.mp3 in Resources */ = {isa = PBXBuildFile; fileRef = BC5100D42D2C3B2200095938 /* sound.mp3 */; }; + BC5100D92D2EB42700095938 /* MediaListViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = BC5100D82D2EB42700095938 /* MediaListViewController.swift */; }; C77C23B538A4DFB02C0E0773 /* LazyView.generated.swift in Sources */ = {isa = PBXBuildFile; fileRef = 095989173871C8CDA70C49E1 /* LazyView.generated.swift */; }; C9555693B015ADE12795997D /* NavigationBarColor.generated.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C9189D92DAF37F9815ECFDC /* NavigationBarColor.generated.swift */; }; D0F06CF9B138426F812F3B0B /* ShapeView.generated.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9730959B8EF5B66133F731D2 /* ShapeView.generated.swift */; }; @@ -221,6 +222,7 @@ A1AD210B87A46DBD057A7D28 /* Navigation.generated.swift */ = {isa = PBXFileReference; explicitFileType = sourcecode.swift; path = Navigation.generated.swift; sourceTree = ""; }; A8678F48E60D40E6AB06177C /* LifecycleViewModel.generated.swift */ = {isa = PBXFileReference; explicitFileType = sourcecode.swift; path = LifecycleViewModel.generated.swift; sourceTree = ""; }; BC5100D42D2C3B2200095938 /* sound.mp3 */ = {isa = PBXFileReference; lastKnownFileType = audio.mp3; path = sound.mp3; sourceTree = ""; }; + BC5100D82D2EB42700095938 /* MediaListViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MediaListViewController.swift; sourceTree = ""; }; CCF6BD3638095E5452785FF3 /* KalugaLabel+SwiftUI.generated.swift */ = {isa = PBXFileReference; explicitFileType = sourcecode.swift; path = "KalugaLabel+SwiftUI.generated.swift"; sourceTree = ""; }; D7A59766D89528BA5F4327BD /* Subject.generated.swift */ = {isa = PBXFileReference; explicitFileType = sourcecode.swift; path = Subject.generated.swift; sourceTree = ""; }; EA317627E5725424F6B7569C /* KalugaDate+Extensions.generated.swift */ = {isa = PBXFileReference; explicitFileType = sourcecode.swift; path = "KalugaDate+Extensions.generated.swift"; sourceTree = ""; }; @@ -265,6 +267,7 @@ children = ( BC5100D42D2C3B2200095938 /* sound.mp3 */, 2025CA2A29C9FC480037DF68 /* MediaViewController.swift */, + BC5100D82D2EB42700095938 /* MediaListViewController.swift */, ); path = Media; sourceTree = ""; @@ -721,6 +724,7 @@ buildActionMask = 2147483647; files = ( 20BB9E7729642FEB0096E836 /* PermissionViewController.swift in Sources */, + BC5100D92D2EB42700095938 /* MediaListViewController.swift in Sources */, 207AB7A72A74002E00B4A30F /* BluetoothListDeviceView.swift in Sources */, 205747572397C9C400CDE25E /* KeyboardManagerViewController.swift in Sources */, 74671D1F8F6A31F1DD3F7912 /* AppDelegate.swift in Sources */, diff --git a/example/ios/Demo/Base.lproj/Localizable.strings b/example/ios/Demo/Base.lproj/Localizable.strings index 2657dc327..a9568620b 100644 --- a/example/ios/Demo/Base.lproj/Localizable.strings +++ b/example/ios/Demo/Base.lproj/Localizable.strings @@ -9,6 +9,7 @@ "feature_keyboard" = "Keyboard"; "feature_location" = "Location"; "feature_media" = "Media"; +"feature_media_sound" = "Sound"; "feature_permissions" = "Permissions"; "feature_links" = "Links"; "feature_beacons" = "Beacons"; diff --git a/example/ios/Demo/Base.lproj/Main.storyboard b/example/ios/Demo/Base.lproj/Main.storyboard index 9d176c9c3..fbfc82f5a 100644 --- a/example/ios/Demo/Base.lproj/Main.storyboard +++ b/example/ios/Demo/Base.lproj/Main.storyboard @@ -392,7 +392,7 @@ - + @@ -1735,7 +1735,56 @@ - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/example/ios/Demo/FeaturesList/FeaturesListViewController.swift b/example/ios/Demo/FeaturesList/FeaturesListViewController.swift index 92e49d91a..2498890d1 100644 --- a/example/ios/Demo/FeaturesList/FeaturesListViewController.swift +++ b/example/ios/Demo/FeaturesList/FeaturesListViewController.swift @@ -124,7 +124,7 @@ private extension FeatureListNavigationAction { ) } case is FeatureListNavigationAction.Location: return NavigationSpec.Segue(identifier: "showLocation") - case is FeatureListNavigationAction.Media: return NavigationSpec.Segue(identifier: "showMedia") + case is FeatureListNavigationAction.Media: return NavigationSpec.Segue(identifier: "showMediaList") case is FeatureListNavigationAction.Permissions: return NavigationSpec.Segue(identifier: "showPermissions") case is FeatureListNavigationAction.PlatformSpecific: return NavigationSpec.Segue(identifier: "showPlatformSpecific") case is FeatureListNavigationAction.Resources: return NavigationSpec.Push(animated: true) { diff --git a/example/ios/Demo/Media/MediaListViewController.swift b/example/ios/Demo/Media/MediaListViewController.swift new file mode 100644 index 000000000..d24c0d74d --- /dev/null +++ b/example/ios/Demo/Media/MediaListViewController.swift @@ -0,0 +1,95 @@ +// +// Copyright 2025 Splendo Consulting B.V. The Netherlands +// +// 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 UIKit +import KalugaExampleShared + +class MediaListViewController: UITableViewController { + + private lazy var navigator: ViewControllerNavigator = ViewControllerNavigator(parentVC: self) { action in + NavigationSpec.Segue(identifier: action.segueKey) + } + + private lazy var viewModel = MediaListViewModel(navigator: navigator) + private var lifecycleManager: LifecycleManager! + + private var media = [String]() + private var onSelected: ((Int) -> Void)? + + deinit { + lifecycleManager.unbind() + } + + override func viewDidLoad() { + super.viewDidLoad() + + title = "feature_media".localized() + + lifecycleManager = viewModel.addLifecycleManager(parent: self) { [weak self] in + guard let viewModel = self?.viewModel else { return [] } + return [ + viewModel.media.observeInitialized { next in + let media = next ?? [] + self?.media = media.map { ($0 as? Media)?.title ?? "" } + self?.onSelected = { (index: Int) in + if let media = media[index] as? Media { + viewModel.onMediaSelected(media: media) + } + } + self?.tableView.reloadData() + } + ] + } + } + + override func numberOfSections(in tableView: UITableView) -> Int { + return 1 + } + + override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { + return media.count + } + + override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { + return tableView.dequeueTypedReusableCell(withIdentifier: MediaListCell.Const.identifier, for: indexPath) { (cell: MediaListCell) in + cell.label.text = media[indexPath.row] + } + } + + override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { + _ = onSelected?(indexPath.row) + tableView.deselectRow(at: indexPath, animated: true) + } +} + +class MediaListCell: UITableViewCell { + + enum Const { + static let identifier = "MediaListCell" + } + + @IBOutlet weak var label: UILabel! +} + +private extension MediaListNavigationAction { + var segueKey: String { + switch self { + case is MediaListNavigationAction.Media: return "showMedia" + case is MediaListNavigationAction.Sound: return "showSound" + default: return "" + } + } +} diff --git a/example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/media/MediaListViewModel.kt b/example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/media/MediaListViewModel.kt new file mode 100644 index 000000000..1b31f60a0 --- /dev/null +++ b/example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/media/MediaListViewModel.kt @@ -0,0 +1,52 @@ +/* + Copyright 2025 Splendo Consulting B.V. The Netherlands + + 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. + + */ + +package com.splendo.kaluga.example.shared.viewmodel.media + +import com.splendo.kaluga.architecture.navigation.NavigationAction +import com.splendo.kaluga.architecture.navigation.Navigator +import com.splendo.kaluga.architecture.observable.observableOf +import com.splendo.kaluga.architecture.viewmodel.NavigatingViewModel +import com.splendo.kaluga.resources.localized + +sealed class MediaListNavigationAction : NavigationAction(null) { + + data object Media : MediaListNavigationAction() + data object Sound : MediaListNavigationAction() +} + +enum class Media(private val titleKey: String) { + MEDIA("feature_media"), + SOUND("feature_media_sound"), + ; + + val title: String get() = titleKey.localized() +} + +class MediaListViewModel(navigator: Navigator) : NavigatingViewModel(navigator) { + + val media = observableOf(Media.values().toList()) + + fun onMediaSelected(media: Media) { + navigator.navigate( + when (media) { + Media.MEDIA -> MediaListNavigationAction.Media + Media.SOUND -> MediaListNavigationAction.Sound + }, + ) + } +} \ No newline at end of file diff --git a/example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/media/MediaViewModel.kt b/example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/media/MediaViewModel.kt index 94a0da840..2cbc49608 100644 --- a/example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/media/MediaViewModel.kt +++ b/example/shared/src/commonMain/kotlin/com/splendo/kaluga/example/shared/viewmodel/media/MediaViewModel.kt @@ -35,14 +35,12 @@ import com.splendo.kaluga.base.text.format import com.splendo.kaluga.example.shared.stylable.ButtonStyles import com.splendo.kaluga.media.BaseMediaManager import com.splendo.kaluga.media.DefaultMediaPlayer -import com.splendo.kaluga.media.DefaultSoundPlayer import com.splendo.kaluga.media.MediaPlayer import com.splendo.kaluga.media.MediaSource import com.splendo.kaluga.media.MediaSurfaceProvider import com.splendo.kaluga.media.PlaybackError import com.splendo.kaluga.media.PlaybackState import com.splendo.kaluga.media.Resolution -import com.splendo.kaluga.media.SoundPlayer import com.splendo.kaluga.media.duration import com.splendo.kaluga.media.isVideo import com.splendo.kaluga.media.mediaSourceFromUrl