Implement iPhone now playing experience
This commit is contained in:
@@ -286,166 +286,3 @@ final class iPhoneLibraryViewModelFavoritesTests: XCTestCase {
|
||||
XCTAssertTrue(viewModel.availableOfflineTracks.isEmpty)
|
||||
}
|
||||
}
|
||||
|
||||
@MainActor
|
||||
private final class TestPlayer: iPhoneLocalAudioPlaying {
|
||||
var onStateChange: ((iPhoneNowPlayingState) -> Void)?
|
||||
private(set) var state = iPhoneNowPlayingState(
|
||||
trackID: nil,
|
||||
title: nil,
|
||||
artist: nil,
|
||||
isPlaying: false,
|
||||
errorMessage: nil
|
||||
)
|
||||
|
||||
func play(
|
||||
trackID: String,
|
||||
title: String,
|
||||
artist: String,
|
||||
fileURL: URL
|
||||
) throws {
|
||||
_ = fileURL
|
||||
state = iPhoneNowPlayingState(
|
||||
trackID: trackID,
|
||||
title: title,
|
||||
artist: artist,
|
||||
isPlaying: true,
|
||||
errorMessage: nil
|
||||
)
|
||||
onStateChange?(state)
|
||||
}
|
||||
|
||||
func resume() throws {
|
||||
state.isPlaying = true
|
||||
state.errorMessage = nil
|
||||
onStateChange?(state)
|
||||
}
|
||||
|
||||
func pause() {
|
||||
state.isPlaying = false
|
||||
onStateChange?(state)
|
||||
}
|
||||
}
|
||||
|
||||
private actor TestRemoteLibraryRepository: RemoteLibraryRepository {
|
||||
private let tracks: [RemoteTrack]
|
||||
|
||||
init(tracks: [RemoteTrack]) {
|
||||
self.tracks = tracks
|
||||
}
|
||||
|
||||
func loadCachedRemoteTracks() async throws -> [RemoteTrack] {
|
||||
tracks
|
||||
}
|
||||
|
||||
func syncRemoteTracks(deviceId: String) async throws -> [RemoteTrack] {
|
||||
_ = deviceId
|
||||
return tracks
|
||||
}
|
||||
|
||||
func downloadAudioAsset(assetId: String, deviceId: String) async throws -> Data {
|
||||
_ = assetId
|
||||
_ = deviceId
|
||||
throw TestRepositoryError.unexpectedDownload
|
||||
}
|
||||
|
||||
func downloadArtwork(artworkId: String, deviceId: String) async throws -> Data {
|
||||
_ = artworkId
|
||||
_ = deviceId
|
||||
throw TestRepositoryError.unexpectedDownload
|
||||
}
|
||||
}
|
||||
|
||||
private enum TestRepositoryError: Error {
|
||||
case unexpectedDownload
|
||||
}
|
||||
|
||||
@MainActor
|
||||
private func makeViewModel(
|
||||
remoteTracks: [RemoteTrack],
|
||||
downloadStates: [RemoteTrackDownloadState] = [],
|
||||
favoriteTrackStore: any FavoriteTrackStore = InMemoryFavoriteTrackStore(),
|
||||
audioFiles: [String: Data] = [:],
|
||||
player: (any iPhoneLocalAudioPlaying)? = nil
|
||||
) -> iPhoneLibraryViewModel {
|
||||
let repository = TestRemoteLibraryRepository(tracks: remoteTracks)
|
||||
let downloadStateStore = InMemoryRemoteTrackDownloadStateStore(states: downloadStates)
|
||||
let audioFileStore = InMemoryOfflineAudioFileStore(files: audioFiles)
|
||||
let artworkStore = InMemoryArtworkStore()
|
||||
let syncService = RemoteLibrarySyncService(
|
||||
repository: repository,
|
||||
downloadStateStore: downloadStateStore,
|
||||
audioFileStore: audioFileStore,
|
||||
artworkStore: artworkStore
|
||||
)
|
||||
let offlineLibraryService = OfflineLibraryService(
|
||||
syncService: syncService,
|
||||
audioFileStore: audioFileStore,
|
||||
artworkStore: artworkStore
|
||||
)
|
||||
|
||||
return iPhoneLibraryViewModel(
|
||||
environment: ServerEnvironment(
|
||||
baseURL: ServerEnvironment.defaultLocalBaseURL,
|
||||
appVersion: "Tests"
|
||||
),
|
||||
apiClient: URLSessionVelodyAPIClient(
|
||||
environment: ServerEnvironment(
|
||||
baseURL: ServerEnvironment.defaultLocalBaseURL,
|
||||
appVersion: "Tests"
|
||||
)
|
||||
),
|
||||
syncService: syncService,
|
||||
offlineLibraryService: offlineLibraryService,
|
||||
favoriteTrackStore: favoriteTrackStore,
|
||||
player: player ?? TestPlayer(),
|
||||
keychainService: MemoryKeychainService()
|
||||
)
|
||||
}
|
||||
|
||||
private func makeRemoteTrack(
|
||||
trackId: String,
|
||||
assetId: String,
|
||||
title: String
|
||||
) -> RemoteTrack {
|
||||
RemoteTrack(
|
||||
trackId: trackId,
|
||||
title: title,
|
||||
artist: "Velody Artist",
|
||||
durationSeconds: 245,
|
||||
sha256: String(repeating: "a", count: 64),
|
||||
assetId: assetId,
|
||||
createdAt: "2026-05-30T08:00:00.000Z",
|
||||
updatedAt: "2026-05-30T08:05:00.000Z"
|
||||
)
|
||||
}
|
||||
|
||||
private func makeDownloadedState(for track: RemoteTrack) -> RemoteTrackDownloadState {
|
||||
RemoteTrackDownloadState(
|
||||
remoteTrackId: track.trackId,
|
||||
assetId: track.assetId,
|
||||
localFilePath: localFilePath(for: track),
|
||||
downloadedAt: Date(timeIntervalSince1970: 1_000),
|
||||
downloadStatus: .downloaded
|
||||
)
|
||||
}
|
||||
|
||||
private func localFilePath(for track: RemoteTrack) -> String {
|
||||
"/in-memory/\(track.assetId).mp3"
|
||||
}
|
||||
|
||||
@MainActor
|
||||
private func remoteRow(
|
||||
in viewModel: iPhoneLibraryViewModel,
|
||||
trackID: String
|
||||
) -> RemoteTrackRowViewData? {
|
||||
viewModel.remoteTracks.first(where: { $0.id == trackID })
|
||||
}
|
||||
|
||||
@MainActor
|
||||
private func offlineRow(
|
||||
in viewModel: iPhoneLibraryViewModel,
|
||||
trackID: String
|
||||
) -> AvailableOfflineTrackRowViewData? {
|
||||
viewModel.availableOfflineTracks.first(where: { $0.id == trackID })
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user