Skip to content
Open
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
40 changes: 35 additions & 5 deletions Amperfy.xcodeproj/project.pbxproj

Large diffs are not rendered by default.

27 changes: 27 additions & 0 deletions Amperfy/Screens/ViewController/EntityPreviewVC.swift
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
import AmperfyKit
import Foundation
import MarqueeLabel
import SwiftUI
import UIKit

typealias GetPlayContextCallback = () -> PlayContext?
Expand Down Expand Up @@ -69,6 +70,7 @@ class EntityPreviewActionBuilder {
private var isGoToSiteUrl = false
private var isShowPodcastDetails = false
private var isShowSongDetails = false
private var isShowSongTags = false
private var isInstantMix = false
private var isShareable = false

Expand Down Expand Up @@ -128,6 +130,10 @@ class EntityPreviewActionBuilder {
let lyricsShowAction = createShowLyricsAction(song: song) {
gotoActions.append(lyricsShowAction)
}
if isShowSongTags,
let song = (entityContainer as? AbstractPlayable)?.asSong {
gotoActions.append(createShowSongTagsAction(song: song))
}
if isShowPodcastDetails,
let podcastEpisode = (entityContainer as? AbstractPlayable)?.asPodcastEpisode {
gotoActions.append(createShowEpisodeDetailsAction(podcastEpisode: podcastEpisode))
Expand Down Expand Up @@ -265,6 +271,7 @@ class EntityPreviewActionBuilder {
isGoToSiteUrl = false
isShowPodcastDetails = false
isShowSongDetails = true
isShowSongTags = SongTagKey.allCases.contains { $0.value(for: song) != nil }
isInstantMix = appDelegate.storage.settings.user.isOnlineMode
isShareable = song.isCached || appDelegate.storage.settings.user.isOnlineMode
}
Expand Down Expand Up @@ -861,6 +868,26 @@ class EntityPreviewActionBuilder {
}
}

private func createShowSongTagsAction(song: Song) -> UIAction {
UIAction(title: "View Tags", image: UIImage(systemName: "tag")) { [weak self] _ in
self?.showSongTags(song: song)
}
}

private func showSongTags(song: Song) {
// Bail out silently if the managed object has been invalidated between menu
// construction and the user tapping the action — prevents an empty/broken sheet.
guard song.managedObject.managedObjectContext != nil else { return }
let tagsView = SongTagsView(song: song)
let hostingVC = UIHostingController(rootView: tagsView)
let navVC = UINavigationController(rootViewController: hostingVC)
if let sheet = navVC.sheetPresentationController {
sheet.detents = [.large()]
sheet.prefersGrabberVisible = true
}
rootView.present(navVC, animated: true)
}

private func createShowLyricsAction(song: Song) -> UIAction? {
guard let playable = entityContainer as? AbstractPlayable,
let song = playable.asSong,
Expand Down
58 changes: 58 additions & 0 deletions Amperfy/SwiftUI/SongTags/SongTagsFilterView.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
//
// SongTagsFilterView.swift
// Amperfy
//
// Created by Amperfy on 25.05.26.
// Copyright (c) 2026 Maximilian Bauer. All rights reserved.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
//

import Foundation
import SwiftUI

// MARK: - SongTagsFilterView

struct SongTagsFilterView: View {
@ObservedObject
var store: TagVisibilityStore
@Environment(\.dismiss)
private var dismiss

var body: some View {
List {
Section {
ForEach(SongTagKey.allCases, id: \.rawValue) { key in
let isOn = Binding<Bool>(
get: { !store.hiddenKeys.contains(key.rawValue) },
set: { store.setVisible(key, visible: $0) }
)
SettingsCheckBoxRow(title: key.displayName, isOn: isOn)
}
}
Section {
Button("Show All") {
store.showAll()
}
}
}
.navigationTitle("Visible Tags")
.navigationBarTitleDisplayMode(.inline)
.toolbar {
ToolbarItem(placement: .navigationBarTrailing) {
Button("Done") { dismiss() }
}
}
}
}
Loading