Merge pull request #6 from hakodeveloper/feature/photolist

Photolist Implementation
This commit is contained in:
Carlos Martinez
2020-02-04 23:21:36 -03:00
committed by GitHub
35 changed files with 444 additions and 58 deletions
@@ -3,7 +3,7 @@ package com.hako.albumlist.domain.usecase
import com.hako.albumlist.domain.datasource.AlbumlistRemoteApi import com.hako.albumlist.domain.datasource.AlbumlistRemoteApi
import com.hako.albumlist.model.AlbumViewable import com.hako.albumlist.model.AlbumViewable
import com.hako.albumlist.model.toAlbumEntity 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 com.hako.base.domain.database.dao.AlbumDao
import io.reactivex.Single import io.reactivex.Single
import io.reactivex.schedulers.Schedulers import io.reactivex.schedulers.Schedulers
@@ -28,13 +28,13 @@ class GetAlbum(private val dao: AlbumDao) : KoinComponent {
api.getAlbums(userId) api.getAlbums(userId)
.doOnSuccess { .doOnSuccess {
dao.saveAll(it.map { album -> album.toAlbumEntity() }) 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() } .doOnSubscribe { onLoading() }
.subscribeOn(Schedulers.io()) .subscribeOn(Schedulers.io())
.subscribe({}, { onError(it) }) .subscribe({}, { onError(it) })
} else { } else {
onSuccess(dbAlbum.map { it.toUserViewable() }) onSuccess(dbAlbum.map { it.toAlbumViewable() })
} }
} }
.subscribe() .subscribe()
@@ -20,5 +20,5 @@ data class AlbumViewable(
fun Album.toAlbumEntity() = AlbumEntity(this.id, this.userId, this.title) 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)
@@ -17,10 +17,10 @@ class AlbumlistViewmodel : ViewModel(), KoinComponent {
val data = MutableLiveData<Either<Throwable, List<AlbumViewable>>>() val data = MutableLiveData<Either<Throwable, List<AlbumViewable>>>()
val requestStatus = MutableLiveData<RequestStatus>() val requestStatus = MutableLiveData<RequestStatus>()
private val getUsers: GetAlbum = get() private val getAlbum: GetAlbum = get()
fun fetchAlbums(userId: Int) { fun fetchAlbums(userId: Int) {
getUsers.execute( getAlbum.execute(
userId, userId,
onSuccess = { onSuccess = {
requestStatus.postValue(Ready) requestStatus.postValue(Ready)
@@ -20,7 +20,7 @@ class AlbumlistAdapter : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
var onItemClick: (AlbumViewable) -> Unit = { } var onItemClick: (AlbumViewable) -> Unit = { }
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder = override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder =
UserViewHolder( AlbumViewHolder(
LayoutInflater LayoutInflater
.from(parent.context) .from(parent.context)
.inflate(R.layout.item_album_card, parent, false), .inflate(R.layout.item_album_card, parent, false),
@@ -37,12 +37,12 @@ class AlbumlistAdapter : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
override fun onBindViewHolder(viewholder: RecyclerView.ViewHolder, position: Int) = override fun onBindViewHolder(viewholder: RecyclerView.ViewHolder, position: Int) =
when (viewholder) { when (viewholder) {
is UserViewHolder -> viewholder.bind(items[position]) is AlbumViewHolder -> viewholder.bind(items[position])
else -> throw NoWhenBranchMatchedException("Undefined viewholder") else -> throw NoWhenBranchMatchedException("Undefined viewholder")
} }
} }
class UserViewHolder(private val view: View, class AlbumViewHolder(private val view: View,
private val onItemClick: (AlbumViewable) -> Unit) : private val onItemClick: (AlbumViewable) -> Unit) :
RecyclerView.ViewHolder(view) { RecyclerView.ViewHolder(view) {
@@ -14,12 +14,15 @@
<TextView <TextView
android:id="@+id/item_album_card_album_name" android:id="@+id/item_album_card_album_name"
android:layout_width="wrap_content" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginStart="24dp" android:layout_marginStart="24dp"
android:layout_marginTop="16dp" android:layout_marginTop="16dp"
android:layout_marginEnd="24dp"
android:ellipsize="end"
android:textSize="18sp" android:textSize="18sp"
android:textStyle="bold" android:textStyle="bold"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" app:layout_constraintTop_toTopOf="parent"
tools:text="Album name" /> tools:text="Album name" />
@@ -9,6 +9,6 @@
android:id="@+id/albumlistFragment" android:id="@+id/albumlistFragment"
android:name="com.hako.albumlist.feature.AlbumlistFragment" android:name="com.hako.albumlist.feature.AlbumlistFragment"
tools:layout="@layout/fragment_albumlist" tools:layout="@layout/fragment_albumlist"
android:label="AlbumListFragment" /> android:label="AlbumlistFragment" />
</navigation> </navigation>
+1
View File
@@ -50,4 +50,5 @@ dependencies {
implementation project(":base") implementation project(":base")
implementation project(":userlist") implementation project(":userlist")
implementation project(":albumlist") implementation project(":albumlist")
implementation project(":photolist")
} }
@@ -4,6 +4,7 @@ import android.app.Application
import com.hako.albumlist.di.albumListModules import com.hako.albumlist.di.albumListModules
import com.hako.userlist.di.userlistModules import com.hako.userlist.di.userlistModules
import com.hako.friendlists.di.appModules import com.hako.friendlists.di.appModules
import com.hako.photolist.di.photoListModules
import org.koin.android.ext.koin.androidContext import org.koin.android.ext.koin.androidContext
import org.koin.core.context.startKoin import org.koin.core.context.startKoin
import timber.log.Timber import timber.log.Timber
@@ -28,7 +29,8 @@ class MainApplication : Application() {
listOf( listOf(
appModules, appModules,
userlistModules, userlistModules,
albumListModules albumListModules,
photoListModules
) )
) )
} }
@@ -4,6 +4,7 @@ import androidx.room.Room
import com.hako.base.domain.database.DatabaseClient import com.hako.base.domain.database.DatabaseClient
import com.hako.base.domain.network.RemoteClient import com.hako.base.domain.network.RemoteClient
import com.hako.friendlists.BuildConfig import com.hako.friendlists.BuildConfig
import com.squareup.picasso.Picasso
import org.koin.dsl.module import org.koin.dsl.module
val appModules = module { val appModules = module {
@@ -15,4 +16,7 @@ val appModules = module {
// Retrofit // Retrofit
single { RemoteClient(BuildConfig.BASE_ENDPOINT) } single { RemoteClient(BuildConfig.BASE_ENDPOINT) }
// Picasso
single { Picasso.get() }
} }
@@ -2,10 +2,12 @@
<navigation xmlns:android="http://schemas.android.com/apk/res/android" <navigation xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/main_navigation" android:id="@+id/main_navigation"
app:startDestination="@id/userlist_navigation"> app:startDestination="@id/photolist_navigation">
<include app:graph="@navigation/userlist_navigation" /> <include app:graph="@navigation/userlist_navigation" />
<include app:graph="@navigation/albumlist_navigation" /> <include app:graph="@navigation/albumlist_navigation" />
<include app:graph="@navigation/photolist_navigation" />
</navigation> </navigation>
+1
View File
@@ -51,6 +51,7 @@ dependencies {
api deps.okhttp_logging_interceptor api deps.okhttp_logging_interceptor
api deps.timber api deps.timber
api deps.lottie api deps.lottie
api deps.picasso
//Testing //Testing
api deps.testing.junit api deps.testing.junit
api deps.testing.koin api deps.testing.koin
@@ -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)
}
}
@@ -18,6 +18,9 @@ interface PhotoDao {
@get:Query("SELECT * FROM ${PhotoEntity.TABLE_NAME}") @get:Query("SELECT * FROM ${PhotoEntity.TABLE_NAME}")
val all: List<PhotoEntity> val all: List<PhotoEntity>
@Query("SELECT * FROM ${PhotoEntity.TABLE_NAME} WHERE albumId = :albumId ORDER BY id ASC")
fun getPhotos(albumId: Int): List<PhotoEntity>
@Query("SELECT COUNT(*) FROM ${PhotoEntity.TABLE_NAME}") @Query("SELECT COUNT(*) FROM ${PhotoEntity.TABLE_NAME}")
fun count(): Int fun count(): Int
@@ -7,6 +7,9 @@ import retrofit2.Retrofit
import retrofit2.adapter.rxjava2.RxJava2CallAdapterFactory import retrofit2.adapter.rxjava2.RxJava2CallAdapterFactory
import retrofit2.converter.gson.GsonConverterFactory import retrofit2.converter.gson.GsonConverterFactory
import timber.log.Timber import timber.log.Timber
import java.util.concurrent.TimeUnit
private const val TIMEOUT_IN_SECONDS = 60L
class RemoteClient(endpoint: String) { class RemoteClient(endpoint: String) {
private val logger = HttpLoggingInterceptor(object : HttpLoggingInterceptor.Logger { private val logger = HttpLoggingInterceptor(object : HttpLoggingInterceptor.Logger {
@@ -17,6 +20,8 @@ class RemoteClient(endpoint: String) {
private val client = OkHttpClient.Builder() private val client = OkHttpClient.Builder()
.addInterceptor(logger) .addInterceptor(logger)
.readTimeout(TIMEOUT_IN_SECONDS, TimeUnit.SECONDS)
.connectTimeout(TIMEOUT_IN_SECONDS, TimeUnit.SECONDS)
.build() .build()
private val retrofit: Retrofit = Retrofit.Builder() private val retrofit: Retrofit = Retrofit.Builder()
@@ -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)
}
}
+2
View File
@@ -20,6 +20,7 @@ versions.test = "1.2.0"
versions.test_ext = "1.1.1" versions.test_ext = "1.1.1"
versions.espresso = "3.2.0" versions.espresso = "3.2.0"
versions.lottie = "3.3.1" versions.lottie = "3.3.1"
versions.picasso = "2.71828"
def deps = [:] def deps = [:]
@@ -77,5 +78,6 @@ deps.testing = testing
deps.okhttp_logging_interceptor = "com.squareup.okhttp3:logging-interceptor:$versions.okhttp_logging_interceptor" deps.okhttp_logging_interceptor = "com.squareup.okhttp3:logging-interceptor:$versions.okhttp_logging_interceptor"
deps.timber = "com.jakewharton.timber:timber:$versions.timber" deps.timber = "com.jakewharton.timber:timber:$versions.timber"
deps.lottie = "com.airbnb.android:lottie:$versions.lottie" deps.lottie = "com.airbnb.android:lottie:$versions.lottie"
deps.picasso = "com.squareup.picasso:picasso:$versions.picasso"
ext.deps = deps ext.deps = deps
+1
View File
@@ -0,0 +1 @@
/build
+6
View File
@@ -0,0 +1,6 @@
apply plugin: 'com.android.library'
apply from: '../core.gradle'
dependencies {
implementation project(':base')
}
View File
+21
View File
@@ -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
+2
View File
@@ -0,0 +1,2 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.hako.photolist" />
@@ -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<RemoteClient>().getClient(PhotolistRemoteApi::class.java) }
factory { GetPhoto(get()) }
viewModel { PhotolistViewmodel() }
}
@@ -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<List<Photo>>
}
@@ -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<PhotoViewable>) -> 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()
}
}
@@ -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(1)
}
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<PhotoViewable>) {
listAdapter.addAll(photos)
}
private fun setRecycler() {
fragment_photolist_recycler_container.apply {
layoutManager = LinearLayoutManager(context)
adapter = listAdapter
}
}
}
@@ -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)
@@ -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<Either<Throwable, List<PhotoViewable>>>()
val requestStatus = MutableLiveData<RequestStatus>()
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))
})
}
}
@@ -0,0 +1,61 @@
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 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<RecyclerView.ViewHolder>() {
private var items by Delegates.observable(emptyList<PhotoViewable>()) { _, 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<PhotoViewable>) {
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), 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
}
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 9.7 KiB

