mirror of
https://github.com/owenlejeune/TVTime.git
synced 2025-11-13 15:22:48 -05:00
show scrolling list of movies
This commit is contained in:
@@ -38,6 +38,7 @@ android {
|
||||
}
|
||||
kotlinOptions {
|
||||
jvmTarget = '1.8'
|
||||
useIR = true
|
||||
}
|
||||
buildFeatures {
|
||||
compose true
|
||||
@@ -55,6 +56,7 @@ android {
|
||||
dependencies {
|
||||
|
||||
implementation Dependencies.AndroidX.ktxCore
|
||||
implementation Dependencies.AndroidX.paging
|
||||
|
||||
implementation Dependencies.Compose.ui
|
||||
implementation Dependencies.Compose.material3
|
||||
@@ -63,6 +65,7 @@ dependencies {
|
||||
implementation Dependencies.Compose.activity
|
||||
implementation Dependencies.Compose.accompanistSystemUi
|
||||
implementation Dependencies.Compose.navigation
|
||||
implementation Dependencies.Compose.paging
|
||||
|
||||
implementation Dependencies.Lifecycle.runtime
|
||||
|
||||
@@ -74,6 +77,8 @@ dependencies {
|
||||
|
||||
implementation Dependencies.DI.koin
|
||||
|
||||
implementation Dependencies.Coil.coil
|
||||
|
||||
testImplementation Dependencies.Testing.junit
|
||||
androidTestImplementation Dependencies.Testing.androidXJunit
|
||||
androidTestImplementation Dependencies.Testing.espressoCore
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
package com.owenlejeune.tvtime.api
|
||||
|
||||
class QueryParam(val key: String, val param: String) {
|
||||
|
||||
constructor(key: String, param: Any): this(key, param.toString())
|
||||
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
package com.owenlejeune.tvtime.api.tmdb
|
||||
|
||||
import com.owenlejeune.tvtime.api.tmdb.model.PopularMoviesResponse
|
||||
import retrofit2.Call
|
||||
import retrofit2.http.GET
|
||||
import retrofit2.http.Query
|
||||
|
||||
interface MoviesApi {
|
||||
|
||||
@GET("movie/popular")
|
||||
fun getPopularMovies(@Query("page") page: Int = 1): Call<PopularMoviesResponse>
|
||||
// suspend fun getPopularMovies(@Query("page") page: Int = 1): PopularMoviesResponse
|
||||
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
package com.owenlejeune.tvtime.api.tmdb
|
||||
|
||||
import com.owenlejeune.tvtime.api.tmdb.model.PopularMoviesResponse
|
||||
import org.koin.core.component.KoinComponent
|
||||
import retrofit2.Call
|
||||
import retrofit2.Callback
|
||||
import retrofit2.Response
|
||||
|
||||
class MoviesService: KoinComponent {
|
||||
|
||||
private val service by lazy { TmdbClient().createMovieService() }
|
||||
|
||||
fun getPopularMovies(page: Int = 1, callback: (isSuccessful: Boolean, response: PopularMoviesResponse?) -> Unit) {
|
||||
service.getPopularMovies(page = page).enqueue(object : Callback<PopularMoviesResponse> {
|
||||
override fun onResponse(
|
||||
call: Call<PopularMoviesResponse>,
|
||||
response: Response<PopularMoviesResponse>
|
||||
) {
|
||||
response.body()?.let { body ->
|
||||
callback.invoke(true, body)
|
||||
} ?: run {
|
||||
callback.invoke(false, null)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onFailure(call: Call<PopularMoviesResponse>, t: Throwable) {
|
||||
callback.invoke(false, null)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
package com.owenlejeune.tvtime.api.tmdb
|
||||
|
||||
import com.owenlejeune.tvtime.BuildConfig
|
||||
import com.owenlejeune.tvtime.api.Client
|
||||
import com.owenlejeune.tvtime.api.QueryParam
|
||||
import com.owenlejeune.tvtime.extensions.addQueryParams
|
||||
import okhttp3.Interceptor
|
||||
import okhttp3.Response
|
||||
import org.koin.core.component.KoinComponent
|
||||
import org.koin.core.component.inject
|
||||
import org.koin.core.parameter.parametersOf
|
||||
|
||||
class TmdbClient: KoinComponent {
|
||||
|
||||
companion object {
|
||||
const val BASE_URL = "https://api.themoviedb.org/3/"
|
||||
}
|
||||
|
||||
private val client: Client by inject { parametersOf(BASE_URL) }
|
||||
|
||||
init {
|
||||
client.addInterceptor(TmdbInterceptor())
|
||||
}
|
||||
|
||||
fun createMovieService(): MoviesApi {
|
||||
return client.create(MoviesApi::class.java)
|
||||
}
|
||||
|
||||
private inner class TmdbInterceptor: Interceptor {
|
||||
override fun intercept(chain: Interceptor.Chain): Response {
|
||||
val apiParam = QueryParam("api_key", BuildConfig.TMDB_ApiKey)
|
||||
|
||||
val request = chain.addQueryParams(apiParam)
|
||||
|
||||
return chain.proceed(request)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
package com.owenlejeune.tvtime.api.tmdb.model
|
||||
|
||||
import com.google.gson.annotations.SerializedName
|
||||
|
||||
class PopularMovie(
|
||||
@SerializedName("poster_path") val posterPath: String?,
|
||||
@SerializedName("title") val title: String
|
||||
)
|
||||
@@ -0,0 +1,9 @@
|
||||
package com.owenlejeune.tvtime.api.tmdb.model
|
||||
|
||||
import com.google.gson.annotations.SerializedName
|
||||
|
||||
data class PopularMoviesResponse(
|
||||
@SerializedName("total_results") val count: Int,
|
||||
@SerializedName("page") val page: Int,
|
||||
@SerializedName("results") val movies: List<PopularMovie>
|
||||
)
|
||||
@@ -0,0 +1,38 @@
|
||||
//package com.owenlejeune.tvtime.api.tmdb.paging
|
||||
//
|
||||
//import androidx.paging.PagingSource
|
||||
//import androidx.paging.PagingState
|
||||
//import com.owenlejeune.tvtime.api.tmdb.TmdbClient
|
||||
//import com.owenlejeune.tvtime.api.tmdb.model.PopularMovie
|
||||
//import retrofit2.HttpException
|
||||
//import java.io.IOException
|
||||
//
|
||||
//class PopularMovieSource: PagingSource<Int, PopularMovie>() {
|
||||
//
|
||||
// companion object {
|
||||
// const val MIN_PAGE = 1
|
||||
// const val MAX_PAGE = 1000
|
||||
// }
|
||||
//
|
||||
// private val movieService by lazy { TmdbClient().createMovieService() }
|
||||
//
|
||||
// override fun getRefreshKey(state: PagingState<Int, PopularMovie>): Int? {
|
||||
// return state.anchorPosition
|
||||
// }
|
||||
//
|
||||
// override suspend fun load(params: LoadParams<Int>): LoadResult<Int, PopularMovie> {
|
||||
// return try {
|
||||
// val nextPage = params.key ?: 1
|
||||
// val movieList = movieService.getPopularMovies(page = nextPage)
|
||||
// LoadResult.Page(
|
||||
// data = movieList.movies,
|
||||
// prevKey = if (nextPage == MIN_PAGE) null else nextPage - 1,
|
||||
// nextKey = if (movieList.count == 0 || nextPage > MAX_PAGE) null else movieList.page + 1
|
||||
// )
|
||||
// } catch (exception: IOException) {
|
||||
// return LoadResult.Error(exception)
|
||||
// } catch (exception: HttpException) {
|
||||
// return LoadResult.Error(exception)
|
||||
// }
|
||||
// }
|
||||
//}
|
||||
@@ -0,0 +1,17 @@
|
||||
//package com.owenlejeune.tvtime.api.tmdb.viewmodel
|
||||
//
|
||||
//import androidx.lifecycle.ViewModel
|
||||
//import androidx.lifecycle.viewModelScope
|
||||
//import androidx.paging.Pager
|
||||
//import androidx.paging.PagingConfig
|
||||
//import androidx.paging.PagingData
|
||||
//import androidx.paging.cachedIn
|
||||
//import com.owenlejeune.tvtime.api.tmdb.model.PopularMovie
|
||||
//import com.owenlejeune.tvtime.api.tmdb.paging.PopularMovieSource
|
||||
//import kotlinx.coroutines.flow.Flow
|
||||
//
|
||||
//class PopularMovieViewModel: ViewModel() {
|
||||
// val moviePage: Flow<PagingData<PopularMovie>> = Pager(PagingConfig(pageSize = PopularMovieSource.MAX_PAGE)) {
|
||||
// PopularMovieSource()
|
||||
// }.flow.cachedIn(viewModelScope)
|
||||
//}
|
||||
@@ -0,0 +1,27 @@
|
||||
package com.owenlejeune.tvtime.extensions
|
||||
|
||||
import androidx.compose.foundation.ExperimentalFoundationApi
|
||||
import androidx.compose.foundation.lazy.LazyGridScope
|
||||
import androidx.compose.foundation.lazy.LazyItemScope
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.paging.compose.LazyPagingItems
|
||||
|
||||
@OptIn(ExperimentalFoundationApi::class)
|
||||
fun <T: Any> LazyGridScope.items(
|
||||
lazyPagingItems: LazyPagingItems<T>,
|
||||
itemContent: @Composable LazyItemScope.(value: T?) -> Unit
|
||||
) {
|
||||
items(lazyPagingItems.itemCount) { index ->
|
||||
itemContent(lazyPagingItems[index])
|
||||
}
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalFoundationApi::class)
|
||||
fun <T: Any> LazyGridScope.items(
|
||||
items: List<T>,
|
||||
itemContent: @Composable (value: T?) -> Unit
|
||||
) {
|
||||
items(items.size) { index ->
|
||||
itemContent(items[index])
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
package com.owenlejeune.tvtime.extensions
|
||||
|
||||
import com.owenlejeune.tvtime.api.QueryParam
|
||||
import okhttp3.Interceptor
|
||||
import okhttp3.Request
|
||||
|
||||
fun Interceptor.Chain.addQueryParams(vararg queryParams: QueryParam): Request {
|
||||
val original = request()
|
||||
val originalHttpUrl = original.url()
|
||||
|
||||
val urlBuilder = originalHttpUrl.newBuilder()
|
||||
queryParams.forEach { param ->
|
||||
urlBuilder.addQueryParameter(param.key, param.param)
|
||||
}
|
||||
val url = urlBuilder.build()
|
||||
|
||||
val requestBuilder = original.newBuilder()
|
||||
.url(url)
|
||||
|
||||
return requestBuilder.build()
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
package com.owenlejeune.tvtime.extensions
|
||||
|
||||
import android.content.Context
|
||||
|
||||
fun Float.dpToPx(context: Context): Float {
|
||||
return this * context.resources.displayMetrics.density
|
||||
}
|
||||
@@ -1,24 +1,68 @@
|
||||
package com.owenlejeune.tvtime.ui.screens
|
||||
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.wrapContentSize
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import android.widget.Toast
|
||||
import androidx.compose.foundation.ExperimentalFoundationApi
|
||||
import androidx.compose.foundation.Image
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.PaddingValues
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.lazy.GridCells
|
||||
import androidx.compose.foundation.lazy.LazyVerticalGrid
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.unit.dp
|
||||
import coil.compose.rememberImagePainter
|
||||
import coil.transform.RoundedCornersTransformation
|
||||
import com.owenlejeune.tvtime.R
|
||||
import com.owenlejeune.tvtime.api.tmdb.MoviesService
|
||||
import com.owenlejeune.tvtime.api.tmdb.model.PopularMovie
|
||||
import com.owenlejeune.tvtime.extensions.dpToPx
|
||||
import com.owenlejeune.tvtime.extensions.items
|
||||
|
||||
@OptIn(ExperimentalFoundationApi::class)
|
||||
@Composable
|
||||
fun MoviesTab() {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.wrapContentSize(Alignment.Center)
|
||||
val context = LocalContext.current
|
||||
// val moviesViewModel = viewModel(PopularMovieViewModel::class.java)
|
||||
// val moviesList = moviesViewModel.moviePage
|
||||
// val movieListItems: LazyPagingItems<PopularMovie> = moviesList.collectAsLazyPagingItems()
|
||||
val moviesList = remember { mutableStateOf(emptyList<PopularMovie>()) }
|
||||
val service = MoviesService()
|
||||
service.getPopularMovies { isSuccessful, response ->
|
||||
if (isSuccessful) {
|
||||
moviesList.value = response!!.movies
|
||||
}
|
||||
}
|
||||
|
||||
LazyVerticalGrid(
|
||||
cells = GridCells.Fixed(count = 3),
|
||||
contentPadding = PaddingValues(8.dp)
|
||||
) {
|
||||
Text(
|
||||
text = "Movies Tab",
|
||||
color = MaterialTheme.colorScheme.onBackground
|
||||
)
|
||||
// items(movieListItems) { item ->
|
||||
items(moviesList.value) { item ->
|
||||
val poster = item?.let { i ->
|
||||
"https://image.tmdb.org/t/p/original${i.posterPath}"
|
||||
}
|
||||
Image(
|
||||
painter = rememberImagePainter(
|
||||
data = poster,
|
||||
builder = {
|
||||
transformations(RoundedCornersTransformation(5f.dpToPx(context)))
|
||||
placeholder(R.drawable.placeholder)
|
||||
}
|
||||
),
|
||||
contentDescription = item?.title,
|
||||
modifier = Modifier
|
||||
.size(190.dp)
|
||||
.padding(5.dp)
|
||||
.clickable {
|
||||
Toast.makeText(context, "${item?.title} clicked", Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
6
app/src/main/res/drawable/placeholder.xml
Normal file
6
app/src/main/res/drawable/placeholder.xml
Normal file
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:shape="rectangle">
|
||||
<corners android:radius="5dp" />
|
||||
<solid android:color="@android:color/darker_gray" />
|
||||
</shape>
|
||||
@@ -5,6 +5,7 @@ object Dependencies {
|
||||
object AndroidX {
|
||||
const val appCompat = "androidx.appcompat:appcompat:${Versions.androidx}"
|
||||
const val ktxCore = "androidx.core:core-ktx:${Versions.core_ktx}"
|
||||
const val paging = "androidx.paging:paging-common-ktx:${Versions.paging}"
|
||||
}
|
||||
|
||||
object Compose {
|
||||
@@ -16,6 +17,7 @@ object Dependencies {
|
||||
const val activity = "androidx.activity:activity-compose:${Versions.activity_compose}"
|
||||
const val accompanistSystemUi = "com.google.accompanist:accompanist-systemuicontroller:${Versions.compose_accompanist}"
|
||||
const val navigation = "androidx.navigation:navigation-compose:${Versions.compose_navigation}"
|
||||
const val paging = "androidx.paging:paging-compose:${Versions.compose_paging}"
|
||||
}
|
||||
|
||||
object Lifecycle {
|
||||
@@ -47,4 +49,8 @@ object Dependencies {
|
||||
object DI {
|
||||
const val koin = "io.insert-koin:koin-android:${Versions.koin}"
|
||||
}
|
||||
|
||||
object Coil {
|
||||
const val coil = "io.coil-kt:coil-compose:${Versions.coil}"
|
||||
}
|
||||
}
|
||||
@@ -6,6 +6,7 @@ object Versions {
|
||||
const val compose_material3 = "1.0.0-alpha04"
|
||||
const val compose_accompanist = "0.22.1-rc"
|
||||
const val compose_navigation = "2.4.0"
|
||||
const val compose_paging = "1.0.0-alpha04"
|
||||
const val gradle = "7.1.0"
|
||||
const val junit = "4.13.2"
|
||||
const val androidx_junit = "1.1.3"
|
||||
@@ -19,5 +20,7 @@ object Versions {
|
||||
const val stetho = "1.6.0"
|
||||
const val gson = "2.8.7"
|
||||
const val koin = "3.1.4"
|
||||
const val paging = "3.1.0"
|
||||
const val coil = "1.4.0"
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user