From c1efba5d63566a9d0616005eaddb9f09bcfb2599 Mon Sep 17 00:00:00 2001 From: Carlos Martinez Date: Tue, 4 Feb 2020 21:51:45 -0300 Subject: [PATCH 1/2] create module and implement it --- .../hako/albumlist/domain/usecase/GetAlbum.kt | 6 +- .../com/hako/albumlist/model/AlbumModels.kt | 2 +- .../albumlist/viewmodel/AlbumlistViewmodel.kt | 4 +- .../hako/albumlist/widget/AlbumlistAdapter.kt | 6 +- .../src/main/res/layout/item_album_card.xml | 5 +- .../res/navigation/albumlist_navigation.xml | 2 +- app/build.gradle | 1 + .../com/hako/friendlists/MainApplication.kt | 4 +- .../main/res/navigation/main_navigation.xml | 4 +- .../com/hako/base/ExampleInstrumentedTest.kt | 24 ------ .../hako/base/domain/database/dao/PhotoDao.kt | 3 + .../java/com/hako/base/ExampleUnitTest.kt | 17 ---- photolist/.gitignore | 1 + photolist/build.gradle | 6 ++ photolist/consumer-rules.pro | 0 photolist/proguard-rules.pro | 21 +++++ photolist/src/main/AndroidManifest.xml | 2 + .../com/hako/photolist/di/PhotolistModules.kt | 15 ++++ .../domain/datasource/PhotolistRemoteApi.kt | 14 ++++ .../hako/photolist/domain/usecase/GetPhoto.kt | 40 ++++++++++ .../photolist/feature/PhotolistFragment.kt | 75 ++++++++++++++++++ .../com/hako/photolist/model/PhotoModels.kt | 27 +++++++ .../photolist/viewmodel/PhotolistViewmodel.kt | 37 +++++++++ .../hako/photolist/widget/PhotolistAdapter.kt | 50 ++++++++++++ .../res/drawable/img_photo_placeholder.png | Bin 0 -> 9902 bytes .../main/res/layout/fragment_photolist.xml | 38 +++++++++ .../src/main/res/layout/item_photo_card.xml | 50 ++++++++++++ .../res/navigation/photolist_navigation.xml | 14 ++++ photolist/src/main/res/values/strings.xml | 3 + settings.gradle | 2 +- .../res/navigation/userlist_navigation.xml | 6 +- 31 files changed, 421 insertions(+), 58 deletions(-) delete mode 100644 base/src/androidTest/java/com/hako/base/ExampleInstrumentedTest.kt delete mode 100644 base/src/test/java/com/hako/base/ExampleUnitTest.kt create mode 100644 photolist/.gitignore create mode 100644 photolist/build.gradle create mode 100644 photolist/consumer-rules.pro create mode 100644 photolist/proguard-rules.pro create mode 100644 photolist/src/main/AndroidManifest.xml create mode 100644 photolist/src/main/java/com/hako/photolist/di/PhotolistModules.kt create mode 100644 photolist/src/main/java/com/hako/photolist/domain/datasource/PhotolistRemoteApi.kt create mode 100644 photolist/src/main/java/com/hako/photolist/domain/usecase/GetPhoto.kt create mode 100644 photolist/src/main/java/com/hako/photolist/feature/PhotolistFragment.kt create mode 100644 photolist/src/main/java/com/hako/photolist/model/PhotoModels.kt create mode 100644 photolist/src/main/java/com/hako/photolist/viewmodel/PhotolistViewmodel.kt create mode 100644 photolist/src/main/java/com/hako/photolist/widget/PhotolistAdapter.kt create mode 100644 photolist/src/main/res/drawable/img_photo_placeholder.png create mode 100644 photolist/src/main/res/layout/fragment_photolist.xml create mode 100644 photolist/src/main/res/layout/item_photo_card.xml create mode 100644 photolist/src/main/res/navigation/photolist_navigation.xml create mode 100644 photolist/src/main/res/values/strings.xml diff --git a/albumlist/src/main/java/com/hako/albumlist/domain/usecase/GetAlbum.kt b/albumlist/src/main/java/com/hako/albumlist/domain/usecase/GetAlbum.kt index 8ec47e6..fe59945 100644 --- a/albumlist/src/main/java/com/hako/albumlist/domain/usecase/GetAlbum.kt +++ b/albumlist/src/main/java/com/hako/albumlist/domain/usecase/GetAlbum.kt @@ -3,7 +3,7 @@ package com.hako.albumlist.domain.usecase import com.hako.albumlist.domain.datasource.AlbumlistRemoteApi import com.hako.albumlist.model.AlbumViewable import com.hako.albumlist.model.toAlbumEntity -import com.hako.albumlist.model.toUserViewable +import com.hako.albumlist.model.toAlbumViewable import com.hako.base.domain.database.dao.AlbumDao import io.reactivex.Single import io.reactivex.schedulers.Schedulers @@ -28,13 +28,13 @@ class GetAlbum(private val dao: AlbumDao) : KoinComponent { api.getAlbums(userId) .doOnSuccess { dao.saveAll(it.map { album -> album.toAlbumEntity() }) - onSuccess(dao.getAlbums(userId).map { album -> album.toUserViewable() }) + onSuccess(dao.getAlbums(userId).map { album -> album.toAlbumViewable() }) } .doOnSubscribe { onLoading() } .subscribeOn(Schedulers.io()) .subscribe({}, { onError(it) }) } else { - onSuccess(dbAlbum.map { it.toUserViewable() }) + onSuccess(dbAlbum.map { it.toAlbumViewable() }) } } .subscribe() diff --git a/albumlist/src/main/java/com/hako/albumlist/model/AlbumModels.kt b/albumlist/src/main/java/com/hako/albumlist/model/AlbumModels.kt index c61f6b7..fde8f21 100644 --- a/albumlist/src/main/java/com/hako/albumlist/model/AlbumModels.kt +++ b/albumlist/src/main/java/com/hako/albumlist/model/AlbumModels.kt @@ -20,5 +20,5 @@ data class AlbumViewable( fun Album.toAlbumEntity() = AlbumEntity(this.id, this.userId, this.title) -fun AlbumEntity.toUserViewable() = AlbumViewable(this.id, this.userId, this.title) +fun AlbumEntity.toAlbumViewable() = AlbumViewable(this.id, this.userId, this.title) diff --git a/albumlist/src/main/java/com/hako/albumlist/viewmodel/AlbumlistViewmodel.kt b/albumlist/src/main/java/com/hako/albumlist/viewmodel/AlbumlistViewmodel.kt index f3f3bbc..ed795b4 100644 --- a/albumlist/src/main/java/com/hako/albumlist/viewmodel/AlbumlistViewmodel.kt +++ b/albumlist/src/main/java/com/hako/albumlist/viewmodel/AlbumlistViewmodel.kt @@ -17,10 +17,10 @@ class AlbumlistViewmodel : ViewModel(), KoinComponent { val data = MutableLiveData>>() val requestStatus = MutableLiveData() - private val getUsers: GetAlbum = get() + private val getAlbum: GetAlbum = get() fun fetchAlbums(userId: Int) { - getUsers.execute( + getAlbum.execute( userId, onSuccess = { requestStatus.postValue(Ready) diff --git a/albumlist/src/main/java/com/hako/albumlist/widget/AlbumlistAdapter.kt b/albumlist/src/main/java/com/hako/albumlist/widget/AlbumlistAdapter.kt index fd646a7..a67596a 100644 --- a/albumlist/src/main/java/com/hako/albumlist/widget/AlbumlistAdapter.kt +++ b/albumlist/src/main/java/com/hako/albumlist/widget/AlbumlistAdapter.kt @@ -20,7 +20,7 @@ class AlbumlistAdapter : RecyclerView.Adapter() { var onItemClick: (AlbumViewable) -> Unit = { } override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder = - UserViewHolder( + AlbumViewHolder( LayoutInflater .from(parent.context) .inflate(R.layout.item_album_card, parent, false), @@ -37,12 +37,12 @@ class AlbumlistAdapter : RecyclerView.Adapter() { override fun onBindViewHolder(viewholder: RecyclerView.ViewHolder, position: Int) = when (viewholder) { - is UserViewHolder -> viewholder.bind(items[position]) + is AlbumViewHolder -> viewholder.bind(items[position]) else -> throw NoWhenBranchMatchedException("Undefined viewholder") } } -class UserViewHolder(private val view: View, +class AlbumViewHolder(private val view: View, private val onItemClick: (AlbumViewable) -> Unit) : RecyclerView.ViewHolder(view) { diff --git a/albumlist/src/main/res/layout/item_album_card.xml b/albumlist/src/main/res/layout/item_album_card.xml index 162c57f..ebd8245 100644 --- a/albumlist/src/main/res/layout/item_album_card.xml +++ b/albumlist/src/main/res/layout/item_album_card.xml @@ -14,12 +14,15 @@ diff --git a/albumlist/src/main/res/navigation/albumlist_navigation.xml b/albumlist/src/main/res/navigation/albumlist_navigation.xml index 68a54ee..97da511 100644 --- a/albumlist/src/main/res/navigation/albumlist_navigation.xml +++ b/albumlist/src/main/res/navigation/albumlist_navigation.xml @@ -9,6 +9,6 @@ android:id="@+id/albumlistFragment" android:name="com.hako.albumlist.feature.AlbumlistFragment" tools:layout="@layout/fragment_albumlist" - android:label="AlbumListFragment" /> + android:label="AlbumlistFragment" /> \ No newline at end of file diff --git a/app/build.gradle b/app/build.gradle index 3019374..feaac15 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -50,4 +50,5 @@ dependencies { implementation project(":base") implementation project(":userlist") implementation project(":albumlist") + implementation project(":photolist") } diff --git a/app/src/main/java/com/hako/friendlists/MainApplication.kt b/app/src/main/java/com/hako/friendlists/MainApplication.kt index fed3f18..14faa3d 100644 --- a/app/src/main/java/com/hako/friendlists/MainApplication.kt +++ b/app/src/main/java/com/hako/friendlists/MainApplication.kt @@ -4,6 +4,7 @@ import android.app.Application import com.hako.albumlist.di.albumListModules import com.hako.userlist.di.userlistModules import com.hako.friendlists.di.appModules +import com.hako.photolist.di.photoListModules import org.koin.android.ext.koin.androidContext import org.koin.core.context.startKoin import timber.log.Timber @@ -28,7 +29,8 @@ class MainApplication : Application() { listOf( appModules, userlistModules, - albumListModules + albumListModules, + photoListModules ) ) } diff --git a/app/src/main/res/navigation/main_navigation.xml b/app/src/main/res/navigation/main_navigation.xml index ae60acb..f235315 100644 --- a/app/src/main/res/navigation/main_navigation.xml +++ b/app/src/main/res/navigation/main_navigation.xml @@ -2,10 +2,12 @@ + app:startDestination="@id/photolist_navigation"> + + \ No newline at end of file diff --git a/base/src/androidTest/java/com/hako/base/ExampleInstrumentedTest.kt b/base/src/androidTest/java/com/hako/base/ExampleInstrumentedTest.kt deleted file mode 100644 index c77bb07..0000000 --- a/base/src/androidTest/java/com/hako/base/ExampleInstrumentedTest.kt +++ /dev/null @@ -1,24 +0,0 @@ -package com.hako.base - -import androidx.test.platform.app.InstrumentationRegistry -import androidx.test.ext.junit.runners.AndroidJUnit4 - -import org.junit.Test -import org.junit.runner.RunWith - -import org.junit.Assert.* - -/** - * Instrumented test, which will execute on an Android device. - * - * See [testing documentation](http://d.android.com/tools/testing). - */ -@RunWith(AndroidJUnit4::class) -class ExampleInstrumentedTest { - @Test - fun useAppContext() { - // Context of the app under test. - val appContext = InstrumentationRegistry.getInstrumentation().targetContext - assertEquals("com.hako.base.test", appContext.packageName) - } -} diff --git a/base/src/main/java/com/hako/base/domain/database/dao/PhotoDao.kt b/base/src/main/java/com/hako/base/domain/database/dao/PhotoDao.kt index 0fc562b..4ad3019 100644 --- a/base/src/main/java/com/hako/base/domain/database/dao/PhotoDao.kt +++ b/base/src/main/java/com/hako/base/domain/database/dao/PhotoDao.kt @@ -18,6 +18,9 @@ interface PhotoDao { @get:Query("SELECT * FROM ${PhotoEntity.TABLE_NAME}") val all: List + @Query("SELECT * FROM ${PhotoEntity.TABLE_NAME} WHERE albumId = :albumId ORDER BY id ASC") + fun getPhotos(albumId: Int): List + @Query("SELECT COUNT(*) FROM ${PhotoEntity.TABLE_NAME}") fun count(): Int diff --git a/base/src/test/java/com/hako/base/ExampleUnitTest.kt b/base/src/test/java/com/hako/base/ExampleUnitTest.kt deleted file mode 100644 index e0e70a0..0000000 --- a/base/src/test/java/com/hako/base/ExampleUnitTest.kt +++ /dev/null @@ -1,17 +0,0 @@ -package com.hako.base - -import org.junit.Test - -import org.junit.Assert.* - -/** - * Example local unit test, which will execute on the development machine (host). - * - * See [testing documentation](http://d.android.com/tools/testing). - */ -class ExampleUnitTest { - @Test - fun addition_isCorrect() { - assertEquals(4, 2 + 2) - } -} diff --git a/photolist/.gitignore b/photolist/.gitignore new file mode 100644 index 0000000..796b96d --- /dev/null +++ b/photolist/.gitignore @@ -0,0 +1 @@ +/build diff --git a/photolist/build.gradle b/photolist/build.gradle new file mode 100644 index 0000000..d51b157 --- /dev/null +++ b/photolist/build.gradle @@ -0,0 +1,6 @@ +apply plugin: 'com.android.library' +apply from: '../core.gradle' + +dependencies { + implementation project(':base') +} \ No newline at end of file diff --git a/photolist/consumer-rules.pro b/photolist/consumer-rules.pro new file mode 100644 index 0000000..e69de29 diff --git a/photolist/proguard-rules.pro b/photolist/proguard-rules.pro new file mode 100644 index 0000000..f1b4245 --- /dev/null +++ b/photolist/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile diff --git a/photolist/src/main/AndroidManifest.xml b/photolist/src/main/AndroidManifest.xml new file mode 100644 index 0000000..5c2fa69 --- /dev/null +++ b/photolist/src/main/AndroidManifest.xml @@ -0,0 +1,2 @@ + diff --git a/photolist/src/main/java/com/hako/photolist/di/PhotolistModules.kt b/photolist/src/main/java/com/hako/photolist/di/PhotolistModules.kt new file mode 100644 index 0000000..41b0c45 --- /dev/null +++ b/photolist/src/main/java/com/hako/photolist/di/PhotolistModules.kt @@ -0,0 +1,15 @@ +package com.hako.photolist.di + +import com.hako.base.domain.network.RemoteClient +import com.hako.photolist.domain.datasource.PhotolistRemoteApi +import com.hako.photolist.domain.usecase.GetPhoto +import com.hako.photolist.viewmodel.PhotolistViewmodel +import org.koin.androidx.viewmodel.dsl.viewModel +import org.koin.dsl.module + +val photoListModules = module { + factory { get().getClient(PhotolistRemoteApi::class.java) } + factory { GetPhoto(get()) } + + viewModel { PhotolistViewmodel() } +} \ No newline at end of file diff --git a/photolist/src/main/java/com/hako/photolist/domain/datasource/PhotolistRemoteApi.kt b/photolist/src/main/java/com/hako/photolist/domain/datasource/PhotolistRemoteApi.kt new file mode 100644 index 0000000..4b8ce88 --- /dev/null +++ b/photolist/src/main/java/com/hako/photolist/domain/datasource/PhotolistRemoteApi.kt @@ -0,0 +1,14 @@ +package com.hako.photolist.domain.datasource + +import com.hako.photolist.model.Photo +import io.reactivex.Single +import retrofit2.http.GET +import retrofit2.http.Query + +interface PhotolistRemoteApi { + + @GET("/photos") + fun getPhotos( + @Query("albumId") albumId: Int + ): Single> +} \ No newline at end of file diff --git a/photolist/src/main/java/com/hako/photolist/domain/usecase/GetPhoto.kt b/photolist/src/main/java/com/hako/photolist/domain/usecase/GetPhoto.kt new file mode 100644 index 0000000..9e172df --- /dev/null +++ b/photolist/src/main/java/com/hako/photolist/domain/usecase/GetPhoto.kt @@ -0,0 +1,40 @@ +package com.hako.photolist.domain.usecase + +import com.hako.base.domain.database.dao.PhotoDao +import com.hako.photolist.domain.datasource.PhotolistRemoteApi +import com.hako.photolist.model.* +import io.reactivex.Single +import io.reactivex.schedulers.Schedulers +import org.koin.core.KoinComponent +import org.koin.core.get + +class GetPhoto(private val dao: PhotoDao) : KoinComponent { + + private val api: PhotolistRemoteApi = get() + + fun execute( + albumId: Int, + onSuccess: (List) -> Unit, + onError: (Throwable) -> Unit, + onLoading: () -> Unit + ) { + Single.fromCallable { dao.getPhotos(albumId) } + .subscribeOn(Schedulers.io()) + .doOnError { onError(it) } + .doOnSuccess { dbAlbum -> + if (dbAlbum.isEmpty() || dbAlbum.count() == 0) { + api.getPhotos(albumId) + .doOnSuccess { + dao.saveAll(it.map { album -> album.toPhotoEntity() }) + onSuccess(dao.getPhotos(albumId).map { album -> album.toPhotoViewable() }) + } + .doOnSubscribe { onLoading() } + .subscribeOn(Schedulers.io()) + .subscribe({}, { onError(it) }) + } else { + onSuccess(dbAlbum.map { it.toPhotoViewable() }) + } + } + .subscribe() + } +} diff --git a/photolist/src/main/java/com/hako/photolist/feature/PhotolistFragment.kt b/photolist/src/main/java/com/hako/photolist/feature/PhotolistFragment.kt new file mode 100644 index 0000000..68731c8 --- /dev/null +++ b/photolist/src/main/java/com/hako/photolist/feature/PhotolistFragment.kt @@ -0,0 +1,75 @@ +package com.hako.photolist.feature + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.fragment.app.Fragment +import androidx.recyclerview.widget.LinearLayoutManager +import com.hako.base.domain.network.RequestStatus +import com.hako.base.extensions.gone +import com.hako.base.extensions.observeNonNull +import com.hako.base.extensions.visible +import com.hako.photolist.R +import com.hako.photolist.model.PhotoViewable +import com.hako.photolist.viewmodel.PhotolistViewmodel +import com.hako.photolist.widget.PhotolistAdapter +import kotlinx.android.synthetic.main.fragment_photolist.* +import org.koin.androidx.viewmodel.ext.android.viewModel +import timber.log.Timber + +class PhotolistFragment : Fragment() { + + private val viewModel: PhotolistViewmodel by viewModel() + private val listAdapter by lazy { PhotolistAdapter() } + + override fun onCreateView( + inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? + ): View = inflater.inflate(R.layout.fragment_photolist, container, false) + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + setRecycler() + setObservers() + // TODO: Get album by bundle + viewModel.fetchPhotos(2) + } + + private fun setObservers() { + viewModel.data.observeNonNull(this) { + it.either(::handleFetchError, ::handleFetchSuccess) + } + + viewModel.requestStatus.observeNonNull(this) { + when (it) { + RequestStatus.Ready -> { + fragment_photolist_error_overlay.gone() + fragment_photolist_loading_overlay.gone() + } + RequestStatus.Loading -> { + fragment_photolist_error_overlay.gone() + fragment_photolist_loading_overlay.visible() + } + RequestStatus.Errored -> { + fragment_photolist_error_overlay.visible() + fragment_photolist_loading_overlay.gone() + } + } + } + } + + private fun handleFetchError(throwable: Throwable) { + Timber.e(throwable) + } + + private fun handleFetchSuccess(photos: List) { + listAdapter.addAll(photos) + } + + private fun setRecycler() { + fragment_photolist_recycler_container.apply { + layoutManager = LinearLayoutManager(context) + adapter = listAdapter + } + } +} \ No newline at end of file diff --git a/photolist/src/main/java/com/hako/photolist/model/PhotoModels.kt b/photolist/src/main/java/com/hako/photolist/model/PhotoModels.kt new file mode 100644 index 0000000..14d2516 --- /dev/null +++ b/photolist/src/main/java/com/hako/photolist/model/PhotoModels.kt @@ -0,0 +1,27 @@ +package com.hako.photolist.model + +import android.os.Parcelable +import com.google.gson.annotations.SerializedName +import com.hako.base.domain.database.entities.PhotoEntity +import kotlinx.android.parcel.Parcelize + +@Parcelize +data class Photo( + @SerializedName("id") val id: Int, + @SerializedName("albumId") val albumId: Int, + @SerializedName("title") val title: String, + @SerializedName("url") val photoUrl: String, + @SerializedName("thumbnailUrl") val thumbnailUrl: String +) : Parcelable + +data class PhotoViewable( + val id: Int, + val albumId: Int, + val title: String, + val photoUrl: String +) + +fun Photo.toPhotoEntity() = PhotoEntity(this.id, this.albumId, this.title, this.photoUrl, this.thumbnailUrl) + +fun PhotoEntity.toPhotoViewable() = PhotoViewable(this.id, this.albumId, this.title, this.photoUrl) + diff --git a/photolist/src/main/java/com/hako/photolist/viewmodel/PhotolistViewmodel.kt b/photolist/src/main/java/com/hako/photolist/viewmodel/PhotolistViewmodel.kt new file mode 100644 index 0000000..3123854 --- /dev/null +++ b/photolist/src/main/java/com/hako/photolist/viewmodel/PhotolistViewmodel.kt @@ -0,0 +1,37 @@ +package com.hako.photolist.viewmodel + +import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.ViewModel +import com.hako.base.domain.network.RequestStatus +import com.hako.base.domain.network.RequestStatus.Ready +import com.hako.base.domain.network.RequestStatus.Loading +import com.hako.base.domain.network.RequestStatus.Errored +import com.hako.base.domain.Either +import com.hako.photolist.domain.usecase.GetPhoto +import com.hako.photolist.model.PhotoViewable +import org.koin.core.KoinComponent +import org.koin.core.get + +class PhotolistViewmodel : ViewModel(), KoinComponent { + + val data = MutableLiveData>>() + val requestStatus = MutableLiveData() + + private val getPhoto: GetPhoto = get() + + fun fetchPhotos(albumId: Int) { + getPhoto.execute( + albumId, + onSuccess = { + requestStatus.postValue(Ready) + data.postValue(Either.Right(it)) + }, + onLoading = { + requestStatus.postValue(Loading) + }, + onError = { + requestStatus.postValue(Errored) + data.postValue(Either.Left(it)) + }) + } +} diff --git a/photolist/src/main/java/com/hako/photolist/widget/PhotolistAdapter.kt b/photolist/src/main/java/com/hako/photolist/widget/PhotolistAdapter.kt new file mode 100644 index 0000000..38e87fc --- /dev/null +++ b/photolist/src/main/java/com/hako/photolist/widget/PhotolistAdapter.kt @@ -0,0 +1,50 @@ +package com.hako.photolist.widget + +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.recyclerview.widget.RecyclerView +import com.hako.base.extensions.autoNotify +import com.hako.photolist.R +import com.hako.photolist.model.PhotoViewable +import kotlinx.android.synthetic.main.item_photo_card.view.* +import kotlin.properties.Delegates + +class PhotolistAdapter : RecyclerView.Adapter() { + + private var items by Delegates.observable(emptyList()) { _, oldList, newList -> + autoNotify(oldList, newList) { old, new -> old.id == new.id } + notifyDataSetChanged() + } + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder = + PhotoViewHolder( + LayoutInflater + .from(parent.context) + .inflate(R.layout.item_photo_card, parent, false) + ) + + fun getItem(position: Int) = items[position] + + fun addAll(list: List) { + items = list + } + + override fun getItemCount() = items.size + + override fun onBindViewHolder(viewholder: RecyclerView.ViewHolder, position: Int) = + when (viewholder) { + is PhotoViewHolder -> viewholder.bind(items[position]) + else -> throw NoWhenBranchMatchedException("Undefined viewholder") + } +} + +class PhotoViewHolder(private val view: View) : + RecyclerView.ViewHolder(view) { + + fun bind(photo: PhotoViewable) = with(view) { + item_photo_card_title.text = photo.title + + // TODO load image + } +} \ No newline at end of file diff --git a/photolist/src/main/res/drawable/img_photo_placeholder.png b/photolist/src/main/res/drawable/img_photo_placeholder.png new file mode 100644 index 0000000000000000000000000000000000000000..266d2fd67b3453058acf21da1a3fc2ab514167df GIT binary patch literal 9902 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 + + + + + + + + + \ No newline at end of file diff --git a/photolist/src/main/res/layout/item_photo_card.xml b/photolist/src/main/res/layout/item_photo_card.xml new file mode 100644 index 0000000..202fbf8 --- /dev/null +++ b/photolist/src/main/res/layout/item_photo_card.xml @@ -0,0 +1,50 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/photolist/src/main/res/navigation/photolist_navigation.xml b/photolist/src/main/res/navigation/photolist_navigation.xml new file mode 100644 index 0000000..a11943a --- /dev/null +++ b/photolist/src/main/res/navigation/photolist_navigation.xml @@ -0,0 +1,14 @@ + + + + + + \ No newline at end of file diff --git a/photolist/src/main/res/values/strings.xml b/photolist/src/main/res/values/strings.xml new file mode 100644 index 0000000..b3c1d70 --- /dev/null +++ b/photolist/src/main/res/values/strings.xml @@ -0,0 +1,3 @@ + + photolist + diff --git a/settings.gradle b/settings.gradle index 1b2d3f9..469cf91 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1,2 +1,2 @@ -include ':app', ':base', ':userlist', ':albumlist' +include ':app', ':base', ':userlist', ':albumlist', ':photolist' rootProject.name='Friendlists' diff --git a/userlist/src/main/res/navigation/userlist_navigation.xml b/userlist/src/main/res/navigation/userlist_navigation.xml index 20c9258..db70b2f 100644 --- a/userlist/src/main/res/navigation/userlist_navigation.xml +++ b/userlist/src/main/res/navigation/userlist_navigation.xml @@ -3,12 +3,12 @@ xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:id="@+id/userlist_navigation" - app:startDestination="@id/userListFragment"> + app:startDestination="@id/userlistFragment"> + android:label="UserlistFragment" /> \ No newline at end of file From 1d11a3e72e8acd2e8bc6cb7a78c10ce24ce473fc Mon Sep 17 00:00:00 2001 From: Carlos Martinez Date: Tue, 4 Feb 2020 23:14:12 -0300 Subject: [PATCH 2/2] implemente picasso and implement images --- .../java/com/hako/friendlists/di/AppModules.kt | 4 ++++ base/build.gradle | 1 + .../hako/base/domain/network/RemoteClient.kt | 5 +++++ base/versions.gradle | 2 ++ .../hako/photolist/feature/PhotolistFragment.kt | 2 +- .../hako/photolist/widget/PhotolistAdapter.kt | 17 ++++++++++++++--- .../src/main/res/layout/item_photo_card.xml | 3 +-- photolist/src/main/res/values/strings.xml | 1 + 8 files changed, 29 insertions(+), 6 deletions(-) diff --git a/app/src/main/java/com/hako/friendlists/di/AppModules.kt b/app/src/main/java/com/hako/friendlists/di/AppModules.kt index 46b2320..98b3376 100644 --- a/app/src/main/java/com/hako/friendlists/di/AppModules.kt +++ b/app/src/main/java/com/hako/friendlists/di/AppModules.kt @@ -4,6 +4,7 @@ import androidx.room.Room import com.hako.base.domain.database.DatabaseClient import com.hako.base.domain.network.RemoteClient import com.hako.friendlists.BuildConfig +import com.squareup.picasso.Picasso import org.koin.dsl.module val appModules = module { @@ -15,4 +16,7 @@ val appModules = module { // Retrofit single { RemoteClient(BuildConfig.BASE_ENDPOINT) } + + // Picasso + single { Picasso.get() } } \ No newline at end of file diff --git a/base/build.gradle b/base/build.gradle index 88cc65c..440ca03 100644 --- a/base/build.gradle +++ b/base/build.gradle @@ -51,6 +51,7 @@ dependencies { api deps.okhttp_logging_interceptor api deps.timber api deps.lottie + api deps.picasso //Testing api deps.testing.junit api deps.testing.koin diff --git a/base/src/main/java/com/hako/base/domain/network/RemoteClient.kt b/base/src/main/java/com/hako/base/domain/network/RemoteClient.kt index e24863d..9d63819 100644 --- a/base/src/main/java/com/hako/base/domain/network/RemoteClient.kt +++ b/base/src/main/java/com/hako/base/domain/network/RemoteClient.kt @@ -7,6 +7,9 @@ import retrofit2.Retrofit import retrofit2.adapter.rxjava2.RxJava2CallAdapterFactory import retrofit2.converter.gson.GsonConverterFactory import timber.log.Timber +import java.util.concurrent.TimeUnit + +private const val TIMEOUT_IN_SECONDS = 60L class RemoteClient(endpoint: String) { private val logger = HttpLoggingInterceptor(object : HttpLoggingInterceptor.Logger { @@ -17,6 +20,8 @@ class RemoteClient(endpoint: String) { private val client = OkHttpClient.Builder() .addInterceptor(logger) + .readTimeout(TIMEOUT_IN_SECONDS, TimeUnit.SECONDS) + .connectTimeout(TIMEOUT_IN_SECONDS, TimeUnit.SECONDS) .build() private val retrofit: Retrofit = Retrofit.Builder() diff --git a/base/versions.gradle b/base/versions.gradle index 050b956..f001542 100644 --- a/base/versions.gradle +++ b/base/versions.gradle @@ -20,6 +20,7 @@ versions.test = "1.2.0" versions.test_ext = "1.1.1" versions.espresso = "3.2.0" versions.lottie = "3.3.1" +versions.picasso = "2.71828" def deps = [:] @@ -77,5 +78,6 @@ deps.testing = testing deps.okhttp_logging_interceptor = "com.squareup.okhttp3:logging-interceptor:$versions.okhttp_logging_interceptor" deps.timber = "com.jakewharton.timber:timber:$versions.timber" deps.lottie = "com.airbnb.android:lottie:$versions.lottie" +deps.picasso = "com.squareup.picasso:picasso:$versions.picasso" ext.deps = deps diff --git a/photolist/src/main/java/com/hako/photolist/feature/PhotolistFragment.kt b/photolist/src/main/java/com/hako/photolist/feature/PhotolistFragment.kt index 68731c8..9c5625a 100644 --- a/photolist/src/main/java/com/hako/photolist/feature/PhotolistFragment.kt +++ b/photolist/src/main/java/com/hako/photolist/feature/PhotolistFragment.kt @@ -32,7 +32,7 @@ class PhotolistFragment : Fragment() { setRecycler() setObservers() // TODO: Get album by bundle - viewModel.fetchPhotos(2) + viewModel.fetchPhotos(1) } private fun setObservers() { diff --git a/photolist/src/main/java/com/hako/photolist/widget/PhotolistAdapter.kt b/photolist/src/main/java/com/hako/photolist/widget/PhotolistAdapter.kt index 38e87fc..66213ca 100644 --- a/photolist/src/main/java/com/hako/photolist/widget/PhotolistAdapter.kt +++ b/photolist/src/main/java/com/hako/photolist/widget/PhotolistAdapter.kt @@ -7,7 +7,10 @@ import androidx.recyclerview.widget.RecyclerView import com.hako.base.extensions.autoNotify import com.hako.photolist.R import com.hako.photolist.model.PhotoViewable +import com.squareup.picasso.Picasso import kotlinx.android.synthetic.main.item_photo_card.view.* +import org.koin.core.KoinComponent +import org.koin.core.inject import kotlin.properties.Delegates class PhotolistAdapter : RecyclerView.Adapter() { @@ -40,11 +43,19 @@ class PhotolistAdapter : RecyclerView.Adapter() { } class PhotoViewHolder(private val view: View) : - RecyclerView.ViewHolder(view) { + RecyclerView.ViewHolder(view), KoinComponent { + + private val picasso: Picasso by inject() + + init { + picasso.setIndicatorsEnabled(true) + } fun bind(photo: PhotoViewable) = with(view) { + picasso.load(photo.photoUrl) + .placeholder(R.drawable.img_photo_placeholder) + .fit() + .into(item_photo_card_photo) item_photo_card_title.text = photo.title - - // TODO load image } } \ No newline at end of file diff --git a/photolist/src/main/res/layout/item_photo_card.xml b/photolist/src/main/res/layout/item_photo_card.xml index 202fbf8..1b66e81 100644 --- a/photolist/src/main/res/layout/item_photo_card.xml +++ b/photolist/src/main/res/layout/item_photo_card.xml @@ -20,8 +20,7 @@ app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" - app:srcCompat="@drawable/img_photo_placeholder" - android:contentDescription="Album photo" /> + android:contentDescription="@string/album_photo" /> photolist + Album photo