Skip to content

Commit

Permalink
Spotify Example (#101)
Browse files Browse the repository at this point in the history
* feat: Add very basic Spotify demo app

* feat: Initial work adding Artists feature

* chore: Migrate to a basic Xcode project

* feat: Finish Spotify example

* chore: remove show sizes

* chore: removing unnecessary files

* chore: move signout logic to AppFeature

* feat: install the authenticationMethod automatically.

* chore: remove spurious package modifier

---------

Co-authored-by: danthorpe <[email protected]>
  • Loading branch information
danthorpe and danthorpe authored Sep 12, 2024
1 parent 930dc0c commit 0cca90e
Show file tree
Hide file tree
Showing 19 changed files with 842 additions and 1 deletion.
7 changes: 7 additions & 0 deletions Examples/Examples.xcworkspace/contents.xcworkspacedata

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>IDEDidComputeMac32BitWarning</key>
<true/>
</dict>
</plist>
212 changes: 212 additions & 0 deletions Examples/Examples.xcworkspace/xcshareddata/swiftpm/Package.resolved
Original file line number Diff line number Diff line change
@@ -0,0 +1,212 @@
{
"pins" : [
{
"identity" : "combine-schedulers",
"kind" : "remoteSourceControl",
"location" : "https://github.com/pointfreeco/combine-schedulers",
"state" : {
"revision" : "9fa31f4403da54855f1e2aeaeff478f4f0e40b13",
"version" : "1.0.2"
}
},
{
"identity" : "swift-algorithms",
"kind" : "remoteSourceControl",
"location" : "https://github.com/apple/swift-algorithms",
"state" : {
"revision" : "f6919dfc309e7f1b56224378b11e28bab5bccc42",
"version" : "1.2.0"
}
},
{
"identity" : "swift-argument-parser",
"kind" : "remoteSourceControl",
"location" : "https://github.com/apple/swift-argument-parser",
"state" : {
"revision" : "41982a3656a71c768319979febd796c6fd111d5c",
"version" : "1.5.0"
}
},
{
"identity" : "swift-async-algorithms",
"kind" : "remoteSourceControl",
"location" : "https://github.com/apple/swift-async-algorithms",
"state" : {
"revision" : "6ae9a051f76b81cc668305ceed5b0e0a7fd93d20",
"version" : "1.0.1"
}
},
{
"identity" : "swift-case-paths",
"kind" : "remoteSourceControl",
"location" : "https://github.com/pointfreeco/swift-case-paths",
"state" : {
"revision" : "642e6aab8e03e5f992d9c83e38c5be98cfad5078",
"version" : "1.5.5"
}
},
{
"identity" : "swift-clocks",
"kind" : "remoteSourceControl",
"location" : "https://github.com/pointfreeco/swift-clocks",
"state" : {
"revision" : "b9b24b69e2adda099a1fa381cda1eeec272d5b53",
"version" : "1.0.5"
}
},
{
"identity" : "swift-collections",
"kind" : "remoteSourceControl",
"location" : "https://github.com/apple/swift-collections",
"state" : {
"revision" : "9bf03ff58ce34478e66aaee630e491823326fd06",
"version" : "1.1.3"
}
},
{
"identity" : "swift-composable-architecture",
"kind" : "remoteSourceControl",
"location" : "https://github.com/pointfreeco/swift-composable-architecture",
"state" : {
"revision" : "0d8980f5bcc5fe6941f1788758667ff2b8c10f03",
"version" : "1.14.0"
}
},
{
"identity" : "swift-composable-loadable",
"kind" : "remoteSourceControl",
"location" : "https://github.com/danthorpe/swift-composable-loadable",
"state" : {
"branch" : "main",
"revision" : "d21a0f5c4931acbdd80fa505d4a08aaee97aefb0"
}
},
{
"identity" : "swift-concurrency-extras",
"kind" : "remoteSourceControl",
"location" : "https://github.com/pointfreeco/swift-concurrency-extras",
"state" : {
"revision" : "bb5059bde9022d69ac516803f4f227d8ac967f71",
"version" : "1.1.0"
}
},
{
"identity" : "swift-custom-dump",
"kind" : "remoteSourceControl",
"location" : "https://github.com/pointfreeco/swift-custom-dump",
"state" : {
"revision" : "82645ec760917961cfa08c9c0c7104a57a0fa4b1",
"version" : "1.3.3"
}
},
{
"identity" : "swift-dependencies",
"kind" : "remoteSourceControl",
"location" : "https://github.com/pointfreeco/swift-dependencies",
"state" : {
"revision" : "3ef38bb702a1a2f39c7e19fc0578403b8ee52b17",
"version" : "1.3.9"
}
},
{
"identity" : "swift-docc-plugin",
"kind" : "remoteSourceControl",
"location" : "https://github.com/apple/swift-docc-plugin",
"state" : {
"revision" : "85e4bb4e1cd62cec64a4b8e769dcefdf0c5b9d64",
"version" : "1.4.3"
}
},
{
"identity" : "swift-docc-symbolkit",
"kind" : "remoteSourceControl",
"location" : "https://github.com/swiftlang/swift-docc-symbolkit",
"state" : {
"revision" : "b45d1f2ed151d057b54504d653e0da5552844e34",
"version" : "1.0.0"
}
},
{
"identity" : "swift-http-types",
"kind" : "remoteSourceControl",
"location" : "https://github.com/apple/swift-http-types",
"state" : {
"revision" : "ae67c8178eb46944fd85e4dc6dd970e1f3ed6ccd",
"version" : "1.3.0"
}
},
{
"identity" : "swift-identified-collections",
"kind" : "remoteSourceControl",
"location" : "https://github.com/pointfreeco/swift-identified-collections",
"state" : {
"revision" : "2f5ab6e091dd032b63dacbda052405756010dc3b",
"version" : "1.1.0"
}
},
{
"identity" : "swift-navigation",
"kind" : "remoteSourceControl",
"location" : "https://github.com/pointfreeco/swift-navigation",
"state" : {
"revision" : "e834b3760731160d7d448509ee6a1408c8582a6b",
"version" : "2.2.0"
}
},
{
"identity" : "swift-numerics",
"kind" : "remoteSourceControl",
"location" : "https://github.com/apple/swift-numerics.git",
"state" : {
"revision" : "0a5bc04095a675662cf24757cc0640aa2204253b",
"version" : "1.0.2"
}
},
{
"identity" : "swift-perception",
"kind" : "remoteSourceControl",
"location" : "https://github.com/pointfreeco/swift-perception",
"state" : {
"revision" : "bc67aa8e461351c97282c2419153757a446ae1c9",
"version" : "1.3.5"
}
},
{
"identity" : "swift-syntax",
"kind" : "remoteSourceControl",
"location" : "https://github.com/swiftlang/swift-syntax",
"state" : {
"revision" : "515f79b522918f83483068d99c68daeb5116342d",
"version" : "600.0.0-prerelease-2024-08-20"
}
},
{
"identity" : "swift-tagged",
"kind" : "remoteSourceControl",
"location" : "https://github.com/pointfreeco/swift-tagged",
"state" : {
"revision" : "3907a9438f5b57d317001dc99f3f11b46882272b",
"version" : "0.10.0"
}
},
{
"identity" : "swift-utilities",
"kind" : "remoteSourceControl",
"location" : "https://github.com/danthorpe/swift-utilities",
"state" : {
"revision" : "615c4fbf115a584057b978b2c18a4c75198f4d61",
"version" : "0.7.0"
}
},
{
"identity" : "xctest-dynamic-overlay",
"kind" : "remoteSourceControl",
"location" : "https://github.com/pointfreeco/xctest-dynamic-overlay",
"state" : {
"revision" : "96beb108a57f24c8476ae1f309239270772b2940",
"version" : "1.2.5"
}
}
],
"version" : 2
}
8 changes: 8 additions & 0 deletions Examples/Spotify/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
.DS_Store
/.build
/Packages
xcuserdata/
DerivedData/
.swiftpm/configuration/registries.json
.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata
.netrc
17 changes: 17 additions & 0 deletions Examples/Spotify/App/Artists/ArtistsFeature.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import ComposableArchitecture
import ComposableLoadable
import Foundation

@Reducer
enum ArtistsFeature {
@ReducerCaseIgnored
case empty
case artists(PaginationFeature<Artist>)
}

extension PaginationFeature<Artist> {
init() {
@Dependency(\.spotify) var spotify
self.init(loadPage: spotify.paginateFollowedArtists)
}
}
72 changes: 72 additions & 0 deletions Examples/Spotify/App/Artists/ArtistsView.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import ComposableArchitecture
import ComposableLoadable
import SwiftUI

struct ArtistsView {
let store: StoreOf<ArtistsFeature>

#if os(macOS)
let columns = Array(repeating: GridItem(.fixed(160), spacing: 0), count: 4)
#elseif os(tvOS)
let columns = Array(repeating: GridItem(.fixed(256), spacing: 0), count: 5)
#else
let columns = Array(repeating: GridItem(.fixed(120), spacing: 0), count: 3)
#endif
}

extension ArtistsView: View {

var body: some View {
WithPerceptionTracking {
contentView
}
}

@ViewBuilder
private var contentView: some View {
switch store.case {
case .empty:
Text("No Artists")
case let .artists(store):
ScrollView(.vertical) {
LazyVGrid(columns: columns, spacing: 0) {
ForEach(store.elements.sorted().reversed()) { artist in
ArtistView(artist: artist)
}

PaginationLoadMore(store, direction: .bottom) { error, _ in
Text("Error fetching next page: \(error)")
} noMoreResults: {
Text("No more artists")
} onActive: {
ProgressView()
}
}
}
}
}
}

struct ArtistView: View {
@Environment(\.displayScale) var displayScale
let artist: Artist
var body: some View {
ZStack(alignment: .bottom) {
AsyncImage(url: artist.images.first?.url, scale: displayScale) { phase in
switch phase {
case let .success(image):
image.resizable()
.aspectRatio(1.0, contentMode: .fit)
default:
EmptyView()
}
}
.clipped()

Text(artist.name)
.padding(.vertical, 8)
.frame(maxWidth: .greatestFiniteMagnitude)
.background(.ultraThinMaterial, in: Rectangle())
}
}
}
15 changes: 15 additions & 0 deletions Examples/Spotify/App/Artists/SpotifyClient+PaginatingArtists.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import ComposableLoadable

extension Spotify.Client {
@Sendable func paginateFollowedArtists(
_ request: PaginationFeature<Artist>.PageRequest
) async throws -> PaginationFeature<Artist>.Page {
let artists = try await followedArtists(after: request.cursor).artists
return PaginationFeature<Artist>
.Page(
previous: artists.cursors.before,
next: artists.cursors.after,
elements: artists.items
)
}
}
Loading

0 comments on commit 0cca90e

Please sign in to comment.