diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 00000000..14fc3a40 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,11 @@ +# To get started with Dependabot version updates, you'll need to specify which +# package ecosystems to update and where the package manifests are located. +# Please see the documentation for all configuration options: +# https://help.github.com/github/administering-a-repository/configuration-options-for-dependency-updates + +version: 2 +updates: + - package-ecosystem: "gradle" # See documentation for possible values + directory: "/" # Location of package manifests + schedule: + interval: "daily" diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index e4223f55..599419c7 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -14,10 +14,10 @@ jobs: steps: - name: Checkout the code uses: actions/checkout@v2 - - name: Set up JDK 1.8 + - name: Set up JDK 11 uses: actions/setup-java@v1 with: - java-version: 1.8 + java-version: 11 - name: Run ktlint run: ./gradlew ktlint tests: @@ -31,9 +31,9 @@ jobs: GOOGLE_SERVICES_JSON: ${{ secrets.GOOGLE_SERVICES_JSON }} run: | echo "${GOOGLE_SERVICES_JSON}" | base64 --decode > app/google-services.json - - name: Set up JDK 1.8 + - name: Set up JDK 11 uses: actions/setup-java@v1 with: - java-version: 1.8 + java-version: 11 - name: Tests run: ./gradlew test \ No newline at end of file diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index a1b7dfb4..2e006038 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -20,10 +20,10 @@ jobs: GOOGLE_SERVICES_JSON: ${{ secrets.GOOGLE_SERVICES_JSON }} run: | echo "${GOOGLE_SERVICES_JSON}" | base64 --decode > app/google-services.json - - name: Set up JDK 1.8 + - name: Set up JDK 11 uses: actions/setup-java@v1 with: - java-version: 1.8 + java-version: 11 - name: Assemble release env: KEY_ALIAS: ${{ secrets.KEY_ALIAS }} diff --git a/app/build.gradle b/app/build.gradle index d46ea751..e3ce00c0 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -1,7 +1,7 @@ apply plugin: 'com.android.application' apply plugin: 'kotlin-android' -apply plugin: 'kotlin-android-extensions' apply plugin: 'kotlin-kapt' +apply plugin: 'kotlin-parcelize' apply plugin: 'kotlinx-serialization' apply plugin: 'com.google.gms.google-services' apply plugin: 'com.google.firebase.crashlytics' @@ -33,17 +33,15 @@ def getGitSha = { -> } android { + compileSdkVersion 30 - androidExtensions { - experimental = true - } - buildToolsVersion '29.0.3' + buildToolsVersion '30.0.3' defaultConfig { applicationId "fi.kroon.vadret" minSdkVersion 21 targetSdkVersion 30 - versionCode 29 - versionName "2.0.2" + versionCode 30 + versionName "2.0.3" vectorDrawables.useSupportLibrary = true flavorDimensions "default" testInstrumentationRunner "androidx.top.runner.AndroidJUnitRunner" @@ -116,11 +114,14 @@ android { abortOnError false } compileOptions { - sourceCompatibility JavaVersion.VERSION_1_8 - targetCompatibility JavaVersion.VERSION_1_8 + sourceCompatibility JavaVersion.VERSION_11 + targetCompatibility JavaVersion.VERSION_11 } kotlinOptions { - jvmTarget = JavaVersion.VERSION_1_8.toString() + jvmTarget = JavaVersion.VERSION_11.toString() + } + buildFeatures { + viewBinding true } } @@ -132,39 +133,39 @@ dependencies { def APPCOMPAT_VERSION = "1.2.0" def ASSERTJ_VERSION = "3.14.0" - def COIL_VERSION = "0.13.0" + def COIL_VERSION = "1.1.1" def COMMONS_CSV_VERSION = "1.7" - def CONSTRAINT_LAYOUT_VERSION = "2.0.2" - def CORBIND_VERSION = "1.4.0" + def CONSTRAINT_LAYOUT_VERSION = "2.0.4" + def CORBIND_VERSION = "1.5.0" def CORE_KTX_VERSION = "1.3.2" - def COROUTINES_VERSION = "1.4.2" - def DAGGER_VERSION = "2.30.1" + def COROUTINES_VERSION = "1.4.3" + def DAGGER_VERSION = "2.33" def EITHER_VERSION = "1.2.0" - def FRAGMENT_VERSION = "1.3.0-beta01" + def FRAGMENT_VERSION = "1.3.2" def JUNIT_VERSION = "4.13" - def KOTLIN_STDLIB_VERSION = "1.4.10" - def KOTLINX_SERIALIZATION = "1.0.1" - def KTLINT_VERSION = "0.40.0" - def MATERIAL_VERSION = "1.2.1" + def KOTLIN_STDLIB_VERSION = "1.4.32" + def KOTLINX_SERIALIZATION = "1.1.0" + def KTLINT_VERSION = "0.41.0" + def MATERIAL_VERSION = "1.3.0" def MOCKITO_CORE_VERSION = "3.1.0" def MOSHI_VERSION = "1.9.2" - def NAVIGATION_VERSION = "2.3.2" + def NAVIGATION_VERSION = "2.3.4" def OKHTTP_VERSION = "4.2.2" def OKIO_VERSION = "2.6.0" - def OSMDROID_VERSION = "6.1.2" - def PERMISSIONS_DISPATCHER_VERSION = "4.7.0" + def OSMDROID_VERSION = "6.1.10" + def PERMISSIONS_DISPATCHER_VERSION = "4.8.0" def PREFERENCE_VERSION = "1.1.1" def RETROFIT_VERSION = "2.9.0" - def ROOM_VERSION = "2.2.5" + def ROOM_VERSION = "2.2.6" def RXANDROID_VERSION = "2.1.1" def RXBINDING_VERSION = "3.1.0" def RXJAVA_VERSION = "2.2.19" def RXKOTLIN_VERSION = "2.4.0" def RXK_PREFS_VERSION = "1.2.5" def THREETEN_ABP_VERSION = "1.2.1" - def THREETEN_BP_VERSION = "1.4.0" + def THREETEN_BP_VERSION = "1.5.0" def TIMBER_VERSION = "4.7.1" - def LIFECYCLE_VERSION = "2.2.0" + def LIFECYCLE_VERSION = "2.3.0" implementation fileTree(dir: 'libs', include: ['*.jar']) @@ -181,7 +182,7 @@ dependencies { // Lifecycle implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:$LIFECYCLE_VERSION" implementation "androidx.lifecycle:lifecycle-runtime-ktx:$LIFECYCLE_VERSION" - kapt "androidx.lifecycle:lifecycle-compiler:$LIFECYCLE_VERSION" + kapt "androidx.lifecycle:lifecycle-common-java8:$LIFECYCLE_VERSION" // Coroutines implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:${COROUTINES_VERSION}" @@ -231,7 +232,7 @@ dependencies { implementation "com.afollestad:rxkprefs:${RXK_PREFS_VERSION}" implementation "io.coil-kt:coil:${COIL_VERSION}" implementation "org.apache.commons:commons-csv:${COMMONS_CSV_VERSION}" - implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:${KOTLIN_STDLIB_VERSION}" + implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:${KOTLIN_STDLIB_VERSION}" implementation "org.osmdroid:osmdroid-android:${OSMDROID_VERSION}" implementation "io.github.sphrak:either:${EITHER_VERSION}" implementation "org.permissionsdispatcher:permissionsdispatcher:${PERMISSIONS_DISPATCHER_VERSION}" @@ -242,7 +243,7 @@ dependencies { testImplementation "org.threeten:threetenbp:${THREETEN_BP_VERSION}" // Debugging, Testing, Linting, Analytics - debugImplementation "com.squareup.leakcanary:leakcanary-android:2.5" + debugImplementation "com.squareup.leakcanary:leakcanary-android:2.6" googleImplementation platform('com.google.firebase:firebase-bom:26.0.0') googleImplementation 'com.google.firebase:firebase-crashlytics-ktx' implementation "com.jakewharton.timber:timber:${TIMBER_VERSION}" diff --git a/app/src/main/java/fi/kroon/vadret/data/autocomplete/model/AutoCompleteItem.kt b/app/src/main/java/fi/kroon/vadret/data/autocomplete/model/AutoCompleteItem.kt index e4e41225..fd6f72b5 100644 --- a/app/src/main/java/fi/kroon/vadret/data/autocomplete/model/AutoCompleteItem.kt +++ b/app/src/main/java/fi/kroon/vadret/data/autocomplete/model/AutoCompleteItem.kt @@ -1,7 +1,7 @@ package fi.kroon.vadret.data.autocomplete.model import android.os.Parcelable -import kotlinx.android.parcel.Parcelize +import kotlinx.parcelize.Parcelize @Parcelize data class AutoCompleteItem( diff --git a/app/src/main/java/fi/kroon/vadret/data/district/model/DistrictOptionEntity.kt b/app/src/main/java/fi/kroon/vadret/data/district/model/DistrictOptionEntity.kt index 08658a3d..14d57dc3 100644 --- a/app/src/main/java/fi/kroon/vadret/data/district/model/DistrictOptionEntity.kt +++ b/app/src/main/java/fi/kroon/vadret/data/district/model/DistrictOptionEntity.kt @@ -4,7 +4,7 @@ import android.os.Parcelable import androidx.room.ColumnInfo import androidx.room.DatabaseView import fi.kroon.vadret.presentation.warning.filter.model.IFilterable -import kotlinx.android.parcel.Parcelize +import kotlinx.parcelize.Parcelize @Parcelize @DatabaseView( diff --git a/app/src/main/java/fi/kroon/vadret/data/feedsource/model/FeedSourceOptionEntity.kt b/app/src/main/java/fi/kroon/vadret/data/feedsource/model/FeedSourceOptionEntity.kt index 278dceba..a98724ae 100644 --- a/app/src/main/java/fi/kroon/vadret/data/feedsource/model/FeedSourceOptionEntity.kt +++ b/app/src/main/java/fi/kroon/vadret/data/feedsource/model/FeedSourceOptionEntity.kt @@ -4,7 +4,7 @@ import android.os.Parcelable import androidx.room.ColumnInfo import androidx.room.DatabaseView import fi.kroon.vadret.presentation.warning.filter.model.IFilterable -import kotlinx.android.parcel.Parcelize +import kotlinx.parcelize.Parcelize @Parcelize @DatabaseView( diff --git a/app/src/main/java/fi/kroon/vadret/data/nominatim/model/Address.kt b/app/src/main/java/fi/kroon/vadret/data/nominatim/model/Address.kt index 6adf5bc6..5159508a 100644 --- a/app/src/main/java/fi/kroon/vadret/data/nominatim/model/Address.kt +++ b/app/src/main/java/fi/kroon/vadret/data/nominatim/model/Address.kt @@ -3,7 +3,7 @@ package fi.kroon.vadret.data.nominatim.model import android.os.Parcelable import com.squareup.moshi.Json import com.squareup.moshi.JsonClass -import kotlinx.android.parcel.Parcelize +import kotlinx.parcelize.Parcelize @Parcelize @JsonClass(generateAdapter = true) diff --git a/app/src/main/java/fi/kroon/vadret/data/nominatim/model/Locality.kt b/app/src/main/java/fi/kroon/vadret/data/nominatim/model/Locality.kt index 7d5fd098..951f30d9 100644 --- a/app/src/main/java/fi/kroon/vadret/data/nominatim/model/Locality.kt +++ b/app/src/main/java/fi/kroon/vadret/data/nominatim/model/Locality.kt @@ -3,7 +3,7 @@ package fi.kroon.vadret.data.nominatim.model import android.os.Parcelable import androidx.annotation.StringRes import fi.kroon.vadret.R -import kotlinx.android.parcel.Parcelize +import kotlinx.parcelize.Parcelize @Parcelize data class Locality( diff --git a/app/src/main/java/fi/kroon/vadret/data/nominatim/model/Nominatim.kt b/app/src/main/java/fi/kroon/vadret/data/nominatim/model/Nominatim.kt index f7ff7f67..7ce6a4e3 100644 --- a/app/src/main/java/fi/kroon/vadret/data/nominatim/model/Nominatim.kt +++ b/app/src/main/java/fi/kroon/vadret/data/nominatim/model/Nominatim.kt @@ -3,7 +3,7 @@ package fi.kroon.vadret.data.nominatim.model import android.os.Parcelable import com.squareup.moshi.Json import com.squareup.moshi.JsonClass -import kotlinx.android.parcel.Parcelize +import kotlinx.parcelize.Parcelize @Parcelize @JsonClass(generateAdapter = true) diff --git a/app/src/main/java/fi/kroon/vadret/data/nominatim/model/NominatimList.kt b/app/src/main/java/fi/kroon/vadret/data/nominatim/model/NominatimList.kt index 45d1a4d6..7135fe58 100644 --- a/app/src/main/java/fi/kroon/vadret/data/nominatim/model/NominatimList.kt +++ b/app/src/main/java/fi/kroon/vadret/data/nominatim/model/NominatimList.kt @@ -1,7 +1,7 @@ package fi.kroon.vadret.data.nominatim.model import android.os.Parcelable -import kotlinx.android.parcel.Parcelize +import kotlinx.parcelize.Parcelize @Parcelize data class NominatimList( diff --git a/app/src/main/java/fi/kroon/vadret/data/weatherforecast/model/WeatherOut.kt b/app/src/main/java/fi/kroon/vadret/data/weatherforecast/model/WeatherOut.kt index a4bb60e3..e63cd3b8 100644 --- a/app/src/main/java/fi/kroon/vadret/data/weatherforecast/model/WeatherOut.kt +++ b/app/src/main/java/fi/kroon/vadret/data/weatherforecast/model/WeatherOut.kt @@ -4,7 +4,7 @@ import android.os.Parcelable import fi.kroon.vadret.util.PMP3G_CATEGORY import fi.kroon.vadret.util.POINT_GEOTYPE import fi.kroon.vadret.util.SMHI_BASE_API_VERSION -import kotlinx.android.parcel.Parcelize +import kotlinx.parcelize.Parcelize @Parcelize data class WeatherOut( diff --git a/app/src/main/java/fi/kroon/vadret/presentation/aboutapp/AboutAppFragment.kt b/app/src/main/java/fi/kroon/vadret/presentation/aboutapp/AboutAppFragment.kt index f1140485..82f44c52 100644 --- a/app/src/main/java/fi/kroon/vadret/presentation/aboutapp/AboutAppFragment.kt +++ b/app/src/main/java/fi/kroon/vadret/presentation/aboutapp/AboutAppFragment.kt @@ -1,20 +1,23 @@ package fi.kroon.vadret.presentation.aboutapp import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup import androidx.fragment.app.Fragment import androidx.lifecycle.lifecycleScope -import fi.kroon.vadret.R +import fi.kroon.vadret.databinding.AboutAppFragmentBinding import fi.kroon.vadret.presentation.aboutapp.di.AboutAppComponent import fi.kroon.vadret.presentation.aboutapp.di.DaggerAboutAppComponent import fi.kroon.vadret.util.extension.lazyAndroid -import kotlinx.android.synthetic.main.about_app_fragment.* -import kotlinx.coroutines.ExperimentalCoroutinesApi -import kotlinx.coroutines.flow.collect -import kotlinx.coroutines.launch +import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.onEach import timber.log.Timber -@ExperimentalCoroutinesApi -class AboutAppFragment : Fragment(R.layout.about_app_fragment) { +class AboutAppFragment : Fragment() { + + private var _binding: AboutAppFragmentBinding? = null + private val binding: AboutAppFragmentBinding get() = _binding!! private lateinit var aboutAppFragmentPagerAdapter: AboutAppFragmentPagerAdapter @@ -28,15 +31,22 @@ class AboutAppFragment : Fragment(R.layout.about_app_fragment) { component.provideViewModel() } - override fun onActivityCreated(savedInstanceState: Bundle?) { - super.onActivityCreated(savedInstanceState) + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle? + ): View { + _binding = AboutAppFragmentBinding.inflate(inflater, container, false) + return binding.root + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) - lifecycleScope - .launch { - viewModel - .viewState - .collect(::render) - } + viewModel + .viewState + .onEach(::render) + .launchIn(viewLifecycleOwner.lifecycleScope) setupRecyclerView() } @@ -44,9 +54,8 @@ class AboutAppFragment : Fragment(R.layout.about_app_fragment) { override fun onDestroyView() { super.onDestroyView() Timber.d("ON VIEW DESTROY") - aboutAppViewPager?.apply { - adapter = null - } + binding.aboutAppViewPager.adapter = null + _binding = null } private fun setupRecyclerView() { @@ -54,10 +63,9 @@ class AboutAppFragment : Fragment(R.layout.about_app_fragment) { childFragmentManager, requireContext() ) - aboutAppViewPager?.adapter = aboutAppFragmentPagerAdapter - - aboutAppTabLayout?.apply { - setupWithViewPager(aboutAppViewPager) + binding.apply { + aboutAppViewPager.adapter = aboutAppFragmentPagerAdapter + aboutAppTabLayout.setupWithViewPager(aboutAppViewPager) } } diff --git a/app/src/main/java/fi/kroon/vadret/presentation/aboutapp/AboutAppView.kt b/app/src/main/java/fi/kroon/vadret/presentation/aboutapp/AboutAppView.kt index ddee91ef..a83875b0 100644 --- a/app/src/main/java/fi/kroon/vadret/presentation/aboutapp/AboutAppView.kt +++ b/app/src/main/java/fi/kroon/vadret/presentation/aboutapp/AboutAppView.kt @@ -1,7 +1,7 @@ package fi.kroon.vadret.presentation.aboutapp import android.os.Parcelable -import kotlinx.android.parcel.Parcelize +import kotlinx.parcelize.Parcelize object AboutAppView { diff --git a/app/src/main/java/fi/kroon/vadret/presentation/aboutapp/AboutAppViewModel.kt b/app/src/main/java/fi/kroon/vadret/presentation/aboutapp/AboutAppViewModel.kt index efa86130..01f890b2 100644 --- a/app/src/main/java/fi/kroon/vadret/presentation/aboutapp/AboutAppViewModel.kt +++ b/app/src/main/java/fi/kroon/vadret/presentation/aboutapp/AboutAppViewModel.kt @@ -2,14 +2,12 @@ package fi.kroon.vadret.presentation.aboutapp import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope -import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.SharedFlow import kotlinx.coroutines.flow.asSharedFlow import kotlinx.coroutines.launch import javax.inject.Inject -@ExperimentalCoroutinesApi class AboutAppViewModel @Inject constructor( private val state: MutableSharedFlow, private var stateModel: AboutAppView.State, diff --git a/app/src/main/java/fi/kroon/vadret/presentation/aboutapp/about/AboutAppAboutAdapter.kt b/app/src/main/java/fi/kroon/vadret/presentation/aboutapp/about/AboutAppAboutAdapter.kt index d4647126..9d66423c 100644 --- a/app/src/main/java/fi/kroon/vadret/presentation/aboutapp/about/AboutAppAboutAdapter.kt +++ b/app/src/main/java/fi/kroon/vadret/presentation/aboutapp/about/AboutAppAboutAdapter.kt @@ -1,15 +1,13 @@ package fi.kroon.vadret.presentation.aboutapp.about import android.view.LayoutInflater -import android.view.View import android.view.ViewGroup import android.widget.TextView import androidx.recyclerview.widget.RecyclerView -import fi.kroon.vadret.R import fi.kroon.vadret.data.aboutinfo.model.AboutInfo +import fi.kroon.vadret.databinding.AboutAppAboutItemBinding import fi.kroon.vadret.presentation.aboutapp.extension.onClickThrottled import fi.kroon.vadret.util.extension.toGone -import kotlinx.android.synthetic.main.about_app_about_item.view.* class AboutAppAboutAdapter constructor( private val onAboutAppAboutInfoItemClicked: (AboutInfo) -> Unit @@ -19,10 +17,9 @@ class AboutAppAboutAdapter constructor( override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder = ViewHolder( - LayoutInflater - .from(parent.context) + AboutAppAboutItemBinding .inflate( - R.layout.about_app_about_item, + LayoutInflater.from(parent.context), parent, false ) @@ -32,23 +29,25 @@ class AboutAppAboutAdapter constructor( override fun onBindViewHolder(holder: ViewHolder, position: Int) = holder.bind(list[position]) - inner class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) { + inner class ViewHolder( + private val itemBinding: AboutAppAboutItemBinding + ) : RecyclerView.ViewHolder(itemBinding.root) { init { - itemView.onClickThrottled { + itemBinding.root.onClickThrottled { onAboutAppAboutInfoItemClicked(list[adapterPosition]) } } fun bind(entity: AboutInfo) { - itemView.apply { + itemBinding.apply { with(entity) { iconResourceId?.let { id -> aboutAppAboutInfoItemIconImageView.setImageResource(id) } - setTextOrMakeGone(itemView.aboutAppLibraryItemTitleTextView, titleResourceId) - setTextOrMakeGone(itemView.aboutAppAboutInfoItemHintTextView, hintResourceId) + setTextOrMakeGone(itemBinding.aboutAppLibraryItemTitleTextView, titleResourceId) + setTextOrMakeGone(itemBinding.aboutAppAboutInfoItemHintTextView, hintResourceId) } } } diff --git a/app/src/main/java/fi/kroon/vadret/presentation/aboutapp/about/AboutAppAboutFragment.kt b/app/src/main/java/fi/kroon/vadret/presentation/aboutapp/about/AboutAppAboutFragment.kt index 8fe898bf..51dd3782 100644 --- a/app/src/main/java/fi/kroon/vadret/presentation/aboutapp/about/AboutAppAboutFragment.kt +++ b/app/src/main/java/fi/kroon/vadret/presentation/aboutapp/about/AboutAppAboutFragment.kt @@ -3,23 +3,27 @@ package fi.kroon.vadret.presentation.aboutapp.about import android.content.Intent import android.net.Uri import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup import androidx.fragment.app.Fragment import androidx.lifecycle.lifecycleScope import androidx.recyclerview.widget.LinearLayoutManager import fi.kroon.vadret.R import fi.kroon.vadret.data.aboutinfo.model.AboutInfo +import fi.kroon.vadret.databinding.AboutAppAboutFragmentBinding import fi.kroon.vadret.presentation.aboutapp.about.di.AboutAppAboutComponent import fi.kroon.vadret.presentation.aboutapp.about.di.DaggerAboutAppAboutComponent import fi.kroon.vadret.util.extension.lazyAndroid -import kotlinx.android.synthetic.main.about_app_about_fragment.* -import kotlinx.coroutines.ExperimentalCoroutinesApi -import kotlinx.coroutines.flow.collect -import kotlinx.coroutines.launch +import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.onEach import timber.log.Timber -@ExperimentalCoroutinesApi class AboutAppAboutFragment : Fragment(R.layout.about_app_about_fragment) { + private var _binding: AboutAppAboutFragmentBinding? = null + private val binding: AboutAppAboutFragmentBinding get() = _binding!! + companion object { fun newInstance(): AboutAppAboutFragment = AboutAppAboutFragment() } @@ -36,32 +40,43 @@ class AboutAppAboutFragment : Fragment(R.layout.about_app_about_fragment) { component.provideViewModel() } - override fun onActivityCreated(savedInstanceState: Bundle?) { - super.onActivityCreated(savedInstanceState) + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle? + ): View { + _binding = AboutAppAboutFragmentBinding.inflate(inflater, container, false) + return binding.root + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) setupRecyclerView() - lifecycleScope - .launch { - viewModel - .viewState - .collect(::render) - } + + viewModel + .viewState + .onEach(::render) + .launchIn(viewLifecycleOwner.lifecycleScope) + viewModel.send(AboutAppAboutView.Event.OnViewInitialised) } override fun onDestroyView() { super.onDestroyView() Timber.d("ON DESTROY VIEW") - aboutAppAboutInfoTextRecyclerView.apply { - adapter = null - } + binding.aboutAppAboutInfoTextRecyclerView + .apply { + adapter = null + } + _binding = null } private fun setupRecyclerView() { aboutAppAboutAdapter = AboutAppAboutAdapter { aboutInfo: AboutInfo -> viewModel.send(AboutAppAboutView.Event.OnItemClick(aboutInfo)) } - aboutAppAboutInfoTextRecyclerView.apply { + binding.aboutAppAboutInfoTextRecyclerView.apply { layoutManager = LinearLayoutManager(requireContext()) adapter = aboutAppAboutAdapter } diff --git a/app/src/main/java/fi/kroon/vadret/presentation/aboutapp/about/AboutAppAboutViewModel.kt b/app/src/main/java/fi/kroon/vadret/presentation/aboutapp/about/AboutAppAboutViewModel.kt index 60c305fe..7e2d9b9e 100644 --- a/app/src/main/java/fi/kroon/vadret/presentation/aboutapp/about/AboutAppAboutViewModel.kt +++ b/app/src/main/java/fi/kroon/vadret/presentation/aboutapp/about/AboutAppAboutViewModel.kt @@ -7,7 +7,6 @@ import fi.kroon.vadret.R import fi.kroon.vadret.data.aboutinfo.model.AboutInfo import fi.kroon.vadret.data.failure.Failure import fi.kroon.vadret.domain.aboutapp.GetAboutInfoTask -import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.SharedFlow import kotlinx.coroutines.flow.asSharedFlow @@ -15,7 +14,6 @@ import kotlinx.coroutines.launch import kotlinx.coroutines.rx2.await import javax.inject.Inject -@ExperimentalCoroutinesApi class AboutAppAboutViewModel @Inject constructor( private val state: MutableSharedFlow, private var stateModel: AboutAppAboutView.State, diff --git a/app/src/main/java/fi/kroon/vadret/presentation/aboutapp/about/di/AboutAppAboutComponent.kt b/app/src/main/java/fi/kroon/vadret/presentation/aboutapp/about/di/AboutAppAboutComponent.kt index 2f0782aa..34b3d10f 100644 --- a/app/src/main/java/fi/kroon/vadret/presentation/aboutapp/about/di/AboutAppAboutComponent.kt +++ b/app/src/main/java/fi/kroon/vadret/presentation/aboutapp/about/di/AboutAppAboutComponent.kt @@ -4,9 +4,7 @@ import android.content.Context import dagger.BindsInstance import dagger.Component import fi.kroon.vadret.presentation.aboutapp.about.AboutAppAboutViewModel -import kotlinx.coroutines.ExperimentalCoroutinesApi -@ExperimentalCoroutinesApi @AboutAppAboutScope @Component( modules = [ diff --git a/app/src/main/java/fi/kroon/vadret/presentation/aboutapp/about/di/AboutAppAboutModule.kt b/app/src/main/java/fi/kroon/vadret/presentation/aboutapp/about/di/AboutAppAboutModule.kt index 25ade982..04c1cb41 100644 --- a/app/src/main/java/fi/kroon/vadret/presentation/aboutapp/about/di/AboutAppAboutModule.kt +++ b/app/src/main/java/fi/kroon/vadret/presentation/aboutapp/about/di/AboutAppAboutModule.kt @@ -3,11 +3,9 @@ package fi.kroon.vadret.presentation.aboutapp.about.di import dagger.Module import dagger.Provides import fi.kroon.vadret.presentation.aboutapp.about.AboutAppAboutView -import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.MutableSharedFlow @Module -@ExperimentalCoroutinesApi object AboutAppAboutModule { @Provides diff --git a/app/src/main/java/fi/kroon/vadret/presentation/aboutapp/di/AboutAppComponent.kt b/app/src/main/java/fi/kroon/vadret/presentation/aboutapp/di/AboutAppComponent.kt index 5805fb46..2c50f375 100644 --- a/app/src/main/java/fi/kroon/vadret/presentation/aboutapp/di/AboutAppComponent.kt +++ b/app/src/main/java/fi/kroon/vadret/presentation/aboutapp/di/AboutAppComponent.kt @@ -5,9 +5,7 @@ import dagger.BindsInstance import dagger.Component import fi.kroon.vadret.presentation.aboutapp.AboutAppViewModel import fi.kroon.vadret.presentation.aboutapp.about.di.AboutAppAboutScope -import kotlinx.coroutines.ExperimentalCoroutinesApi -@ExperimentalCoroutinesApi @AboutAppAboutScope @Component( modules = [ diff --git a/app/src/main/java/fi/kroon/vadret/presentation/aboutapp/di/AboutAppModule.kt b/app/src/main/java/fi/kroon/vadret/presentation/aboutapp/di/AboutAppModule.kt index ac643917..32e8572a 100644 --- a/app/src/main/java/fi/kroon/vadret/presentation/aboutapp/di/AboutAppModule.kt +++ b/app/src/main/java/fi/kroon/vadret/presentation/aboutapp/di/AboutAppModule.kt @@ -4,11 +4,9 @@ import dagger.Module import dagger.Provides import fi.kroon.vadret.presentation.aboutapp.AboutAppView import fi.kroon.vadret.presentation.aboutapp.about.di.AboutAppAboutScope -import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.MutableSharedFlow @Module -@ExperimentalCoroutinesApi object AboutAppModule { @Provides diff --git a/app/src/main/java/fi/kroon/vadret/presentation/aboutapp/library/AboutAppLibraryAdapter.kt b/app/src/main/java/fi/kroon/vadret/presentation/aboutapp/library/AboutAppLibraryAdapter.kt index c2619bf7..437d1d4b 100644 --- a/app/src/main/java/fi/kroon/vadret/presentation/aboutapp/library/AboutAppLibraryAdapter.kt +++ b/app/src/main/java/fi/kroon/vadret/presentation/aboutapp/library/AboutAppLibraryAdapter.kt @@ -1,38 +1,39 @@ package fi.kroon.vadret.presentation.aboutapp.library import android.view.LayoutInflater -import android.view.View import android.view.ViewGroup import androidx.recyclerview.widget.RecyclerView -import fi.kroon.vadret.R import fi.kroon.vadret.data.library.model.Library +import fi.kroon.vadret.databinding.AboutAppLibraryItemBinding import fi.kroon.vadret.presentation.aboutapp.extension.onClickThrottled -import kotlinx.android.synthetic.main.about_app_library_item.view.* class AboutAppLibraryAdapter constructor( private val onProjectUrlClicked: (Library) -> Unit, private val onSourceUrlClicked: (Library) -> Unit, private val onLicenseUrlClicked: (Library) -> Unit -) : RecyclerView.Adapter() { +) : RecyclerView.Adapter() { private val list: MutableList = mutableListOf() - override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder = - ViewHolder( - LayoutInflater - .from(parent.context) - .inflate(R.layout.about_app_library_item, parent, false) + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): AboutAppLibraryViewHolder = + AboutAppLibraryViewHolder( + AboutAppLibraryItemBinding + .inflate( + LayoutInflater.from(parent.context), + parent, + false + ) ) override fun getItemCount(): Int = list.size - override fun onBindViewHolder(holder: ViewHolder, position: Int) { + override fun onBindViewHolder(holder: AboutAppLibraryViewHolder, position: Int) { holder.bind(list[position]) } - inner class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) { + inner class AboutAppLibraryViewHolder(private val itemBinding: AboutAppLibraryItemBinding) : RecyclerView.ViewHolder(itemBinding.root) { init { - itemView.apply { + itemBinding.apply { aboutAppLibraryItemProjectUrlButton .onClickThrottled { onProjectUrlClicked(list[adapterPosition]) @@ -51,7 +52,7 @@ class AboutAppLibraryAdapter constructor( } fun bind(library: Library) { - itemView.apply { + itemBinding.apply { aboutAppLibraryItemTitleTextView.text = library.title aboutAppLibraryItemDescriptionTextView.text = library.description } diff --git a/app/src/main/java/fi/kroon/vadret/presentation/aboutapp/library/AboutAppLibraryFragment.kt b/app/src/main/java/fi/kroon/vadret/presentation/aboutapp/library/AboutAppLibraryFragment.kt index e5d251ef..8db61df1 100644 --- a/app/src/main/java/fi/kroon/vadret/presentation/aboutapp/library/AboutAppLibraryFragment.kt +++ b/app/src/main/java/fi/kroon/vadret/presentation/aboutapp/library/AboutAppLibraryFragment.kt @@ -3,23 +3,27 @@ package fi.kroon.vadret.presentation.aboutapp.library import android.content.Intent import android.net.Uri import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup import android.widget.Toast import androidx.fragment.app.Fragment import androidx.lifecycle.lifecycleScope import androidx.recyclerview.widget.LinearLayoutManager import fi.kroon.vadret.R +import fi.kroon.vadret.databinding.AboutAppLibraryFragmentBinding import fi.kroon.vadret.presentation.aboutapp.library.di.AboutAppLibraryComponent import fi.kroon.vadret.presentation.aboutapp.library.di.DaggerAboutAppLibraryComponent import fi.kroon.vadret.util.extension.lazyAndroid -import kotlinx.android.synthetic.main.about_app_library_fragment.* -import kotlinx.coroutines.ExperimentalCoroutinesApi -import kotlinx.coroutines.flow.collect -import kotlinx.coroutines.launch +import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.onEach import timber.log.Timber -@ExperimentalCoroutinesApi class AboutAppLibraryFragment : Fragment(R.layout.about_app_library_fragment) { + private var _binding: AboutAppLibraryFragmentBinding? = null + private val binding: AboutAppLibraryFragmentBinding get() = _binding!! + companion object { fun newInstance(): AboutAppLibraryFragment = AboutAppLibraryFragment() } @@ -36,25 +40,35 @@ class AboutAppLibraryFragment : Fragment(R.layout.about_app_library_fragment) { .create(context = requireContext()) } - override fun onActivityCreated(savedInstanceState: Bundle?) { - super.onActivityCreated(savedInstanceState) + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle? + ): View { + _binding = AboutAppLibraryFragmentBinding.inflate(inflater, container, false) + return binding.root + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) setupRecyclerView() - lifecycleScope - .launch { - viewModel - .viewState - .collect(::render) - } + + viewModel + .viewState + .onEach(::render) + .launchIn(viewLifecycleOwner.lifecycleScope) + viewModel.send(AboutAppLibraryView.Event.OnViewInitialised) } override fun onDestroyView() { super.onDestroyView() Timber.d("ON DESTROY VIEW") - aboutAppLibraryRecyclerView.apply { + binding.aboutAppLibraryRecyclerView.apply { adapter = null } + _binding = null } private fun setupRecyclerView() { @@ -69,7 +83,7 @@ class AboutAppLibraryFragment : Fragment(R.layout.about_app_library_fragment) { viewModel.send(AboutAppLibraryView.Event.OnLicenseUrlClick(it)) } ) - aboutAppLibraryRecyclerView + binding.aboutAppLibraryRecyclerView .apply { layoutManager = LinearLayoutManager(requireContext()) adapter = aboutAppLibraryAdapter diff --git a/app/src/main/java/fi/kroon/vadret/presentation/aboutapp/library/AboutAppLibraryViewModel.kt b/app/src/main/java/fi/kroon/vadret/presentation/aboutapp/library/AboutAppLibraryViewModel.kt index eaf22068..e46f232b 100644 --- a/app/src/main/java/fi/kroon/vadret/presentation/aboutapp/library/AboutAppLibraryViewModel.kt +++ b/app/src/main/java/fi/kroon/vadret/presentation/aboutapp/library/AboutAppLibraryViewModel.kt @@ -5,7 +5,6 @@ import androidx.lifecycle.viewModelScope import fi.kroon.vadret.data.failure.Failure import fi.kroon.vadret.data.library.model.Library import fi.kroon.vadret.domain.aboutapp.GetAboutLibraryTask -import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.SharedFlow import kotlinx.coroutines.flow.asSharedFlow @@ -14,7 +13,6 @@ import kotlinx.coroutines.rx2.await import timber.log.Timber import javax.inject.Inject -@ExperimentalCoroutinesApi class AboutAppLibraryViewModel @Inject constructor( private val state: MutableSharedFlow, private var stateModel: AboutAppLibraryView.State, diff --git a/app/src/main/java/fi/kroon/vadret/presentation/aboutapp/library/di/AboutAppLibraryComponent.kt b/app/src/main/java/fi/kroon/vadret/presentation/aboutapp/library/di/AboutAppLibraryComponent.kt index f8c7247c..b0d2e52e 100644 --- a/app/src/main/java/fi/kroon/vadret/presentation/aboutapp/library/di/AboutAppLibraryComponent.kt +++ b/app/src/main/java/fi/kroon/vadret/presentation/aboutapp/library/di/AboutAppLibraryComponent.kt @@ -4,9 +4,7 @@ import android.content.Context import dagger.BindsInstance import dagger.Component import fi.kroon.vadret.presentation.aboutapp.library.AboutAppLibraryViewModel -import kotlinx.coroutines.ExperimentalCoroutinesApi -@ExperimentalCoroutinesApi @AboutAppLibraryScope @Component( modules = [ diff --git a/app/src/main/java/fi/kroon/vadret/presentation/aboutapp/library/di/AboutAppLibraryModule.kt b/app/src/main/java/fi/kroon/vadret/presentation/aboutapp/library/di/AboutAppLibraryModule.kt index 7248debc..b50ed6be 100644 --- a/app/src/main/java/fi/kroon/vadret/presentation/aboutapp/library/di/AboutAppLibraryModule.kt +++ b/app/src/main/java/fi/kroon/vadret/presentation/aboutapp/library/di/AboutAppLibraryModule.kt @@ -3,11 +3,9 @@ package fi.kroon.vadret.presentation.aboutapp.library.di import dagger.Module import dagger.Provides import fi.kroon.vadret.presentation.aboutapp.library.AboutAppLibraryView -import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.MutableSharedFlow @Module -@ExperimentalCoroutinesApi object AboutAppLibraryModule { @Provides diff --git a/app/src/main/java/fi/kroon/vadret/presentation/main/MainActivity.kt b/app/src/main/java/fi/kroon/vadret/presentation/main/MainActivity.kt index 7dc6579c..92e1938a 100644 --- a/app/src/main/java/fi/kroon/vadret/presentation/main/MainActivity.kt +++ b/app/src/main/java/fi/kroon/vadret/presentation/main/MainActivity.kt @@ -1,29 +1,34 @@ package fi.kroon.vadret.presentation.main import android.os.Bundle +import androidx.appcompat.app.AppCompatActivity import androidx.navigation.fragment.NavHostFragment import androidx.navigation.ui.setupWithNavController import androidx.preference.PreferenceManager import com.google.android.material.bottomnavigation.BottomNavigationView import fi.kroon.vadret.R import fi.kroon.vadret.data.failure.Failure +import fi.kroon.vadret.data.nominatim.model.Locality import fi.kroon.vadret.data.theme.model.Theme +import fi.kroon.vadret.databinding.MainActivityBinding import fi.kroon.vadret.presentation.main.di.DaggerMainActivityComponent import fi.kroon.vadret.presentation.main.di.MainActivityComponent -import fi.kroon.vadret.presentation.shared.BaseActivity import fi.kroon.vadret.util.DEFAULT_SETTINGS import fi.kroon.vadret.util.Scheduler import fi.kroon.vadret.util.extension.coreComponent import fi.kroon.vadret.util.extension.lazyAndroid +import fi.kroon.vadret.util.extension.toGone import fi.kroon.vadret.util.extension.toObservable +import fi.kroon.vadret.util.extension.toVisible import io.reactivex.Observable import io.reactivex.disposables.CompositeDisposable import io.reactivex.rxkotlin.addTo import io.reactivex.subjects.PublishSubject -import kotlinx.android.synthetic.main.activity_main.* import timber.log.Timber -class MainActivity : BaseActivity() { +class MainActivity : AppCompatActivity() { + + private lateinit var binding: MainActivityBinding private val component: MainActivityComponent by lazyAndroid { DaggerMainActivityComponent @@ -66,7 +71,8 @@ class MainActivity : BaseActivity() { */ preSetupEvents() - setContentView(R.layout.activity_main) + binding = MainActivityBinding.inflate(layoutInflater) + setContentView(binding.root) setupSupportActionBar() if (savedInstanceState == null) { @@ -96,20 +102,22 @@ class MainActivity : BaseActivity() { ) }.blockingGet() - override fun onStop() { - super.onStop() - subscriptions.clear() - } - override fun onRestoreInstanceState(savedInstanceState: Bundle) { super.onRestoreInstanceState(savedInstanceState) // Now that BottomNavigationBar has restored its instance state // and its selectedItemId, we can proceed with setting up the // BottomNavigationBar with Navigation - Timber.d("ON RESTORE INSTANCE STATE") + // + // This is needed in order to apply themes from settings. + setupBottomNavigationBar() } + override fun onStop() { + super.onStop() + subscriptions.clear() + } + private fun setupBottomNavigationBar() { Timber.d("setupBottomNavigationBar") @@ -121,7 +129,7 @@ class MainActivity : BaseActivity() { } private fun setupSupportActionBar() { - setSupportActionBar(toolBar) + setSupportActionBar(binding.toolBar) } private fun setupPreferences() { @@ -175,7 +183,16 @@ class MainActivity : BaseActivity() { recreate() } - override fun renderError(errorCode: Int) { + private fun renderError(errorCode: Int) { Timber.e("Rendering error code: ${getString(errorCode)}") } + + fun hideLocalityActionBar() = binding.currentLocationName.toGone() + + fun setLocalityActionBar(locality: Locality) { + locality.name?.let { + binding.currentLocationName.text = locality.name + } ?: binding.currentLocationName.setText(R.string.unknown_area) + binding.currentLocationName.toVisible() + } } \ No newline at end of file diff --git a/app/src/main/java/fi/kroon/vadret/presentation/radar/RadarFragment.kt b/app/src/main/java/fi/kroon/vadret/presentation/radar/RadarFragment.kt index 2a6ae92f..8950dcbd 100644 --- a/app/src/main/java/fi/kroon/vadret/presentation/radar/RadarFragment.kt +++ b/app/src/main/java/fi/kroon/vadret/presentation/radar/RadarFragment.kt @@ -3,8 +3,11 @@ package fi.kroon.vadret.presentation.radar import android.graphics.Bitmap import android.graphics.drawable.Drawable import android.os.Bundle +import android.view.LayoutInflater import android.view.View +import android.view.ViewGroup import androidx.core.graphics.drawable.toBitmap +import androidx.fragment.app.Fragment import coil.ImageLoader import coil.request.ImageRequest import com.jakewharton.rxbinding3.view.clicks @@ -12,9 +15,9 @@ import com.jakewharton.rxbinding3.widget.changes import com.jakewharton.rxbinding3.widget.userChanges import fi.kroon.vadret.BuildConfig import fi.kroon.vadret.R +import fi.kroon.vadret.databinding.RadarFragmentBinding import fi.kroon.vadret.presentation.radar.di.DaggerRadarComponent import fi.kroon.vadret.presentation.radar.di.RadarComponent -import fi.kroon.vadret.presentation.shared.BaseFragment import fi.kroon.vadret.util.DEFAULT_BOUNDINGBOX_CENTER_LATITUDE import fi.kroon.vadret.util.DEFAULT_BOUNDINGBOX_CENTER_LONGITUDE import fi.kroon.vadret.util.DEFAULT_BOUNDINGBOX_LATITUDE_MAX @@ -36,9 +39,6 @@ import io.reactivex.disposables.CompositeDisposable import io.reactivex.disposables.Disposable import io.reactivex.rxkotlin.addTo import io.reactivex.subjects.PublishSubject -import kotlinx.android.synthetic.main.radar_fragment.radarMapView -import kotlinx.android.synthetic.main.radar_fragment.radarPlayFab -import kotlinx.android.synthetic.main.radar_fragment.radarSeekBar import org.osmdroid.config.Configuration import org.osmdroid.config.IConfigurationProvider import org.osmdroid.tileprovider.tilesource.XYTileSource @@ -52,7 +52,7 @@ import java.util.concurrent.TimeUnit typealias RadarFile = fi.kroon.vadret.data.radar.model.File -class RadarFragment : BaseFragment() { +class RadarFragment : Fragment() { private companion object { const val A_NAME = "wikimedia" @@ -62,9 +62,12 @@ class RadarFragment : BaseFragment() { const val STATE_PARCEL_KEY = "STATE_PARCEL_KEY" } + private var _binding: RadarFragmentBinding? = null + private val binding: RadarFragmentBinding get() = _binding!! private lateinit var disposable: Disposable private var bundle: Bundle? = null private var stateParcel: RadarView.StateParcel? = null + private var isConfigChangeOrProcessDeath = false private val component: RadarComponent by lazyAndroid { DaggerRadarComponent @@ -157,9 +160,7 @@ class RadarFragment : BaseFragment() { arrayOf(WIKIMEDIA_TILE_SOURCE_URL) ) - override fun layoutId(): Int = R.layout.radar_fragment - - override fun renderError(errorCode: Int) { + private fun renderError(errorCode: Int) { snack(errorCode) Timber.e("Rendering error code: ${getString(errorCode)}") onFailureHandledSubject.onNext( @@ -180,10 +181,19 @@ class RadarFragment : BaseFragment() { } } - override fun onStop() { - super.onStop() - Timber.d("ON STOP") - isConfigChangeOrProcessDeath = true + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle? + ): View { + _binding = RadarFragmentBinding.inflate(inflater, container, false) + return binding.root + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + setupRadarMapView() + setupEvents() } override fun onSaveInstanceState(outState: Bundle) { @@ -196,6 +206,12 @@ class RadarFragment : BaseFragment() { } } + override fun onStop() { + super.onStop() + Timber.d("ON STOP") + isConfigChangeOrProcessDeath = true + } + private fun setupMapViewConfiguration() { Timber.d("setupMapViewConfiguration") val configuration: IConfigurationProvider = Configuration.getInstance() @@ -206,15 +222,9 @@ class RadarFragment : BaseFragment() { } } - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) - setupRadarMapView() - setupEvents() - } - override fun onResume() { super.onResume() - radarMapView.onResume() + binding.radarMapView.onResume() if (isConfigChangeOrProcessDeath) { setupEvents() isConfigChangeOrProcessDeath = false @@ -222,15 +232,16 @@ class RadarFragment : BaseFragment() { } override fun onPause() { - radarMapView.onPause() + binding.radarMapView.onPause() super.onPause() } override fun onDestroyView() { super.onDestroyView() - radarMapView.onDetach() + binding.radarMapView.onDetach() disposeSeekBar() subscriptions.clear() + _binding = null } private fun disposeSeekBar() { @@ -241,7 +252,7 @@ class RadarFragment : BaseFragment() { private fun setupRadarMapView() { Timber.d("setupRadarMapView") - radarMapView.apply { + binding.radarMapView.apply { setTileSource(defaultTileSource) isTilesScaledToDpi = true setMultiTouchControls(true) @@ -290,7 +301,7 @@ class RadarFragment : BaseFragment() { .toObservable(), onPlayButtonStoppedSubject .toObservable(), - radarSeekBar + binding.radarSeekBar .userChanges() .throttleFirst(RADAR_DEBOUNCE_MILLIS, TimeUnit.MILLISECONDS) .map { position: Int -> @@ -300,7 +311,7 @@ class RadarFragment : BaseFragment() { position ) }, - radarSeekBar + binding.radarSeekBar .changes() .skipInitialValue() .map { position: Int -> @@ -308,7 +319,7 @@ class RadarFragment : BaseFragment() { .Event .OnRadarImageDisplayed(position) }, - radarPlayFab + binding.radarPlayFab .clicks() .map { RadarView @@ -393,14 +404,14 @@ class RadarFragment : BaseFragment() { } private fun setRadarSeekBarPosition(viewState: RadarView.State) { - radarSeekBar?.run { + binding.radarSeekBar.run { progress = viewState.currentSeekBarIndex max = viewState.seekBarMax } } private fun setPlayButtonToPlaying() { - radarPlayFab.setImageResource(R.drawable.ic_pause_white_24dp) + binding.radarPlayFab.setImageResource(R.drawable.ic_pause_white_24dp) Timber.d("setPlayButtonToPlaying") onPlayButtonStartedSubject.onNext( RadarView @@ -410,7 +421,7 @@ class RadarFragment : BaseFragment() { } private fun setPlayButtonToStopped() { - radarPlayFab.setImageResource(R.drawable.ic_play_arrow_white_24dp) + binding.radarPlayFab.setImageResource(R.drawable.ic_play_arrow_white_24dp) Timber.d("setPlayButtonToStopped") onPlayButtonStoppedSubject.onNext( RadarView @@ -424,7 +435,7 @@ class RadarFragment : BaseFragment() { RADAR_DEBOUNCE_MILLIS, TimeUnit.MILLISECONDS ).map { _ -> - radarSeekBar?.run { + binding.radarSeekBar.run { progress += state.seekStep max = state.seekBarMax } @@ -483,7 +494,7 @@ class RadarFragment : BaseFragment() { ) } - radarMapView?.apply { + binding.radarMapView.apply { overlays.clear() overlayManager.add(radarOverlay) invalidate() diff --git a/app/src/main/java/fi/kroon/vadret/presentation/radar/RadarView.kt b/app/src/main/java/fi/kroon/vadret/presentation/radar/RadarView.kt index 74c4dfe8..edbae00b 100644 --- a/app/src/main/java/fi/kroon/vadret/presentation/radar/RadarView.kt +++ b/app/src/main/java/fi/kroon/vadret/presentation/radar/RadarView.kt @@ -4,7 +4,7 @@ import android.os.Parcelable import androidx.annotation.StringRes import fi.kroon.vadret.data.radar.model.File import fi.kroon.vadret.util.NIL_INT -import kotlinx.android.parcel.Parcelize +import kotlinx.parcelize.Parcelize object RadarView { diff --git a/app/src/main/java/fi/kroon/vadret/presentation/shared/BaseActivity.kt b/app/src/main/java/fi/kroon/vadret/presentation/shared/BaseActivity.kt deleted file mode 100644 index c09fe540..00000000 --- a/app/src/main/java/fi/kroon/vadret/presentation/shared/BaseActivity.kt +++ /dev/null @@ -1,22 +0,0 @@ -package fi.kroon.vadret.presentation.shared - -import androidx.appcompat.app.AppCompatActivity -import fi.kroon.vadret.R -import fi.kroon.vadret.data.nominatim.model.Locality -import fi.kroon.vadret.util.extension.toGone -import fi.kroon.vadret.util.extension.toVisible -import kotlinx.android.synthetic.main.activity_main.* - -abstract class BaseActivity : AppCompatActivity() { - - abstract fun renderError(errorCode: Int) - - fun hideLocalityActionBar() = currentLocationName.toGone() - - fun setLocalityActionBar(locality: Locality) { - locality.name?.let { - currentLocationName.text = locality.name - } ?: currentLocationName.setText(R.string.unknown_area) - currentLocationName.toVisible() - } -} \ No newline at end of file diff --git a/app/src/main/java/fi/kroon/vadret/presentation/warning/display/WarningAdapter.kt b/app/src/main/java/fi/kroon/vadret/presentation/warning/display/WarningAdapter.kt index f408a619..74ec6377 100644 --- a/app/src/main/java/fi/kroon/vadret/presentation/warning/display/WarningAdapter.kt +++ b/app/src/main/java/fi/kroon/vadret/presentation/warning/display/WarningAdapter.kt @@ -1,11 +1,11 @@ package fi.kroon.vadret.presentation.warning.display import android.view.LayoutInflater -import android.view.View import android.view.ViewGroup import androidx.annotation.StringRes import androidx.recyclerview.widget.RecyclerView import fi.kroon.vadret.R +import fi.kroon.vadret.databinding.WarningDisplayItemBinding import fi.kroon.vadret.presentation.warning.display.WarningUtil.getChipFeedSourceBackgroundColor import fi.kroon.vadret.presentation.warning.display.WarningUtil.getChipFeedSourceStrokeColor import fi.kroon.vadret.presentation.warning.display.model.IWarningModel @@ -17,7 +17,6 @@ import fi.kroon.vadret.util.extension.empty import fi.kroon.vadret.util.extension.getAttribute import fi.kroon.vadret.util.extension.toGone import fi.kroon.vadret.util.extension.toVisible -import kotlinx.android.synthetic.main.warning_display_item.view.* import timber.log.Timber import javax.inject.Inject @@ -29,13 +28,13 @@ class WarningAdapter @Inject constructor() : RecyclerView.Adapter = mutableListOf() - inner class WarningViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) { + inner class WarningViewHolder(private val itemBinding: WarningDisplayItemBinding) : RecyclerView.ViewHolder(itemBinding.root) { fun bind(entity: IWarningModel) { entity as WarningModel Timber.d("RENDER: $entity") - with(itemView) { + with(itemBinding) { warningDisplayTitle.text = entity.headline if (entity.preamble != String.empty()) { @@ -92,23 +91,14 @@ class WarningAdapter @Inject constructor() : RecyclerView.Adapter WarningViewHolder( - LayoutInflater - .from(parent.context) + WarningDisplayItemBinding .inflate( - R.layout.warning_display_item, - parent, - false - ) - ) - else -> WarningViewHolder( - LayoutInflater - .from(parent.context) - .inflate( - R.layout.warning_display_item, + LayoutInflater.from(parent.context), parent, false ) ) + else -> throw IllegalStateException("invalid view type $viewType") } override fun getItemViewType(position: Int): Int = diff --git a/app/src/main/java/fi/kroon/vadret/presentation/warning/display/WarningFragment.kt b/app/src/main/java/fi/kroon/vadret/presentation/warning/display/WarningFragment.kt index 1d487ecf..7cf22494 100644 --- a/app/src/main/java/fi/kroon/vadret/presentation/warning/display/WarningFragment.kt +++ b/app/src/main/java/fi/kroon/vadret/presentation/warning/display/WarningFragment.kt @@ -2,13 +2,16 @@ package fi.kroon.vadret.presentation.warning.display import android.os.Bundle import android.os.Parcelable +import android.view.LayoutInflater import android.view.View +import android.view.ViewGroup import androidx.fragment.app.Fragment import androidx.lifecycle.lifecycleScope import androidx.navigation.NavController import androidx.navigation.fragment.findNavController import androidx.recyclerview.widget.LinearLayoutManager import fi.kroon.vadret.R +import fi.kroon.vadret.databinding.WarningFragmentBinding import fi.kroon.vadret.presentation.warning.display.di.DaggerWarningComponent import fi.kroon.vadret.presentation.warning.display.di.WarningComponent import fi.kroon.vadret.presentation.warning.display.model.IWarningModel @@ -17,22 +20,16 @@ import fi.kroon.vadret.util.extension.lazyAndroid import fi.kroon.vadret.util.extension.snack import fi.kroon.vadret.util.extension.toGone import fi.kroon.vadret.util.extension.toVisible -import kotlinx.android.synthetic.main.warning_display_fragment.warningDisplayNoWarningsIssued -import kotlinx.android.synthetic.main.warning_display_fragment.warningFilterButton -import kotlinx.android.synthetic.main.warning_display_fragment.warningLoadingProgressBar -import kotlinx.android.synthetic.main.warning_display_fragment.warningRecyclerView -import kotlinx.android.synthetic.main.warning_display_fragment.warningSwipeRefreshView import kotlinx.coroutines.ExperimentalCoroutinesApi -import kotlinx.coroutines.flow.collect import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.map -import kotlinx.coroutines.launch +import kotlinx.coroutines.flow.onEach import ru.ldralighieri.corbind.swiperefreshlayout.refreshes import ru.ldralighieri.corbind.view.clicks import timber.log.Timber @ExperimentalCoroutinesApi -class WarningFragment : Fragment(R.layout.warning_display_fragment) { +class WarningFragment : Fragment() { companion object { const val STATE_PARCEL_KEY: String = "STATE_PARCEL_KEY" @@ -41,6 +38,8 @@ class WarningFragment : Fragment(R.layout.warning_display_fragment) { const val RESULT_OK = "result_ok" } + private var _binding: WarningFragmentBinding? = null + private val binding: WarningFragmentBinding get() = _binding!! private var recyclerViewParcelable: Parcelable? = null private var stateParcel: WarningView.StateParcel? = null private var bundle: Bundle? = null @@ -82,14 +81,23 @@ class WarningFragment : Fragment(R.layout.warning_display_fragment) { } } + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle? + ): View { + _binding = WarningFragmentBinding.inflate(inflater, container, false) + return binding.root + } + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) - lifecycleScope - .launch { - viewModel - .viewState - .collect(::render) - } + + viewModel + .viewState + .onEach(::render) + .launchIn(viewLifecycleOwner.lifecycleScope) + listenForWarningFilterDialogResult() setup() } @@ -104,7 +112,7 @@ class WarningFragment : Fragment(R.layout.warning_display_fragment) { recyclerViewParcelable?.run { putParcelable(SCROLL_POSITION_KEY, this) - } ?: warningRecyclerView?.layoutManager?.run { + } ?: binding.warningRecyclerView.layoutManager?.run { putParcelable( SCROLL_POSITION_KEY, (this as LinearLayoutManager) @@ -120,7 +128,7 @@ class WarningFragment : Fragment(R.layout.warning_display_fragment) { bundle?.apply { putParcelable( SCROLL_POSITION_KEY, - (warningRecyclerView.layoutManager as LinearLayoutManager) + (binding.warningRecyclerView.layoutManager as LinearLayoutManager) .onSaveInstanceState() ) } @@ -130,9 +138,10 @@ class WarningFragment : Fragment(R.layout.warning_display_fragment) { override fun onDestroyView() { super.onDestroyView() - warningRecyclerView.apply { + binding.warningRecyclerView.apply { adapter = null } + _binding = null } override fun onResume() { @@ -151,7 +160,7 @@ class WarningFragment : Fragment(R.layout.warning_display_fragment) { private fun setupEvents() { - warningSwipeRefreshView + binding.warningSwipeRefreshView .refreshes() .map { viewModel.send( @@ -161,7 +170,7 @@ class WarningFragment : Fragment(R.layout.warning_display_fragment) { ) }.launchIn(lifecycleScope) - warningFilterButton + binding.warningFilterButton .clicks() .map { viewModel.send( @@ -181,7 +190,7 @@ class WarningFragment : Fragment(R.layout.warning_display_fragment) { } private fun setupRecyclerView() { - warningRecyclerView.apply { + binding.warningRecyclerView.apply { layoutManager = LinearLayoutManager(requireContext()) adapter = warningAdapter hasFixedSize() @@ -207,7 +216,7 @@ class WarningFragment : Fragment(R.layout.warning_display_fragment) { } private fun displayNoWarningsIssued() { - warningDisplayNoWarningsIssued.toVisible() + binding.warningDisplayNoWarningsIssued.toVisible() viewModel.send( WarningView @@ -218,7 +227,7 @@ class WarningFragment : Fragment(R.layout.warning_display_fragment) { private fun startProgressBarEffect() { Timber.d("START PROGRESS BAR") - warningLoadingProgressBar.apply { + binding.warningLoadingProgressBar.apply { toVisible() } @@ -228,11 +237,8 @@ class WarningFragment : Fragment(R.layout.warning_display_fragment) { private fun stopProgressBarEffect() { Timber.d("STOP PROGRESS BAR") - warningLoadingProgressBar.apply { - toGone() - } - - warningSwipeRefreshView.apply { + binding.warningLoadingProgressBar.toGone() + binding.warningSwipeRefreshView.apply { isRefreshing = false } @@ -252,7 +258,7 @@ class WarningFragment : Fragment(R.layout.warning_display_fragment) { private fun restoreScrollPosition() { Timber.d("restoreScrollPosition") bundle?.run { - (warningRecyclerView.layoutManager as LinearLayoutManager) + (binding.warningRecyclerView.layoutManager as LinearLayoutManager) .onRestoreInstanceState( getParcelable(SCROLL_POSITION_KEY) ) @@ -263,7 +269,7 @@ class WarningFragment : Fragment(R.layout.warning_display_fragment) { private fun displayWarningList(list: MutableList) { Timber.d("DISPLAY WARNING LIST") - warningDisplayNoWarningsIssued + binding.warningDisplayNoWarningsIssued .toGone() warningAdapter.updateList(list) viewModel.send(WarningView.Event.OnWarningListDisplayed) diff --git a/app/src/main/java/fi/kroon/vadret/presentation/warning/display/WarningView.kt b/app/src/main/java/fi/kroon/vadret/presentation/warning/display/WarningView.kt index 6ee905c6..d2cb7a0f 100644 --- a/app/src/main/java/fi/kroon/vadret/presentation/warning/display/WarningView.kt +++ b/app/src/main/java/fi/kroon/vadret/presentation/warning/display/WarningView.kt @@ -3,7 +3,7 @@ package fi.kroon.vadret.presentation.warning.display import android.os.Parcelable import androidx.annotation.StringRes import fi.kroon.vadret.presentation.warning.display.model.IWarningModel -import kotlinx.android.parcel.Parcelize +import kotlinx.parcelize.Parcelize object WarningView { diff --git a/app/src/main/java/fi/kroon/vadret/presentation/warning/filter/WarningFilterAdapter.kt b/app/src/main/java/fi/kroon/vadret/presentation/warning/filter/WarningFilterAdapter.kt index 23741b19..daf9dfd3 100644 --- a/app/src/main/java/fi/kroon/vadret/presentation/warning/filter/WarningFilterAdapter.kt +++ b/app/src/main/java/fi/kroon/vadret/presentation/warning/filter/WarningFilterAdapter.kt @@ -1,13 +1,15 @@ package fi.kroon.vadret.presentation.warning.filter import android.view.LayoutInflater -import android.view.View import android.view.ViewGroup import androidx.recyclerview.widget.RecyclerView import com.google.android.material.chip.Chip import fi.kroon.vadret.R import fi.kroon.vadret.data.district.model.DistrictOptionEntity import fi.kroon.vadret.data.feedsource.model.FeedSourceOptionEntity +import fi.kroon.vadret.databinding.WarningFilterDistrictChipGroupBinding +import fi.kroon.vadret.databinding.WarningFilterFeedSourceChipGroupBinding +import fi.kroon.vadret.databinding.WarningFilterTitleItemBinding import fi.kroon.vadret.presentation.shared.IViewHolder import fi.kroon.vadret.presentation.warning.filter.WarningFilterUtil.getChipDistrictBackgroundColor import fi.kroon.vadret.presentation.warning.filter.WarningFilterUtil.getChipDistrictStrokeColor @@ -16,9 +18,6 @@ import fi.kroon.vadret.presentation.warning.filter.WarningFilterUtil.getChipFeed import fi.kroon.vadret.presentation.warning.filter.model.IFilterable import fi.kroon.vadret.presentation.warning.filter.model.TitleModel import fi.kroon.vadret.util.extension.getAttribute -import kotlinx.android.synthetic.main.warning_filter_district_chip_group.view.* -import kotlinx.android.synthetic.main.warning_filter_feed_source_chip_group.view.* -import kotlinx.android.synthetic.main.warning_filter_title_item.view.* import timber.log.Timber class WarningFilterAdapter constructor( @@ -34,23 +33,23 @@ class WarningFilterAdapter constructor( private val list: MutableList = mutableListOf() - inner class TitleViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView), IViewHolder { + inner class TitleViewHolder(private val itemBinding: WarningFilterTitleItemBinding) : RecyclerView.ViewHolder(itemBinding.root), IViewHolder { override fun bind(entity: IFilterable) { entity as TitleModel Timber.d("TITLE_VIEW_TYPE VIEW HOLDER") - with(itemView) { - warningFilterTitleText.text = context.getString(entity.resId) + with(itemBinding) { + warningFilterTitleText.text = root.context.getString(entity.resId) } } } - inner class FeedSourceViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView), IViewHolder { + inner class FeedSourceViewHolder(private val itemBinding: WarningFilterFeedSourceChipGroupBinding) : RecyclerView.ViewHolder(itemBinding.root), IViewHolder { override fun bind(entity: IFilterable) { entity as FeedSourceOptionEntity - with(itemView) { + with(itemBinding) { warningFilterFeedSourceChipGroup.removeAllViews() val chip = Chip(warningFilterFeedSourceChipGroup.context) chip.text = entity.name @@ -78,12 +77,12 @@ class WarningFilterAdapter constructor( } } - inner class DistrictViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView), IViewHolder { + inner class DistrictViewHolder(private val itemBinding: WarningFilterDistrictChipGroupBinding) : RecyclerView.ViewHolder(itemBinding.root), IViewHolder { override fun bind(entity: IFilterable) { entity as DistrictOptionEntity - with(itemView) { + with(itemBinding) { warningFilterDistrictChipGroup.removeAllViews() val chip = Chip(warningFilterDistrictChipGroup.context) @@ -112,19 +111,28 @@ class WarningFilterAdapter constructor( override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder = when (viewType) { TITLE_VIEW_TYPE -> TitleViewHolder( - LayoutInflater - .from(parent.context) - .inflate(R.layout.warning_filter_title_item, parent, false) + WarningFilterTitleItemBinding + .inflate( + LayoutInflater.from(parent.context), + parent, + false + ) ) FEED_SOURCE_VIEW_TYPE -> FeedSourceViewHolder( - LayoutInflater - .from(parent.context) - .inflate(R.layout.warning_filter_feed_source_chip_group, parent, false) + WarningFilterFeedSourceChipGroupBinding + .inflate( + LayoutInflater.from(parent.context), + parent, + false + ) ) else -> DistrictViewHolder( - LayoutInflater - .from(parent.context) - .inflate(R.layout.warning_filter_district_chip_group, parent, false) + WarningFilterDistrictChipGroupBinding + .inflate( + LayoutInflater.from(parent.context), + parent, + false + ) ) } diff --git a/app/src/main/java/fi/kroon/vadret/presentation/warning/filter/WarningFilterDialogFragment.kt b/app/src/main/java/fi/kroon/vadret/presentation/warning/filter/WarningFilterDialogFragment.kt index 2b45cb47..7a4cc1fd 100644 --- a/app/src/main/java/fi/kroon/vadret/presentation/warning/filter/WarningFilterDialogFragment.kt +++ b/app/src/main/java/fi/kroon/vadret/presentation/warning/filter/WarningFilterDialogFragment.kt @@ -10,7 +10,7 @@ import androidx.navigation.NavController import androidx.navigation.fragment.findNavController import androidx.recyclerview.widget.LinearLayoutManager import com.google.android.material.bottomsheet.BottomSheetDialogFragment -import fi.kroon.vadret.R +import fi.kroon.vadret.databinding.WarningFilterDialogFragmentBinding import fi.kroon.vadret.presentation.warning.display.WarningFragment.Companion.RESULT_OK import fi.kroon.vadret.presentation.warning.display.WarningFragment.Companion.WARNING_FILTER_DIALOG_RESULT import fi.kroon.vadret.presentation.warning.filter.di.DaggerWarningFilterComponent @@ -18,13 +18,10 @@ import fi.kroon.vadret.presentation.warning.filter.di.WarningFilterComponent import fi.kroon.vadret.presentation.warning.filter.model.IFilterable import fi.kroon.vadret.util.extension.coreComponent import fi.kroon.vadret.util.extension.lazyAndroid -import kotlinx.android.synthetic.main.warning_filter_dialog_fragment.warningFilterApplyButton -import kotlinx.android.synthetic.main.warning_filter_dialog_fragment.warningFilterRecyclerView import kotlinx.coroutines.ExperimentalCoroutinesApi -import kotlinx.coroutines.flow.collect import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.map -import kotlinx.coroutines.launch +import kotlinx.coroutines.flow.onEach import ru.ldralighieri.corbind.view.clicks import timber.log.Timber @@ -34,6 +31,8 @@ class WarningFilterDialogFragment : BottomSheetDialogFragment() { private var isConfigChangeOrProcessDeath = false private var stateParcel: WarningFilterView.StateParcel? = null private var bundle: Bundle? = null + private var _binding: WarningFilterDialogFragmentBinding? = null + private val binding: WarningFilterDialogFragmentBinding get() = _binding!! private companion object { const val STATE_PARCEL_KEY: String = "STATE_PARCEL_WARNING_FILTER_KEY" @@ -56,15 +55,6 @@ class WarningFilterDialogFragment : BottomSheetDialogFragment() { component.provideWarningFilterViewModel() } - override fun onCreateView( - inflater: LayoutInflater, - container: ViewGroup?, - savedInstanceState: Bundle? - ): View? { - Timber.d("ON CREATE VIEW") - return inflater.inflate(R.layout.warning_filter_dialog_fragment, container, false) - } - override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) Timber.d("ON CREATE") @@ -76,16 +66,24 @@ class WarningFilterDialogFragment : BottomSheetDialogFragment() { } } + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle? + ): View { + Timber.d("ON CREATE VIEW") + _binding = WarningFilterDialogFragmentBinding.inflate(inflater, container, false) + return binding.root + } + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) Timber.d("ON VIEW CREATED") - lifecycleScope - .launch { - viewModel - .viewState - .collect(::render) - } + viewModel + .viewState + .onEach(::render) + .launchIn(viewLifecycleOwner.lifecycleScope) setup() } @@ -106,7 +104,7 @@ class WarningFilterDialogFragment : BottomSheetDialogFragment() { bundle?.apply { putParcelable( SCROLL_POSITION_KEY, - (warningFilterRecyclerView.layoutManager as LinearLayoutManager) + (binding.warningFilterRecyclerView.layoutManager as LinearLayoutManager) .onSaveInstanceState() ) } @@ -115,10 +113,11 @@ class WarningFilterDialogFragment : BottomSheetDialogFragment() { override fun onDestroyView() { super.onDestroyView() - warningFilterRecyclerView + binding.warningFilterRecyclerView .apply { adapter = null } + _binding = null } override fun onResume() { @@ -153,7 +152,7 @@ class WarningFilterDialogFragment : BottomSheetDialogFragment() { } ) - warningFilterRecyclerView.apply { + binding.warningFilterRecyclerView.apply { layoutManager = LinearLayoutManager(requireContext()) adapter = warningFilterAdapter hasFixedSize() @@ -162,7 +161,7 @@ class WarningFilterDialogFragment : BottomSheetDialogFragment() { private fun setupEvents() { - warningFilterApplyButton + binding.warningFilterApplyButton .clicks() .map { viewModel.send( diff --git a/app/src/main/java/fi/kroon/vadret/presentation/warning/filter/WarningFilterView.kt b/app/src/main/java/fi/kroon/vadret/presentation/warning/filter/WarningFilterView.kt index a819a5d1..e10074c4 100644 --- a/app/src/main/java/fi/kroon/vadret/presentation/warning/filter/WarningFilterView.kt +++ b/app/src/main/java/fi/kroon/vadret/presentation/warning/filter/WarningFilterView.kt @@ -4,7 +4,7 @@ import android.os.Parcelable import fi.kroon.vadret.data.district.model.DistrictOptionEntity import fi.kroon.vadret.data.feedsource.model.FeedSourceOptionEntity import fi.kroon.vadret.presentation.warning.filter.model.IFilterable -import kotlinx.android.parcel.Parcelize +import kotlinx.parcelize.Parcelize object WarningFilterView { diff --git a/app/src/main/java/fi/kroon/vadret/presentation/weatherforecast/WeatherForecastAdapter.kt b/app/src/main/java/fi/kroon/vadret/presentation/weatherforecast/WeatherForecastAdapter.kt index 12c5a427..213b2f7c 100644 --- a/app/src/main/java/fi/kroon/vadret/presentation/weatherforecast/WeatherForecastAdapter.kt +++ b/app/src/main/java/fi/kroon/vadret/presentation/weatherforecast/WeatherForecastAdapter.kt @@ -1,11 +1,15 @@ package fi.kroon.vadret.presentation.weatherforecast import android.view.LayoutInflater -import android.view.View import android.view.ViewGroup +import androidx.core.content.ContextCompat import androidx.recyclerview.widget.DiffUtil import androidx.recyclerview.widget.RecyclerView import fi.kroon.vadret.R +import fi.kroon.vadret.databinding.WeatherForecastDateItemBinding +import fi.kroon.vadret.databinding.WeatherForecastHeadlineItemBinding +import fi.kroon.vadret.databinding.WeatherForecastItemBinding +import fi.kroon.vadret.databinding.WeatherForecastSplashItemBinding import fi.kroon.vadret.presentation.weatherforecast.model.IWeatherForecastModel import fi.kroon.vadret.presentation.weatherforecast.model.WeatherForecastDateItemModel import fi.kroon.vadret.presentation.weatherforecast.model.WeatherForecastHeadlineModel @@ -21,10 +25,6 @@ import fi.kroon.vadret.util.extension.empty import fi.kroon.vadret.util.extension.toGone import fi.kroon.vadret.util.extension.toInvisible import fi.kroon.vadret.util.extension.toVisible -import kotlinx.android.synthetic.main.weather_forecast_date_item.view.* -import kotlinx.android.synthetic.main.weather_forecast_headline_item.view.* -import kotlinx.android.synthetic.main.weather_forecast_item.view.* -import kotlinx.android.synthetic.main.weather_forecast_splash_item.view.* import org.threeten.bp.format.DateTimeFormatter import org.threeten.bp.format.FormatStyle import org.threeten.bp.format.TextStyle @@ -47,7 +47,7 @@ class WeatherForecastAdapter @Inject constructor() : RecyclerView.Adapter @@ -61,7 +61,7 @@ class WeatherForecastAdapter @Inject constructor() : RecyclerView.Adapter - itemView.precipitationCode.setText(getPrecipitationResourceId(precipitationCodeInt)) - } ?: itemView.precipitationCode.toGone() + itemBinding.precipitationCode.setText(getPrecipitationResourceId(precipitationCodeInt)) + } ?: itemBinding.precipitationCode.toGone() if (item.sunriseDateTime != null && item.sunsetDateTime != null) { val format = DateTimeFormatter.ofLocalizedTime(FormatStyle.SHORT) - itemView.sunriseDateTime.text = item.sunriseDateTime.toLocalTime().format(format) - itemView.sunsetDateTime.text = item.sunsetDateTime.toLocalTime().format(format) + itemBinding.sunriseDateTime.text = item.sunriseDateTime.toLocalTime().format(format) + itemBinding.sunsetDateTime.text = item.sunsetDateTime.toLocalTime().format(format) } else { - itemView.sunriseDateTime.setText(R.string.sun_wont_rise_today) - itemView.sunsetDateTime.setText(R.string.sun_wont_set_today) + itemBinding.sunriseDateTime.setText(R.string.sun_wont_rise_today) + itemBinding.sunsetDateTime.setText(R.string.sun_wont_set_today) } item.precipitationCode?.let { intCode -> if (intCode > 0) { - itemView.precipitationCode.setText(getPrecipitationResourceId(intCode)) + itemBinding.precipitationCode.setText(getPrecipitationResourceId(intCode)) } - } ?: itemView.precipitationCode.toGone() + } ?: itemBinding.precipitationCode.toGone() } } - inner class ForecastViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) { + inner class WeatherForecastViewHolder(private val itemBinding: WeatherForecastItemBinding) : RecyclerView.ViewHolder(itemBinding.root) { init { - itemView.setOnClickListener { + itemBinding.root.setOnClickListener { Timber.d("ForecastViewHolder: Item $adapterPosition clicked of ${list.size}") } } fun bind(item: WeatherForecastItemModel) { - itemView.time.text = item.time - itemView.temperature.text = item.temperature.toString() - itemView.wsymb2Description.setText(getWsymb2ResourceId(item.weatherDescription)) - itemView.wsymb2Icon.setImageResource(getWsymb2IconResourceId(item.weatherIcon)) - itemView.temperature_indicator_flair.setBackgroundResource(getTemperatureColorResourceId(item.temperature)) + itemBinding.time.text = item.time + itemBinding.temperature.text = item.temperature.toString() + itemBinding.wsymb2Description.setText(getWsymb2ResourceId(item.weatherDescription)) + itemBinding.wsymb2Icon.setImageResource(getWsymb2IconResourceId(item.weatherIcon)) + itemBinding.temperatureIndicatorFlair.setBackgroundResource(getTemperatureColorResourceId(item.temperature)) item.feelsLikeTemperature?.let { - itemView.feelsLikeTemperature.text = item.feelsLikeTemperature - itemView.feelsLike.toVisible() - itemView.feelsLikeTemperature.toVisible() - itemView.feelsLikeTempUnit.toVisible() + itemBinding.feelsLikeTemperature.text = item.feelsLikeTemperature + itemBinding.feelsLike.toVisible() + itemBinding.feelsLikeTemperature.toVisible() + itemBinding.feelsLikeTempUnit.toVisible() } ?: run { - itemView.feelsLikeTemperature.toInvisible() - itemView.feelsLike.toInvisible() - itemView.feelsLikeTempUnit.toInvisible() + itemBinding.feelsLikeTemperature.toInvisible() + itemBinding.feelsLike.toInvisible() + itemBinding.feelsLikeTempUnit.toInvisible() } } } @@ -191,20 +191,35 @@ class WeatherForecastAdapter @Inject constructor() : RecyclerView.Adapter WeekdayViewHolder( - LayoutInflater.from(parent.context) - .inflate(R.layout.weather_forecast_date_item, parent, false) + WeatherForecastDateItemBinding.inflate( + LayoutInflater.from(parent.context), + parent, + false + ) ) TYPE_WEATHER_SPLASH -> ForecastSplashViewHolder( - LayoutInflater.from(parent.context) - .inflate(R.layout.weather_forecast_splash_item, parent, false) + WeatherForecastSplashItemBinding + .inflate( + LayoutInflater.from(parent.context), + parent, + false + ) ) - TYPE_WEATHER_HEADER -> HeadlineViewHolder( - LayoutInflater.from(parent.context) - .inflate(R.layout.weather_forecast_headline_item, parent, false) + TYPE_WEATHER_HEADER -> WeatherForecastHeadlineViewHolder( + WeatherForecastHeadlineItemBinding + .inflate( + LayoutInflater.from(parent.context), + parent, + false + ) ) - else -> ForecastViewHolder( - LayoutInflater.from(parent.context) - .inflate(R.layout.weather_forecast_item, parent, false) + else -> WeatherForecastViewHolder( + WeatherForecastItemBinding + .inflate( + LayoutInflater.from(parent.context), + parent, + false + ) ) } } @@ -215,13 +230,13 @@ class WeatherForecastAdapter @Inject constructor() : RecyclerView.Adapter - (holder as ForecastViewHolder) + (holder as WeatherForecastViewHolder) .bind(list[position] as WeatherForecastItemModel) TYPE_WEATHER_WEEKDAY -> (holder as WeekdayViewHolder) .bind(list[position] as WeatherForecastDateItemModel) TYPE_WEATHER_HEADER -> - (holder as HeadlineViewHolder) + (holder as WeatherForecastHeadlineViewHolder) .bind(list[position] as WeatherForecastHeadlineModel) } } diff --git a/app/src/main/java/fi/kroon/vadret/presentation/weatherforecast/WeatherForecastFragment.kt b/app/src/main/java/fi/kroon/vadret/presentation/weatherforecast/WeatherForecastFragment.kt index aaa57163..6a439868 100644 --- a/app/src/main/java/fi/kroon/vadret/presentation/weatherforecast/WeatherForecastFragment.kt +++ b/app/src/main/java/fi/kroon/vadret/presentation/weatherforecast/WeatherForecastFragment.kt @@ -4,7 +4,9 @@ import android.Manifest import android.graphics.drawable.Drawable import android.os.Bundle import android.os.Parcelable +import android.view.LayoutInflater import android.view.View +import android.view.ViewGroup import androidx.core.content.ContextCompat import androidx.fragment.app.Fragment import androidx.lifecycle.lifecycleScope @@ -13,6 +15,7 @@ import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView import fi.kroon.vadret.R import fi.kroon.vadret.data.nominatim.model.Locality +import fi.kroon.vadret.databinding.WeatherForecastFragmentBinding import fi.kroon.vadret.presentation.main.MainActivity import fi.kroon.vadret.presentation.weatherforecast.autocomplete.AutoCompleteAdapter import fi.kroon.vadret.presentation.weatherforecast.autocomplete.AutoCompleteAdapterCallback @@ -23,19 +26,11 @@ import fi.kroon.vadret.util.extension.lazyAndroid import fi.kroon.vadret.util.extension.toGone import fi.kroon.vadret.util.extension.toInvisible import fi.kroon.vadret.util.extension.toVisible -import kotlinx.android.synthetic.main.weather_forecast_fragment.autoCompleteRecyclerView -import kotlinx.android.synthetic.main.weather_forecast_fragment.weatherForecastLoadingProgressBar -import kotlinx.android.synthetic.main.weather_forecast_fragment.weatherForecastLocationSearchButton -import kotlinx.android.synthetic.main.weather_forecast_fragment.weatherForecastRecyclerView -import kotlinx.android.synthetic.main.weather_forecast_fragment.weatherForecastRefresh -import kotlinx.android.synthetic.main.weather_forecast_fragment.weatherForecastSearchView -import kotlinx.coroutines.FlowPreview -import kotlinx.coroutines.flow.collect import kotlinx.coroutines.flow.debounce import kotlinx.coroutines.flow.drop import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.map -import kotlinx.coroutines.launch +import kotlinx.coroutines.flow.onEach import permissions.dispatcher.NeedsPermission import permissions.dispatcher.OnNeverAskAgain import permissions.dispatcher.OnPermissionDenied @@ -46,7 +41,7 @@ import ru.ldralighieri.corbind.view.clicks import timber.log.Timber @RuntimePermissions -class WeatherForecastFragment : Fragment(R.layout.weather_forecast_fragment) { +class WeatherForecastFragment : Fragment() { private companion object { const val STATE_PARCEL_KEY: String = "STATE_PARCEL_KEY" @@ -57,6 +52,8 @@ class WeatherForecastFragment : Fragment(R.layout.weather_forecast_fragment) { private var stateParcel: WeatherForecastView.StateParcel? = null private var bundle: Bundle? = null private var recyclerViewParcelable: Parcelable? = null + private var _binding: WeatherForecastFragmentBinding? = null + private val binding: WeatherForecastFragmentBinding get() = _binding!! private val component: WeatherForecastComponent by lazyAndroid { DaggerWeatherForecastComponent @@ -101,19 +98,27 @@ class WeatherForecastFragment : Fragment(R.layout.weather_forecast_fragment) { } } + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle? + ): View { + _binding = WeatherForecastFragmentBinding.inflate(inflater, container, false) + return binding.root + } + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) Timber.d("ON VIEW CREATED -- WEATHER FORECAST") - lifecycleScope - .launch { - viewModel - .viewState - .collect(::render) - } - setupRecyclerView() - setupEvents() + + viewModel + .viewState + .onEach(::render) + .launchIn(viewLifecycleOwner.lifecycleScope) + + setupListeners() viewModel.send( WeatherForecastView @@ -145,14 +150,19 @@ class WeatherForecastFragment : Fragment(R.layout.weather_forecast_fragment) { super.onDestroyView() Timber.d("ON DESTROY VIEW -- WEATHER FORECAST") - recyclerViewParcelable = (weatherForecastRecyclerView.layoutManager as LinearLayoutManager) - .onSaveInstanceState() + recyclerViewParcelable = + (binding.weatherForecastRecyclerView.layoutManager as LinearLayoutManager) + .onSaveInstanceState() - weatherForecastRecyclerView.adapter = null - autoCompleteRecyclerView.adapter = null - weatherForecastSearchView.setOnQueryTextListener(null) + binding.apply { + weatherForecastRecyclerView.adapter = null + autoCompleteRecyclerView.adapter = null + weatherForecastSearchView.setOnQueryTextListener(null) + } hideActionBarLocalityName() + + _binding = null } override fun onDestroy() { @@ -176,7 +186,7 @@ class WeatherForecastFragment : Fragment(R.layout.weather_forecast_fragment) { */ recyclerViewParcelable?.run { putParcelable(SCROLL_POSITION_KEY, this) - } ?: weatherForecastRecyclerView?.layoutManager?.run { + } ?: _binding?.weatherForecastRecyclerView?.layoutManager?.run { putParcelable( SCROLL_POSITION_KEY, (this as LinearLayoutManager) @@ -190,23 +200,22 @@ class WeatherForecastFragment : Fragment(R.layout.weather_forecast_fragment) { super.onResume() Timber.d("ON RESUME -- WEATHER FORECAST") if (isConfigChangeOrProcessDeath) { - setupEvents() + setupListeners() isConfigChangeOrProcessDeath = false } } private fun setupRecyclerView() { - weatherForecastRecyclerView.apply { + binding.weatherForecastRecyclerView.apply { layoutManager = LinearLayoutManager(requireContext()) adapter = weatherForecastAdapter hasFixedSize() } } - @OptIn(FlowPreview::class) - private fun setupEvents() { + private fun setupListeners() { - weatherForecastSearchView + binding.weatherForecastSearchView .setOnCloseListener { viewModel.send( @@ -218,7 +227,7 @@ class WeatherForecastFragment : Fragment(R.layout.weather_forecast_fragment) { true } - weatherForecastLocationSearchButton + binding.weatherForecastLocationSearchButton .clicks() .map { viewModel @@ -227,13 +236,13 @@ class WeatherForecastFragment : Fragment(R.layout.weather_forecast_fragment) { ) }.launchIn(viewLifecycleOwner.lifecycleScope) - weatherForecastRefresh + binding.weatherForecastRefresh .refreshes() .map { viewModel.send(WeatherForecastView.Event.OnSwipedToRefresh) }.launchIn(viewLifecycleOwner.lifecycleScope) - weatherForecastSearchView + binding.weatherForecastSearchView .queryTextChangeEvents() .debounce(200) .drop(1) @@ -281,9 +290,7 @@ class WeatherForecastFragment : Fragment(R.layout.weather_forecast_fragment) { private fun startProgressBarEffect() { Timber.d("startProgressBarEffect") - weatherForecastLoadingProgressBar.apply { - toVisible() - } + binding.weatherForecastLoadingProgressBar.toVisible() viewModel.send( WeatherForecastView @@ -295,11 +302,8 @@ class WeatherForecastFragment : Fragment(R.layout.weather_forecast_fragment) { private fun stopProgressBarEffect() { Timber.d("stopProgressBarEffect") - weatherForecastLoadingProgressBar.apply { - toGone() - } - - weatherForecastRefresh.apply { + binding.weatherForecastLoadingProgressBar.toGone() + binding.weatherForecastRefresh.apply { isRefreshing = false } @@ -312,7 +316,7 @@ class WeatherForecastFragment : Fragment(R.layout.weather_forecast_fragment) { private fun displayAutoCompleteList(renderEvent: WeatherForecastView.RenderEvent.DisplayAutoComplete) { autoCompleteAdapter.updateList(renderEvent.newFilteredList) - autoCompleteRecyclerView.adapter?.run { + binding.autoCompleteRecyclerView.adapter?.run { renderEvent.diffResult?.dispatchUpdatesTo(this) } } @@ -320,7 +324,7 @@ class WeatherForecastFragment : Fragment(R.layout.weather_forecast_fragment) { private fun restoreScrollPosition() { Timber.d("restoreScrollPosition") bundle?.run { - (weatherForecastRecyclerView.layoutManager as LinearLayoutManager) + (binding.weatherForecastRecyclerView.layoutManager as LinearLayoutManager) .onRestoreInstanceState( getParcelable(SCROLL_POSITION_KEY) ) @@ -353,16 +357,14 @@ class WeatherForecastFragment : Fragment(R.layout.weather_forecast_fragment) { private fun disableSearchView(renderEvent: WeatherForecastView.RenderEvent.DisableSearchView) { Timber.d("disableSearchView") - weatherForecastLocationSearchButton.apply { - toVisible() - } + binding.weatherForecastLocationSearchButton.toVisible() - weatherForecastSearchView.apply { + binding.weatherForecastSearchView.apply { toInvisible() setQuery(renderEvent.text, false) } - autoCompleteRecyclerView.apply { + binding.autoCompleteRecyclerView.apply { adapter = null toInvisible() } @@ -377,14 +379,14 @@ class WeatherForecastFragment : Fragment(R.layout.weather_forecast_fragment) { private fun enableSearchView() { autoCompleteAdapter.clearList() - weatherForecastSearchView.apply { + binding.weatherForecastSearchView.apply { toVisible() isFocusable = true isIconified = false requestFocusFromTouch() } - autoCompleteRecyclerView + binding.autoCompleteRecyclerView .apply { adapter = autoCompleteAdapter layoutManager = LinearLayoutManager(requireContext(), RecyclerView.VERTICAL, false) @@ -397,7 +399,7 @@ class WeatherForecastFragment : Fragment(R.layout.weather_forecast_fragment) { visibility = View.VISIBLE } - weatherForecastLocationSearchButton.visibility = View.INVISIBLE + binding.weatherForecastLocationSearchButton.visibility = View.INVISIBLE } private fun displayWeatherForecast(renderEvent: WeatherForecastView.RenderEvent.DisplayWeatherForecast) { diff --git a/app/src/main/java/fi/kroon/vadret/presentation/weatherforecast/WeatherForecastView.kt b/app/src/main/java/fi/kroon/vadret/presentation/weatherforecast/WeatherForecastView.kt index fe49d7f4..2c6cc455 100644 --- a/app/src/main/java/fi/kroon/vadret/presentation/weatherforecast/WeatherForecastView.kt +++ b/app/src/main/java/fi/kroon/vadret/presentation/weatherforecast/WeatherForecastView.kt @@ -8,7 +8,7 @@ import fi.kroon.vadret.data.nominatim.model.Locality import fi.kroon.vadret.presentation.weatherforecast.model.IWeatherForecastModel import fi.kroon.vadret.util.AUTOMATIC_LOCATION_MODE_KEY import fi.kroon.vadret.util.extension.empty -import kotlinx.android.parcel.Parcelize +import kotlinx.parcelize.Parcelize object WeatherForecastView { diff --git a/app/src/main/java/fi/kroon/vadret/presentation/weatherforecast/autocomplete/AutoCompleteAdapter.kt b/app/src/main/java/fi/kroon/vadret/presentation/weatherforecast/autocomplete/AutoCompleteAdapter.kt index 7b3edfa1..1492b850 100644 --- a/app/src/main/java/fi/kroon/vadret/presentation/weatherforecast/autocomplete/AutoCompleteAdapter.kt +++ b/app/src/main/java/fi/kroon/vadret/presentation/weatherforecast/autocomplete/AutoCompleteAdapter.kt @@ -1,13 +1,11 @@ package fi.kroon.vadret.presentation.weatherforecast.autocomplete import android.view.LayoutInflater -import android.view.View import android.view.ViewGroup import androidx.recyclerview.widget.RecyclerView -import fi.kroon.vadret.R import fi.kroon.vadret.data.autocomplete.model.AutoCompleteItem +import fi.kroon.vadret.databinding.WeatherForecastAutoCompleteItemBinding import fi.kroon.vadret.presentation.weatherforecast.WeatherForecastView -import kotlinx.android.synthetic.main.weather_forecast_auto_complete_item.view.* class AutoCompleteAdapter constructor( private val callback: AutoCompleteAdapterCallback @@ -15,32 +13,32 @@ class AutoCompleteAdapter constructor( private val list: MutableList = mutableListOf() - inner class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) { + inner class ViewHolder(private val itemBinding: WeatherForecastAutoCompleteItemBinding) : RecyclerView.ViewHolder(itemBinding.root) { init { - itemView.setOnClickListener { - callback - .onAutoCompleteItemClicked( - WeatherForecastView - .Event - .OnAutoCompleteItemClicked(list[adapterPosition]) - ) - } + itemBinding.root + .setOnClickListener { + callback + .onAutoCompleteItemClicked( + WeatherForecastView + .Event + .OnAutoCompleteItemClicked(list[adapterPosition]) + ) + } } fun bind(autoCompleteItem: AutoCompleteItem) { val description = "${autoCompleteItem.municipality}, ${autoCompleteItem.county}" - itemView.municipalityCounty.text = description - itemView.city.text = autoCompleteItem.locality + itemBinding.municipalityCounty.text = description + itemBinding.city.text = autoCompleteItem.locality } } override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder = ViewHolder( - LayoutInflater - .from(parent.context) + WeatherForecastAutoCompleteItemBinding .inflate( - R.layout.weather_forecast_auto_complete_item, + LayoutInflater.from(parent.context), parent, false ) diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/main_activity.xml similarity index 98% rename from app/src/main/res/layout/activity_main.xml rename to app/src/main/res/layout/main_activity.xml index fc2ffb31..250e941f 100644 --- a/app/src/main/res/layout/activity_main.xml +++ b/app/src/main/res/layout/main_activity.xml @@ -28,7 +28,7 @@ android:layout_marginTop="0dp" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" - tools:showIn="@layout/activity_main"> + tools:showIn="@layout/main_activity">