From 8130956484f1d4b96013215de1111cef3c3a3ba7 Mon Sep 17 00:00:00 2001 From: Carlos Martinez Date: Wed, 5 Feb 2020 15:12:53 -0300 Subject: [PATCH] add navigation framework and implement it --- .../albumlist/feature/AlbumlistFragment.kt | 16 +++++-- .../navigation/AlbumlistNavigation.kt | 7 +++ app/src/main/AndroidManifest.xml | 1 + .../com/hako/friendlists/di/AppModules.kt | 7 +++ .../com/hako/friendlists/view/MainActivity.kt | 23 ++++++++++ .../viewmodel/NavigationViewmodel.kt | 46 +++++++++++++++++++ .../main/res/navigation/main_navigation.xml | 16 ++++++- ...iveDataExtensions.kt => DataExtensions.kt} | 2 +- .../hako/base/extensions/ListExtensions.kt | 23 ---------- .../base/extensions/NavigationExtensions.kt | 17 +------ .../hako/base/extensions/ViewExtensions.kt | 21 +++++++++ .../hako/base/navigation/NavigationEvent.kt | 7 +++ .../hako/base/navigation/NavigationRouter.kt | 13 ++++++ base/src/main/res/anim/slide_in_left.xml | 5 ++ base/src/main/res/anim/slide_in_right.xml | 5 ++ base/src/main/res/anim/slide_out_left.xml | 5 ++ base/src/main/res/anim/slide_out_right.xml | 5 ++ .../photolist/feature/PhotolistFragment.kt | 10 +++- .../hako/photolist/widget/PhotolistAdapter.kt | 4 -- .../src/main/res/layout/item_photo_card.xml | 4 +- .../hako/userlist/feature/UserlistFragment.kt | 6 ++- .../userlist/navigation/UserlistNavigation.kt | 7 +++ .../src/main/res/layout/item_user_card.xml | 2 +- 23 files changed, 200 insertions(+), 52 deletions(-) create mode 100644 albumlist/src/main/java/com/hako/albumlist/navigation/AlbumlistNavigation.kt create mode 100644 app/src/main/java/com/hako/friendlists/viewmodel/NavigationViewmodel.kt rename base/src/main/java/com/hako/base/extensions/{LiveDataExtensions.kt => DataExtensions.kt} (99%) delete mode 100644 base/src/main/java/com/hako/base/extensions/ListExtensions.kt create mode 100644 base/src/main/java/com/hako/base/navigation/NavigationEvent.kt create mode 100644 base/src/main/java/com/hako/base/navigation/NavigationRouter.kt create mode 100644 base/src/main/res/anim/slide_in_left.xml create mode 100644 base/src/main/res/anim/slide_in_right.xml create mode 100644 base/src/main/res/anim/slide_out_left.xml create mode 100644 base/src/main/res/anim/slide_out_right.xml create mode 100644 userlist/src/main/java/com/hako/userlist/navigation/UserlistNavigation.kt diff --git a/albumlist/src/main/java/com/hako/albumlist/feature/AlbumlistFragment.kt b/albumlist/src/main/java/com/hako/albumlist/feature/AlbumlistFragment.kt index e7a4eca..e201a63 100644 --- a/albumlist/src/main/java/com/hako/albumlist/feature/AlbumlistFragment.kt +++ b/albumlist/src/main/java/com/hako/albumlist/feature/AlbumlistFragment.kt @@ -8,6 +8,7 @@ import androidx.fragment.app.Fragment import androidx.recyclerview.widget.LinearLayoutManager import com.hako.albumlist.R import com.hako.albumlist.model.AlbumViewable +import com.hako.albumlist.navigation.AlbumlistNavigation import com.hako.albumlist.viewmodel.AlbumlistViewmodel import com.hako.albumlist.widget.AlbumlistAdapter import com.hako.base.domain.network.RequestStatus @@ -15,14 +16,19 @@ import com.hako.base.extensions.gone import com.hako.base.extensions.observeNonNull import com.hako.base.extensions.toast import com.hako.base.extensions.visible +import com.hako.base.navigation.NavigationRouter import kotlinx.android.synthetic.main.fragment_albumlist.* +import org.koin.android.ext.android.inject import org.koin.androidx.viewmodel.ext.android.viewModel import timber.log.Timber +const val ALBUMLIST_FRAGMENT_BUNDLE_USER_ID = "ALBUMLIST_FRAGMENT_BUNDLE_USER_ID" + class AlbumlistFragment : Fragment() { private val viewModel: AlbumlistViewmodel by viewModel() private val listAdapter by lazy { AlbumlistAdapter() } + private val navigation: NavigationRouter by inject() override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? @@ -32,8 +38,12 @@ class AlbumlistFragment : Fragment() { super.onViewCreated(view, savedInstanceState) setRecycler() setObservers() - // TODO: Get user by bundle - viewModel.fetchAlbums(2) + doRequest() + } + + private fun doRequest() { + arguments?.getInt(ALBUMLIST_FRAGMENT_BUNDLE_USER_ID)?.let { viewModel.fetchAlbums(it) } ?: + throw UninitializedPropertyAccessException("The UserId is expected but it wasn't provided") } private fun setObservers() { @@ -72,7 +82,7 @@ class AlbumlistFragment : Fragment() { layoutManager = LinearLayoutManager(context) adapter = listAdapter.apply { onItemClick = { - context.toast(it.title) + navigation.sendNavigation(AlbumlistNavigation.ClickedOnAlbum(it.id)) } } } diff --git a/albumlist/src/main/java/com/hako/albumlist/navigation/AlbumlistNavigation.kt b/albumlist/src/main/java/com/hako/albumlist/navigation/AlbumlistNavigation.kt new file mode 100644 index 0000000..bc15d99 --- /dev/null +++ b/albumlist/src/main/java/com/hako/albumlist/navigation/AlbumlistNavigation.kt @@ -0,0 +1,7 @@ +package com.hako.albumlist.navigation + +import com.hako.base.navigation.NavigationEvent + +sealed class AlbumlistNavigation : NavigationEvent { + data class ClickedOnAlbum(val albumId: Int) : AlbumlistNavigation() +} diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index c89e347..ba6cfa1 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -15,6 +15,7 @@ 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 98b3376..46e5d03 100644 --- a/app/src/main/java/com/hako/friendlists/di/AppModules.kt +++ b/app/src/main/java/com/hako/friendlists/di/AppModules.kt @@ -3,8 +3,11 @@ package com.hako.friendlists.di import androidx.room.Room import com.hako.base.domain.database.DatabaseClient import com.hako.base.domain.network.RemoteClient +import com.hako.base.navigation.NavigationRouter import com.hako.friendlists.BuildConfig +import com.hako.friendlists.viewmodel.NavigationViewmodel import com.squareup.picasso.Picasso +import org.koin.androidx.viewmodel.dsl.viewModel import org.koin.dsl.module val appModules = module { @@ -19,4 +22,8 @@ val appModules = module { // Picasso single { Picasso.get() } + + // Navigation + single { NavigationRouter() } + viewModel { NavigationViewmodel() } } \ No newline at end of file diff --git a/app/src/main/java/com/hako/friendlists/view/MainActivity.kt b/app/src/main/java/com/hako/friendlists/view/MainActivity.kt index 3652198..ecd5ff1 100644 --- a/app/src/main/java/com/hako/friendlists/view/MainActivity.kt +++ b/app/src/main/java/com/hako/friendlists/view/MainActivity.kt @@ -3,20 +3,43 @@ package com.hako.friendlists.view import android.os.Bundle import androidx.appcompat.app.AppCompatActivity import androidx.navigation.findNavController +import com.hako.base.extensions.observeNonNull +import com.hako.base.navigation.NavigationRouter +import com.hako.friendlists.BuildConfig import com.hako.friendlists.R +import com.hako.friendlists.viewmodel.NavigationViewmodel +import com.squareup.picasso.Picasso +import org.koin.android.ext.android.inject +import org.koin.androidx.viewmodel.ext.android.viewModel class MainActivity : AppCompatActivity() { private val navController by lazy { findNavController(R.id.main_fragment_container) } + private val navRouter: NavigationRouter by inject() + private val picasso: Picasso by inject() + private val viewModel: NavigationViewmodel by viewModel() override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) setupNavigation() + setupPicasso() } private fun setupNavigation() { navController.setGraph(R.navigation.main_navigation) + navRouter.setOnNavigationEvent { + viewModel.onNavigationEvent(it) + } + viewModel.navigate.observeNonNull(this) { pair -> + // Pair.first is a Navigation Id + // Pair.second is a Bundle + navController.navigate(pair.first, pair.second) + } } + private fun setupPicasso() { + // Show cache indicator on images just for debug builds + picasso.setIndicatorsEnabled(BuildConfig.DEBUG) + } } \ No newline at end of file diff --git a/app/src/main/java/com/hako/friendlists/viewmodel/NavigationViewmodel.kt b/app/src/main/java/com/hako/friendlists/viewmodel/NavigationViewmodel.kt new file mode 100644 index 0000000..5082324 --- /dev/null +++ b/app/src/main/java/com/hako/friendlists/viewmodel/NavigationViewmodel.kt @@ -0,0 +1,46 @@ +package com.hako.friendlists.viewmodel + +import android.os.Bundle +import androidx.annotation.IdRes +import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.ViewModel +import com.hako.albumlist.feature.ALBUMLIST_FRAGMENT_BUNDLE_USER_ID +import com.hako.albumlist.navigation.AlbumlistNavigation +import com.hako.base.extensions.buildNavigation +import com.hako.base.navigation.NavigationEvent +import com.hako.friendlists.R +import com.hako.photolist.feature.PHOTOLIST_FRAGMENT_BUNDLE_ALBUM_ID +import com.hako.userlist.navigation.UserlistNavigation + +class NavigationViewmodel : ViewModel() { + + val navigate = MutableLiveData>() + + fun onNavigationEvent(event: NavigationEvent) { + when (event) { + is UserlistNavigation -> handleUserlistNavigation(event) + is AlbumlistNavigation -> handleAlbumlistNavigation(event) + else -> throw NoWhenBranchMatchedException("Undefined navigation event parent") + } + } + + private fun handleUserlistNavigation(event: UserlistNavigation) { + when (event) { + is UserlistNavigation.ClickedOnUser -> navigate.postValue( + buildNavigation(R.id.action_userlistFragment_to_albumlistFragment, Bundle().apply { + putInt(ALBUMLIST_FRAGMENT_BUNDLE_USER_ID, event.userId) + }) + ) + } + } + + private fun handleAlbumlistNavigation(event: AlbumlistNavigation) { + when (event) { + is AlbumlistNavigation.ClickedOnAlbum -> navigate.postValue( + buildNavigation(R.id.action_albumlistFragment_to_photolistFragment, Bundle().apply { + putInt(PHOTOLIST_FRAGMENT_BUNDLE_ALBUM_ID, event.albumId) + }) + ) + } + } +} \ No newline at end of file diff --git a/app/src/main/res/navigation/main_navigation.xml b/app/src/main/res/navigation/main_navigation.xml index f235315..eafa966 100644 --- a/app/src/main/res/navigation/main_navigation.xml +++ b/app/src/main/res/navigation/main_navigation.xml @@ -2,11 +2,25 @@ + app:startDestination="@id/userlist_navigation"> + + diff --git a/base/src/main/java/com/hako/base/extensions/LiveDataExtensions.kt b/base/src/main/java/com/hako/base/extensions/DataExtensions.kt similarity index 99% rename from base/src/main/java/com/hako/base/extensions/LiveDataExtensions.kt rename to base/src/main/java/com/hako/base/extensions/DataExtensions.kt index 8aa5111..f4ad2c3 100644 --- a/base/src/main/java/com/hako/base/extensions/LiveDataExtensions.kt +++ b/base/src/main/java/com/hako/base/extensions/DataExtensions.kt @@ -10,4 +10,4 @@ fun LiveData.observeNonNull(owner: LifecycleOwner, func: (T) -> Unit) { func(it) } }) -} \ No newline at end of file +} diff --git a/base/src/main/java/com/hako/base/extensions/ListExtensions.kt b/base/src/main/java/com/hako/base/extensions/ListExtensions.kt deleted file mode 100644 index 61a3ded..0000000 --- a/base/src/main/java/com/hako/base/extensions/ListExtensions.kt +++ /dev/null @@ -1,23 +0,0 @@ -package com.hako.base.extensions - -import androidx.recyclerview.widget.DiffUtil -import androidx.recyclerview.widget.RecyclerView - -fun RecyclerView.Adapter<*>.autoNotify(oldList: List, newList: List, compare: (T, T) -> Boolean) { - val diff = DiffUtil.calculateDiff(object : DiffUtil.Callback() { - - override fun areItemsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean { - return compare(oldList[oldItemPosition], newList[newItemPosition]) - } - - override fun areContentsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean { - return oldList[oldItemPosition] == newList[newItemPosition] - } - - override fun getOldListSize() = oldList.size - - override fun getNewListSize() = newList.size - }) - - diff.dispatchUpdatesTo(this) -} \ No newline at end of file diff --git a/base/src/main/java/com/hako/base/extensions/NavigationExtensions.kt b/base/src/main/java/com/hako/base/extensions/NavigationExtensions.kt index 0045856..24eff13 100644 --- a/base/src/main/java/com/hako/base/extensions/NavigationExtensions.kt +++ b/base/src/main/java/com/hako/base/extensions/NavigationExtensions.kt @@ -1,19 +1,6 @@ package com.hako.base.extensions +import android.os.Bundle import androidx.annotation.IdRes -import androidx.appcompat.app.AppCompatActivity -import androidx.fragment.app.Fragment -import androidx.navigation.fragment.NavHostFragment -fun AppCompatActivity.findNavHostFragment(@IdRes id: Int) = - supportFragmentManager.findFragmentById(id) as NavHostFragment - -fun Fragment.findNavHostFragment(@IdRes id: Int) = - childFragmentManager.findFragmentById(id) as NavHostFragment - -fun Fragment.findNavController(@IdRes id: Int) = - androidx.navigation.Navigation.findNavController(view?.findViewById(id) ?: viewNotFound(id, this)) - -private fun viewNotFound(@IdRes id: Int, fragment: Fragment): Nothing = throw IllegalStateException( - "View ID $id at '${fragment::class.java.simpleName}' not found." -) \ No newline at end of file +fun buildNavigation(@IdRes id: Int, bundle: Bundle = Bundle()) = Pair(id, bundle) diff --git a/base/src/main/java/com/hako/base/extensions/ViewExtensions.kt b/base/src/main/java/com/hako/base/extensions/ViewExtensions.kt index 39105ff..bef05cf 100644 --- a/base/src/main/java/com/hako/base/extensions/ViewExtensions.kt +++ b/base/src/main/java/com/hako/base/extensions/ViewExtensions.kt @@ -4,6 +4,8 @@ import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import androidx.annotation.LayoutRes +import androidx.recyclerview.widget.DiffUtil +import androidx.recyclerview.widget.RecyclerView fun View.enable() { isEnabled = true @@ -37,3 +39,22 @@ fun ViewGroup.inflate(@LayoutRes layout: Int, attachToRoot: Boolean = false): Vi LayoutInflater .from(context) .inflate(layout, this, attachToRoot) + +fun RecyclerView.Adapter<*>.autoNotify(oldList: List, newList: List, compare: (T, T) -> Boolean) { + val diff = DiffUtil.calculateDiff(object : DiffUtil.Callback() { + + override fun areItemsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean { + return compare(oldList[oldItemPosition], newList[newItemPosition]) + } + + override fun areContentsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean { + return oldList[oldItemPosition] == newList[newItemPosition] + } + + override fun getOldListSize() = oldList.size + + override fun getNewListSize() = newList.size + }) + + diff.dispatchUpdatesTo(this) +} diff --git a/base/src/main/java/com/hako/base/navigation/NavigationEvent.kt b/base/src/main/java/com/hako/base/navigation/NavigationEvent.kt new file mode 100644 index 0000000..8096c2d --- /dev/null +++ b/base/src/main/java/com/hako/base/navigation/NavigationEvent.kt @@ -0,0 +1,7 @@ +package com.hako.base.navigation + +interface NavigationEvent + +interface NavigationController { + fun sendNavigation(event: NavigationEvent) +} \ No newline at end of file diff --git a/base/src/main/java/com/hako/base/navigation/NavigationRouter.kt b/base/src/main/java/com/hako/base/navigation/NavigationRouter.kt new file mode 100644 index 0000000..5ec8c91 --- /dev/null +++ b/base/src/main/java/com/hako/base/navigation/NavigationRouter.kt @@ -0,0 +1,13 @@ +package com.hako.base.navigation + +class NavigationRouter : NavigationController { + private var onNavigationEvent: (NavigationEvent) -> Unit = {} + + override fun sendNavigation(event: NavigationEvent) { + onNavigationEvent(event) + } + + fun setOnNavigationEvent(listener: (NavigationEvent) -> Unit) { + onNavigationEvent = listener + } +} \ No newline at end of file diff --git a/base/src/main/res/anim/slide_in_left.xml b/base/src/main/res/anim/slide_in_left.xml new file mode 100644 index 0000000..54cde3f --- /dev/null +++ b/base/src/main/res/anim/slide_in_left.xml @@ -0,0 +1,5 @@ + + + + \ No newline at end of file diff --git a/base/src/main/res/anim/slide_in_right.xml b/base/src/main/res/anim/slide_in_right.xml new file mode 100644 index 0000000..921430c --- /dev/null +++ b/base/src/main/res/anim/slide_in_right.xml @@ -0,0 +1,5 @@ + + + + \ No newline at end of file diff --git a/base/src/main/res/anim/slide_out_left.xml b/base/src/main/res/anim/slide_out_left.xml new file mode 100644 index 0000000..e740176 --- /dev/null +++ b/base/src/main/res/anim/slide_out_left.xml @@ -0,0 +1,5 @@ + + + + \ No newline at end of file diff --git a/base/src/main/res/anim/slide_out_right.xml b/base/src/main/res/anim/slide_out_right.xml new file mode 100644 index 0000000..ca712a7 --- /dev/null +++ b/base/src/main/res/anim/slide_out_right.xml @@ -0,0 +1,5 @@ + + + + \ No newline at end of file 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 9c5625a..99904a7 100644 --- a/photolist/src/main/java/com/hako/photolist/feature/PhotolistFragment.kt +++ b/photolist/src/main/java/com/hako/photolist/feature/PhotolistFragment.kt @@ -18,6 +18,8 @@ import kotlinx.android.synthetic.main.fragment_photolist.* import org.koin.androidx.viewmodel.ext.android.viewModel import timber.log.Timber +const val PHOTOLIST_FRAGMENT_BUNDLE_ALBUM_ID = "PHOTOLIST_FRAGMENT_BUNDLE_ALBUM_ID" + class PhotolistFragment : Fragment() { private val viewModel: PhotolistViewmodel by viewModel() @@ -31,8 +33,12 @@ class PhotolistFragment : Fragment() { super.onViewCreated(view, savedInstanceState) setRecycler() setObservers() - // TODO: Get album by bundle - viewModel.fetchPhotos(1) + doRequest() + } + + private fun doRequest() { + arguments?.getInt(PHOTOLIST_FRAGMENT_BUNDLE_ALBUM_ID)?.let { viewModel.fetchPhotos(it) } ?: + throw UninitializedPropertyAccessException("The AlbumId is expected but it wasn't provided") } 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 66213ca..f4f143e 100644 --- a/photolist/src/main/java/com/hako/photolist/widget/PhotolistAdapter.kt +++ b/photolist/src/main/java/com/hako/photolist/widget/PhotolistAdapter.kt @@ -47,10 +47,6 @@ class PhotoViewHolder(private val view: View) : 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) diff --git a/photolist/src/main/res/layout/item_photo_card.xml b/photolist/src/main/res/layout/item_photo_card.xml index 1b66e81..402cf9f 100644 --- a/photolist/src/main/res/layout/item_photo_card.xml +++ b/photolist/src/main/res/layout/item_photo_card.xml @@ -35,9 +35,11 @@ android:layout_width="0dp" android:layout_height="wrap_content" android:layout_marginStart="24dp" - android:layout_marginTop="16dp" + android:layout_marginTop="12dp" android:layout_marginEnd="24dp" android:ellipsize="end" + android:gravity="center_vertical" + android:lines="2" android:textSize="18sp" android:textStyle="bold" app:layout_constraintEnd_toEndOf="parent" diff --git a/userlist/src/main/java/com/hako/userlist/feature/UserlistFragment.kt b/userlist/src/main/java/com/hako/userlist/feature/UserlistFragment.kt index b34fbd8..3822d10 100644 --- a/userlist/src/main/java/com/hako/userlist/feature/UserlistFragment.kt +++ b/userlist/src/main/java/com/hako/userlist/feature/UserlistFragment.kt @@ -11,11 +11,14 @@ import com.hako.base.extensions.gone import com.hako.base.extensions.observeNonNull import com.hako.base.extensions.toast import com.hako.base.extensions.visible +import com.hako.base.navigation.NavigationRouter import com.hako.userlist.model.UserViewable import com.hako.userlist.viewmodel.UserlistViewmodel import com.hako.userlist.widget.UserlistAdapter import com.hako.friendlist_userlist.R +import com.hako.userlist.navigation.UserlistNavigation import kotlinx.android.synthetic.main.fragment_userlist.* +import org.koin.android.ext.android.inject import org.koin.androidx.viewmodel.ext.android.viewModel import timber.log.Timber @@ -23,6 +26,7 @@ class UserlistFragment : Fragment() { private val viewModel: UserlistViewmodel by viewModel() private val listAdapter by lazy { UserlistAdapter() } + private val navigation: NavigationRouter by inject() override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? @@ -71,7 +75,7 @@ class UserlistFragment : Fragment() { layoutManager = LinearLayoutManager(context) adapter = listAdapter.apply { onItemClick = { - context.toast(it.realName) + navigation.sendNavigation(UserlistNavigation.ClickedOnUser(it.id)) } onFavoriteClick = { diff --git a/userlist/src/main/java/com/hako/userlist/navigation/UserlistNavigation.kt b/userlist/src/main/java/com/hako/userlist/navigation/UserlistNavigation.kt new file mode 100644 index 0000000..f2d106e --- /dev/null +++ b/userlist/src/main/java/com/hako/userlist/navigation/UserlistNavigation.kt @@ -0,0 +1,7 @@ +package com.hako.userlist.navigation + +import com.hako.base.navigation.NavigationEvent + +sealed class UserlistNavigation : NavigationEvent { + data class ClickedOnUser(val userId: Int) : UserlistNavigation() +} diff --git a/userlist/src/main/res/layout/item_user_card.xml b/userlist/src/main/res/layout/item_user_card.xml index 36a66aa..9913984 100644 --- a/userlist/src/main/res/layout/item_user_card.xml +++ b/userlist/src/main/res/layout/item_user_card.xml @@ -17,7 +17,7 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginStart="24dp" - android:layout_marginTop="16dp" + android:layout_marginTop="12dp" android:textSize="18sp" android:textStyle="bold" app:layout_constraintStart_toStartOf="parent"