Skip to content

Commit d4a7cf4

Browse files
committed
add Collections to pager, init collections data layer
1 parent 1beb0d8 commit d4a7cf4

File tree

15 files changed

+576
-106
lines changed

15 files changed

+576
-106
lines changed

app/build.gradle

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -51,8 +51,10 @@ android {
5151

5252
dependencies {
5353

54-
implementation "com.google.accompanist:accompanist-pager:0.19.0"
55-
implementation "com.google.accompanist:accompanist-appcompat-theme:0.16.0"
54+
def accompanistVersion = '0.19.0'
55+
implementation "com.google.accompanist:accompanist-pager:$accompanistVersion"
56+
implementation "com.google.accompanist:accompanist-placeholder:$accompanistVersion"
57+
implementation "com.google.accompanist:accompanist-appcompat-theme:$accompanistVersion"
5658

5759
/*Navigation*/
5860
implementation "androidx.navigation:navigation-compose:2.4.0-alpha10"

app/src/main/java/st/slex/csplashscreen/MainActivity.kt

Lines changed: 22 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,31 +4,38 @@ import android.os.Bundle
44
import androidx.activity.ComponentActivity
55
import androidx.activity.compose.setContent
66
import androidx.activity.viewModels
7+
import androidx.compose.material.ExperimentalMaterialApi
78
import androidx.compose.material.Scaffold
89
import androidx.compose.runtime.Composable
910
import androidx.lifecycle.ViewModelProvider
1011
import androidx.navigation.NavHostController
12+
import androidx.navigation.NavType
1113
import androidx.navigation.compose.NavHost
1214
import androidx.navigation.compose.composable
1315
import androidx.navigation.compose.rememberNavController
16+
import androidx.navigation.navArgument
17+
import coil.annotation.ExperimentalCoilApi
1418
import com.google.accompanist.pager.ExperimentalPagerApi
1519
import dagger.Lazy
1620
import kotlinx.coroutines.ExperimentalCoroutinesApi
21+
import st.slex.csplashscreen.ui.collection.Collection
1722
import st.slex.csplashscreen.ui.detail.ImageDetailScreen
1823
import st.slex.csplashscreen.ui.main.MainScreen
19-
import st.slex.csplashscreen.ui.main.PhotosViewModel
24+
import st.slex.csplashscreen.ui.main.MainViewModel
2025
import st.slex.csplashscreen.ui.theme.CSplashScreenTheme
2126
import javax.inject.Inject
2227

28+
@ExperimentalCoilApi
29+
@ExperimentalMaterialApi
30+
@ExperimentalPagerApi
2331
@ExperimentalCoroutinesApi
2432
class MainActivity : ComponentActivity() {
2533

2634
@Inject
2735
lateinit var viewModelFactory: Lazy<ViewModelProvider.Factory>
2836

29-
private val photosViewModel: PhotosViewModel by viewModels { viewModelFactory.get() }
37+
private val viewModel: MainViewModel by viewModels { viewModelFactory.get() }
3038

31-
@ExperimentalPagerApi
3239
override fun onCreate(savedInstanceState: Bundle?) {
3340
appComponent.inject(this)
3441
super.onCreate(savedInstanceState)
@@ -38,28 +45,37 @@ class MainActivity : ComponentActivity() {
3845
Scaffold {
3946
NavigationComponent(
4047
navController = navController,
41-
photosViewModel = photosViewModel
48+
viewModel = viewModel
4249
)
4350
}
4451
}
4552
}
4653
}
4754
}
4855

56+
@ExperimentalCoilApi
57+
@ExperimentalMaterialApi
4958
@ExperimentalPagerApi
5059
@ExperimentalCoroutinesApi
5160
@Composable
52-
fun NavigationComponent(navController: NavHostController, photosViewModel: PhotosViewModel) {
61+
fun NavigationComponent(navController: NavHostController, viewModel: MainViewModel) {
5362
NavHost(
5463
navController = navController,
5564
startDestination = "main"
5665
) {
5766
composable("main") {
58-
MainScreen(navController, photosViewModel)
67+
MainScreen(navController, viewModel)
5968
}
6069

6170
composable("detail") {
6271
ImageDetailScreen()
6372
}
73+
74+
composable(
75+
route = "collection/{collectionId}",
76+
arguments = listOf(navArgument("collectionId") { type = NavType.StringType })
77+
) {
78+
Collection(navController, viewModel, it.arguments?.getString("collectionId").toString())
79+
}
6480
}
6581
}
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
package st.slex.csplashscreen.data.collections
2+
3+
import retrofit2.Response
4+
import retrofit2.http.GET
5+
import retrofit2.http.Path
6+
import retrofit2.http.Query
7+
import st.slex.csplashscreen.data.model.remote.collection.RemoteCollectionModel
8+
import st.slex.csplashscreen.utiles.QUERY_API_KEY
9+
import st.slex.csplashscreen.utiles.QUERY_PAGE
10+
import st.slex.csplashscreen.utiles.QUERY_PAGE_SIZE
11+
12+
interface CollectionService {
13+
@GET("{q}")
14+
suspend fun getCollections(
15+
@Path("q") query: String,
16+
@Query(QUERY_PAGE) page: Int,
17+
@Query(QUERY_PAGE_SIZE) page_size: Int,
18+
@Query(QUERY_API_KEY) api_key: String
19+
): Response<List<RemoteCollectionModel>>
20+
21+
@GET("{q1}/{q2}/{q3}")
22+
suspend fun getCollections(
23+
@Path("q1") query1: String,
24+
@Path("q2") query2: String,
25+
@Path("q3") query3: String,
26+
@Query(QUERY_PAGE) page: Int,
27+
@Query(QUERY_PAGE_SIZE) page_size: Int,
28+
@Query(QUERY_API_KEY) api_key: String
29+
): Response<List<RemoteCollectionModel>>
30+
}
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
package st.slex.csplashscreen.data.collections
2+
3+
import androidx.paging.PagingSource
4+
import androidx.paging.PagingState
5+
import dagger.assisted.Assisted
6+
import dagger.assisted.AssistedFactory
7+
import dagger.assisted.AssistedInject
8+
import retrofit2.HttpException
9+
import st.slex.csplashscreen.data.core.toCollectionModel
10+
import st.slex.csplashscreen.data.model.ui.collection.CollectionModel
11+
import st.slex.csplashscreen.utiles.API_KEY
12+
13+
class CollectionsPagingSource @AssistedInject constructor(
14+
private val service: CollectionService,
15+
@Assisted("query") private val query: List<String>
16+
) : PagingSource<Int, CollectionModel>() {
17+
18+
override fun getRefreshKey(state: PagingState<Int, CollectionModel>): Int? {
19+
val anchorPosition = state.anchorPosition ?: return null
20+
val anchorPage = state.closestPageToPosition(anchorPosition) ?: return null
21+
return anchorPage.prevKey?.plus(1) ?: anchorPage.nextKey?.minus(1)
22+
}
23+
24+
override suspend fun load(params: LoadParams<Int>): LoadResult<Int, CollectionModel> {
25+
if (query.isEmpty()) {
26+
return LoadResult.Page(emptyList(), prevKey = null, nextKey = null)
27+
}
28+
try {
29+
val pageNumber = params.key ?: INITIAL_PAGE_NUMBER
30+
val pageSize = params.loadSize
31+
32+
val response = if (query.size > 1) {
33+
service.getCollections(
34+
query[0],
35+
query[1],
36+
query[2],
37+
pageNumber,
38+
pageSize,
39+
API_KEY
40+
)
41+
} else {
42+
service.getCollections(query[0], pageNumber, pageSize, API_KEY)
43+
}
44+
45+
return if (response.isSuccessful) {
46+
val photos = response.body()!!.map {
47+
it.toCollectionModel()
48+
}
49+
val nextPageNumber = if (photos.isEmpty()) null else pageNumber + 1
50+
val prevPageNumber = if (pageNumber > 1) pageNumber - 1 else null
51+
LoadResult.Page(photos, prevPageNumber, nextPageNumber)
52+
} else {
53+
LoadResult.Error(HttpException(response))
54+
}
55+
} catch (e: HttpException) {
56+
return LoadResult.Error(e)
57+
} catch (e: Exception) {
58+
return LoadResult.Error(e)
59+
}
60+
}
61+
62+
@AssistedFactory
63+
interface Factory {
64+
fun create(@Assisted("query") query: List<String>): CollectionsPagingSource
65+
}
66+
67+
companion object {
68+
const val INITIAL_PAGE_NUMBER = 0
69+
}
70+
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
package st.slex.csplashscreen.data.collections
2+
3+
import androidx.paging.PagingSource
4+
import st.slex.csplashscreen.data.model.ui.collection.CollectionModel
5+
import javax.inject.Inject
6+
7+
interface CollectionsRepository {
8+
9+
fun queryAll(query: List<String>): PagingSource<Int, CollectionModel>
10+
11+
class Base @Inject constructor(
12+
private val collectionsPagingSourceFactory: CollectionsPagingSource.Factory
13+
) : CollectionsRepository {
14+
15+
override fun queryAll(query: List<String>): PagingSource<Int, CollectionModel> =
16+
collectionsPagingSourceFactory.create(query)
17+
}
18+
}

app/src/main/java/st/slex/csplashscreen/di/module/NetworkServiceModule.kt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package st.slex.csplashscreen.di.module
33
import dagger.Module
44
import dagger.Provides
55
import retrofit2.Retrofit
6+
import st.slex.csplashscreen.data.collections.CollectionService
67
import st.slex.csplashscreen.data.photos.PhotosService
78

89
@Module(includes = [RetrofitModule::class])
@@ -11,4 +12,8 @@ class NetworkServiceModule {
1112
@Provides
1213
fun providesPhotosService(retrofit: Retrofit): PhotosService =
1314
retrofit.create(PhotosService::class.java)
15+
16+
@Provides
17+
fun providesCollectionsService(retrofit: Retrofit): CollectionService =
18+
retrofit.create(CollectionService::class.java)
1419
}

app/src/main/java/st/slex/csplashscreen/di/module/RepositoryModule.kt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,15 @@ package st.slex.csplashscreen.di.module
22

33
import dagger.Binds
44
import dagger.Module
5+
import st.slex.csplashscreen.data.collections.CollectionsRepository
56
import st.slex.csplashscreen.data.photos.PhotosRepository
67

78
@Module
89
interface RepositoryModule {
910

1011
@Binds
1112
fun bindsPhotosRepository(repository: PhotosRepository.Base): PhotosRepository
13+
14+
@Binds
15+
fun bindsCollectionsRepository(repository: CollectionsRepository.Base): CollectionsRepository
1216
}

app/src/main/java/st/slex/csplashscreen/di/module/ViewModelModule.kt

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,14 +6,14 @@ import dagger.Module
66
import dagger.multibindings.IntoMap
77
import kotlinx.coroutines.ExperimentalCoroutinesApi
88
import st.slex.csplashscreen.di.key.ViewModelKey
9-
import st.slex.csplashscreen.ui.main.PhotosViewModel
9+
import st.slex.csplashscreen.ui.main.MainViewModel
1010

1111
@ExperimentalCoroutinesApi
1212
@Module
1313
interface ViewModelModule {
1414

1515
@IntoMap
1616
@Binds
17-
@ViewModelKey(PhotosViewModel::class)
18-
fun bindsPhotosViewModel(viewModel: PhotosViewModel): ViewModel
17+
@ViewModelKey(MainViewModel::class)
18+
fun bindsPhotosViewModel(viewModel: MainViewModel): ViewModel
1919
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
package st.slex.csplashscreen.ui.collection
2+
3+
import androidx.compose.material.Text
4+
import androidx.compose.runtime.Composable
5+
import androidx.navigation.NavController
6+
import kotlinx.coroutines.ExperimentalCoroutinesApi
7+
import st.slex.csplashscreen.ui.main.MainViewModel
8+
9+
@ExperimentalCoroutinesApi
10+
@Composable
11+
fun Collection(navController: NavController, viewModel: MainViewModel, collectionId: String) {
12+
Text(text = collectionId)
13+
}
Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
package st.slex.csplashscreen.ui.main
2+
3+
import android.annotation.SuppressLint
4+
import androidx.compose.foundation.Image
5+
import androidx.compose.foundation.layout.*
6+
import androidx.compose.foundation.shape.CircleShape
7+
import androidx.compose.material.Card
8+
import androidx.compose.material.ExperimentalMaterialApi
9+
import androidx.compose.material.Text
10+
import androidx.compose.runtime.Composable
11+
import androidx.compose.ui.Modifier
12+
import androidx.compose.ui.draw.clip
13+
import androidx.compose.ui.draw.clipToBounds
14+
import androidx.compose.ui.draw.shadow
15+
import androidx.compose.ui.graphics.graphicsLayer
16+
import androidx.compose.ui.unit.dp
17+
import androidx.navigation.NavController
18+
import coil.annotation.ExperimentalCoilApi
19+
import coil.compose.rememberImagePainter
20+
import coil.transform.RoundedCornersTransformation
21+
import com.google.accompanist.pager.ExperimentalPagerApi
22+
import com.google.accompanist.pager.PagerScope
23+
import com.google.accompanist.pager.calculateCurrentOffsetForPage
24+
import com.google.android.material.animation.AnimationUtils
25+
import st.slex.csplashscreen.data.model.ui.collection.CollectionModel
26+
27+
@SuppressLint("RestrictedApi")
28+
@ExperimentalMaterialApi
29+
@ExperimentalPagerApi
30+
@ExperimentalCoilApi
31+
@Composable
32+
fun CollectionItem(
33+
item: CollectionModel?,
34+
navController: NavController,
35+
page: Int,
36+
scope: PagerScope
37+
) {
38+
Column(
39+
modifier = Modifier
40+
.padding(16.dp)
41+
.graphicsLayer {
42+
val pageOffset = scope.calculateCurrentOffsetForPage(page)
43+
AnimationUtils
44+
.lerp(
45+
0.85f,
46+
1f,
47+
1f - pageOffset.coerceIn(0f, 1f)
48+
)
49+
.also { scale ->
50+
scaleX = scale
51+
scaleY = scale
52+
}
53+
// We animate the alpha, between 50% and 100%
54+
alpha = AnimationUtils.lerp(
55+
0.5f,
56+
1f,
57+
1f - pageOffset.coerceIn(0f, 1f)
58+
)
59+
}
60+
.aspectRatio(1f)) {
61+
Row(modifier = Modifier.padding(bottom = 8.dp)) {
62+
Image(
63+
modifier = Modifier
64+
.size(32.dp)
65+
.clip(CircleShape),
66+
painter = rememberImagePainter(
67+
data = item?.user?.profile_image?.medium.toString(),
68+
builder = {
69+
allowHardware(false)
70+
crossfade(500)
71+
}
72+
),
73+
contentDescription = "User Avatar"
74+
)
75+
Text(
76+
modifier = Modifier
77+
.padding(start = 8.dp),
78+
text = item?.user?.username.toString()
79+
)
80+
}
81+
82+
Card(
83+
modifier = Modifier.shadow(elevation = 8.dp, clip = true),
84+
onClick = { navController.navigate("collection/${item?.id}") }
85+
) {
86+
Image(
87+
modifier = Modifier
88+
.fillMaxWidth()
89+
.height(300.dp)
90+
.clipToBounds(),
91+
painter = rememberImagePainter(
92+
data = item?.cover_photo?.urls?.regular.toString(),
93+
builder = {
94+
transformations(RoundedCornersTransformation())
95+
allowHardware(false)
96+
crossfade(500)
97+
}
98+
),
99+
contentDescription = "Image",
100+
)
101+
}
102+
}
103+
}

0 commit comments

Comments
 (0)