diff --git a/app/build.gradle b/app/build.gradle index 952fb2a9..78096397 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -19,6 +19,7 @@ plugins { id("jacoco-android") id("com.mikepenz.aboutlibraries.plugin") id("com.google.firebase.crashlytics") + id("androidx.navigation.safeargs") } jacoco { @@ -144,6 +145,10 @@ dependencies { implementation appDependencies.koinAndroid implementation appDependencies.koinAndroidViewModel + //navigation + implementation appDependencies.navigationFragment + implementation appDependencies.navigationUi + //Test Libs testImplementation testDependencies.junit testImplementation testDependencies.robolectric diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 08462a89..01c10730 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -10,25 +10,9 @@ android:roundIcon="@mipmap/ic_launcher_round" android:theme="@style/AppTheme"> - - - - - - diff --git a/app/src/main/kotlin/com/k0d4black/theforce/activities/SettingsActivity.kt b/app/src/main/kotlin/com/k0d4black/theforce/activities/SettingsActivity.kt deleted file mode 100644 index 45e9ae98..00000000 --- a/app/src/main/kotlin/com/k0d4black/theforce/activities/SettingsActivity.kt +++ /dev/null @@ -1,35 +0,0 @@ -/** - * - * Copyright 2020 David Odari - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except - * in compliance with the License. You may obtain a copy of the License at - * http://www.apache.org/licenses/LICENSE-2.0 - * Unless required by applicable law or agreed to in writing, software distributed under the License - * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express - * or implied. See the License for the specific language governing permissions and limitations under - * the License. - * - **/ -package com.k0d4black.theforce.activities - -import android.os.Bundle -import android.view.View -import com.k0d4black.theforce.base.BaseActivity -import com.k0d4black.theforce.commons.startActivity -import com.k0d4black.theforce.databinding.ActivitySettingsBinding -import com.k0d4black.theforce.activities.AboutActivity - -internal class SettingsActivity : BaseActivity() { - - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - val binding = ActivitySettingsBinding.inflate(layoutInflater) - setContentView(binding.root) - setSupportActionBar(binding.settingsToolbar) - supportActionBar?.setDisplayHomeAsUpEnabled(true) - } - - fun openAboutActivity(view: View) = startActivity() - -} diff --git a/app/src/main/kotlin/com/k0d4black/theforce/base/BaseFavoritesActivity.kt b/app/src/main/kotlin/com/k0d4black/theforce/base/BaseFavoritesFragment.kt similarity index 76% rename from app/src/main/kotlin/com/k0d4black/theforce/base/BaseFavoritesActivity.kt rename to app/src/main/kotlin/com/k0d4black/theforce/base/BaseFavoritesFragment.kt index ca6beac8..a88732b6 100644 --- a/app/src/main/kotlin/com/k0d4black/theforce/base/BaseFavoritesActivity.kt +++ b/app/src/main/kotlin/com/k0d4black/theforce/base/BaseFavoritesFragment.kt @@ -16,19 +16,19 @@ package com.k0d4black.theforce.base import android.os.Bundle -import android.view.Menu -import android.view.MenuItem -import android.view.ViewGroup +import android.view.* +import androidx.annotation.LayoutRes import androidx.lifecycle.Observer import com.k0d4black.theforce.R -import com.k0d4black.theforce.activities.IFavoritesBinder import com.k0d4black.theforce.commons.NavigationUtils import com.k0d4black.theforce.commons.showSnackbar import com.k0d4black.theforce.models.FavoritePresentation +import com.k0d4black.theforce.ui.IFavoritesBinder import com.k0d4black.theforce.viewmodel.FavoriteViewModel import org.koin.androidx.viewmodel.ext.android.viewModel -internal abstract class BaseFavoritesActivity : BaseActivity(), IFavoritesBinder { +internal abstract class BaseFavoritesFragment(@LayoutRes contentLayoutId: Int) : + BaseFragment(contentLayoutId), IFavoritesBinder { // region Members @@ -44,34 +44,35 @@ internal abstract class BaseFavoritesActivity : BaseActivity(), IFavoritesBinder // region Android API - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + val favorite = - intent.getParcelableExtra(NavigationUtils.FAVORITE_PARCEL_KEY) + requireArguments().get(NavigationUtils.FAVORITE_PARCEL_KEY) as FavoritePresentation? favorite?.let { favoritePresentation -> bindFavorite(favoritePresentation) characterName = favoritePresentation.characterPresentation.name this.favoritePresentation = favoritePresentation checkIfFavorite() - invalidateOptionsMenu() + requireActivity().invalidateOptionsMenu() } observeFavoriteViewState() } - override fun onCreateOptionsMenu(menu: Menu?): Boolean { - menuInflater.inflate(R.menu.favorites_menu, menu) - return super.onCreateOptionsMenu(menu) + override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) { + inflater.inflate(R.menu.favorites_menu, menu) + super.onCreateOptionsMenu(menu, inflater) } - override fun onPrepareOptionsMenu(menu: Menu?): Boolean { - val menuItem = menu?.getItem(0) + override fun onPrepareOptionsMenu(menu: Menu) { + val menuItem = menu.getItem(0) if (isFavorite) menuItem?.setIcon(R.drawable.ic_favs_24dp) else menuItem?.setIcon(R.drawable.ic_no_favs_24dp) - return super.onPrepareOptionsMenu(menu) + super.onPrepareOptionsMenu(menu) } override fun onOptionsItemSelected(item: MenuItem): Boolean { @@ -86,7 +87,7 @@ internal abstract class BaseFavoritesActivity : BaseActivity(), IFavoritesBinder isFavorite = !isFavorite } } - invalidateOptionsMenu() + requireActivity().invalidateOptionsMenu() true } else -> super.onOptionsItemSelected(item) @@ -122,9 +123,9 @@ internal abstract class BaseFavoritesActivity : BaseActivity(), IFavoritesBinder } private fun observeFavoriteViewState() { - favoritesViewModel.favoriteViewState.observe(this, Observer { + favoritesViewModel.favoriteViewState.observe(viewLifecycleOwner, Observer { isFavorite = it.isFavorite - invalidateOptionsMenu() + requireActivity().invalidateOptionsMenu() it.error?.let { e -> showSnackbar(rootViewGroup, getString(e.message)) } diff --git a/app/src/main/kotlin/com/k0d4black/theforce/base/BaseActivity.kt b/app/src/main/kotlin/com/k0d4black/theforce/base/BaseFragment.kt similarity index 74% rename from app/src/main/kotlin/com/k0d4black/theforce/base/BaseActivity.kt rename to app/src/main/kotlin/com/k0d4black/theforce/base/BaseFragment.kt index c0342d7f..bdb68194 100644 --- a/app/src/main/kotlin/com/k0d4black/theforce/base/BaseActivity.kt +++ b/app/src/main/kotlin/com/k0d4black/theforce/base/BaseFragment.kt @@ -16,12 +16,14 @@ package com.k0d4black.theforce.base import android.os.Build import android.os.Bundle import android.view.View -import androidx.appcompat.app.AppCompatActivity +import androidx.annotation.LayoutRes +import androidx.fragment.app.Fragment import androidx.lifecycle.Observer import com.k0d4black.theforce.commons.NetworkUtils import com.k0d4black.theforce.commons.versionFrom -internal open class BaseActivity : AppCompatActivity() { +internal open class BaseFragment(@LayoutRes layoutId : Int) : Fragment(layoutId) { + override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) whiteStatusBar() @@ -29,13 +31,13 @@ internal open class BaseActivity : AppCompatActivity() { private fun whiteStatusBar() { if (versionFrom(Build.VERSION_CODES.M)) { - window.decorView.systemUiVisibility = View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR - window.statusBarColor = getColor(android.R.color.white) + requireActivity().window.decorView.systemUiVisibility = View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR + requireActivity().window.statusBarColor = requireContext().getColor(android.R.color.white) } } protected fun onNetworkChange(block: (Boolean) -> Unit) { - NetworkUtils.getNetworkStatus(this) + NetworkUtils.getNetworkStatus(requireContext()) .observe(this, Observer { isConnected -> block(isConnected) }) diff --git a/app/src/main/kotlin/com/k0d4black/theforce/commons/ContextExtensions.kt b/app/src/main/kotlin/com/k0d4black/theforce/commons/ContextExtensions.kt index fd382cb4..6416092b 100644 --- a/app/src/main/kotlin/com/k0d4black/theforce/commons/ContextExtensions.kt +++ b/app/src/main/kotlin/com/k0d4black/theforce/commons/ContextExtensions.kt @@ -15,23 +15,10 @@ */ package com.k0d4black.theforce.commons -import android.app.Activity import android.content.Context -import android.content.Intent -import android.widget.Toast import androidx.annotation.ColorRes import androidx.core.content.ContextCompat internal fun Context.loadColor(@ColorRes colorRes: Int): Int { return ContextCompat.getColor(this, colorRes) -} - -internal fun Context.showToast(message: String) { - Toast.makeText(this, message, Toast.LENGTH_SHORT).show() -} - -internal inline fun Context.startActivity(block: Intent.() -> Unit = {}) { - val intent = Intent(this, T::class.java) - block(intent) - startActivity(intent) } \ No newline at end of file diff --git a/app/src/main/kotlin/com/k0d4black/theforce/commons/ActivityExtensions.kt b/app/src/main/kotlin/com/k0d4black/theforce/commons/FragmentExtensions.kt similarity index 68% rename from app/src/main/kotlin/com/k0d4black/theforce/commons/ActivityExtensions.kt rename to app/src/main/kotlin/com/k0d4black/theforce/commons/FragmentExtensions.kt index 4d50606b..b17dbc3a 100644 --- a/app/src/main/kotlin/com/k0d4black/theforce/commons/ActivityExtensions.kt +++ b/app/src/main/kotlin/com/k0d4black/theforce/commons/FragmentExtensions.kt @@ -15,20 +15,20 @@ */ package com.k0d4black.theforce.commons -import android.app.Activity import android.view.View +import androidx.fragment.app.Fragment import com.google.android.material.snackbar.Snackbar import com.k0d4black.theforce.R -internal fun Activity.showSnackbar(view: View, message: String, isError: Boolean = false) { +internal fun Fragment.showSnackbar(view: View, message: String, isError: Boolean = false) { val sb = Snackbar.make(view, message, Snackbar.LENGTH_LONG) if (isError) - sb.setBackgroundTint(loadColor(R.color.colorError)) - .setTextColor(loadColor(R.color.colorOnError)) + sb.setBackgroundTint(requireContext().loadColor(R.color.colorError)) + .setTextColor(requireContext().loadColor(R.color.colorOnError)) .show() else - sb.setBackgroundTint(loadColor(R.color.colorSecondary)) - .setTextColor(loadColor(R.color.colorOnSecondary)) + sb.setBackgroundTint(requireContext().loadColor(R.color.colorSecondary)) + .setTextColor(requireContext().loadColor(R.color.colorOnSecondary)) .show() } \ No newline at end of file diff --git a/app/src/main/kotlin/com/k0d4black/theforce/commons/NavigationUtils.kt b/app/src/main/kotlin/com/k0d4black/theforce/commons/NavigationUtils.kt index 0e9ac366..454c6d08 100644 --- a/app/src/main/kotlin/com/k0d4black/theforce/commons/NavigationUtils.kt +++ b/app/src/main/kotlin/com/k0d4black/theforce/commons/NavigationUtils.kt @@ -14,6 +14,6 @@ package com.k0d4black.theforce.commons internal object NavigationUtils { - const val CHARACTER_PARCEL_KEY = "character_url" + const val CHARACTER_PARCEL_KEY = "character" const val FAVORITE_PARCEL_KEY = "favorite" } diff --git a/app/src/main/kotlin/com/k0d4black/theforce/models/CharacterPresentation.kt b/app/src/main/kotlin/com/k0d4black/theforce/models/CharacterPresentation.kt index 43b980a6..ebd7b500 100644 --- a/app/src/main/kotlin/com/k0d4black/theforce/models/CharacterPresentation.kt +++ b/app/src/main/kotlin/com/k0d4black/theforce/models/CharacterPresentation.kt @@ -17,7 +17,7 @@ import android.os.Parcelable import kotlinx.android.parcel.Parcelize @Parcelize -internal data class CharacterPresentation( +data class CharacterPresentation( val name: String, val birthYear: String, val heightInCm: String, diff --git a/app/src/main/kotlin/com/k0d4black/theforce/models/FavoritePresentation.kt b/app/src/main/kotlin/com/k0d4black/theforce/models/FavoritePresentation.kt index 03a619ff..c4f381c4 100644 --- a/app/src/main/kotlin/com/k0d4black/theforce/models/FavoritePresentation.kt +++ b/app/src/main/kotlin/com/k0d4black/theforce/models/FavoritePresentation.kt @@ -17,7 +17,7 @@ import android.os.Parcelable import kotlinx.android.parcel.Parcelize @Parcelize -internal data class FavoritePresentation( +data class FavoritePresentation( val characterPresentation: CharacterPresentation, val planetPresentation: PlanetPresentation, val speciePresentation: List, diff --git a/app/src/main/kotlin/com/k0d4black/theforce/models/FilmPresentation.kt b/app/src/main/kotlin/com/k0d4black/theforce/models/FilmPresentation.kt index bfb17988..77364fee 100644 --- a/app/src/main/kotlin/com/k0d4black/theforce/models/FilmPresentation.kt +++ b/app/src/main/kotlin/com/k0d4black/theforce/models/FilmPresentation.kt @@ -17,4 +17,4 @@ import android.os.Parcelable import kotlinx.android.parcel.Parcelize @Parcelize -internal data class FilmPresentation(val title: String, val openingCrawl: String) : Parcelable +data class FilmPresentation(val title: String, val openingCrawl: String) : Parcelable diff --git a/app/src/main/kotlin/com/k0d4black/theforce/models/PlanetPresentation.kt b/app/src/main/kotlin/com/k0d4black/theforce/models/PlanetPresentation.kt index 5eada6f1..2b611830 100644 --- a/app/src/main/kotlin/com/k0d4black/theforce/models/PlanetPresentation.kt +++ b/app/src/main/kotlin/com/k0d4black/theforce/models/PlanetPresentation.kt @@ -17,4 +17,4 @@ import android.os.Parcelable import kotlinx.android.parcel.Parcelize @Parcelize -internal data class PlanetPresentation(val name: String, val population: Long) : Parcelable \ No newline at end of file +data class PlanetPresentation(val name: String, val population: Long) : Parcelable \ No newline at end of file diff --git a/app/src/main/kotlin/com/k0d4black/theforce/models/SpeciePresentation.kt b/app/src/main/kotlin/com/k0d4black/theforce/models/SpeciePresentation.kt index 490edb87..33b634b4 100644 --- a/app/src/main/kotlin/com/k0d4black/theforce/models/SpeciePresentation.kt +++ b/app/src/main/kotlin/com/k0d4black/theforce/models/SpeciePresentation.kt @@ -17,4 +17,4 @@ import android.os.Parcelable import kotlinx.android.parcel.Parcelize @Parcelize -internal data class SpeciePresentation(val name: String, val language: String) : Parcelable +data class SpeciePresentation(val name: String, val language: String) : Parcelable diff --git a/app/src/main/kotlin/com/k0d4black/theforce/activities/AboutActivity.kt b/app/src/main/kotlin/com/k0d4black/theforce/ui/AboutFragment.kt similarity index 58% rename from app/src/main/kotlin/com/k0d4black/theforce/activities/AboutActivity.kt rename to app/src/main/kotlin/com/k0d4black/theforce/ui/AboutFragment.kt index 3d2ddd0e..e93f17ea 100644 --- a/app/src/main/kotlin/com/k0d4black/theforce/activities/AboutActivity.kt +++ b/app/src/main/kotlin/com/k0d4black/theforce/ui/AboutFragment.kt @@ -11,28 +11,29 @@ * the License. * **/ -package com.k0d4black.theforce.activities +package com.k0d4black.theforce.ui import android.os.Bundle +import android.view.View import com.k0d4black.theforce.R -import com.k0d4black.theforce.base.BaseActivity -import com.k0d4black.theforce.databinding.ActivityAboutBinding +import com.k0d4black.theforce.base.BaseFragment import com.mikepenz.aboutlibraries.LibsBuilder +import kotlinx.android.synthetic.main.fragment_about.* -internal class AboutActivity : BaseActivity() { +internal class AboutFragment : BaseFragment(R.layout.fragment_about) { - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - val binding = ActivityAboutBinding.inflate(layoutInflater) - setContentView(binding.root) - setSupportActionBar(binding.aboutToolbar) - supportActionBar?.setDisplayHomeAsUpEnabled(true) + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + + (requireActivity() as DashboardActivity).setSupportActionBar(about_toolbar) + (requireActivity() as DashboardActivity).supportActionBar?.setDisplayHomeAsUpEnabled(true) val fragment = LibsBuilder() .withAboutIconShown(true) .supportFragment() - supportFragmentManager.beginTransaction() + requireActivity().supportFragmentManager.beginTransaction() .add(R.id.fragment_container, fragment).commit() } + } diff --git a/app/src/main/kotlin/com/k0d4black/theforce/activities/CharacterDetailsActivity.kt b/app/src/main/kotlin/com/k0d4black/theforce/ui/CharacterDetailsFragment.kt similarity index 66% rename from app/src/main/kotlin/com/k0d4black/theforce/activities/CharacterDetailsActivity.kt rename to app/src/main/kotlin/com/k0d4black/theforce/ui/CharacterDetailsFragment.kt index 68031730..e0186a60 100644 --- a/app/src/main/kotlin/com/k0d4black/theforce/activities/CharacterDetailsActivity.kt +++ b/app/src/main/kotlin/com/k0d4black/theforce/ui/CharacterDetailsFragment.kt @@ -11,49 +11,69 @@ * the License. * **/ -package com.k0d4black.theforce.activities +package com.k0d4black.theforce.ui import android.os.Bundle +import android.view.LayoutInflater +import android.view.View import android.view.ViewGroup import androidx.databinding.DataBindingUtil import androidx.lifecycle.Observer import com.k0d4black.theforce.R -import com.k0d4black.theforce.base.BaseFavoritesActivity -import com.k0d4black.theforce.commons.* -import com.k0d4black.theforce.databinding.ActivityCharacterDetailBinding -import com.k0d4black.theforce.idlingresource.EspressoIdlingResource -import com.k0d4black.theforce.models.* import com.k0d4black.theforce.adapters.createFilmsAdapter import com.k0d4black.theforce.adapters.createSpeciesAdapter +import com.k0d4black.theforce.base.BaseFavoritesFragment +import com.k0d4black.theforce.commons.hide +import com.k0d4black.theforce.commons.remove +import com.k0d4black.theforce.commons.show +import com.k0d4black.theforce.commons.showSnackbar +import com.k0d4black.theforce.databinding.FragmentCharacterDetailBinding +import com.k0d4black.theforce.idlingresource.EspressoIdlingResource +import com.k0d4black.theforce.models.* import com.k0d4black.theforce.viewmodel.CharacterDetailViewModel +import kotlinx.android.synthetic.main.fragment_character_detail.* +import kotlinx.android.synthetic.main.layout_films.view.* +import kotlinx.android.synthetic.main.layout_planet.view.* +import kotlinx.android.synthetic.main.layout_specie.view.* import org.koin.androidx.viewmodel.ext.android.viewModel -internal class CharacterDetailsActivity : BaseFavoritesActivity(), ICharacterDetailsBinder { +internal class CharacterDetailsFragment : BaseFavoritesFragment(R.layout.fragment_character_detail), + ICharacterDetailsBinder { // region Members private val characterDetailViewModel by viewModel() - private lateinit var binding: ActivityCharacterDetailBinding - private val filmsAdapter = createFilmsAdapter() private val speciesAdapter = createSpeciesAdapter() + private lateinit var binding: FragmentCharacterDetailBinding + // endregion // region Android API override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) + (requireActivity() as DashboardActivity).setSupportActionBar(details_toolbar) + (requireActivity() as DashboardActivity).supportActionBar?.setDisplayHomeAsUpEnabled(true) + } - binding = DataBindingUtil.setContentView(this, R.layout.activity_character_detail) + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle? + ): View { + binding = DataBindingUtil.inflate( + inflater, R.layout.fragment_character_detail, container, false) + return binding.root + } - setSupportActionBar(binding.detailsToolbar) - supportActionBar?.setDisplayHomeAsUpEnabled(true) + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) - val character = - intent.getParcelableExtra(NavigationUtils.CHARACTER_PARCEL_KEY) + val character = CharacterDetailsFragmentArgs.fromBundle(requireArguments()).character if (character == null) { characterDetailViewModel @@ -79,14 +99,14 @@ internal class CharacterDetailsActivity : BaseFavoritesActivity(), ICharacterDet } override val rootViewGroup: ViewGroup - get() = binding.characterDetailsLayout + get() = character_details_layout // endregion // region Private API private fun observeDetailViewState() { - characterDetailViewModel.detailViewState.observe(this, Observer { + characterDetailViewModel.detailViewState.observe(viewLifecycleOwner, Observer { bindCharacterBasicInfo(it.info) bindSpecies(it.specie) bindFilms(it.films) @@ -96,7 +116,7 @@ internal class CharacterDetailsActivity : BaseFavoritesActivity(), ICharacterDet } if (it.isComplete) { showSnackbar( - binding.characterDetailsLayout, + character_details_layout, getString(R.string.info_loading_complete) ) characterDetailViewModel.createFavoritePresentationFromRemoteCharacter() @@ -106,28 +126,29 @@ internal class CharacterDetailsActivity : BaseFavoritesActivity(), ICharacterDet private fun observeFavoritePresentationCreationFromRemote() { characterDetailViewModel.remoteToFavoritePresentation - .observe(this, Observer { favPresentation -> + .observe(viewLifecycleOwner, Observer { favPresentation -> favoritePresentation = favPresentation }) } private fun onError(message: String) { - binding.filmsLayout.filmsProgressBar.hide() - binding.planetLayout.planetProgressBar.hide() - binding.specieLayout.speciesProgressBar.hide() - binding.filmsLayout.filmsErrorTextView.show() - binding.planetLayout.planetErrorTextView.show() - binding.specieLayout.specieErrorTextView.show() - showSnackbar(binding.characterDetailsLayout, message, isError = true) + films_layout.films_progress_bar.hide() + planet_layout.planet_progress_bar.hide() + specie_layout.species_progress_bar.hide() + films_layout.films_error_text_view.show() + planet_layout.planet_error_text_view.show() + specie_layout.specie_error_text_view.show() + showSnackbar(character_details_layout, message, isError = true) } private fun onErrorResolved() { - binding.filmsLayout.filmsErrorTextView.remove() - binding.planetLayout.planetErrorTextView.remove() - binding.specieLayout.specieErrorTextView.remove() - binding.filmsLayout.filmsProgressBar.show() - binding.planetLayout.planetProgressBar.show() - binding.specieLayout.speciesProgressBar.show() + films_layout.films_error_text_view.remove() + planet_layout.planet_error_text_view.remove() + specie_layout.specie_error_text_view.remove() + + films_layout.films_progress_bar.show() + planet_layout.planet_progress_bar.show() + specie_layout.species_progress_bar.show() } private fun observeNetworkChanges(characterUrl: String) { @@ -146,7 +167,7 @@ internal class CharacterDetailsActivity : BaseFavoritesActivity(), ICharacterDet // region ICharacterDetailsBinder override fun bindCharacterBasicInfo(character: CharacterPresentation?) { - supportActionBar?.title = character?.name ?: "" + requireActivity().actionBar?.title = character?.name ?: "" binding.infoLayout.character = character } diff --git a/app/src/main/kotlin/com/k0d4black/theforce/ui/DashboardActivity.kt b/app/src/main/kotlin/com/k0d4black/theforce/ui/DashboardActivity.kt new file mode 100644 index 00000000..c7809cb8 --- /dev/null +++ b/app/src/main/kotlin/com/k0d4black/theforce/ui/DashboardActivity.kt @@ -0,0 +1,21 @@ +package com.k0d4black.theforce.ui + +import android.os.Bundle +import android.view.MenuItem +import androidx.appcompat.app.AppCompatActivity +import com.k0d4black.theforce.R + +class DashboardActivity: AppCompatActivity() { + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(R.layout.activity_dashboard) + } + + override fun onOptionsItemSelected(item: MenuItem): Boolean { + if (item.itemId != R.id.action_settings) + onBackPressed() + return false + } + +} \ No newline at end of file diff --git a/app/src/main/kotlin/com/k0d4black/theforce/activities/DashboardActivity.kt b/app/src/main/kotlin/com/k0d4black/theforce/ui/DashboardFragment.kt similarity index 68% rename from app/src/main/kotlin/com/k0d4black/theforce/activities/DashboardActivity.kt rename to app/src/main/kotlin/com/k0d4black/theforce/ui/DashboardFragment.kt index 2f32e1c1..561dfbb7 100644 --- a/app/src/main/kotlin/com/k0d4black/theforce/activities/DashboardActivity.kt +++ b/app/src/main/kotlin/com/k0d4black/theforce/ui/DashboardFragment.kt @@ -11,50 +11,42 @@ * the License. * **/ -package com.k0d4black.theforce.activities +package com.k0d4black.theforce.ui import android.os.Bundle -import android.view.Menu -import android.view.MenuItem -import android.view.View +import android.view.* import androidx.core.widget.doOnTextChanged -import androidx.databinding.DataBindingUtil import androidx.lifecycle.Observer +import androidx.navigation.fragment.findNavController import com.k0d4black.theforce.R -import com.k0d4black.theforce.base.BaseActivity +import com.k0d4black.theforce.adapters.createFavoritesAdapter +import com.k0d4black.theforce.adapters.createSearchResultAdapter +import com.k0d4black.theforce.base.BaseFragment import com.k0d4black.theforce.commons.* -import com.k0d4black.theforce.databinding.ActivityDashboardBinding import com.k0d4black.theforce.idlingresource.EspressoIdlingResource import com.k0d4black.theforce.models.CharacterPresentation import com.k0d4black.theforce.models.FavoritePresentation import com.k0d4black.theforce.models.states.DashboardFavoritesViewState import com.k0d4black.theforce.models.states.DashboardSearchViewState -import com.k0d4black.theforce.adapters.createFavoritesAdapter -import com.k0d4black.theforce.adapters.createSearchResultAdapter import com.k0d4black.theforce.viewmodel.DashboardFavoritesViewModel import com.k0d4black.theforce.viewmodel.DashboardSearchViewModel import jp.wasabeef.recyclerview.adapters.ScaleInAnimationAdapter +import kotlinx.android.synthetic.main.fragment_dashboard.* import org.koin.androidx.viewmodel.ext.android.viewModel -internal class DashboardActivity : BaseActivity() { +internal class DashboardFragment : BaseFragment(R.layout.fragment_dashboard) { // region Members private val characterSearchViewModel by viewModel() private val favoritesViewModel by viewModel() - private lateinit var binding: ActivityDashboardBinding - private val searchResultAdapter = createSearchResultAdapter { - startActivity { - putExtra(NavigationUtils.CHARACTER_PARCEL_KEY, it) - } + findNavController().navigate(DashboardFragmentDirections.actionToCharacterDetailsFragment(it)) } private val favoritesAdapter = createFavoritesAdapter { - startActivity { - putExtra(NavigationUtils.FAVORITE_PARCEL_KEY, it) - } + findNavController().navigate(DashboardFragmentDirections.actionToFavoriteDetailsFragment(it)) } // endregion @@ -63,71 +55,72 @@ internal class DashboardActivity : BaseActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - binding = DataBindingUtil.setContentView(this, R.layout.activity_dashboard) + setHasOptionsMenu(true) + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) configSupportActionBar() observeSearchViewState() observeFavoritesViewState() onInitialEditTextClick() - handleTextChanges() + handleUpButtonClick() } override fun onResume() { super.onResume() - if (binding.dashboardLayout.currentState == binding.dashboardLayout.startState) + if (dashboard_layout.currentState == dashboard_layout.startState) favoritesViewModel.getAllFavorites() } - override fun onCreateOptionsMenu(menu: Menu): Boolean { - menuInflater.inflate(R.menu.main_menu, menu) - return super.onCreateOptionsMenu(menu) + override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) { + inflater.inflate(R.menu.main_menu, menu) } override fun onOptionsItemSelected(item: MenuItem): Boolean { return if (item.itemId == R.id.action_settings) { - startActivity() + findNavController().navigate(DashboardFragmentDirections.actionToSettingsFragment()) true } else super.onOptionsItemSelected(item) } // endregion - // region Public API - - fun handleUpButtonClick(view: View) { - binding.dashboardLayout.transitionToStart() - } - - // endregion - // region Private API private fun configSupportActionBar() { - setSupportActionBar(binding.searchToolbar) - supportActionBar?.setDisplayShowTitleEnabled(false) + (requireActivity() as DashboardActivity).setSupportActionBar(search_toolbar) + (requireActivity() as DashboardActivity).supportActionBar?.setDisplayShowTitleEnabled(false) } private fun onInitialEditTextClick() { - binding.searchEditText.setOnFocusChangeListener { _, _ -> - binding.dashboardLayout.transitionToEnd() + search_edit_text.setOnFocusChangeListener { _, _ -> + dashboard_layout.transitionToEnd() } } private fun handleTextChanges() { - binding.searchEditText.doOnTextChanged { text, _, _, _ -> + search_edit_text.doOnTextChanged { text, _, _, _ -> text?.let { name -> if (name.length >= 2) { - binding.dashboardLayout.transitionToEnd() + dashboard_layout.transitionToEnd() characterSearchViewModel.executeCharacterSearch(name.toString()) } } } } + private fun handleUpButtonClick() { + back_to_start_state_image_button.setOnClickListener { + dashboard_layout.transitionToStart() + } + } + private fun observeFavoritesViewState() { - favoritesViewModel.favoritesViewState.observe(this, Observer { state -> + favoritesViewModel.favoritesViewState.observe(viewLifecycleOwner, Observer { state -> handleFavoritesLoading(state) @@ -135,7 +128,7 @@ internal class DashboardActivity : BaseActivity() { if (favorites.isNotEmpty()) { handleFavorites(favorites) } else { - binding.noFavoritesTextView.show() + no_favorites_text_view.show() } } @@ -144,7 +137,7 @@ internal class DashboardActivity : BaseActivity() { } private fun observeSearchViewState() { - characterSearchViewModel.searchViewState.observe(this, Observer { state -> + characterSearchViewModel.searchViewState.observe(viewLifecycleOwner, Observer { state -> handleSearchLoading(state) @@ -164,42 +157,42 @@ internal class DashboardActivity : BaseActivity() { private fun handleSearchLoading(state: DashboardSearchViewState) { EspressoIdlingResource.decrement() if (state.isLoading) { - binding.searchResultsRecyclerView.hide() - binding.searchResultsProgressBar.show() + search_results_recycler_view.hide() + search_results_progress_bar.show() } else { - binding.searchResultsProgressBar.hide() - binding.searchResultsRecyclerView.show() + search_results_progress_bar.hide() + search_results_recycler_view.show() } } private fun handleFavoritesLoading(state: DashboardFavoritesViewState) { EspressoIdlingResource.decrement() if (state.isLoading) { - binding.favoritesProgressBar.show() + favorites_progress_bar.show() } else { - binding.favoritesProgressBar.hide() + favorites_progress_bar.hide() } } private fun handleSearchResults(searchResults: List) { EspressoIdlingResource.decrement() showSnackbar( - binding.searchResultsRecyclerView, + search_results_recycler_view, getString(R.string.info_search_done) ) - binding.searchResultsRecyclerView.apply { + search_results_recycler_view.apply { adapter = ScaleInAnimationAdapter(searchResultAdapter.apply { submitList(searchResults) EspressoIdlingResource.decrement() }) - initRecyclerViewWithLineDecoration(this@DashboardActivity) + initRecyclerViewWithLineDecoration(requireContext()) } } private fun handleFavorites(favorites: List) { - binding.noFavoritesTextView.hide() - binding.favoritesRecyclerView.show() - binding.favoritesRecyclerView.apply { + no_favorites_text_view.hide() + favorites_recycler_view.show() + favorites_recycler_view.apply { adapter = ScaleInAnimationAdapter(favoritesAdapter.apply { submitList(favorites) EspressoIdlingResource.decrement() @@ -209,9 +202,9 @@ internal class DashboardActivity : BaseActivity() { private fun handleNoSearchResults() { EspressoIdlingResource.decrement() - binding.searchResultsRecyclerView.hide() + search_results_recycler_view.hide() showSnackbar( - binding.searchResultsRecyclerView, + search_results_recycler_view, getString(R.string.info_no_results) ) } @@ -220,7 +213,7 @@ internal class DashboardActivity : BaseActivity() { EspressoIdlingResource.decrement() state.error?.run { showSnackbar( - binding.searchResultsRecyclerView, + search_results_recycler_view, getString(this.message), isError = true ) @@ -231,7 +224,7 @@ internal class DashboardActivity : BaseActivity() { EspressoIdlingResource.decrement() state.error?.run { showSnackbar( - binding.favoritesRecyclerView, + favorites_recycler_view, getString(this.message), isError = true ) diff --git a/app/src/main/kotlin/com/k0d4black/theforce/activities/FavoriteDetailsActivity.kt b/app/src/main/kotlin/com/k0d4black/theforce/ui/FavoriteDetailsFragment.kt similarity index 69% rename from app/src/main/kotlin/com/k0d4black/theforce/activities/FavoriteDetailsActivity.kt rename to app/src/main/kotlin/com/k0d4black/theforce/ui/FavoriteDetailsFragment.kt index 10b9866c..fbb26f4c 100644 --- a/app/src/main/kotlin/com/k0d4black/theforce/activities/FavoriteDetailsActivity.kt +++ b/app/src/main/kotlin/com/k0d4black/theforce/ui/FavoriteDetailsFragment.kt @@ -1,22 +1,26 @@ -package com.k0d4black.theforce.activities +package com.k0d4black.theforce.ui import android.os.Bundle +import android.view.LayoutInflater +import android.view.View import android.view.ViewGroup import androidx.databinding.DataBindingUtil import com.k0d4black.theforce.R -import com.k0d4black.theforce.base.BaseFavoritesActivity +import com.k0d4black.theforce.adapters.createFilmsAdapter +import com.k0d4black.theforce.adapters.createSpeciesAdapter +import com.k0d4black.theforce.base.BaseFavoritesFragment import com.k0d4black.theforce.commons.remove import com.k0d4black.theforce.commons.show -import com.k0d4black.theforce.databinding.ActivityFavoritesBinding +import com.k0d4black.theforce.databinding.FragmentFavoritesBinding import com.k0d4black.theforce.models.* -import com.k0d4black.theforce.adapters.createFilmsAdapter -import com.k0d4black.theforce.adapters.createSpeciesAdapter +import kotlinx.android.synthetic.main.fragment_favorites.* -internal class FavoriteDetailsActivity : BaseFavoritesActivity(), ICharacterDetailsBinder { +internal class FavoriteDetailsFragment : BaseFavoritesFragment(R.layout.fragment_favorites), + ICharacterDetailsBinder { // region Members - private lateinit var binding: ActivityFavoritesBinding + private lateinit var binding: FragmentFavoritesBinding private val filmsAdapter = createFilmsAdapter() @@ -27,14 +31,23 @@ internal class FavoriteDetailsActivity : BaseFavoritesActivity(), ICharacterDeta // region Android API override fun onCreate(savedInstanceState: Bundle?) { - binding = DataBindingUtil.setContentView(this, R.layout.activity_favorites) setupToolbar() super.onCreate(savedInstanceState) } + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle? + ): View { + binding = DataBindingUtil.inflate( + inflater, R.layout.fragment_favorites, container, false) + return binding.root + } + // endregion - // region BaseFavoritesActivity + // region BaseFavoritesFragment override fun bindFavorite(favoritePresentation: FavoritePresentation) { bindCharacterBasicInfo(favoritePresentation.characterPresentation) @@ -44,15 +57,15 @@ internal class FavoriteDetailsActivity : BaseFavoritesActivity(), ICharacterDeta } override val rootViewGroup: ViewGroup - get() = binding.favoritesLayout + get() = favorites_layout // endregion // region Private API private fun setupToolbar() { - setSupportActionBar(binding.favoritesToolbar) - supportActionBar?.setDisplayHomeAsUpEnabled(true) + (requireActivity() as DashboardActivity).setSupportActionBar(favorites_toolbar) + (requireActivity() as DashboardActivity).supportActionBar?.setDisplayHomeAsUpEnabled(true) } // endregion @@ -60,7 +73,7 @@ internal class FavoriteDetailsActivity : BaseFavoritesActivity(), ICharacterDeta // region ICharacterDetailsBinder override fun bindCharacterBasicInfo(character: CharacterPresentation?) { - supportActionBar?.title = character?.name ?: "" + (requireActivity() as DashboardActivity).supportActionBar?.title = character?.name ?: "" binding.infoLayout.character = character } diff --git a/app/src/main/kotlin/com/k0d4black/theforce/activities/ICharacterDetailsBinder.kt b/app/src/main/kotlin/com/k0d4black/theforce/ui/ICharacterDetailsBinder.kt similarity index 96% rename from app/src/main/kotlin/com/k0d4black/theforce/activities/ICharacterDetailsBinder.kt rename to app/src/main/kotlin/com/k0d4black/theforce/ui/ICharacterDetailsBinder.kt index 4706c5d2..5e2f0912 100644 --- a/app/src/main/kotlin/com/k0d4black/theforce/activities/ICharacterDetailsBinder.kt +++ b/app/src/main/kotlin/com/k0d4black/theforce/ui/ICharacterDetailsBinder.kt @@ -13,7 +13,7 @@ * the License. * */ -package com.k0d4black.theforce.activities +package com.k0d4black.theforce.ui import com.k0d4black.theforce.models.CharacterPresentation import com.k0d4black.theforce.models.FilmPresentation diff --git a/app/src/main/kotlin/com/k0d4black/theforce/activities/IFavoritesBinder.kt b/app/src/main/kotlin/com/k0d4black/theforce/ui/IFavoritesBinder.kt similarity index 94% rename from app/src/main/kotlin/com/k0d4black/theforce/activities/IFavoritesBinder.kt rename to app/src/main/kotlin/com/k0d4black/theforce/ui/IFavoritesBinder.kt index cd8d82ff..4636d0dc 100644 --- a/app/src/main/kotlin/com/k0d4black/theforce/activities/IFavoritesBinder.kt +++ b/app/src/main/kotlin/com/k0d4black/theforce/ui/IFavoritesBinder.kt @@ -13,7 +13,7 @@ * the License. * */ -package com.k0d4black.theforce.activities +package com.k0d4black.theforce.ui import com.k0d4black.theforce.models.FavoritePresentation diff --git a/app/src/main/kotlin/com/k0d4black/theforce/ui/SettingsFragment.kt b/app/src/main/kotlin/com/k0d4black/theforce/ui/SettingsFragment.kt new file mode 100644 index 00000000..6e02c6dd --- /dev/null +++ b/app/src/main/kotlin/com/k0d4black/theforce/ui/SettingsFragment.kt @@ -0,0 +1,34 @@ +/** + * + * Copyright 2020 David Odari + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + * + **/ +package com.k0d4black.theforce.ui + +import android.os.Bundle +import android.view.View +import androidx.navigation.fragment.findNavController +import com.k0d4black.theforce.R +import com.k0d4black.theforce.base.BaseFragment +import kotlinx.android.synthetic.main.fragment_settings.* + +internal class SettingsFragment : BaseFragment(R.layout.fragment_settings) { + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + (requireActivity() as DashboardActivity).setSupportActionBar(settings_toolbar) + (requireActivity() as DashboardActivity).supportActionBar?.setDisplayHomeAsUpEnabled(true) + + about_card_view.setOnClickListener { + findNavController().navigate(SettingsFragmentDirections.actionToAboutFragment()) + } + } +} diff --git a/app/src/main/res/layout/activity_dashboard.xml b/app/src/main/res/layout/activity_dashboard.xml index 929e08a1..9df643ff 100644 --- a/app/src/main/res/layout/activity_dashboard.xml +++ b/app/src/main/res/layout/activity_dashboard.xml @@ -1,112 +1,15 @@ - - - - + android:layout_width="match_parent" + android:layout_height="match_parent"> - - - - - - - - - - - - - - - - - - - - - + app:defaultNavHost="true" + app:navGraph="@navigation/nav_graph" /> - - \ No newline at end of file + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_about.xml b/app/src/main/res/layout/fragment_about.xml similarity index 97% rename from app/src/main/res/layout/activity_about.xml rename to app/src/main/res/layout/fragment_about.xml index 3a7a102b..5c563d76 100644 --- a/app/src/main/res/layout/activity_about.xml +++ b/app/src/main/res/layout/fragment_about.xml @@ -19,7 +19,7 @@ xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" - tools:context=".activities.AboutActivity"> + tools:context=".ui.AboutFragment"> + tools:context=".ui.CharacterDetailsFragment"> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_favorites.xml b/app/src/main/res/layout/fragment_favorites.xml similarity index 96% rename from app/src/main/res/layout/activity_favorites.xml rename to app/src/main/res/layout/fragment_favorites.xml index 553d944f..6b235c2c 100644 --- a/app/src/main/res/layout/activity_favorites.xml +++ b/app/src/main/res/layout/fragment_favorites.xml @@ -25,7 +25,7 @@ android:layout_width="match_parent" android:layout_height="match_parent" android:paddingBottom="@dimen/margin_default" - tools:context=".activities.FavoriteDetailsActivity"> + tools:context=".ui.FavoriteDetailsFragment"> + tools:context=".ui.SettingsFragment"> diff --git a/app/src/main/res/navigation/nav_graph.xml b/app/src/main/res/navigation/nav_graph.xml new file mode 100644 index 00000000..9a4b8fa2 --- /dev/null +++ b/app/src/main/res/navigation/nav_graph.xml @@ -0,0 +1,63 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index ba47ca50..e58bd624 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -14,7 +14,7 @@ The Force - + No matching results No Matching Search Results No Favorite Characters @@ -27,10 +27,10 @@ Removed From Favorites Added To Favorites - + Settings - + Birth Year Height Planet @@ -50,11 +50,11 @@ %s inches Language : %s - + Settings Star Wars and all associated names are copyright Lucasfilm ltd. - + About Version %s diff --git a/build.gradle b/build.gradle index 59611d5a..b816f04f 100644 --- a/build.gradle +++ b/build.gradle @@ -24,6 +24,7 @@ buildscript { classpath 'com.dicedmelon.gradle:jacoco-android:0.1.4' classpath 'com.google.firebase:firebase-crashlytics-gradle:2.1.1' classpath "com.mikepenz.aboutlibraries.plugin:aboutlibraries-plugin:8.1.2" + classpath "android.arch.navigation:navigation-safe-args-gradle-plugin:1.0.0" } } diff --git a/dependencies.gradle b/dependencies.gradle index fd63ae06..4ca50ff9 100644 --- a/dependencies.gradle +++ b/dependencies.gradle @@ -32,6 +32,7 @@ ext { aboutLibsVersion = '8.0.0-rc01' stethoVersion = '1.5.1' kielVersion = '1.2.1' + navigation = '2.2.2' // Data retrofitVersion = '2.7.0' @@ -76,7 +77,9 @@ ext { koinLifeCycleScope : "org.koin:koin-android-scope:$koinVersion", koinAndroidViewModel : "org.koin:koin-androidx-viewmodel:$koinVersion", stetho : "com.facebook.stetho:stetho:$stethoVersion", - kiel : "me.ibrahimyilmaz:kiel:$kielVersion" + kiel : "me.ibrahimyilmaz:kiel:$kielVersion", + navigationFragment : "androidx.navigation:navigation-fragment-ktx:$navigation", + navigationUi : "androidx.navigation:navigation-ui-ktx:$navigation" ] dataDependencies = [