Skip to content

Commit

Permalink
Add RankableSortList (#804)
Browse files Browse the repository at this point in the history
- ArtistList and VenueList now populate RankableSortList behind the
scenes.
- Sharing more code!
  • Loading branch information
bolsinga committed Jun 8, 2024
1 parent 65c16b3 commit dab56e1
Show file tree
Hide file tree
Showing 5 changed files with 103 additions and 130 deletions.
1 change: 1 addition & 0 deletions Sources/Site/Music/Rankable.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import Foundation

protocol Rankable: LibraryComparable, Hashable, PathRestorable {
var firstSet: FirstSet { get }
var showRank: Ranking { get }
func ranking(for sort: RankingSort) -> Ranking
}

Expand Down
36 changes: 12 additions & 24 deletions Sources/Site/Music/UI/ArchiveCategoryDetail.swift
Original file line number Diff line number Diff line change
Expand Up @@ -39,33 +39,21 @@ struct ArchiveCategoryDetail: View {
case .venues:
let venueDigests = model.filteredVenueDigests(nearbyModel).names(
filteredBy: venueSearchString)
VenueList(
venueDigests: venueDigests, sectioner: vault.sectioner,
title: String(localized: "Venues", bundle: .module),
associatedRankName: String(localized: "Sort By Artist Count", bundle: .module),
associatedRankSectionHeader: { $0.artistsCountView },
sort: $venueSort
)
.archiveSearchable(
searchPrompt: String(localized: "Venue Names", bundle: .module),
searchString: $venueSearchString, contentsEmpty: venueDigests.isEmpty
)
.locationFilter(nearbyModel, filteredDataIsEmpty: venueDigests.isEmpty)
VenueList(venueDigests: venueDigests, sectioner: vault.sectioner, sort: $venueSort)
.archiveSearchable(
searchPrompt: String(localized: "Venue Names", bundle: .module),
searchString: $venueSearchString, contentsEmpty: venueDigests.isEmpty
)
.locationFilter(nearbyModel, filteredDataIsEmpty: venueDigests.isEmpty)
case .artists:
let artistDigests = model.filteredArtistDigests(nearbyModel).names(
filteredBy: artistSearchString)
ArtistList(
artistDigests: artistDigests, sectioner: vault.sectioner,
title: String(localized: "Artists", bundle: .module),
associatedRankName: String(localized: "Sort By Venue Count", bundle: .module),
associatedRankSectionHeader: { $0.venuesCountView },
sort: $artistSort
)
.archiveSearchable(
searchPrompt: String(localized: "Artist Names", bundle: .module),
searchString: $artistSearchString, contentsEmpty: artistDigests.isEmpty
)
.locationFilter(nearbyModel, filteredDataIsEmpty: artistDigests.isEmpty)
ArtistList(artistDigests: artistDigests, sectioner: vault.sectioner, sort: $artistSort)
.archiveSearchable(
searchPrompt: String(localized: "Artist Names", bundle: .module),
searchString: $artistSearchString, contentsEmpty: artistDigests.isEmpty
)
.locationFilter(nearbyModel, filteredDataIsEmpty: artistDigests.isEmpty)
}
}
.shareCategory(category, url: url)
Expand Down
62 changes: 7 additions & 55 deletions Sources/Site/Music/UI/ArtistList.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,73 +7,25 @@

import SwiftUI

