Skip to content

Commit

Permalink
iOS - Move the Playlist Items to a Sheet.
Browse files Browse the repository at this point in the history
  • Loading branch information
JPKribs committed Mar 3, 2025
1 parent 6268a57 commit d1cf7d5
Show file tree
Hide file tree
Showing 3 changed files with 154 additions and 51 deletions.
10 changes: 7 additions & 3 deletions Swiftfin.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -224,6 +224,7 @@
4EC3B7F92D62C23F00E29FF1 /* AddToPlaylistView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4EC3B7F82D62C23800E29FF1 /* AddToPlaylistView.swift */; };
4EC50D612C934B3A00FC3D0E /* ServerTasksViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4EC50D602C934B3A00FC3D0E /* ServerTasksViewModel.swift */; };
4EC6C16B2C92999800FC904B /* TranscodeSection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4EC6C16A2C92999800FC904B /* TranscodeSection.swift */; };
4EC8787F2D76562E00C23853 /* EditPlaylistView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4EC8787E2D76562900C23853 /* EditPlaylistView.swift */; };
4ECDAA9E2C920A8E0030F2F5 /* TranscodeReason.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4ECDAA9D2C920A8E0030F2F5 /* TranscodeReason.swift */; };
4ECDAA9F2C920A8E0030F2F5 /* TranscodeReason.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4ECDAA9D2C920A8E0030F2F5 /* TranscodeReason.swift */; };
4ECF5D882D0A3D0200F066B1 /* AddAccessScheduleView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4ECF5D812D0A3D0200F066B1 /* AddAccessScheduleView.swift */; };
Expand Down Expand Up @@ -1445,6 +1446,7 @@
4EC50D602C934B3A00FC3D0E /* ServerTasksViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ServerTasksViewModel.swift; sourceTree = "<group>"; };
4EC6C16A2C92999800FC904B /* TranscodeSection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TranscodeSection.swift; sourceTree = "<group>"; };
4EC71FBB2D161FE300D0B3A8 /* AlphabetizeStrings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AlphabetizeStrings.swift; sourceTree = "<group>"; };
4EC8787E2D76562900C23853 /* EditPlaylistView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EditPlaylistView.swift; sourceTree = "<group>"; };
4ECDAA9D2C920A8E0030F2F5 /* TranscodeReason.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TranscodeReason.swift; sourceTree = "<group>"; };
4ECF5D812D0A3D0200F066B1 /* AddAccessScheduleView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddAccessScheduleView.swift; sourceTree = "<group>"; };
4ECF5D892D0A57EF00F066B1 /* DynamicDayOfWeek.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DynamicDayOfWeek.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -2946,12 +2948,13 @@
path = PlaylistViewModel;
sourceTree = "<group>";
};
4EC3B7F72D62C23100E29FF1 /* AddToPlaylistView */ = {
4EC3B7F72D62C23100E29FF1 /* PlaylistView */ = {
isa = PBXGroup;
children = (
4EC8787E2D76562900C23853 /* EditPlaylistView.swift */,
4EC3B7F82D62C23800E29FF1 /* AddToPlaylistView.swift */,
);
path = AddToPlaylistView;
path = PlaylistView;
sourceTree = "<group>";
};
4EC71FBA2D161FD800D0B3A8 /* Scripts */ = {
Expand Down Expand Up @@ -4930,7 +4933,7 @@
isa = PBXGroup;
children = (
E18ACA902A15A2D600BB4F35 /* AboutView */,
4EC3B7F72D62C23100E29FF1 /* AddToPlaylistView */,
4EC3B7F72D62C23100E29FF1 /* PlaylistView */,
E18E01D9288747230022598C /* ActionButtonHStack.swift */,
E18E01D7288747230022598C /* AttributeHStack.swift */,
E17FB55628C1256400311DFE /* CastAndCrewHStack.swift */,
Expand Down Expand Up @@ -6784,6 +6787,7 @@
E11C15352BF7C505006BC9B6 /* UserProfileImageCoordinator.swift in Sources */,
4EA78B232D2B5CFC0093BFCE /* ItemPhotoCropView.swift in Sources */,
E1D8428F2933F2D900D1041A /* MediaSourceInfo.swift in Sources */,
4EC8787F2D76562E00C23853 /* EditPlaylistView.swift in Sources */,
E1BDF2EC2952290200CC0294 /* AspectFillActionButton.swift in Sources */,
BD0BA22B2AD6503B00306A8D /* OnlineVideoPlayerManager.swift in Sources */,
E1BDF2F529524E6400CC0294 /* PlayNextItemActionButton.swift in Sources */,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,12 +40,10 @@ struct AddToPlaylistView: View {
// @State
// private var playlistPublic: Bool = false

// MARK: - Remove Item Variables
// MARK: - Edit Playlist View State

@State
private var isPresentingRemovalConfirmation: Bool = false
@State
private var selectedItem: BaseItemDto? = nil
private var isPresentingContents: Bool = false

// MARK: - Error State

Expand Down Expand Up @@ -130,17 +128,15 @@ struct AddToPlaylistView: View {
.navigationBarCloseButton {
router.dismissCoordinator()
}
.confirmationDialog(
L10n.removeItem(selectedItem?.name ?? selectedItem?.type?.displayTitle ?? L10n.items),
isPresented: $isPresentingRemovalConfirmation
) {
Button(L10n.remove, role: .destructive) {
if let item = selectedItem {
viewModel.send(.removeItems([item.id!]))
.sheet(isPresented: $isPresentingContents) {
isPresentingContents = false
} content: {
EditPlaylistView(
viewModel: viewModel,
onClose: {
isPresentingContents = false
}
}
} message: {
Text(L10n.removeItemConfirmationMessage)
)
}
.onFirstAppear {
viewModel.send(.getPlaylists)
Expand All @@ -150,8 +146,7 @@ struct AddToPlaylistView: View {
case .created, .added, .updated:
router.dismissCoordinator()
case .removed:
selectedItem = nil
isPresentingRemovalConfirmation = false
break
case let .error(eventError):
error = eventError
}
Expand All @@ -162,14 +157,10 @@ struct AddToPlaylistView: View {
// MARK: - Content View

private var contentView: some View {
VStack {
List {
playlistPickerView

playlistDetailsView
}
List {
playlistPickerView

playlistItemsView
playlistDetailsView
}
}

Expand Down Expand Up @@ -210,6 +201,13 @@ struct AddToPlaylistView: View {
Text(overview)
}
}

Section(L10n.options) {
ChevronButton(L10n.existingItems)
.onSelect {
isPresentingContents = true
}
}
} else {
Section(L10n.createPlaylist) {
TextField(L10n.name, text: $playlistName)
Expand All @@ -226,29 +224,4 @@ struct AddToPlaylistView: View {
}
}
}

// MARK: - Playlist Items View

@ViewBuilder
private var playlistItemsView: some View {
if !viewModel.selectedPlaylistItems.isEmpty {
ScrollView {
ForEach(BaseItemKind.allCases, id: \.self) { sectionType in
let sectionItems = viewModel.selectedPlaylistItems.filter { $0.type == sectionType }

if !sectionItems.isEmpty {
PosterHStack(
title: sectionType.displayTitle,
type: .portrait,
items: sectionItems
)
.onSelect {
selectedItem = $0
isPresentingRemovalConfirmation = true
}
}
}
}
}
}
}
126 changes: 126 additions & 0 deletions Swiftfin/Views/ItemView/Components/PlaylistView/EditPlaylistView.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
//
// Swiftfin is subject to the terms of the Mozilla Public
// License, v2.0. If a copy of the MPL was not distributed with this
// file, you can obtain one at https://mozilla.org/MPL/2.0/.
//
// Copyright (c) 2025 Jellyfin & Jellyfin Contributors
//

import Factory
import JellyfinAPI
import SwiftUI

struct EditPlaylistView: View {

// MARK: - Observed Object

@ObservedObject
private var viewModel: PlaylistViewModel

// MARK: - Close Action

private let onClose: () -> Void

// MARK: - Remove Item Variables

@State
private var isPresentingRemovalConfirmation: Bool = false
@State
private var selectedItem: BaseItemDto? = nil

// MARK: - Error State

@State
private var error: Error?

// MARK: - Initializer

init(viewModel: PlaylistViewModel, onClose: @escaping () -> Void) {
self._viewModel = ObservedObject(wrappedValue: viewModel)
self.onClose = onClose
}

// MARK: - Body

var body: some View {
NavigationView {
ZStack {
switch viewModel.state {
case .initial:
ProgressView()
case .content:
if viewModel.selectedPlaylistItems.isEmpty {
emptyView
} else {
contentView
}
case let .error(error):
ErrorView(error: error)
}
}
.topBarTrailing {
if viewModel.backgroundStates.contains(.updatingPlaylist) {
ProgressView()
}
}
.navigationBarTitle(viewModel.selectedPlaylist?.displayTitle ?? L10n.playlist)
.navigationBarTitleDisplayMode(.inline)
.navigationBarCloseButton {
onClose()
}
}
.confirmationDialog(
L10n.removeItem(selectedItem?.name ?? selectedItem?.type?.displayTitle ?? L10n.items),
isPresented: $isPresentingRemovalConfirmation
) {
Button(L10n.remove, role: .destructive) {
if let item = selectedItem {
viewModel.send(.removeItems([item.id!]))
}
}
} message: {
Text(L10n.removeItemConfirmationMessage)
}
.onReceive(viewModel.events) { event in
switch event {
case .created, .added, .updated:
break
case .removed:
selectedItem = nil
isPresentingRemovalConfirmation = false
case let .error(eventError):
error = eventError
}
}
.errorMessage($error)
}

// MARK: - Content View

private var contentView: some View {
ScrollView {
ForEach(BaseItemKind.allCases, id: \.self) { sectionType in
let sectionItems = viewModel.selectedPlaylistItems.filter { $0.type == sectionType }

if sectionItems.isNotEmpty {
PosterHStack(
title: sectionType.displayTitle,
type: .portrait,
items: sectionItems
)
.onSelect {
selectedItem = $0
isPresentingRemovalConfirmation = true
}
}
}
}
}

// MARK: - Empty View

private var emptyView: some View {
Text(L10n.noResults)
.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .center)
}
}

0 comments on commit d1cf7d5

Please sign in to comment.