diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 07f6287..b1f35e9 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -68,6 +68,7 @@ android { dependencies { implementation(project(":feature:home")) implementation(project(":feature:photo")) + implementation(project(":feature:video")) implementation(project(":feature:contactme")) implementation(project(":core:designsystem")) diff --git a/app/src/main/java/com/wei/picquest/navigation/PqNavHost.kt b/app/src/main/java/com/wei/picquest/navigation/PqNavHost.kt index d9dd5de..b907916 100644 --- a/app/src/main/java/com/wei/picquest/navigation/PqNavHost.kt +++ b/app/src/main/java/com/wei/picquest/navigation/PqNavHost.kt @@ -10,6 +10,7 @@ import com.wei.picquest.feature.home.home.navigation.homeGraph import com.wei.picquest.feature.home.home.navigation.homeRoute import com.wei.picquest.feature.photo.photolibrary.navigation.photoLibraryGraph import com.wei.picquest.feature.photo.photosearch.navigation.photoSearchGraph +import com.wei.picquest.feature.video.videolibrary.navigation.videoLibraryGraph import com.wei.picquest.ui.PqAppState /** @@ -45,6 +46,9 @@ fun PqNavHost( photoLibraryGraph(navController = navController) }, ) + videoLibraryGraph( + navController = navController, + ) contactMeGraph( navController = navController, contentType = contentType, diff --git a/app/src/main/java/com/wei/picquest/navigation/TopLevelDestination.kt b/app/src/main/java/com/wei/picquest/navigation/TopLevelDestination.kt index 751c06e..6c081e5 100644 --- a/app/src/main/java/com/wei/picquest/navigation/TopLevelDestination.kt +++ b/app/src/main/java/com/wei/picquest/navigation/TopLevelDestination.kt @@ -27,6 +27,12 @@ enum class TopLevelDestination( iconTextId = R.string.photo, titleTextId = R.string.photo, ), + VIDEO( + selectedIcon = PqIcons.VideoLibrary, + unselectedIcon = PqIcons.VideoLibraryBorder, + iconTextId = R.string.video, + titleTextId = R.string.video, + ), CONTACT_ME( selectedIcon = PqIcons.ContactMe, unselectedIcon = PqIcons.ContactMeBorder, diff --git a/app/src/main/java/com/wei/picquest/ui/PqAppState.kt b/app/src/main/java/com/wei/picquest/ui/PqAppState.kt index 7928336..2dc2bff 100644 --- a/app/src/main/java/com/wei/picquest/ui/PqAppState.kt +++ b/app/src/main/java/com/wei/picquest/ui/PqAppState.kt @@ -31,6 +31,8 @@ import com.wei.picquest.feature.home.home.navigation.homeRoute import com.wei.picquest.feature.home.home.navigation.navigateToHome import com.wei.picquest.feature.photo.photosearch.navigation.navigateToPhotoSearch import com.wei.picquest.feature.photo.photosearch.navigation.photoSearchRoute +import com.wei.picquest.feature.video.videolibrary.navigation.navigateToVideoLibrary +import com.wei.picquest.feature.video.videolibrary.navigation.videoLibraryRoute import com.wei.picquest.navigation.TopLevelDestination import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.SharingStarted @@ -154,6 +156,7 @@ class PqAppState( @Composable get() = when (currentDestination?.route) { homeRoute -> TopLevelDestination.HOME photoSearchRoute -> TopLevelDestination.PHOTO + videoLibraryRoute -> TopLevelDestination.VIDEO contactMeRoute -> TopLevelDestination.CONTACT_ME else -> null } @@ -206,6 +209,10 @@ class PqAppState( topLevelNavOptions, ) + TopLevelDestination.VIDEO -> navController.navigateToVideoLibrary( + topLevelNavOptions, + ) + TopLevelDestination.CONTACT_ME -> navController.navigateToContactMe( topLevelNavOptions, ) diff --git a/app/src/main/res/values-zh-rTW/strings.xml b/app/src/main/res/values-zh-rTW/strings.xml index 27cda33..02387f9 100644 --- a/app/src/main/res/values-zh-rTW/strings.xml +++ b/app/src/main/res/values-zh-rTW/strings.xml @@ -5,4 +5,5 @@ 照片 首頁 聯絡我 + 影片 \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index d34aeec..b29b70a 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -4,6 +4,7 @@ Photo Home Contact Me + Video PqBottomBar diff --git a/core/data/src/main/java/com/wei/picquest/core/data/model/VideoDetail.kt b/core/data/src/main/java/com/wei/picquest/core/data/model/VideoDetail.kt new file mode 100644 index 0000000..d45241d --- /dev/null +++ b/core/data/src/main/java/com/wei/picquest/core/data/model/VideoDetail.kt @@ -0,0 +1,67 @@ +package com.wei.picquest.core.data.model + +import com.wei.picquest.core.network.model.NetworkVideoDetail +import com.wei.picquest.core.network.model.NetworkVideoDetailSize +import com.wei.picquest.core.network.model.NetworkVideoStreams + +data class VideoDetail( + val id: Int, + val pageURL: String, + val type: String, + val tags: String, + val duration: Int, + val pictureId: String, + val videos: VideoStreams, + val views: Int, + val downloads: Int, + val likes: Int, + val comments: Int, + val userId: Int, + val user: String, + val userImageURL: String, +) + +data class VideoStreams( + val large: VideoDetailSize, + val medium: VideoDetailSize, + val small: VideoDetailSize, + val tiny: VideoDetailSize, +) + +data class VideoDetailSize( + val url: String, + val width: Int, + val height: Int, + val size: Long, +) + +fun NetworkVideoDetail.asExternalModel() = VideoDetail( + id = this.id, + pageURL = this.pageURL, + type = this.type, + tags = this.tags, + duration = this.duration, + pictureId = this.pictureId, + videos = this.videos.asExternalModel(), + views = this.views, + downloads = this.downloads, + likes = this.likes, + comments = this.comments, + userId = this.userId, + user = this.user, + userImageURL = this.userImageURL, +) + +fun NetworkVideoStreams.asExternalModel() = VideoStreams( + large = this.large.asExternalModel(), + medium = this.medium.asExternalModel(), + small = this.small.asExternalModel(), + tiny = this.tiny.asExternalModel(), +) + +fun NetworkVideoDetailSize.asExternalModel() = VideoDetailSize( + url = this.url, + width = this.width, + height = this.height, + size = this.size, +) diff --git a/core/data/src/main/java/com/wei/picquest/core/data/repository/DefaultSearchImagesRepository.kt b/core/data/src/main/java/com/wei/picquest/core/data/repository/DefaultSearchImagesRepository.kt index 3f922cd..d0b41a8 100644 --- a/core/data/src/main/java/com/wei/picquest/core/data/repository/DefaultSearchImagesRepository.kt +++ b/core/data/src/main/java/com/wei/picquest/core/data/repository/DefaultSearchImagesRepository.kt @@ -7,7 +7,7 @@ import androidx.paging.map import com.wei.picquest.core.data.model.ImageDetail import com.wei.picquest.core.data.model.asExternalModel import com.wei.picquest.core.network.PqNetworkDataSource -import com.wei.picquest.core.network.pagingsource.PixabayPagingSource +import com.wei.picquest.core.network.pagingsource.PixabayImagePagingSource import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.map import javax.inject.Inject @@ -23,7 +23,7 @@ class DefaultSearchImagesRepository @Inject constructor( prefetchDistance = 5, enablePlaceholders = false, ), - pagingSourceFactory = { PixabayPagingSource(pqNetworkDataSource, query) }, + pagingSourceFactory = { PixabayImagePagingSource(pqNetworkDataSource, query) }, ).flow.map { pagingData -> pagingData.map { it.asExternalModel() } } diff --git a/core/data/src/main/java/com/wei/picquest/core/data/repository/DefaultSearchVideoRepository.kt b/core/data/src/main/java/com/wei/picquest/core/data/repository/DefaultSearchVideoRepository.kt new file mode 100644 index 0000000..8f75180 --- /dev/null +++ b/core/data/src/main/java/com/wei/picquest/core/data/repository/DefaultSearchVideoRepository.kt @@ -0,0 +1,31 @@ +package com.wei.picquest.core.data.repository + +import androidx.paging.Pager +import androidx.paging.PagingConfig +import androidx.paging.PagingData +import androidx.paging.map +import com.wei.picquest.core.data.model.VideoDetail +import com.wei.picquest.core.data.model.asExternalModel +import com.wei.picquest.core.network.PqNetworkDataSource +import com.wei.picquest.core.network.pagingsource.PixabayVideoPagingSource +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.map +import javax.inject.Inject + +class DefaultSearchVideoRepository @Inject constructor( + private val pqNetworkDataSource: PqNetworkDataSource, +) : SearchVideoRepository { + + override suspend fun getSearchVideo(query: String): Flow> { + return Pager( + config = PagingConfig( + pageSize = 20, + prefetchDistance = 5, + enablePlaceholders = false, + ), + pagingSourceFactory = { PixabayVideoPagingSource(pqNetworkDataSource, query) }, + ).flow.map { pagingData -> + pagingData.map { it.asExternalModel() } + } + } +} diff --git a/core/data/src/main/java/com/wei/picquest/core/data/repository/SearchVideoRepository.kt b/core/data/src/main/java/com/wei/picquest/core/data/repository/SearchVideoRepository.kt new file mode 100644 index 0000000..cda6f63 --- /dev/null +++ b/core/data/src/main/java/com/wei/picquest/core/data/repository/SearchVideoRepository.kt @@ -0,0 +1,10 @@ +package com.wei.picquest.core.data.repository + +import androidx.paging.PagingData +import com.wei.picquest.core.data.model.VideoDetail +import kotlinx.coroutines.flow.Flow + +interface SearchVideoRepository { + + suspend fun getSearchVideo(query: String): Flow> +} diff --git a/core/designsystem/src/main/java/com/wei/picquest/core/designsystem/icon/PqIcons.kt b/core/designsystem/src/main/java/com/wei/picquest/core/designsystem/icon/PqIcons.kt index 1f4b5c5..de09ede 100644 --- a/core/designsystem/src/main/java/com/wei/picquest/core/designsystem/icon/PqIcons.kt +++ b/core/designsystem/src/main/java/com/wei/picquest/core/designsystem/icon/PqIcons.kt @@ -6,6 +6,7 @@ import androidx.compose.material.icons.outlined.Info import androidx.compose.material.icons.outlined.PhotoLibrary import androidx.compose.material.icons.outlined.SupportAgent import androidx.compose.material.icons.outlined.Upcoming +import androidx.compose.material.icons.outlined.VideoLibrary import androidx.compose.material.icons.rounded.Add import androidx.compose.material.icons.rounded.ArrowBack import androidx.compose.material.icons.rounded.ArrowBackIosNew @@ -25,6 +26,7 @@ import androidx.compose.material.icons.rounded.Settings import androidx.compose.material.icons.rounded.Star import androidx.compose.material.icons.rounded.SupportAgent import androidx.compose.material.icons.rounded.Upcoming +import androidx.compose.material.icons.rounded.VideoLibrary import androidx.compose.ui.graphics.vector.ImageVector /** @@ -46,6 +48,8 @@ object PqIcons { val ContactMeBorder = Icons.Outlined.SupportAgent val PhotoLibrary = Icons.Rounded.PhotoLibrary val PhotoLibraryBorder = Icons.Outlined.PhotoLibrary + val VideoLibrary = Icons.Rounded.VideoLibrary + val VideoLibraryBorder = Icons.Outlined.VideoLibrary val Phone = Icons.Rounded.Phone val Upcoming = Icons.Rounded.Upcoming val UpcomingBorder = Icons.Outlined.Upcoming diff --git a/core/network/src/main/java/com/wei/picquest/core/network/PqNetworkDataSource.kt b/core/network/src/main/java/com/wei/picquest/core/network/PqNetworkDataSource.kt index 2ddc85c..8f71795 100644 --- a/core/network/src/main/java/com/wei/picquest/core/network/PqNetworkDataSource.kt +++ b/core/network/src/main/java/com/wei/picquest/core/network/PqNetworkDataSource.kt @@ -1,6 +1,7 @@ package com.wei.picquest.core.network import com.wei.picquest.core.network.model.NetworkSearchImages +import com.wei.picquest.core.network.model.NetworkSearchVideos /** * Interface representing network calls to the PicQuest backend @@ -8,4 +9,6 @@ import com.wei.picquest.core.network.model.NetworkSearchImages interface PqNetworkDataSource { suspend fun searchImages(query: String, page: Int, perPage: Int): NetworkSearchImages + + suspend fun searchVideos(query: String, page: Int, perPage: Int): NetworkSearchVideos } diff --git a/core/network/src/main/java/com/wei/picquest/core/network/model/NetworkSearchImages.kt b/core/network/src/main/java/com/wei/picquest/core/network/model/NetworkSearchImages.kt index 3394a5b..a2e82bf 100644 --- a/core/network/src/main/java/com/wei/picquest/core/network/model/NetworkSearchImages.kt +++ b/core/network/src/main/java/com/wei/picquest/core/network/model/NetworkSearchImages.kt @@ -4,7 +4,7 @@ import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable /** - * Network representation of [SearchImages] when fetched from / + * Network representation of [NetworkSearchImages] when fetched from / */ @Serializable data class NetworkSearchImages( diff --git a/core/network/src/main/java/com/wei/picquest/core/network/model/NetworkSearchVideos.kt b/core/network/src/main/java/com/wei/picquest/core/network/model/NetworkSearchVideos.kt new file mode 100644 index 0000000..2381156 --- /dev/null +++ b/core/network/src/main/java/com/wei/picquest/core/network/model/NetworkSearchVideos.kt @@ -0,0 +1,73 @@ +package com.wei.picquest.core.network.model + +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +/** + * Network representation of [NetworkSearchVideos] when fetched from /videos/ + */ +@Serializable +data class NetworkSearchVideos( + @SerialName("total") + val total: Int, + @SerialName("totalHits") + val totalHits: Int, + @SerialName("hits") + val hits: List, +) + +@Serializable +data class NetworkVideoDetail( + @SerialName("id") + val id: Int, + @SerialName("pageURL") + val pageURL: String, + @SerialName("type") + val type: String, + @SerialName("tags") + val tags: String, + @SerialName("duration") + val duration: Int, + @SerialName("picture_id") + val pictureId: String, + @SerialName("videos") + val videos: NetworkVideoStreams, + @SerialName("views") + val views: Int, + @SerialName("downloads") + val downloads: Int, + @SerialName("likes") + val likes: Int, + @SerialName("comments") + val comments: Int, + @SerialName("user_id") + val userId: Int, + @SerialName("user") + val user: String, + @SerialName("userImageURL") + val userImageURL: String, +) + +@Serializable +data class NetworkVideoStreams( + @SerialName("large") + val large: NetworkVideoDetailSize, + @SerialName("medium") + val medium: NetworkVideoDetailSize, + @SerialName("small") + val small: NetworkVideoDetailSize, + @SerialName("tiny") + val tiny: NetworkVideoDetailSize, +) + +@Serializable +data class NetworkVideoDetailSize( + @SerialName("url") + val url: String, + @SerialName("width") + val width: Int, + @SerialName("height") + val height: Int, + @SerialName("size") + val size: Long, +) diff --git a/core/network/src/main/java/com/wei/picquest/core/network/pagingsource/PixabayPagingSource.kt b/core/network/src/main/java/com/wei/picquest/core/network/pagingsource/PixabayImagePagingSource.kt similarity index 97% rename from core/network/src/main/java/com/wei/picquest/core/network/pagingsource/PixabayPagingSource.kt rename to core/network/src/main/java/com/wei/picquest/core/network/pagingsource/PixabayImagePagingSource.kt index a77c907..290ed18 100644 --- a/core/network/src/main/java/com/wei/picquest/core/network/pagingsource/PixabayPagingSource.kt +++ b/core/network/src/main/java/com/wei/picquest/core/network/pagingsource/PixabayImagePagingSource.kt @@ -5,7 +5,7 @@ import androidx.paging.PagingState import com.wei.picquest.core.network.PqNetworkDataSource import com.wei.picquest.core.network.model.NetworkImageDetail -class PixabayPagingSource( +class PixabayImagePagingSource( private val pqNetworkDataSource: PqNetworkDataSource, private val query: String, ) : PagingSource() { diff --git a/core/network/src/main/java/com/wei/picquest/core/network/pagingsource/PixabayVideoPagingSource.kt b/core/network/src/main/java/com/wei/picquest/core/network/pagingsource/PixabayVideoPagingSource.kt new file mode 100644 index 0000000..c8603aa --- /dev/null +++ b/core/network/src/main/java/com/wei/picquest/core/network/pagingsource/PixabayVideoPagingSource.kt @@ -0,0 +1,37 @@ +package com.wei.picquest.core.network.pagingsource + +import androidx.paging.PagingSource +import androidx.paging.PagingState +import com.wei.picquest.core.network.PqNetworkDataSource +import com.wei.picquest.core.network.model.NetworkVideoDetail + +class PixabayVideoPagingSource( + private val pqNetworkDataSource: PqNetworkDataSource, + private val query: String, +) : PagingSource() { + + override suspend fun load(params: LoadParams): LoadResult { + try { + val currentPage = params.key ?: 1 + val response = pqNetworkDataSource.searchVideos( + query = query, + page = currentPage, + perPage = 20, + ) + + val endOfPaginationReached = response.hits.isEmpty() + + return LoadResult.Page( + data = response.hits, + prevKey = if (currentPage == 1) null else currentPage - 1, + nextKey = if (endOfPaginationReached) null else currentPage + 1, + ) + } catch (exception: Exception) { + return LoadResult.Error(exception) + } + } + + override fun getRefreshKey(state: PagingState): Int? { + return state.anchorPosition + } +} diff --git a/core/network/src/main/java/com/wei/picquest/core/network/retrofit/RetrofitPqNetwork.kt b/core/network/src/main/java/com/wei/picquest/core/network/retrofit/RetrofitPqNetwork.kt index 6315504..d0148d2 100644 --- a/core/network/src/main/java/com/wei/picquest/core/network/retrofit/RetrofitPqNetwork.kt +++ b/core/network/src/main/java/com/wei/picquest/core/network/retrofit/RetrofitPqNetwork.kt @@ -4,6 +4,7 @@ import com.jakewharton.retrofit2.converter.kotlinx.serialization.asConverterFact import com.wei.core.network.BuildConfig import com.wei.picquest.core.network.PqNetworkDataSource import com.wei.picquest.core.network.model.NetworkSearchImages +import com.wei.picquest.core.network.model.NetworkSearchVideos import kotlinx.serialization.json.Json import okhttp3.Call import okhttp3.MediaType.Companion.toMediaType @@ -32,6 +33,19 @@ interface RetrofitPixabayApi { @Query("perPage") perPage: Int, // Add more parameters as needed ): NetworkSearchImages + + /** + * https://pixabay.com/api/videos/?key=${api key}&q=yellow+flowers + */ + @GET("/videos/") + suspend fun searchVideos( + @Query("key") apiKey: String = API_KEY, + @Query("q") query: String, + @Query("video_type") videoType: String = "film", + @Query("page") page: Int, + @Query("perPage") perPage: Int, + // Add more parameters as needed + ): NetworkSearchVideos } /** @@ -55,4 +69,8 @@ class RetrofitPqNetwork @Inject constructor( override suspend fun searchImages(query: String, page: Int, perPage: Int): NetworkSearchImages { return pixabayApi.searchImages(query = query, page = page, perPage = perPage) } + + override suspend fun searchVideos(query: String, page: Int, perPage: Int): NetworkSearchVideos { + return pixabayApi.searchVideos(query = query, page = page, perPage = perPage) + } } diff --git a/feature/video/.gitignore b/feature/video/.gitignore new file mode 100644 index 0000000..42afabf --- /dev/null +++ b/feature/video/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/feature/video/build.gradle.kts b/feature/video/build.gradle.kts new file mode 100644 index 0000000..0bb8a1f --- /dev/null +++ b/feature/video/build.gradle.kts @@ -0,0 +1,16 @@ +plugins { + alias(libs.plugins.pq.android.feature) + alias(libs.plugins.pq.android.library.compose) + alias(libs.plugins.pq.android.hilt) +} + +android { + namespace = "com.wei.picquest.feature.video" +} + +dependencies { + // Navigation + implementation(libs.media3.exoplayer) + implementation(libs.media3.exoplayer.dash) + implementation(libs.media3.ui) +} \ No newline at end of file diff --git a/feature/video/src/main/AndroidManifest.xml b/feature/video/src/main/AndroidManifest.xml new file mode 100644 index 0000000..a5918e6 --- /dev/null +++ b/feature/video/src/main/AndroidManifest.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/feature/video/src/main/java/com/wei/picquest/feature/video/videolibrary/VideoLibraryScreen.kt b/feature/video/src/main/java/com/wei/picquest/feature/video/videolibrary/VideoLibraryScreen.kt new file mode 100644 index 0000000..ac331d7 --- /dev/null +++ b/feature/video/src/main/java/com/wei/picquest/feature/video/videolibrary/VideoLibraryScreen.kt @@ -0,0 +1,112 @@ +package com.wei.picquest.feature.video.videolibrary + +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.WindowInsets +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.safeDrawing +import androidx.compose.foundation.layout.windowInsetsBottomHeight +import androidx.compose.foundation.layout.windowInsetsTopHeight +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Surface +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.semantics.contentDescription +import androidx.compose.ui.semantics.semantics +import androidx.navigation.NavController +import com.wei.picquest.core.designsystem.component.FunctionalityNotAvailablePopup +import com.wei.picquest.core.designsystem.component.ThemePreviews +import com.wei.picquest.core.designsystem.theme.PqTheme + +/** + * + * UI 事件決策樹 + * 下圖顯示了一個決策樹,用於查找處理特定事件用例的最佳方法。 + * + * ┌───────┐ + * │ Start │ + * └───┬───┘ + * ↓ + * ┌───────────────────────────────────┐ + * │ Where is event originated? │ + * └──────┬─────────────────────┬──────┘ + * ↓ ↓ + * UI ViewModel + * │ │ + * ┌─────────────────────────┐ ┌───────────────┐ + * │ When the event requires │ │ Update the UI │ + * │ ... │ │ State │ + * └─┬─────────────────────┬─┘ └───────────────┘ + * ↓ ↓ + * Business logic UI behavior logic + * │ │ + * ┌─────────────────────────────────┐ ┌──────────────────────────────────────┐ + * │ Delegate the business logic to │ │ Modify the UI element state in the │ + * │ the ViewModel │ │ UI directly │ + * └─────────────────────────────────┘ └──────────────────────────────────────┘ + * + * + */ +@Composable +internal fun VideoLibraryRoute( + navController: NavController, +) { + VideoLibraryScreen() +} + +@Composable +internal fun VideoLibraryScreen( + withTopSpacer: Boolean = true, + withBottomSpacer: Boolean = true, +) { + val showPopup = remember { mutableStateOf(false) } + + if (showPopup.value) { + FunctionalityNotAvailablePopup( + onDismiss = { + showPopup.value = false + }, + ) + } + + Surface( + modifier = Modifier.fillMaxSize(), + color = MaterialTheme.colorScheme.background, + ) { + Column( + horizontalAlignment = Alignment.CenterHorizontally, + ) { + if (withTopSpacer) { + Spacer(Modifier.windowInsetsTopHeight(WindowInsets.safeDrawing)) + } + + Column { + Spacer(modifier = Modifier.weight(1f)) + Text( + text = "Screen not available \uD83D\uDE48", + color = MaterialTheme.colorScheme.error, + style = MaterialTheme.typography.headlineMedium, + modifier = Modifier + .semantics { contentDescription = "" }, + ) + Spacer(modifier = Modifier.weight(1f)) + } + + if (withBottomSpacer) { + Spacer(Modifier.windowInsetsBottomHeight(WindowInsets.safeDrawing)) + } + } + } +} + +@ThemePreviews +@Composable +fun VideoLibraryScreenPreview() { + PqTheme { + VideoLibraryScreen() + } +} diff --git a/feature/video/src/main/java/com/wei/picquest/feature/video/videolibrary/navigation/VideoLibraryNavigation.kt b/feature/video/src/main/java/com/wei/picquest/feature/video/videolibrary/navigation/VideoLibraryNavigation.kt new file mode 100644 index 0000000..caeef7f --- /dev/null +++ b/feature/video/src/main/java/com/wei/picquest/feature/video/videolibrary/navigation/VideoLibraryNavigation.kt @@ -0,0 +1,23 @@ +package com.wei.picquest.feature.video.videolibrary.navigation + +import androidx.navigation.NavController +import androidx.navigation.NavGraphBuilder +import androidx.navigation.NavOptions +import androidx.navigation.compose.composable +import com.wei.picquest.feature.video.videolibrary.VideoLibraryRoute + +const val videoLibraryRoute = "video_library_route" + +fun NavController.navigateToVideoLibrary(navOptions: NavOptions? = null) { + this.navigate(videoLibraryRoute, navOptions) +} + +fun NavGraphBuilder.videoLibraryGraph( + navController: NavController, +) { + composable(route = videoLibraryRoute) { + VideoLibraryRoute( + navController = navController, + ) + } +} diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 5c78329..42725cf 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -41,6 +41,7 @@ robolectric = "4.10.3" roborazzi = "1.5.0-alpha-2" paging = "3.2.1" pagingCompose = "3.3.0-alpha02" +media = "1.2.0" [libraries] @@ -177,6 +178,11 @@ paging-runtime = { group = "androidx.paging", name = "paging-runtime", version.r paging-common = { group = "androidx.paging", name = "paging-common", version.ref = "paging" } paging-compose = { group = "androidx.paging", name = "paging-compose", version.ref = "pagingCompose" } +# media3 +media3-exoplayer = { group = "androidx.media3", name = "media3-exoplayer", version.ref = "media" } +media3-exoplayer-dash = { group = "androidx.media3", name = "media3-exoplayer-dash", version.ref = "media" } +media3-ui = { group = "androidx.media3", name = "media3-ui", version.ref = "media" } + # Dependencies of the included build-logic android-gradlePlugin = { group = "com.android.tools.build", name = "gradle", version.ref = "androidGradlePlugin" } kotlin-gradlePlugin = { group = "org.jetbrains.kotlin", name = "kotlin-gradle-plugin", version.ref = "kotlin" } diff --git a/settings.gradle.kts b/settings.gradle.kts index 0648db4..8ced8de 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -32,3 +32,4 @@ include(":ui-test-hilt-manifest") include(":feature:home") include(":feature:photo") include(":feature:contactme") +include(":feature:video")