@@ -0,0 +1,38 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:id="@+id/fragment_photolist_base">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/fragment_photolist_recycler_container"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<com.hako.base.widgets.LoadingOverlay
android:id="@+id/fragment_photolist_loading_overlay"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:visibility="gone"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<com.hako.base.widgets.NetworkErrorOverlay
android:id="@+id/fragment_photolist_error_overlay"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:visibility="gone"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
@@ -0,0 +1,49 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/item_photo_card_container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginTop="8dp"
android:layout_marginEnd="8dp"
android:background="@drawable/bg_card"
android:elevation="2dp"
android:orientation="vertical">
<ImageView
android:id="@+id/item_photo_card_photo"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:adjustViewBounds="true"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
android:contentDescription="@string/album_photo" />
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/item_photo_card_footer_container"
android:layout_width="match_parent"
android:layout_height="70dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/item_photo_card_photo">
<TextView
android:id="@+id/item_photo_card_title"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="24dp"
android:layout_marginTop="16dp"
android:layout_marginEnd="24dp"
android:ellipsize="end"
android:textSize="18sp"
android:textStyle="bold"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:text="Album name" />
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
@@ -0,0 +1,14 @@
<?xml version="1.0" encoding="utf-8"?>
<navigation xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/photolist_navigation"
app:startDestination="@id/photolistFragment">
<fragment
android:id="@+id/photolistFragment"
android:name="com.hako.photolist.feature.PhotolistFragment"
tools:layout="@layout/fragment_photolist"
android:label="PhotolistFragment" />
</navigation>
@@ -0,0 +1,4 @@
<resources>
<string name="app_name">photolist</string>
<string name="album_photo">Album photo</string>
</resources>
+1 -1
View File
@@ -1,2 +1,2 @@
include ':app', ':base', ':userlist', ':albumlist' include ':app', ':base', ':userlist', ':albumlist', ':photolist'
rootProject.name='Friendlists' rootProject.name='Friendlists'
@@ -3,12 +3,12 @@
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/userlist_navigation" android:id="@+id/userlist_navigation"
app:startDestination="@id/userListFragment"> app:startDestination="@id/userlistFragment">
<fragment <fragment
android:id="@+id/userListFragment" android:id="@+id/userlistFragment"
android:name="com.hako.userlist.feature.UserlistFragment" android:name="com.hako.userlist.feature.UserlistFragment"
tools:layout="@layout/fragment_userlist" tools:layout="@layout/fragment_userlist"
android:label="UserListFragment" /> android:label="UserlistFragment" />
</navigation> </navigation>