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

Fix #20, suppor playing next and playing last #42

Merged
merged 3 commits into from
Dec 1, 2023
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
33 changes: 33 additions & 0 deletions BlackCandy.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,9 @@
6446762A2A5FF2EF00CD9794 /* LoginView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 644676292A5FF2EF00CD9794 /* LoginView.swift */; };
644E9B41295AC796000DB3E3 /* SideBarNavigationViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 644E9B40295AC796000DB3E3 /* SideBarNavigationViewController.swift */; };
644E9B43295AC7C0000DB3E3 /* TabBarViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 644E9B42295AC7C0000DB3E3 /* TabBarViewController.swift */; };
6451FF3D2B19AB580098FD11 /* FlashMessageClient.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6451FF3C2B19AB580098FD11 /* FlashMessageClient.swift */; };
6451FF3F2B19ACB60098FD11 /* LiveFlashMessageClient.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6451FF3E2B19ACB60098FD11 /* LiveFlashMessageClient.swift */; };
6451FF422B19B34C0098FD11 /* AlertKit in Frameworks */ = {isa = PBXBuildFile; productRef = 6451FF412B19B34C0098FD11 /* AlertKit */; };
645AF4222A5E86AE00D35670 /* WindowClient.swift in Sources */ = {isa = PBXBuildFile; fileRef = 645AF4212A5E86AE00D35670 /* WindowClient.swift */; };
645AF4242A5E887500D35670 /* LiveWindowClient.swift in Sources */ = {isa = PBXBuildFile; fileRef = 645AF4232A5E887500D35670 /* LiveWindowClient.swift */; };
645D7067299B18F30020146A /* PlaylistTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 645D7066299B18F30020146A /* PlaylistTests.swift */; };
Expand Down Expand Up @@ -153,6 +156,8 @@
644676292A5FF2EF00CD9794 /* LoginView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoginView.swift; sourceTree = "<group>"; };
644E9B40295AC796000DB3E3 /* SideBarNavigationViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SideBarNavigationViewController.swift; sourceTree = "<group>"; };
644E9B42295AC7C0000DB3E3 /* TabBarViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TabBarViewController.swift; sourceTree = "<group>"; };
6451FF3C2B19AB580098FD11 /* FlashMessageClient.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FlashMessageClient.swift; sourceTree = "<group>"; };
6451FF3E2B19ACB60098FD11 /* LiveFlashMessageClient.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LiveFlashMessageClient.swift; sourceTree = "<group>"; };
645AF4212A5E86AE00D35670 /* WindowClient.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WindowClient.swift; sourceTree = "<group>"; };
645AF4232A5E887500D35670 /* LiveWindowClient.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LiveWindowClient.swift; sourceTree = "<group>"; };
645D7066299B18F30020146A /* PlaylistTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlaylistTests.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -228,6 +233,7 @@
files = (
DD6E0EA32884FEE100F39DB3 /* ComposableArchitecture in Frameworks */,
DD83970B28DC043000BC8C11 /* Alamofire in Frameworks */,
6451FF422B19B34C0098FD11 /* AlertKit in Frameworks */,
DD62F4F727C76C13005A5D0B /* Turbo in Frameworks */,
646315252971A2910016EBCA /* LNPopupController in Frameworks */,
);
Expand Down Expand Up @@ -353,6 +359,15 @@
path = ViewControllers;
sourceTree = "<group>";
};
6451FF3B2B19AB200098FD11 /* FlashMessageClient */ = {
isa = PBXGroup;
children = (
6451FF3C2B19AB580098FD11 /* FlashMessageClient.swift */,
6451FF3E2B19ACB60098FD11 /* LiveFlashMessageClient.swift */,
);
path = FlashMessageClient;
sourceTree = "<group>";
};
645AF4202A5E864000D35670 /* WindowClient */ = {
isa = PBXGroup;
children = (
Expand Down Expand Up @@ -584,6 +599,7 @@
DD751E0A2887A37700B8128E /* Clients */ = {
isa = PBXGroup;
children = (
6451FF3B2B19AB200098FD11 /* FlashMessageClient */,
64D798B72A739EC800F0C92C /* GlobalQueueClient */,
645AF4202A5E864000D35670 /* WindowClient */,
641B5D8A2A11FCB700ECA220 /* NowPlayingClient */,
Expand Down Expand Up @@ -631,6 +647,7 @@
DD6E0EA22884FEE100F39DB3 /* ComposableArchitecture */,
DD83970A28DC043000BC8C11 /* Alamofire */,
646315242971A2910016EBCA /* LNPopupController */,
6451FF412B19B34C0098FD11 /* AlertKit */,
);
productName = BlackCandy;
productReference = DD62F4C827C769CC005A5D0B /* BlackCandy.app */;
Expand Down Expand Up @@ -713,6 +730,7 @@
DD83970928DC043000BC8C11 /* XCRemoteSwiftPackageReference "Alamofire" */,
646315232971A2900016EBCA /* XCRemoteSwiftPackageReference "LNPopupController" */,
648AA714299F6D8F002EED64 /* XCRemoteSwiftPackageReference "OHHTTPStubs" */,
6451FF402B19B34C0098FD11 /* XCRemoteSwiftPackageReference "AlertKit" */,
);
productRefGroup = DD62F4C927C769CC005A5D0B /* Products */;
projectDirPath = "";
Expand Down Expand Up @@ -808,8 +826,10 @@
6476237C2949B60300333378 /* PlayerReducer.swift in Sources */,
64783D3B2AA8554F009F075B /* LoginViewController.swift in Sources */,
DD87EAE228CF0A940062DFE6 /* Playlist.swift in Sources */,
6451FF3D2B19AB580098FD11 /* FlashMessageClient.swift in Sources */,
DDB453A127CF11EF003BA1F9 /* TurboNavigationController.swift in Sources */,
DDA1A70828D3041F009D80C6 /* PlayerClient.swift in Sources */,
6451FF3F2B19ACB60098FD11 /* LiveFlashMessageClient.swift in Sources */,
64FF43A62A5D36A100BF3055 /* SceneDelegate.swift in Sources */,
645AF4242A5E887500D35670 /* LiveWindowClient.swift in Sources */,
64654A622A692883008ADCF8 /* LoginReducer.swift in Sources */,
Expand Down Expand Up @@ -1173,6 +1193,14 @@
/* End XCConfigurationList section */

/* Begin XCRemoteSwiftPackageReference section */
6451FF402B19B34C0098FD11 /* XCRemoteSwiftPackageReference "AlertKit" */ = {
isa = XCRemoteSwiftPackageReference;
repositoryURL = "https://github.com/sparrowcode/AlertKit";
requirement = {
kind = upToNextMinorVersion;
minimumVersion = 5.1.8;
};
};
646315232971A2900016EBCA /* XCRemoteSwiftPackageReference "LNPopupController" */ = {
isa = XCRemoteSwiftPackageReference;
repositoryURL = "https://github.com/LeoNatan/LNPopupController";
Expand Down Expand Up @@ -1216,6 +1244,11 @@
/* End XCRemoteSwiftPackageReference section */

/* Begin XCSwiftPackageProductDependency section */
6451FF412B19B34C0098FD11 /* AlertKit */ = {
isa = XCSwiftPackageProductDependency;
package = 6451FF402B19B34C0098FD11 /* XCRemoteSwiftPackageReference "AlertKit" */;
productName = AlertKit;
};
646315242971A2910016EBCA /* LNPopupController */ = {
isa = XCSwiftPackageProductDependency;
package = 646315232971A2900016EBCA /* XCRemoteSwiftPackageReference "LNPopupController" */;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,15 @@
"version" : "5.6.2"
}
},
{
"identity" : "alertkit",
"kind" : "remoteSourceControl",
"location" : "https://github.com/sparrowcode/AlertKit",
"state" : {
"revision" : "3d35de60ad26aab58395ebecdd63b533546862ed",
"version" : "5.1.8"
}
},
{
"identity" : "combine-schedulers",
"kind" : "remoteSourceControl",
Expand Down
10 changes: 8 additions & 2 deletions BlackCandy/Clients/APIClient/APIClient.swift
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,9 @@ struct APIClient {
var moveSongInCurrentPlaylist: (Int, Int) async throws -> NoContentResponse
var getSong: (Int) async throws -> Song
var getSystemInfo: (ServerAddressState) async throws -> SystemInfo
var addSongToCurrentPlaylist: (Int, Song?) async throws -> Song
var addSongToCurrentPlaylist: (Int, Song?, String?) async throws -> Song
var replaceCurrentPlaylistWithAlbumSongs: (Int) async throws -> [Song]
var replaceCurrentPlaylistWithPlaylistSongs: (Int) async throws -> [Song]
}

extension APIClient: TestDependencyKey {
Expand All @@ -39,7 +41,11 @@ extension APIClient: TestDependencyKey {

getSystemInfo: unimplemented("\(Self.self).getSystemInfo"),

addSongToCurrentPlaylist: unimplemented("\(Self.self).addSongToCurrentPlaylist")
addSongToCurrentPlaylist: unimplemented("\(Self.self).addSongToCurrentPlaylist"),

replaceCurrentPlaylistWithAlbumSongs: unimplemented("\(Self.self).replaceCurrentPlaylistWithAlbumSongs"),

replaceCurrentPlaylistWithPlaylistSongs: unimplemented("\(Self.self).replaceCurrentPlaylistWithPlaylistSongs")
)

static let previewValue = testValue
Expand Down
36 changes: 34 additions & 2 deletions BlackCandy/Clients/APIClient/LiveAPIClient.swift
Original file line number Diff line number Diff line change
Expand Up @@ -245,13 +245,17 @@ extension APIClient: DependencyKey {
}
},

addSongToCurrentPlaylist: { songId, currentSong in
var parameters = ["song_id": songId]
addSongToCurrentPlaylist: { songId, currentSong, location in
var parameters: [String: Any] = ["song_id": songId]

if let currentSongId = currentSong?.id {
parameters["current_song_id"] = currentSongId
}

if let location = location {
parameters["location"] = location
}

let request = AF.request(
requestURL("/current_playlist/songs"),
method: .post,
Expand All @@ -261,6 +265,34 @@ extension APIClient: DependencyKey {
.validate()
.serializingDecodable(Song.self, decoder: jsonDecoder)

return try await handleRequest(request) { request, _ in
try await request.value
}
},

replaceCurrentPlaylistWithAlbumSongs: { albumId in
let request = AF.request(
requestURL("/current_playlist/songs/albums/\(albumId)"),
method: .put,
headers: headers
)
.validate()
.serializingDecodable([Song].self, decoder: jsonDecoder)

return try await handleRequest(request) { request, _ in
try await request.value
}
},

replaceCurrentPlaylistWithPlaylistSongs: { playlistId in
let request = AF.request(
requestURL("/current_playlist/songs/playlists/\(playlistId)"),
method: .put,
headers: headers
)
.validate()
.serializingDecodable([Song].self, decoder: jsonDecoder)

return try await handleRequest(request) { request, _ in
try await request.value
}
Expand Down
19 changes: 19 additions & 0 deletions BlackCandy/Clients/FlashMessageClient/FlashMessageClient.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import Foundation
import Dependencies

struct FlashMessageClient {
var showMessage: (String.LocalizationValue) -> Void
}

extension FlashMessageClient: TestDependencyKey {
static let testValue = Self(
showMessage: { _ in }
)
}

extension DependencyValues {
var flashMessageClient: FlashMessageClient {
get { self[FlashMessageClient.self] }
set { self[FlashMessageClient.self] = newValue }
}
}
11 changes: 11 additions & 0 deletions BlackCandy/Clients/FlashMessageClient/LiveFlashMessageClient.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import Foundation
import Dependencies
import AlertKit

extension FlashMessageClient: DependencyKey {
static let liveValue = Self(
showMessage: { message in
AlertKitAPI.present(title: String(localized: message), style: .iOS16AppleMusic)
}
)
}
1 change: 1 addition & 0 deletions BlackCandy/Localizable.strings
Original file line number Diff line number Diff line change
Expand Up @@ -25,3 +25,4 @@
"text.unknownNetworkError" = "Unknown Network Error";
"text.badRequest" = "Bad Request";
"text.unsupportedServer" = "Unsupported Black Candy Server";
"text.addedToPlaylist" = "Added to Playlist";
5 changes: 5 additions & 0 deletions BlackCandy/Models/Playlist.swift
Original file line number Diff line number Diff line change
Expand Up @@ -55,4 +55,9 @@ struct Playlist: Equatable {
orderedSongs.insert(song, at: index)
shuffledSongs.insert(song, at: index)
}

mutating func append(_ song: Song) {
orderedSongs.append(song)
shuffledSongs.append(song)
}
}
65 changes: 58 additions & 7 deletions BlackCandy/Store/PlayerReducer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ struct PlayerReducer: Reducer {
@Dependency(\.playerClient) var playerClient
@Dependency(\.nowPlayingClient) var nowPlayingClient
@Dependency(\.cookiesClient) var cookiesClient
@Dependency(\.flashMessageClient) var flashMessageClient

struct State: Equatable {
var alert: AlertState<AppReducer.AlertAction>?
Expand All @@ -29,6 +30,13 @@ struct PlayerReducer: Reducer {
var hasCurrentSong: Bool {
currentSong != nil
}

mutating func insertSongNextToCurrent(song: Song) -> Int {
let insertIndex = min(currentIndex + 1, playlist.songs.endIndex)
playlist.insert(song, at: insertIndex)

return insertIndex
}
}

enum Action: Equatable {
Expand All @@ -55,10 +63,14 @@ struct PlayerReducer: Reducer {
case moveSongsResponse(TaskResult<APIClient.NoContentResponse>)
case getCurrentPlaylist
case currentPlaylistResponse(TaskResult<[Song]>)
case playAll
case playAll(String, Int)
case playAllResponse(TaskResult<[Song]>)
case playSong(Int)
case playNext(Int)
case playLast(Int)
case playSongResponse(TaskResult<Song>)
case playNextResponse(TaskResult<Song>)
case playLastResponse(TaskResult<Song>)
}

var body: some ReducerOf<Self> {
Expand Down Expand Up @@ -273,11 +285,20 @@ struct PlayerReducer: Reducer {

return .none

case .playAll:
case let .playAll(resourceType, resourceId):
return .run { send in
await send(
.playAllResponse(
TaskResult { try await apiClient.getSongsFromCurrentPlaylist() }
TaskResult {
switch resourceType {
case "albums":
return try await apiClient.replaceCurrentPlaylistWithAlbumSongs(resourceId)
case "playlists":
return try await apiClient.replaceCurrentPlaylistWithPlaylistSongs(resourceId)
default:
throw APIClient.APIError.invalidRequest
}
}
)
)
}
Expand All @@ -295,23 +316,53 @@ struct PlayerReducer: Reducer {
return .run { [currentSong = state.currentSong] send in
await send(
.playSongResponse(
TaskResult { try await apiClient.addSongToCurrentPlaylist(songId, currentSong) }
TaskResult { try await apiClient.addSongToCurrentPlaylist(songId, currentSong, nil) }
)
)
}
}

case let .playSongResponse(.success(song)):
let insertIndex = min(state.currentIndex + 1, state.playlist.songs.endIndex)
state.playlist.insert(song, at: insertIndex)
case let .playNext(songId):
return .run { [currentSong = state.currentSong] send in
await send(
.playNextResponse(
TaskResult { try await apiClient.addSongToCurrentPlaylist(songId, currentSong, nil) }
)
)
}

case let .playLast(songId):
return .run { send in
await send(
.playLastResponse(
TaskResult { try await apiClient.addSongToCurrentPlaylist(songId, nil, "last") }
)
)
}

case let .playSongResponse(.success(song)):
let insertIndex = state.insertSongNextToCurrent(song: song)
return self.playOn(state: &state, index: insertIndex)

case let .playNextResponse(.success(song)):
_ = state.insertSongNextToCurrent(song: song)
flashMessageClient.showMessage("text.addedToPlaylist")

return .none

case let .playLastResponse(.success(song)):
state.playlist.append(song)
flashMessageClient.showMessage("text.addedToPlaylist")

return .none

case let .deleteSongsResponse(.failure(error)),
let .moveSongsResponse(.failure(error)),
let .currentPlaylistResponse(.failure(error)),
let .playAllResponse(.failure(error)),
let .playSongResponse(.failure(error)),
let .playNextResponse(.failure(error)),
let .playLastResponse(.failure(error)),
let .toggleFavoriteResponse(.failure(error)):
guard let error = error as? APIClient.APIError else { return .none }

Expand Down
11 changes: 10 additions & 1 deletion BlackCandy/Turbo/TurboScriptMessageHandler.swift
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,19 @@ class TurboScriptMessageHandler: NSObject, WKScriptMessageHandler {

switch actionName {
case "playAll":
store.send(.player(.playAll))
guard let resourceType = body["resourceType"] as? String,
let resourceId = body["resourceId"] as? Int else { return }

store.send(.player(.playAll(resourceType, resourceId)))
case "playSong":
guard let songId = body["songId"] as? Int else { return }
store.send(.player(.playSong(songId)))
case "playNext":
guard let songId = body["songId"] as? Int else { return }
store.send(.player(.playNext(songId)))
case "playLast":
guard let songId = body["songId"] as? Int else { return }
store.send(.player(.playLast(songId)))
case "updateTheme":
guard
let theme = body["theme"] as? String,
Expand Down
2 changes: 1 addition & 1 deletion BlackCandyTests/Clients/APIClientTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -219,7 +219,7 @@ final class APIClientTests: XCTestCase {
return .init(data: responseJSON, statusCode: 200, headers: nil)
}

let response = try await apiClient.addSongToCurrentPlaylist(1, currentSong)
let response = try await apiClient.addSongToCurrentPlaylist(1, currentSong, nil)

XCTAssertEqual(response.id, 1)
XCTAssertEqual(response.name, "sample1")
Expand Down
Loading
Loading