diff --git a/iosApp/iosApp.xcodeproj/project.pbxproj b/iosApp/iosApp.xcodeproj/project.pbxproj index f0ade88..17c7785 100644 --- a/iosApp/iosApp.xcodeproj/project.pbxproj +++ b/iosApp/iosApp.xcodeproj/project.pbxproj @@ -207,8 +207,8 @@ INFOPLIST_FILE = iosApp/Info.plist; INFOPLIST_KEY_CFBundleDisplayName = "$(APP_NAME)"; INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; - INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; - INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; + INFOPLIST_KEY_UISupportedInterfaceOrientations = UIInterfaceOrientationPortrait; + INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown"; IPHONEOS_DEPLOYMENT_TARGET = 16.6; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", @@ -237,8 +237,8 @@ INFOPLIST_FILE = iosApp/Info.plist; INFOPLIST_KEY_CFBundleDisplayName = "$(APP_NAME)"; INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; - INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; - INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; + INFOPLIST_KEY_UISupportedInterfaceOrientations = UIInterfaceOrientationPortrait; + INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown"; IPHONEOS_DEPLOYMENT_TARGET = 16.6; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", diff --git a/iosApp/iosApp/Assets.xcassets/AccentColor.colorset/Contents.json b/iosApp/iosApp/Assets.xcassets/AccentColor.colorset/Contents.json index eb87897..e8e9f6d 100644 --- a/iosApp/iosApp/Assets.xcassets/AccentColor.colorset/Contents.json +++ b/iosApp/iosApp/Assets.xcassets/AccentColor.colorset/Contents.json @@ -1,6 +1,33 @@ { "colors" : [ { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0x8D", + "green" : "0x42", + "red" : "0x77" + } + }, + "idiom" : "universal" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0xBE", + "green" : "0x5A", + "red" : "0xA0" + } + }, "idiom" : "universal" } ], diff --git a/iosApp/iosApp/Assets.xcassets/AppIcon.appiconset/Contents.json b/iosApp/iosApp/Assets.xcassets/AppIcon.appiconset/Contents.json index 2305880..fcff12f 100644 --- a/iosApp/iosApp/Assets.xcassets/AppIcon.appiconset/Contents.json +++ b/iosApp/iosApp/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -1,28 +1,7 @@ { "images" : [ { - "idiom" : "universal", - "platform" : "ios", - "size" : "1024x1024" - }, - { - "appearances" : [ - { - "appearance" : "luminosity", - "value" : "dark" - } - ], - "idiom" : "universal", - "platform" : "ios", - "size" : "1024x1024" - }, - { - "appearances" : [ - { - "appearance" : "luminosity", - "value" : "tinted" - } - ], + "filename" : "blackcandy_logo.png", "idiom" : "universal", "platform" : "ios", "size" : "1024x1024" diff --git a/iosApp/iosApp/Assets.xcassets/AppIcon.appiconset/blackcandy_logo.png b/iosApp/iosApp/Assets.xcassets/AppIcon.appiconset/blackcandy_logo.png new file mode 100644 index 0000000..635d86d Binary files /dev/null and b/iosApp/iosApp/Assets.xcassets/AppIcon.appiconset/blackcandy_logo.png differ diff --git a/iosApp/iosApp/Utils/CustomStyle.swift b/iosApp/iosApp/Utils/CustomStyle.swift index 5223353..9223b67 100644 --- a/iosApp/iosApp/Utils/CustomStyle.swift +++ b/iosApp/iosApp/Utils/CustomStyle.swift @@ -36,7 +36,8 @@ struct CustomStyle { static let playerImageSize: CGFloat = 200 static let playerMaxWidth: CGFloat = 350 - static let sideBarPlayerHeight: CGFloat = 550 + static let playlistMaxHeight: CGFloat = 550 + static let loginFormMaxWidth: CGFloat = 600 static func spacing(_ spacing: Spacing) -> CGFloat { spacing.rawValue diff --git a/iosApp/iosApp/ViewControllers/MainViewController.swift b/iosApp/iosApp/ViewControllers/MainViewController.swift index 877eefa..ff08a2c 100644 --- a/iosApp/iosApp/ViewControllers/MainViewController.swift +++ b/iosApp/iosApp/ViewControllers/MainViewController.swift @@ -3,38 +3,18 @@ import HotwireNative import LNPopupUI import sharedKit -class MainViewController: UISplitViewController, UISplitViewControllerDelegate { +class MainViewController: HotwireTabBarController { private let musicServiceViewModel: MusicServiceViewModel = KoinHelper().getMusicServiceViewModel() init(serverAddress: String) { - super.init(style: .doubleColumn) + super.init() - preferredDisplayMode = .oneBesideSecondary - preferredSplitBehavior = .tile - presentsWithGesture = false - delegate = self + load(buildMainTabs(serverAddress: serverAddress)) - let tabBarController = HotwireTabBarController(navigatorDelegate: self) - let tabs = buildMainTabs(serverAddress: serverAddress) - - tabBarController.load(tabs) - tabBarController.presentPopupBar { + presentPopupBar { PlayerScreen() } - setViewController(tabBarController, for: .secondary) - setViewController(tabBarController, for: .compact) - musicServiceViewModel.setupMusicServiceController() } - - required init?(coder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } -} - -extension MainViewController: NavigatorDelegate { - func handle(proposal: HotwireNative.VisitProposal, from navigator: HotwireNative.Navigator) -> HotwireNative.ProposalResult { - return .accept - } } diff --git a/iosApp/iosApp/Views/LoginScreen.swift b/iosApp/iosApp/Views/LoginScreen.swift index 2113486..23e2baa 100644 --- a/iosApp/iosApp/Views/LoginScreen.swift +++ b/iosApp/iosApp/Views/LoginScreen.swift @@ -18,6 +18,7 @@ struct LoginScreen: View { viewModel.updateServerAddress(serverAddress: serverAddress) } ) + .frame(maxWidth: CustomStyle.loginFormMaxWidth) .navigationDestination(for: Route.self) { route in switch route { case .authentication: @@ -36,6 +37,7 @@ struct LoginScreen: View { viewModel.updatePassword(password: password) } ) + .frame(maxWidth: CustomStyle.loginFormMaxWidth) } } } diff --git a/iosApp/iosApp/Views/Player/FullPlayer.swift b/iosApp/iosApp/Views/Player/FullPlayer.swift index 8f3109d..4896c6f 100644 --- a/iosApp/iosApp/Views/Player/FullPlayer.swift +++ b/iosApp/iosApp/Views/Player/FullPlayer.swift @@ -2,6 +2,7 @@ import SwiftUI import sharedKit struct FullPlayer: View { + let isCompactMode: Bool let currentSong: Song? let currentPosition: Double let playbackMode: PlaybackMode @@ -11,14 +12,16 @@ struct FullPlayer: View { let onNextButtonClicked: (() -> Void) let onPlayButtonClicked: (() -> Void) let onPauseButtonClicked: (() -> Void) - let onPlaylistButtonClicked: (() -> Void) + let onPlaylistButtonClicked: (() -> Void)? let onModeSwitchButtonClicked: (() -> Void) let onFavoriteButtonClicked: (() -> Void) let onSeek: ((Double) -> Void) var body: some View { VStack { - Spacer() + if isCompactMode { + Spacer() + } PlayerArt(imageURL: currentSong?.albumImageUrl.large) .padding(.bottom, CustomStyle.spacing(.extraWide)) @@ -38,7 +41,9 @@ struct FullPlayer: View { ) .padding(.horizontal, CustomStyle.spacing(.large)) - Spacer() + if isCompactMode { + Spacer() + } PlayerActions( playbackMode: playbackMode, diff --git a/iosApp/iosApp/Views/Player/PlayerActions.swift b/iosApp/iosApp/Views/Player/PlayerActions.swift index b22f792..fd5da2a 100644 --- a/iosApp/iosApp/Views/Player/PlayerActions.swift +++ b/iosApp/iosApp/Views/Player/PlayerActions.swift @@ -6,7 +6,7 @@ struct PlayerActions: View { let isFavorited: Bool let onModeSwitchButtonClicked: (() -> Void) let onFavoriteButtonClicked: (() -> Void) - let onPlaylistButtonClicked: (() -> Void) + let onPlaylistButtonClicked: (() -> Void)? var body: some View { HStack { @@ -40,18 +40,20 @@ struct PlayerActions: View { ) .padding(CustomStyle.spacing(.narrow)) - Spacer() + if let onPlaylistButtonClicked { + Spacer() - Button( - action: { - onPlaylistButtonClicked() - }, - label: { - Image(systemName: "list.bullet") - } - ) - .padding(CustomStyle.spacing(.narrow)) - .cornerRadius(CustomStyle.cornerRadius(.medium)) + Button( + action: { + onPlaylistButtonClicked() + }, + label: { + Image(systemName: "list.bullet") + } + ) + .padding(CustomStyle.spacing(.narrow)) + .cornerRadius(CustomStyle.cornerRadius(.medium)) + } } } diff --git a/iosApp/iosApp/Views/PlayerScreen.swift b/iosApp/iosApp/Views/PlayerScreen.swift index 57f4838..8095b7b 100644 --- a/iosApp/iosApp/Views/PlayerScreen.swift +++ b/iosApp/iosApp/Views/PlayerScreen.swift @@ -5,6 +5,7 @@ import sharedKit struct PlayerScreen: View { private let viewModel: PlayerViewModel = KoinHelper().getPlayerViewModel() + @Environment(\.horizontalSizeClass) var horizontalSizeClass @State private var path = NavigationPath() @State private var albumImage: UIImage? @State private var currentSong: Song? @@ -12,25 +13,34 @@ struct PlayerScreen: View { var body: some View { Observing(viewModel.uiState) { uiState in - NavigationStack(path: $path) { - FullPlayer( - currentSong: uiState.musicState.currentSong, - currentPosition: uiState.currentPosition, - playbackMode: uiState.musicState.playbackMode, - isPlaying: uiState.musicState.isPlaying, - isLoading: uiState.musicState.isLoading, - onPreviousButtonClicked: { viewModel.previous() }, - onNextButtonClicked: { viewModel.next() }, - onPlayButtonClicked: { viewModel.play() }, - onPauseButtonClicked: { viewModel.pause() }, - onPlaylistButtonClicked: { path.append(Route.playlist) }, - onModeSwitchButtonClicked: { viewModel.nextMode() }, - onFavoriteButtonClicked: { viewModel.toggleFavorite() }, - onSeek: { viewModel.seekToRatio(ratio: $0) } - ) - .navigationDestination(for: Route.self) { route in - switch route { - case .playlist: + if horizontalSizeClass == .regular { + HStack(spacing: CustomStyle.spacing(.ultraWide)) { + FullPlayer( + isCompactMode: false, + currentSong: uiState.musicState.currentSong, + currentPosition: uiState.currentPosition, + playbackMode: uiState.musicState.playbackMode, + isPlaying: uiState.musicState.isPlaying, + isLoading: uiState.musicState.isLoading, + onPreviousButtonClicked: { viewModel.previous() }, + onNextButtonClicked: { viewModel.next() }, + onPlayButtonClicked: { viewModel.play() }, + onPauseButtonClicked: { viewModel.pause() }, + onPlaylistButtonClicked: nil, + onModeSwitchButtonClicked: { viewModel.nextMode() }, + onFavoriteButtonClicked: { viewModel.toggleFavorite() }, + onSeek: { viewModel.seekToRatio(ratio: $0) } + ) + + VStack { + HStack { + Text("label.tracks(\(uiState.musicState.playlist.count))") + Spacer() + EditButton() + } + .padding(CustomStyle.spacing(.medium)) + .cornerRadius(CustomStyle.cornerRadius(.large)) + PlayerPlaylist( playlist: uiState.musicState.playlist, currentSong: uiState.musicState.currentSong, @@ -38,6 +48,40 @@ struct PlayerScreen: View { onItemSweepToDismiss: { viewModel.removeSongFromPlaylist(songId: $0) }, onItemMoved: { from, to in viewModel.moveSongInPlaylist(from: Int32(from), to: Int32(to)) } ) + + } + + .frame(maxHeight: CustomStyle.playlistMaxHeight) + } + } else { + NavigationStack(path: $path) { + FullPlayer( + isCompactMode: true, + currentSong: uiState.musicState.currentSong, + currentPosition: uiState.currentPosition, + playbackMode: uiState.musicState.playbackMode, + isPlaying: uiState.musicState.isPlaying, + isLoading: uiState.musicState.isLoading, + onPreviousButtonClicked: { viewModel.previous() }, + onNextButtonClicked: { viewModel.next() }, + onPlayButtonClicked: { viewModel.play() }, + onPauseButtonClicked: { viewModel.pause() }, + onPlaylistButtonClicked: { path.append(Route.playlist) }, + onModeSwitchButtonClicked: { viewModel.nextMode() }, + onFavoriteButtonClicked: { viewModel.toggleFavorite() }, + onSeek: { viewModel.seekToRatio(ratio: $0) } + ) + .navigationDestination(for: Route.self) { route in + switch route { + case .playlist: + PlayerPlaylist( + playlist: uiState.musicState.playlist, + currentSong: uiState.musicState.currentSong, + onItemClicked: { viewModel.playOn(songId: $0) }, + onItemSweepToDismiss: { viewModel.removeSongFromPlaylist(songId: $0) }, + onItemMoved: { from, to in viewModel.moveSongInPlaylist(from: Int32(from), to: Int32(to)) } + ) + } } } } @@ -46,6 +90,17 @@ struct PlayerScreen: View { .popupImage(albumImage != nil ? Image(uiImage: albumImage!) : nil) .popupBarButtons { ToolbarItemGroup(placement: .popupBar) { + if horizontalSizeClass == .regular { + Button( + action: { + viewModel.previous() + }, + label: { + Image(systemName: "backward.fill") + .tint(.primary) + } + ) + } Button( action: { if isPlaying {