Skip to content
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
5 changes: 5 additions & 0 deletions media-sample/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,11 @@ dependencies {
implementation libs.androidx.datastore.preferences
implementation libs.androidx.complications.rendering

testImplementation libs.junit
testImplementation libs.truth
testImplementation libs.androidx.test.ext.ktx
testImplementation libs.kotlinx.coroutines.test

androidTestImplementation libs.compose.ui.test.junit4
androidTestImplementation libs.espresso.core
androidTestImplementation libs.junit
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2021 The Android Open Source Project
* Copyright 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand All @@ -14,16 +14,17 @@
* limitations under the License.
*/

package com.google.android.horologist.mediasample.catalog
package com.google.android.horologist.mediasample.data.api

import com.google.android.horologist.mediasample.catalog.model.Catalog
import com.google.android.horologist.mediasample.data.api.model.CatalogApiModel
import retrofit2.http.GET

interface UampService {

@GET("catalog.json")
suspend fun catalog(): Catalog
suspend fun catalog(): CatalogApiModel

companion object {
const val BaseUrl = "https://storage.googleapis.com/uamp/"
const val BASE_URL = "https://storage.googleapis.com/uamp/"
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,11 @@
* limitations under the License.
*/

package com.google.android.horologist.mediasample.catalog.model
package com.google.android.horologist.mediasample.data.api.model

import com.squareup.moshi.JsonClass

@JsonClass(generateAdapter = true)
data class Catalog(
val music: List<Music>
data class CatalogApiModel(
val music: List<MusicApiModel>
)
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,13 @@
* limitations under the License.
*/

package com.google.android.horologist.mediasample.catalog.model
package com.google.android.horologist.mediasample.data.api.model

import com.google.android.horologist.media.model.MediaItem
import com.squareup.moshi.JsonClass

@JsonClass(generateAdapter = true)
data class Music(
data class MusicApiModel(
val album: String,
val artist: String,
val duration: Int,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
/*
* Copyright 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package com.google.android.horologist.mediasample.data.datasource

import com.google.android.horologist.mediasample.data.api.UampService
import com.google.android.horologist.mediasample.data.mapper.PlaylistMapper
import com.google.android.horologist.mediasample.domain.model.Playlist
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.flow.flowOn

class PlaylistRemoteDataSource(
private val ioDispatcher: CoroutineDispatcher,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: I'm curious about ioDispatcher here. The network layer, retrofit should be executing these correctly on a background thread. This would seem mainly to affect the JSON parsing, is that the intent? If so is the default dispatcher better here?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

good question, wouldn't retrofit also take care of executing the JSON parsing in a background thread?
if so, here the dispatcher would be just for the mapper, which we could move to default as suggested

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, I suspect you are right. But I will confirm later, since there is some evidence that it isn't always the case. https://github.com/square/retrofit/pull/3693/files

private val uampService: UampService,
) {

fun getPlaylists(): Flow<List<Playlist>> = flow {
emit(PlaylistMapper.map(uampService.catalog()))
}.flowOn(ioDispatcher)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
/*
* Copyright 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package com.google.android.horologist.mediasample.data.mapper

import com.google.android.horologist.media.model.MediaItem
import com.google.android.horologist.mediasample.data.api.model.MusicApiModel

/**
* Maps a [MusicApiModel] into [MediaItem].
*/
object MediaItemMapper {

fun map(musicApiModel: MusicApiModel): MediaItem = MediaItem(
id = musicApiModel.id,
uri = musicApiModel.source,
title = musicApiModel.title,
artist = musicApiModel.artist,
artworkUri = musicApiModel.image
)

fun map(musicApiModels: List<MusicApiModel>): List<MediaItem> = musicApiModels.map(::map)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
/*
* Copyright 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package com.google.android.horologist.mediasample.data.mapper

import com.google.android.horologist.mediasample.data.api.model.CatalogApiModel
import com.google.android.horologist.mediasample.domain.model.Playlist

/**
* Maps a [CatalogApiModel] into a [List] of [Playlist].
*/
object PlaylistMapper {

fun map(catalog: CatalogApiModel): List<Playlist> =
catalog.music
.groupBy { it.genre }
.map { entry ->
Playlist(
id = sanitize(sanitize(entry.key)),
name = entry.key,
mediaItems = MediaItemMapper.map(entry.value)
)
}

private fun sanitize(it: String): String {
return it.replace("[^A-Za-z]".toRegex(), "")
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
/*
* Copyright 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package com.google.android.horologist.mediasample.data.repository

import com.google.android.horologist.mediasample.data.datasource.PlaylistRemoteDataSource
import com.google.android.horologist.mediasample.domain.PlaylistRepository
import com.google.android.horologist.mediasample.domain.model.Playlist
import kotlinx.coroutines.flow.Flow

class PlaylistRepositoryImpl(
private val playlistRemoteDataSource: PlaylistRemoteDataSource
) : PlaylistRepository {

override fun getPlaylists(): Flow<List<Playlist>> = playlistRemoteDataSource.getPlaylists()
}
Original file line number Diff line number Diff line change
Expand Up @@ -39,12 +39,12 @@ import com.google.android.horologist.media3.navigation.NavDeepLinkIntentBuilder
import com.google.android.horologist.media3.offload.AudioOffloadManager
import com.google.android.horologist.media3.rules.PlaybackRules
import com.google.android.horologist.mediasample.AppConfig
import com.google.android.horologist.mediasample.catalog.UampService
import com.google.android.horologist.mediasample.complication.DataUpdates
import com.google.android.horologist.mediasample.complication.MediaStatusComplicationService
import com.google.android.horologist.mediasample.components.MediaActivity
import com.google.android.horologist.mediasample.components.MediaApplication
import com.google.android.horologist.mediasample.components.PlaybackService
import com.google.android.horologist.mediasample.data.api.UampService
import com.google.android.horologist.mediasample.domain.SettingsRepository
import com.google.android.horologist.mediasample.system.Logging
import com.google.android.horologist.mediasample.tile.MediaCollectionsTileService
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ import coil.ImageLoader
import coil.decode.SvgDecoder
import coil.disk.DiskCache
import coil.request.CachePolicy
import com.google.android.horologist.mediasample.catalog.UampService
import com.google.android.horologist.mediasample.data.api.UampService
import com.google.android.horologist.networks.data.DataRequestRepository
import com.google.android.horologist.networks.data.RequestType
import com.google.android.horologist.networks.logging.NetworkStatusLogger
Expand Down Expand Up @@ -138,7 +138,7 @@ class NetworkModule(
val retrofit by lazy {
Retrofit.Builder()
.addConverterFactory(MoshiConverterFactory.create(moshi))
.baseUrl("https://storage.googleapis.com/uamp/")
.baseUrl(UampService.BASE_URL)
.callFactory(NetworkAwareCallFactory(networkAwareCallFactory, RequestType.ApiRequest))
.build()
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
/*
* Copyright 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package com.google.android.horologist.mediasample.domain

import com.google.android.horologist.mediasample.domain.model.Playlist
import kotlinx.coroutines.flow.Flow

/**
* A repository of [Playlist].
*/
interface PlaylistRepository {

fun getPlaylists(): Flow<List<Playlist>>
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import androidx.datastore.preferences.core.MutablePreferences
import androidx.datastore.preferences.core.Preferences
import androidx.datastore.preferences.core.booleanPreferencesKey
import androidx.datastore.preferences.core.edit
import com.google.android.horologist.mediasample.domain.model.Settings
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.map

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
/*
* Copyright 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package com.google.android.horologist.mediasample.domain.model

import com.google.android.horologist.media.model.MediaItem

data class Playlist(
val id: String,
val name: String,
val mediaItems: List<MediaItem>
)
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
* limitations under the License.
*/

package com.google.android.horologist.mediasample.domain
package com.google.android.horologist.mediasample.domain.model

data class Settings(
val showTimeTextInfo: Boolean = false,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,8 @@ import com.google.android.horologist.media.ui.tiles.MediaCollectionsTileRenderer
import com.google.android.horologist.media.ui.tiles.toTileColors
import com.google.android.horologist.mediasample.BuildConfig
import com.google.android.horologist.mediasample.R
import com.google.android.horologist.mediasample.catalog.UampService
import com.google.android.horologist.mediasample.components.MediaActivity
import com.google.android.horologist.mediasample.data.api.UampService
import com.google.android.horologist.mediasample.di.ServiceContainer
import com.google.android.horologist.mediasample.ui.app.UampColors
import com.google.android.horologist.tiles.CoroutinesTileService
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,10 @@ import androidx.lifecycle.viewmodel.viewModelFactory
import com.google.android.horologist.media.repository.PlayerRepository
import com.google.android.horologist.media3.offload.AudioOffloadManager
import com.google.android.horologist.mediasample.AppConfig
import com.google.android.horologist.mediasample.catalog.UampService
import com.google.android.horologist.mediasample.data.api.UampService
import com.google.android.horologist.mediasample.di.MediaApplicationContainer
import com.google.android.horologist.mediasample.domain.Settings
import com.google.android.horologist.mediasample.domain.SettingsRepository
import com.google.android.horologist.mediasample.domain.model.Settings
import com.google.android.horologist.mediasample.ui.debug.OffloadState
import com.google.android.horologist.networks.data.DataRequestRepository
import com.google.android.horologist.networks.data.DataUsageReport
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ import com.google.android.horologist.compose.layout.StateUtils.rememberStateWith
import com.google.android.horologist.media.ui.screens.playlist.PlaylistScreen
import com.google.android.horologist.media.ui.screens.playlist.PlaylistScreenState
import com.google.android.horologist.mediasample.R
import com.google.android.horologist.mediasample.domain.Settings
import com.google.android.horologist.mediasample.domain.model.Settings

@Composable
fun UampPlaylistsScreen(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ import com.google.android.horologist.media.ui.snackbar.SnackbarViewModel
import com.google.android.horologist.media.ui.snackbar.UiMessage
import com.google.android.horologist.media.ui.state.mapper.MediaItemUiModelMapper
import com.google.android.horologist.media.ui.state.model.MediaItemUiModel
import com.google.android.horologist.mediasample.catalog.UampService
import com.google.android.horologist.mediasample.data.api.UampService
import com.google.android.horologist.mediasample.di.MediaApplicationContainer
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ import com.google.android.horologist.media.ui.screens.DefaultPlayerScreenControl
import com.google.android.horologist.media.ui.screens.PlayerScreen
import com.google.android.horologist.media.ui.state.PlayerUiState
import com.google.android.horologist.media.ui.state.PlayerViewModel
import com.google.android.horologist.mediasample.domain.Settings
import com.google.android.horologist.mediasample.domain.model.Settings

@Composable
fun UampMediaPlayerScreen(
Expand Down
Loading