Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[iOS] Media Item Menu - Edit Arrays (People, Genres, Studios, & Tags) #1336

Merged
merged 20 commits into from
Dec 6, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
112 changes: 112 additions & 0 deletions Shared/Coordinators/ItemEditorCoordinator.swift
Original file line number Diff line number Diff line change
Expand Up @@ -19,19 +19,131 @@ final class ItemEditorCoordinator: ObservableObject, NavigationCoordinatable {

private let viewModel: ItemViewModel

// MARK: - Route to Metadata

@Route(.modal)
var editMetadata = makeEditMetadata

// MARK: - Route to Genres

@Route(.push)
var editGenres = makeEditGenres
@Route(.modal)
var addGenre = makeAddGenre

// MARK: - Route to Tags

@Route(.push)
var editTags = makeEditTags
@Route(.modal)
var addTag = makeAddTag

// MARK: - Route to Studios

@Route(.push)
var editStudios = makeEditStudios
@Route(.modal)
var addStudio = makeAddStudio

// MARK: - Route to People

@Route(.push)
var editPeople = makeEditPeople
@Route(.modal)
var addPeople = makeAddPeople

// MARK: - Initializer

init(viewModel: ItemViewModel) {
self.viewModel = viewModel
}

// MARK: - Item Metadata

func makeEditMetadata(item: BaseItemDto) -> NavigationViewCoordinator<BasicNavigationViewCoordinator> {
NavigationViewCoordinator {
EditMetadataView(viewModel: ItemEditorViewModel(item: item))
}
}

// MARK: - Item Genres

@ViewBuilder
func makeEditGenres(item: BaseItemDto) -> some View {
EditItemElementView<String>(
viewModel: GenreEditorViewModel(item: item),
type: .genres,
route: { router, viewModel in
router.route(to: \.addGenre, viewModel as! GenreEditorViewModel)
}
)
}

func makeAddGenre(viewModel: GenreEditorViewModel) -> NavigationViewCoordinator<BasicNavigationViewCoordinator> {
NavigationViewCoordinator {
AddItemElementView(viewModel: viewModel, type: .genres)
}
}

// MARK: - Item Tags

@ViewBuilder
func makeEditTags(item: BaseItemDto) -> some View {
EditItemElementView<String>(
viewModel: TagEditorViewModel(item: item),
type: .tags,
route: { router, viewModel in
router.route(to: \.addTag, viewModel as! TagEditorViewModel)
}
)
}

func makeAddTag(viewModel: TagEditorViewModel) -> NavigationViewCoordinator<BasicNavigationViewCoordinator> {
NavigationViewCoordinator {
AddItemElementView(viewModel: viewModel, type: .tags)
}
}

// MARK: - Item Studios

@ViewBuilder
func makeEditStudios(item: BaseItemDto) -> some View {
EditItemElementView<NameGuidPair>(
viewModel: StudioEditorViewModel(item: item),
type: .studios,
route: { router, viewModel in
router.route(to: \.addStudio, viewModel as! StudioEditorViewModel)
}
)
}

func makeAddStudio(viewModel: StudioEditorViewModel) -> NavigationViewCoordinator<BasicNavigationViewCoordinator> {
NavigationViewCoordinator {
AddItemElementView(viewModel: viewModel, type: .studios)
}
}

// MARK: - Item People

@ViewBuilder
func makeEditPeople(item: BaseItemDto) -> some View {
EditItemElementView<BaseItemPerson>(
viewModel: PeopleEditorViewModel(item: item),
type: .people,
route: { router, viewModel in
router.route(to: \.addPeople, viewModel as! PeopleEditorViewModel)
}
)
}

func makeAddPeople(viewModel: PeopleEditorViewModel) -> NavigationViewCoordinator<BasicNavigationViewCoordinator> {
NavigationViewCoordinator {
AddItemElementView(viewModel: viewModel, type: .people)
}
}

// MARK: - Start

@ViewBuilder
func makeStart() -> some View {
ItemEditorView(viewModel: viewModel)
Expand Down
4 changes: 4 additions & 0 deletions Shared/Extensions/Collection.swift
Original file line number Diff line number Diff line change
Expand Up @@ -21,4 +21,8 @@ extension Collection {
subscript(safe index: Index) -> Element? {
indices.contains(index) ? self[index] : nil
}

func keyed<Key>(using: KeyPath<Element, Key>) -> [Key: Element] {
Dictionary(uniqueKeysWithValues: map { ($0[keyPath: using], $0) })
}
}
97 changes: 97 additions & 0 deletions Shared/Extensions/JellyfinAPI/PersonKind.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
//
// 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) 2024 Jellyfin & Jellyfin Contributors
//

import Foundation
import JellyfinAPI

// TODO: No longer needed in 10.9+
public enum PersonKind: String, Codable, CaseIterable {
case unknown = "Unknown"
case actor = "Actor"
case director = "Director"
case composer = "Composer"
case writer = "Writer"
case guestStar = "GuestStar"
case producer = "Producer"
case conductor = "Conductor"
case lyricist = "Lyricist"
case arranger = "Arranger"
case engineer = "Engineer"
case mixer = "Mixer"
case remixer = "Remixer"
case creator = "Creator"
case artist = "Artist"
case albumArtist = "AlbumArtist"
case author = "Author"
case illustrator = "Illustrator"
case penciller = "Penciller"
case inker = "Inker"
case colorist = "Colorist"
case letterer = "Letterer"
case coverArtist = "CoverArtist"
case editor = "Editor"
case translator = "Translator"
}

// TODO: Still needed in 10.9+
extension PersonKind: Displayable {
var displayTitle: String {
switch self {
case .unknown:
return L10n.unknown
case .actor:
return L10n.actor
case .director:
return L10n.director
case .composer:
return L10n.composer
case .writer:
return L10n.writer
case .guestStar:
return L10n.guestStar
case .producer:
return L10n.producer
case .conductor:
return L10n.conductor
case .lyricist:
return L10n.lyricist
case .arranger:
return L10n.arranger
case .engineer:
return L10n.engineer
case .mixer:
return L10n.mixer
case .remixer:
return L10n.remixer
case .creator:
return L10n.creator
case .artist:
return L10n.artist
case .albumArtist:
return L10n.albumArtist
case .author:
return L10n.author
case .illustrator:
return L10n.illustrator
case .penciller:
return L10n.penciller
case .inker:
return L10n.inker
case .colorist:
return L10n.colorist
case .letterer:
return L10n.letterer
case .coverArtist:
return L10n.coverArtist
case .editor:
return L10n.editor
case .translator:
return L10n.translator
}
}
}
112 changes: 112 additions & 0 deletions Shared/Objects/ItemArrayElements.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
//
// 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) 2024 Jellyfin & Jellyfin Contributors
//

import Foundation
import JellyfinAPI
import SwiftUI

enum ItemArrayElements: Displayable {
case studios
case genres
case tags
case people

// MARK: - Localized Title

var displayTitle: String {
switch self {
case .studios:
return L10n.studios
case .genres:
return L10n.genres
case .tags:
return L10n.tags
case .people:
return L10n.people
}
}

// MARK: - Localized Description

var description: String {
switch self {
case .studios:
return L10n.studiosDescription
case .genres:
return L10n.genresDescription
case .tags:
return L10n.tagsDescription
case .people:
return L10n.peopleDescription
}
}

// MARK: - Create Element from Components

func createElement<T: Hashable>(
name: String,
id: String?,
personRole: String?,
personKind: String?
) -> T {
switch self {
case .genres, .tags:
return name as! T
case .studios:
return NameGuidPair(id: id, name: name) as! T
case .people:
return BaseItemPerson(
id: id,
name: name,
role: personRole,
type: personKind
) as! T
}
}

// MARK: - Get the Element from the BaseItemDto Based on Type

func getElement<T: Hashable>(for item: BaseItemDto) -> [T] {
switch self {
case .studios:
return item.studios as? [T] ?? []
case .genres:
return item.genres as? [T] ?? []
case .tags:
return item.tags as? [T] ?? []
case .people:
return item.people as? [T] ?? []
}
}

// MARK: - Get the Name from the Element Based on Type

func getId(for element: AnyHashable) -> String? {
switch self {
case .genres, .tags:
return nil
case .studios:
return (element.base as? NameGuidPair)?.id
case .people:
return (element.base as? BaseItemPerson)?.id
}
}

// MARK: - Get the Id from the Element Based on Type

func getName(for element: AnyHashable) -> String {
switch self {
case .genres, .tags:
return element.base as? String ?? L10n.unknown
case .studios:
return (element.base as? NameGuidPair)?.name ?? L10n.unknown
case .people:
return (element.base as? BaseItemPerson)?.name ?? L10n.unknown
}
}
}
4 changes: 2 additions & 2 deletions Shared/Objects/LibraryDisplayType.swift
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,9 @@ enum LibraryDisplayType: String, CaseIterable, Displayable, Storable, SystemImag
var displayTitle: String {
switch self {
case .grid:
"Grid"
L10n.grid
case .list:
"List"
L10n.list
}
}

Expand Down
Loading