struct ArtistList<SectionHeaderContent: View>: View {
struct ArtistList: View {
let artistDigests: [ArtistDigest]
let sectioner: LibrarySectioner
let title: String
let associatedRankName: String
@ViewBuilder let associatedRankSectionHeader: (Ranking) -> SectionHeaderContent

@Binding var sort: RankingSort

@ViewBuilder private func showCount(for artistDigest: ArtistDigest) -> some View {
Text("\(artistDigest.showRank.value) Show(s)", bundle: .module)
}

@ViewBuilder private var listElement: some View {
if sort.isAlphabetical {
RankingList(
items: artistDigests,
rankingMapBuilder: { sectioner.sectionMap(for: $0) },
itemContentView: { showCount(for: $0) },
sectionHeaderView: { $0.representingView })
} else if sort.isFirstSeen {
RankingList(
items: artistDigests,
rankingMapBuilder: { $0.firstSeen },
rankSorted: PartialDate.compareWithUnknownsMuted(lhs:rhs:),
itemContentView: { Text($0.firstSet.rank.formatted(.hash)) },
sectionHeaderView: { Text($0.formatted(.compact)) })
} else {
RankingList(
items: artistDigests,
rankingMapBuilder: { $0.ranked(by: sort) },
itemContentView: {
if sort.isShowYearRange {
showCount(for: $0)
}
},
sectionHeaderView: {
switch sort {
case .associatedRank:
associatedRankSectionHeader($0)
default:
$0.sectionHeader(for: sort)
}
})
}
}

var body: some View {
listElement
.navigationTitle(Text(title))
.sortable(algorithm: $sort) {
switch $0 {
case .associatedRank:
return associatedRankName
default:
return $0.localizedString
}
}
RankableSortList(
items: artistDigests, sectioner: sectioner,
title: String(localized: "Artists", bundle: .module),
associatedRankName: String(localized: "Sort By Venue Count", bundle: .module),
associatedRankSectionHeader: { $0.venuesCountView }, sort: $sort)
}
}

#Preview {
NavigationStack {
ArtistList(
artistDigests: vaultPreviewData.artistDigests, sectioner: vaultPreviewData.sectioner,
title: "Artists", associatedRankName: "Sort By Venue Count",
associatedRankSectionHeader: { $0.venuesCountView }, sort: .constant(.alphabetical)
sort: .constant(.alphabetical)
)
.musicDestinations(vaultPreviewData)
}
Expand Down
76 changes: 76 additions & 0 deletions Sources/Site/Music/UI/RankableSortList.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
//
// RankableSortList.swift
//
//
// Created by Greg Bolsinga on 4/10/23.
//

import SwiftUI

struct RankableSortList<T, SectionHeaderContent: View>: View where T: Rankable, T.ID == String {
let items: [T]
let sectioner: LibrarySectioner
let title: String
let associatedRankName: String
@ViewBuilder let associatedRankSectionHeader: (Ranking) -> SectionHeaderContent

@Binding var sort: RankingSort

@ViewBuilder private func showCount(for venueDigest: T) -> some View {
Text("\(venueDigest.showRank.value) Show(s)", bundle: .module)
}

@ViewBuilder private var listElement: some View {
if sort.isAlphabetical {
RankingList(
items: items,
rankingMapBuilder: { sectioner.sectionMap(for: $0) },
itemContentView: { showCount(for: $0) },
sectionHeaderView: { $0.representingView })
} else if sort.isFirstSeen {
RankingList(
items: items,
rankingMapBuilder: { $0.firstSeen },
rankSorted: PartialDate.compareWithUnknownsMuted(lhs:rhs:),
itemContentView: { Text($0.firstSet.rank.formatted(.hash)) },
sectionHeaderView: { Text($0.formatted(.compact)) })
} else {
RankingList(
items: items,
rankingMapBuilder: { $0.ranked(by: sort) },
itemContentView: { if sort.isShowYearRange { showCount(for: $0) } },
sectionHeaderView: {
switch sort {
case .associatedRank:
associatedRankSectionHeader($0)
default:
$0.sectionHeader(for: sort)
}
})
}
}

var body: some View {
listElement
.navigationTitle(Text(title))
.sortable(algorithm: $sort) {
switch $0 {
case .associatedRank:
return associatedRankName
default:
return $0.localizedString
}
}
}
}

#Preview {
NavigationStack {
RankableSortList(
items: vaultPreviewData.venueDigests, sectioner: vaultPreviewData.sectioner,
title: "Venues", associatedRankName: "Sort By Artist Count",
associatedRankSectionHeader: { $0.artistsCountView }, sort: .constant(.alphabetical)
)
.musicDestinations(vaultPreviewData)
}
}
58 changes: 7 additions & 51 deletions Sources/Site/Music/UI/VenueList.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,69 +7,25 @@

import SwiftUI

struct VenueList<SectionHeaderContent: View>: View {
struct VenueList: View {
let venueDigests: [VenueDigest]
let sectioner: LibrarySectioner
let title: String
let associatedRankName: String
@ViewBuilder let associatedRankSectionHeader: (Ranking) -> SectionHeaderContent

@Binding var sort: RankingSort

@ViewBuilder private func showCount(for venueDigest: VenueDigest) -> some View {
Text("\(venueDigest.showRank.value) Show(s)", bundle: .module)
}

@ViewBuilder private var listElement: some View {
if sort.isAlphabetical {
RankingList(
items: venueDigests,
rankingMapBuilder: { sectioner.sectionMap(for: $0) },
itemContentView: { showCount(for: $0) },
sectionHeaderView: { $0.representingView })
} else if sort.isFirstSeen {
RankingList(
items: venueDigests,
rankingMapBuilder: { $0.firstSeen },
rankSorted: PartialDate.compareWithUnknownsMuted(lhs:rhs:),
itemContentView: { Text($0.firstSet.rank.formatted(.hash)) },
sectionHeaderView: { Text($0.formatted(.compact)) })
} else {
RankingList(
items: venueDigests,
rankingMapBuilder: { $0.ranked(by: sort) },
itemContentView: { if sort.isShowYearRange { showCount(for: $0) } },
sectionHeaderView: {
switch sort {
case .associatedRank:
associatedRankSectionHeader($0)
default:
$0.sectionHeader(for: sort)
}
})
}
}

var body: some View {
listElement
.navigationTitle(Text(title))
.sortable(algorithm: $sort) {
switch $0 {
case .associatedRank:
return associatedRankName
default:
return $0.localizedString
}
}
RankableSortList(
items: venueDigests, sectioner: sectioner,
title: String(localized: "Venues", bundle: .module),
associatedRankName: String(localized: "Sort By Artist Count", bundle: .module),
associatedRankSectionHeader: { $0.artistsCountView }, sort: $sort)
}
}

#Preview {
NavigationStack {
VenueList(
venueDigests: vaultPreviewData.venueDigests, sectioner: vaultPreviewData.sectioner,
title: "Venues", associatedRankName: "Sort By Artist Count",
associatedRankSectionHeader: { $0.artistsCountView }, sort: .constant(.alphabetical)
sort: .constant(.alphabetical)
)
.musicDestinations(vaultPreviewData)
}
Expand Down

0 comments on commit dab56e1

Please sign in to comment.