From 3a29504fd2f472fc2bb3f43921eda3f85c6ec7bd Mon Sep 17 00:00:00 2001 From: Carlos Martinez Date: Thu, 17 Jun 2021 12:08:48 -0400 Subject: [PATCH 1/3] Add: viewmodel --- .../main/res/xml/network_security_config.xml | 2 +- .../core/domain/network/NetworkErrors.kt | 7 ++++ .../core/domain/network/RequestState.kt | 3 +- .../dev/carlos/core/viewmodel/RxViewModel.kt | 17 +++++++++ .../shortform/data/AcronymsDataRepository.kt | 2 +- .../dev/carlos/shortform/di/AcronymsModule.kt | 4 ++ .../shortform/viewmodels/AcronymsViewmodel.kt | 37 +++++++++++++++++++ 7 files changed, 69 insertions(+), 3 deletions(-) create mode 100644 core/src/main/java/dev/carlos/core/domain/network/NetworkErrors.kt create mode 100644 core/src/main/java/dev/carlos/core/viewmodel/RxViewModel.kt create mode 100644 shortform/src/main/java/dev/carlos/shortform/viewmodels/AcronymsViewmodel.kt diff --git a/app/src/main/res/xml/network_security_config.xml b/app/src/main/res/xml/network_security_config.xml index 8b6c476..754ccf0 100644 --- a/app/src/main/res/xml/network_security_config.xml +++ b/app/src/main/res/xml/network_security_config.xml @@ -3,4 +3,4 @@ www.nactem.ac.uk - \ No newline at end of file + diff --git a/core/src/main/java/dev/carlos/core/domain/network/NetworkErrors.kt b/core/src/main/java/dev/carlos/core/domain/network/NetworkErrors.kt new file mode 100644 index 0000000..83e931f --- /dev/null +++ b/core/src/main/java/dev/carlos/core/domain/network/NetworkErrors.kt @@ -0,0 +1,7 @@ +package dev.carlos.core.domain.network + +enum class RequestError { + NO_NETWORK, + BAD_RESPONSE, + UNKNOWN_PROBLEM +} diff --git a/core/src/main/java/dev/carlos/core/domain/network/RequestState.kt b/core/src/main/java/dev/carlos/core/domain/network/RequestState.kt index 956f293..b710558 100644 --- a/core/src/main/java/dev/carlos/core/domain/network/RequestState.kt +++ b/core/src/main/java/dev/carlos/core/domain/network/RequestState.kt @@ -3,5 +3,6 @@ package dev.carlos.core.domain.network sealed class RequestState { class Success(val data: T) : RequestState() object Loading : RequestState() - class Error(val throwable: Throwable) : RequestState() + class Error(val type: RequestError) : RequestState() + object Empty : RequestState() } diff --git a/core/src/main/java/dev/carlos/core/viewmodel/RxViewModel.kt b/core/src/main/java/dev/carlos/core/viewmodel/RxViewModel.kt new file mode 100644 index 0000000..d6a8e8b --- /dev/null +++ b/core/src/main/java/dev/carlos/core/viewmodel/RxViewModel.kt @@ -0,0 +1,17 @@ +package dev.carlos.core.viewmodel + +import androidx.lifecycle.ViewModel +import io.reactivex.disposables.CompositeDisposable + +abstract class RxViewModel : ViewModel() { + protected val compositeDisposable by lazy { CompositeDisposable() } + + override fun onCleared() { + super.onCleared() + clearCompositeDisposable() + } + + private fun clearCompositeDisposable() { + compositeDisposable.clear() + } +} diff --git a/shortform/src/main/java/dev/carlos/shortform/data/AcronymsDataRepository.kt b/shortform/src/main/java/dev/carlos/shortform/data/AcronymsDataRepository.kt index 30f4b85..4cf7604 100644 --- a/shortform/src/main/java/dev/carlos/shortform/data/AcronymsDataRepository.kt +++ b/shortform/src/main/java/dev/carlos/shortform/data/AcronymsDataRepository.kt @@ -11,6 +11,6 @@ class AcronymsDataRepository( ) : AcronymsRepository { override fun getAcronymDefinition(acronym: String): Single { - return remoteDatasource.getAcronymDefinition(acronym).map { it.single()?.toShortformModel() } + return remoteDatasource.getAcronymDefinition(acronym).map { it.single().toShortformModel() } } } diff --git a/shortform/src/main/java/dev/carlos/shortform/di/AcronymsModule.kt b/shortform/src/main/java/dev/carlos/shortform/di/AcronymsModule.kt index 2324c20..8e6ad5b 100644 --- a/shortform/src/main/java/dev/carlos/shortform/di/AcronymsModule.kt +++ b/shortform/src/main/java/dev/carlos/shortform/di/AcronymsModule.kt @@ -9,6 +9,8 @@ import dev.carlos.shortform.data.cloud.retrofit.AcronymsRemoteDatasource import dev.carlos.shortform.data.cloud.retrofit.AcronymsService import dev.carlos.shortform.domain.AcronymsRepository import dev.carlos.shortform.domain.GetAcronymDefinition +import dev.carlos.shortform.viewmodels.AcronymsViewmodel +import org.koin.androidx.viewmodel.dsl.viewModel import org.koin.dsl.module val acronymsModule = module { @@ -22,4 +24,6 @@ val acronymsModule = module { factory { AcronymsDataRepository(get()) } factory { GetAcronymDefinition(get(), get()) } + + viewModel { AcronymsViewmodel(get()) } } diff --git a/shortform/src/main/java/dev/carlos/shortform/viewmodels/AcronymsViewmodel.kt b/shortform/src/main/java/dev/carlos/shortform/viewmodels/AcronymsViewmodel.kt new file mode 100644 index 0000000..276418b --- /dev/null +++ b/shortform/src/main/java/dev/carlos/shortform/viewmodels/AcronymsViewmodel.kt @@ -0,0 +1,37 @@ +package dev.carlos.shortform.viewmodels + +import androidx.lifecycle.MutableLiveData +import dev.carlos.core.domain.network.RequestError +import dev.carlos.core.domain.network.RequestState +import dev.carlos.core.viewmodel.RxViewModel +import dev.carlos.shortform.data.models.ShortformModel +import dev.carlos.shortform.domain.GetAcronymDefinition +import retrofit2.HttpException +import java.net.UnknownHostException + +class AcronymsViewmodel( + private val GetAcronymsDefinition: GetAcronymDefinition +) : RxViewModel() { + + val acronymDefinition = MutableLiveData() + + fun fetchAcronymDefinition(acronym: String) { + val disposable = GetAcronymsDefinition.getAcronymDefinition(acronym) + .doOnSubscribe { acronymDefinition.postValue(RequestState.Loading) } + .subscribe(::handleSuccess, ::handleError) + compositeDisposable.add(disposable) + } + + private fun handleSuccess(definition: ShortformModel) { + acronymDefinition.postValue(RequestState.Success(definition)) + } + + private fun handleError(exception: Throwable) { + when (exception) { + is NoSuchElementException -> acronymDefinition.postValue(RequestState.Empty) + is UnknownHostException -> acronymDefinition.postValue(RequestState.Error(RequestError.NO_NETWORK)) + is HttpException -> acronymDefinition.postValue(RequestState.Error(RequestError.BAD_RESPONSE)) + else -> acronymDefinition.postValue(RequestState.Error(RequestError.UNKNOWN_PROBLEM)) + } + } +} \ No newline at end of file From 4e53225a2f22dc35cb63c0cb752d16c16a209062 Mon Sep 17 00:00:00 2001 From: Carlos Martinez Date: Thu, 17 Jun 2021 12:12:55 -0400 Subject: [PATCH 2/3] Change: rename acronyms classes to shortform --- .../dev/carlos/acronyms/MainApplication.kt | 4 +-- .../shortform/data/AcronymsDataRepository.kt | 16 ---------- .../shortform/data/ShortformDataRepository.kt | 16 ++++++++++ ...moteSource.kt => ShortformRemoteSource.kt} | 4 +-- .../retrofit/AcronymsRemoteDatasource.kt | 11 ------- .../retrofit/ShortformRemoteDatasource.kt | 11 +++++++ ...AcronymsService.kt => ShortformService.kt} | 4 +-- .../dev/carlos/shortform/di/AcronymsModule.kt | 29 ------------------- .../carlos/shortform/di/ShortformModule.kt | 29 +++++++++++++++++++ ...efinition.kt => GetShortformDefinition.kt} | 8 ++--- ...msRepository.kt => ShortformRepository.kt} | 4 +-- .../shortform/feature/ShortformFragment.kt | 4 +++ ...nymsViewmodel.kt => ShortformViewmodel.kt} | 8 ++--- 13 files changed, 76 insertions(+), 72 deletions(-) delete mode 100644 shortform/src/main/java/dev/carlos/shortform/data/AcronymsDataRepository.kt create mode 100644 shortform/src/main/java/dev/carlos/shortform/data/ShortformDataRepository.kt rename shortform/src/main/java/dev/carlos/shortform/data/cloud/{AcronymsRemoteSource.kt => ShortformRemoteSource.kt} (53%) delete mode 100644 shortform/src/main/java/dev/carlos/shortform/data/cloud/retrofit/AcronymsRemoteDatasource.kt create mode 100644 shortform/src/main/java/dev/carlos/shortform/data/cloud/retrofit/ShortformRemoteDatasource.kt rename shortform/src/main/java/dev/carlos/shortform/data/cloud/retrofit/{AcronymsService.kt => ShortformService.kt} (66%) delete mode 100644 shortform/src/main/java/dev/carlos/shortform/di/AcronymsModule.kt create mode 100644 shortform/src/main/java/dev/carlos/shortform/di/ShortformModule.kt rename shortform/src/main/java/dev/carlos/shortform/domain/{GetAcronymDefinition.kt => GetShortformDefinition.kt} (50%) rename shortform/src/main/java/dev/carlos/shortform/domain/{AcronymsRepository.kt => ShortformRepository.kt} (59%) create mode 100644 shortform/src/main/java/dev/carlos/shortform/feature/ShortformFragment.kt rename shortform/src/main/java/dev/carlos/shortform/viewmodels/{AcronymsViewmodel.kt => ShortformViewmodel.kt} (85%) diff --git a/app/src/main/java/dev/carlos/acronyms/MainApplication.kt b/app/src/main/java/dev/carlos/acronyms/MainApplication.kt index 7f3734d..8c7e978 100644 --- a/app/src/main/java/dev/carlos/acronyms/MainApplication.kt +++ b/app/src/main/java/dev/carlos/acronyms/MainApplication.kt @@ -1,7 +1,7 @@ package dev.carlos.acronyms import android.app.Application -import dev.carlos.shortform.di.acronymsModule +import dev.carlos.shortform.di.shortformModule import dev.carlos.acronyms.di.appModules import org.koin.android.ext.koin.androidContext import org.koin.core.context.startKoin @@ -26,7 +26,7 @@ class MainApplication : Application() { modules( listOf( appModules, - acronymsModule + shortformModule ) ) } diff --git a/shortform/src/main/java/dev/carlos/shortform/data/AcronymsDataRepository.kt b/shortform/src/main/java/dev/carlos/shortform/data/AcronymsDataRepository.kt deleted file mode 100644 index 4cf7604..0000000 --- a/shortform/src/main/java/dev/carlos/shortform/data/AcronymsDataRepository.kt +++ /dev/null @@ -1,16 +0,0 @@ -package dev.carlos.shortform.data - -import dev.carlos.shortform.data.cloud.AcronymsRemoteSource -import dev.carlos.shortform.data.models.ShortformModel -import dev.carlos.shortform.data.models.toShortformModel -import dev.carlos.shortform.domain.AcronymsRepository -import io.reactivex.Single - -class AcronymsDataRepository( - private val remoteDatasource: AcronymsRemoteSource -) : AcronymsRepository { - - override fun getAcronymDefinition(acronym: String): Single { - return remoteDatasource.getAcronymDefinition(acronym).map { it.single().toShortformModel() } - } -} diff --git a/shortform/src/main/java/dev/carlos/shortform/data/ShortformDataRepository.kt b/shortform/src/main/java/dev/carlos/shortform/data/ShortformDataRepository.kt new file mode 100644 index 0000000..d4ca8ef --- /dev/null +++ b/shortform/src/main/java/dev/carlos/shortform/data/ShortformDataRepository.kt @@ -0,0 +1,16 @@ +package dev.carlos.shortform.data + +import dev.carlos.shortform.data.cloud.ShortformRemoteSource +import dev.carlos.shortform.data.models.ShortformModel +import dev.carlos.shortform.data.models.toShortformModel +import dev.carlos.shortform.domain.ShortformRepository +import io.reactivex.Single + +class ShortformDataRepository( + private val remoteDatasource: ShortformRemoteSource +) : ShortformRepository { + + override fun getShortformDefinition(acronym: String): Single { + return remoteDatasource.getShortformDefinition(acronym).map { it.single().toShortformModel() } + } +} diff --git a/shortform/src/main/java/dev/carlos/shortform/data/cloud/AcronymsRemoteSource.kt b/shortform/src/main/java/dev/carlos/shortform/data/cloud/ShortformRemoteSource.kt similarity index 53% rename from shortform/src/main/java/dev/carlos/shortform/data/cloud/AcronymsRemoteSource.kt rename to shortform/src/main/java/dev/carlos/shortform/data/cloud/ShortformRemoteSource.kt index 315eb7c..8c073d2 100644 --- a/shortform/src/main/java/dev/carlos/shortform/data/cloud/AcronymsRemoteSource.kt +++ b/shortform/src/main/java/dev/carlos/shortform/data/cloud/ShortformRemoteSource.kt @@ -3,6 +3,6 @@ package dev.carlos.shortform.data.cloud import dev.carlos.shortform.data.cloud.model.ShortformRemote import io.reactivex.Single -interface AcronymsRemoteSource { - fun getAcronymDefinition(acronym: String): Single> +interface ShortformRemoteSource { + fun getShortformDefinition(acronym: String): Single> } diff --git a/shortform/src/main/java/dev/carlos/shortform/data/cloud/retrofit/AcronymsRemoteDatasource.kt b/shortform/src/main/java/dev/carlos/shortform/data/cloud/retrofit/AcronymsRemoteDatasource.kt deleted file mode 100644 index c02c65d..0000000 --- a/shortform/src/main/java/dev/carlos/shortform/data/cloud/retrofit/AcronymsRemoteDatasource.kt +++ /dev/null @@ -1,11 +0,0 @@ -package dev.carlos.shortform.data.cloud.retrofit - -import dev.carlos.shortform.data.cloud.AcronymsRemoteSource -import dev.carlos.shortform.data.cloud.model.ShortformRemote -import io.reactivex.Single - -class AcronymsRemoteDatasource(private val acronymsService: AcronymsService) : AcronymsRemoteSource { - override fun getAcronymDefinition(acronym: String): Single> { - return acronymsService.getAcronymDefinition(acronym) - } -} diff --git a/shortform/src/main/java/dev/carlos/shortform/data/cloud/retrofit/ShortformRemoteDatasource.kt b/shortform/src/main/java/dev/carlos/shortform/data/cloud/retrofit/ShortformRemoteDatasource.kt new file mode 100644 index 0000000..b72e388 --- /dev/null +++ b/shortform/src/main/java/dev/carlos/shortform/data/cloud/retrofit/ShortformRemoteDatasource.kt @@ -0,0 +1,11 @@ +package dev.carlos.shortform.data.cloud.retrofit + +import dev.carlos.shortform.data.cloud.ShortformRemoteSource +import dev.carlos.shortform.data.cloud.model.ShortformRemote +import io.reactivex.Single + +class ShortformRemoteDatasource(private val shortformService: ShortformService) : ShortformRemoteSource { + override fun getShortformDefinition(acronym: String): Single> { + return shortformService.getShortformDefinition(acronym) + } +} diff --git a/shortform/src/main/java/dev/carlos/shortform/data/cloud/retrofit/AcronymsService.kt b/shortform/src/main/java/dev/carlos/shortform/data/cloud/retrofit/ShortformService.kt similarity index 66% rename from shortform/src/main/java/dev/carlos/shortform/data/cloud/retrofit/AcronymsService.kt rename to shortform/src/main/java/dev/carlos/shortform/data/cloud/retrofit/ShortformService.kt index d44843c..f5dd805 100644 --- a/shortform/src/main/java/dev/carlos/shortform/data/cloud/retrofit/AcronymsService.kt +++ b/shortform/src/main/java/dev/carlos/shortform/data/cloud/retrofit/ShortformService.kt @@ -5,7 +5,7 @@ import io.reactivex.Single import retrofit2.http.GET import retrofit2.http.Query -interface AcronymsService { +interface ShortformService { @GET("/software/acromine/dictionary.py") - fun getAcronymDefinition(@Query("sf") acronym: String): Single> + fun getShortformDefinition(@Query("sf") acronym: String): Single> } diff --git a/shortform/src/main/java/dev/carlos/shortform/di/AcronymsModule.kt b/shortform/src/main/java/dev/carlos/shortform/di/AcronymsModule.kt deleted file mode 100644 index 8e6ad5b..0000000 --- a/shortform/src/main/java/dev/carlos/shortform/di/AcronymsModule.kt +++ /dev/null @@ -1,29 +0,0 @@ -package dev.carlos.shortform.di - -import dev.carlos.core.domain.network.RemoteClient -import dev.carlos.core.scheduler.Scheduler -import dev.carlos.core.scheduler.SchedulerProvider -import dev.carlos.shortform.data.AcronymsDataRepository -import dev.carlos.shortform.data.cloud.AcronymsRemoteSource -import dev.carlos.shortform.data.cloud.retrofit.AcronymsRemoteDatasource -import dev.carlos.shortform.data.cloud.retrofit.AcronymsService -import dev.carlos.shortform.domain.AcronymsRepository -import dev.carlos.shortform.domain.GetAcronymDefinition -import dev.carlos.shortform.viewmodels.AcronymsViewmodel -import org.koin.androidx.viewmodel.dsl.viewModel -import org.koin.dsl.module - -val acronymsModule = module { - single { SchedulerProvider() } - - single { - get().getClient(AcronymsService::class.java) - } - - factory { AcronymsRemoteDatasource(get()) } - factory { AcronymsDataRepository(get()) } - - factory { GetAcronymDefinition(get(), get()) } - - viewModel { AcronymsViewmodel(get()) } -} diff --git a/shortform/src/main/java/dev/carlos/shortform/di/ShortformModule.kt b/shortform/src/main/java/dev/carlos/shortform/di/ShortformModule.kt new file mode 100644 index 0000000..d5e8295 --- /dev/null +++ b/shortform/src/main/java/dev/carlos/shortform/di/ShortformModule.kt @@ -0,0 +1,29 @@ +package dev.carlos.shortform.di + +import dev.carlos.core.domain.network.RemoteClient +import dev.carlos.core.scheduler.Scheduler +import dev.carlos.core.scheduler.SchedulerProvider +import dev.carlos.shortform.data.ShortformDataRepository +import dev.carlos.shortform.data.cloud.ShortformRemoteSource +import dev.carlos.shortform.data.cloud.retrofit.ShortformRemoteDatasource +import dev.carlos.shortform.data.cloud.retrofit.ShortformService +import dev.carlos.shortform.domain.ShortformRepository +import dev.carlos.shortform.domain.GetShortformDefinition +import dev.carlos.shortform.viewmodels.ShortformViewmodel +import org.koin.androidx.viewmodel.dsl.viewModel +import org.koin.dsl.module + +val shortformModule = module { + single { SchedulerProvider() } + + single { + get().getClient(ShortformService::class.java) + } + + factory { ShortformRemoteDatasource(get()) } + factory { ShortformDataRepository(get()) } + + factory { GetShortformDefinition(get(), get()) } + + viewModel { ShortformViewmodel(get()) } +} diff --git a/shortform/src/main/java/dev/carlos/shortform/domain/GetAcronymDefinition.kt b/shortform/src/main/java/dev/carlos/shortform/domain/GetShortformDefinition.kt similarity index 50% rename from shortform/src/main/java/dev/carlos/shortform/domain/GetAcronymDefinition.kt rename to shortform/src/main/java/dev/carlos/shortform/domain/GetShortformDefinition.kt index 2eaf816..fee307e 100644 --- a/shortform/src/main/java/dev/carlos/shortform/domain/GetAcronymDefinition.kt +++ b/shortform/src/main/java/dev/carlos/shortform/domain/GetShortformDefinition.kt @@ -5,11 +5,11 @@ import dev.carlos.core.extensions.runOnIo import dev.carlos.core.scheduler.Scheduler import io.reactivex.Single -class GetAcronymDefinition( - private val acronymsRepository: AcronymsRepository, +class GetShortformDefinition( + private val shortformRepository: ShortformRepository, private val scheduler: Scheduler ) { - fun getAcronymDefinition(acronym: String): Single { - return acronymsRepository.getAcronymDefinition(acronym).runOnIo(scheduler) + fun getShortformDefinition(shortform: String): Single { + return shortformRepository.getShortformDefinition(shortform).runOnIo(scheduler) } } diff --git a/shortform/src/main/java/dev/carlos/shortform/domain/AcronymsRepository.kt b/shortform/src/main/java/dev/carlos/shortform/domain/ShortformRepository.kt similarity index 59% rename from shortform/src/main/java/dev/carlos/shortform/domain/AcronymsRepository.kt rename to shortform/src/main/java/dev/carlos/shortform/domain/ShortformRepository.kt index 64ce4e5..76e6b0a 100644 --- a/shortform/src/main/java/dev/carlos/shortform/domain/AcronymsRepository.kt +++ b/shortform/src/main/java/dev/carlos/shortform/domain/ShortformRepository.kt @@ -4,6 +4,6 @@ import dev.carlos.shortform.data.models.ShortformModel import io.reactivex.Flowable import io.reactivex.Single -interface AcronymsRepository { - fun getAcronymDefinition(acronym: String): Single +interface ShortformRepository { + fun getShortformDefinition(acronym: String): Single } diff --git a/shortform/src/main/java/dev/carlos/shortform/feature/ShortformFragment.kt b/shortform/src/main/java/dev/carlos/shortform/feature/ShortformFragment.kt new file mode 100644 index 0000000..b787a69 --- /dev/null +++ b/shortform/src/main/java/dev/carlos/shortform/feature/ShortformFragment.kt @@ -0,0 +1,4 @@ +package dev.carlos.shortform.feature + +class ShortformFragment { +} \ No newline at end of file diff --git a/shortform/src/main/java/dev/carlos/shortform/viewmodels/AcronymsViewmodel.kt b/shortform/src/main/java/dev/carlos/shortform/viewmodels/ShortformViewmodel.kt similarity index 85% rename from shortform/src/main/java/dev/carlos/shortform/viewmodels/AcronymsViewmodel.kt rename to shortform/src/main/java/dev/carlos/shortform/viewmodels/ShortformViewmodel.kt index 276418b..35e36d7 100644 --- a/shortform/src/main/java/dev/carlos/shortform/viewmodels/AcronymsViewmodel.kt +++ b/shortform/src/main/java/dev/carlos/shortform/viewmodels/ShortformViewmodel.kt @@ -5,18 +5,18 @@ import dev.carlos.core.domain.network.RequestError import dev.carlos.core.domain.network.RequestState import dev.carlos.core.viewmodel.RxViewModel import dev.carlos.shortform.data.models.ShortformModel -import dev.carlos.shortform.domain.GetAcronymDefinition +import dev.carlos.shortform.domain.GetShortformDefinition import retrofit2.HttpException import java.net.UnknownHostException -class AcronymsViewmodel( - private val GetAcronymsDefinition: GetAcronymDefinition +class ShortformViewmodel( + private val getAcronymsDefinition: GetShortformDefinition ) : RxViewModel() { val acronymDefinition = MutableLiveData() fun fetchAcronymDefinition(acronym: String) { - val disposable = GetAcronymsDefinition.getAcronymDefinition(acronym) + val disposable = getAcronymsDefinition.getShortformDefinition(acronym) .doOnSubscribe { acronymDefinition.postValue(RequestState.Loading) } .subscribe(::handleSuccess, ::handleError) compositeDisposable.add(disposable) From 0e1a9850ed997eeac15daf5ef60b0025f2168e48 Mon Sep 17 00:00:00 2001 From: Carlos Martinez Date: Thu, 17 Jun 2021 13:26:48 -0400 Subject: [PATCH 3/3] Add: presentation layer --- .../main/res/navigation/main_navigation.xml | 4 + .../core/domain/network/NetworkErrors.kt | 11 ++- .../carlos/core/extensions/DataExtensions.kt | 2 +- .../core/extensions/StringsExtensions.kt | 5 ++ core/src/main/res/drawable/bg_card.xml | 9 ++ core/src/main/res/values/colors.xml | 5 +- core/src/main/res/values/strings.xml | 6 +- .../shortform/feature/ShortformFragment.kt | 88 ++++++++++++++++++- .../shortform/widgets/LongformAdapter.kt | 60 +++++++++++++ .../layout/fragment_shortform_definition.xml | 79 +++++++++++++++++ .../main/res/layout/item_longform_card.xml | 38 ++++++++ .../res/navigation/shortform_navigation.xml | 16 ++++ shortform/src/main/res/values/strings.xml | 4 +- 13 files changed, 317 insertions(+), 10 deletions(-) create mode 100644 core/src/main/java/dev/carlos/core/extensions/StringsExtensions.kt create mode 100644 core/src/main/res/drawable/bg_card.xml create mode 100644 shortform/src/main/java/dev/carlos/shortform/widgets/LongformAdapter.kt create mode 100644 shortform/src/main/res/layout/fragment_shortform_definition.xml create mode 100644 shortform/src/main/res/layout/item_longform_card.xml create mode 100644 shortform/src/main/res/navigation/shortform_navigation.xml diff --git a/app/src/main/res/navigation/main_navigation.xml b/app/src/main/res/navigation/main_navigation.xml index f17e2a2..69ae5ab 100644 --- a/app/src/main/res/navigation/main_navigation.xml +++ b/app/src/main/res/navigation/main_navigation.xml @@ -1,6 +1,10 @@ + + diff --git a/core/src/main/java/dev/carlos/core/domain/network/NetworkErrors.kt b/core/src/main/java/dev/carlos/core/domain/network/NetworkErrors.kt index 83e931f..39c676e 100644 --- a/core/src/main/java/dev/carlos/core/domain/network/NetworkErrors.kt +++ b/core/src/main/java/dev/carlos/core/domain/network/NetworkErrors.kt @@ -1,7 +1,10 @@ package dev.carlos.core.domain.network -enum class RequestError { - NO_NETWORK, - BAD_RESPONSE, - UNKNOWN_PROBLEM +import androidx.annotation.StringRes +import dev.carlos.core.R + +enum class RequestError(@StringRes val message: Int) { + NO_NETWORK(R.string.network_error_no_network), + BAD_RESPONSE(R.string.network_error_bad_response), + UNKNOWN_PROBLEM(R.string.network_error_unknown) } diff --git a/core/src/main/java/dev/carlos/core/extensions/DataExtensions.kt b/core/src/main/java/dev/carlos/core/extensions/DataExtensions.kt index 1176e23..c9b501b 100644 --- a/core/src/main/java/dev/carlos/core/extensions/DataExtensions.kt +++ b/core/src/main/java/dev/carlos/core/extensions/DataExtensions.kt @@ -5,7 +5,7 @@ import androidx.lifecycle.LiveData import androidx.lifecycle.Observer fun LiveData.observeNonNull(owner: LifecycleOwner, func: (T) -> Unit) { - observe(owner, Observer { + observe(owner, { it?.let { func(it) } diff --git a/core/src/main/java/dev/carlos/core/extensions/StringsExtensions.kt b/core/src/main/java/dev/carlos/core/extensions/StringsExtensions.kt new file mode 100644 index 0000000..e61c927 --- /dev/null +++ b/core/src/main/java/dev/carlos/core/extensions/StringsExtensions.kt @@ -0,0 +1,5 @@ +package dev.carlos.core.extensions + +import java.util.* + +fun String.capitalize() = replaceFirstChar { if (it.isLowerCase()) it.titlecase(Locale.getDefault()) else it.toString() } diff --git a/core/src/main/res/drawable/bg_card.xml b/core/src/main/res/drawable/bg_card.xml new file mode 100644 index 0000000..f686445 --- /dev/null +++ b/core/src/main/res/drawable/bg_card.xml @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/core/src/main/res/values/colors.xml b/core/src/main/res/values/colors.xml index 09837df..9c8f149 100644 --- a/core/src/main/res/values/colors.xml +++ b/core/src/main/res/values/colors.xml @@ -7,4 +7,7 @@ #FF018786 #FF000000 #FFFFFFFF - \ No newline at end of file + #EEEEEE + #00000000 + #FFFFFF + diff --git a/core/src/main/res/values/strings.xml b/core/src/main/res/values/strings.xml index 75f77d2..696b69b 100644 --- a/core/src/main/res/values/strings.xml +++ b/core/src/main/res/values/strings.xml @@ -1,3 +1,5 @@ - core - \ No newline at end of file + Respuesta inesperada + Sin conexión + Error desconocido + diff --git a/shortform/src/main/java/dev/carlos/shortform/feature/ShortformFragment.kt b/shortform/src/main/java/dev/carlos/shortform/feature/ShortformFragment.kt index b787a69..3f8cc3e 100644 --- a/shortform/src/main/java/dev/carlos/shortform/feature/ShortformFragment.kt +++ b/shortform/src/main/java/dev/carlos/shortform/feature/ShortformFragment.kt @@ -1,4 +1,90 @@ package dev.carlos.shortform.feature -class ShortformFragment { +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.recyclerview.widget.LinearLayoutManager +import dev.carlos.core.domain.network.RequestError +import dev.carlos.core.domain.network.RequestState +import dev.carlos.core.extensions.gone +import dev.carlos.core.extensions.observeNonNull +import dev.carlos.core.extensions.visible +import dev.carlos.shortform.R +import dev.carlos.shortform.data.models.ShortformModel +import dev.carlos.shortform.databinding.FragmentShortformDefinitionBinding +import dev.carlos.shortform.viewmodels.ShortformViewmodel +import dev.carlos.shortform.widgets.LongformAdapter +import org.koin.androidx.viewmodel.ext.android.viewModel + +class ShortformFragment : Fragment() { + private val viewModel: ShortformViewmodel by viewModel() + private val listAdapter by lazy { LongformAdapter() } + private lateinit var binding: FragmentShortformDefinitionBinding + + override fun onCreateView( + inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? + ): View = inflater.inflate(R.layout.fragment_shortform_definition, container, false) + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + setBinding(view) + setRecycler() + setObservers() + } + + private fun setBinding(view: View) { + binding = FragmentShortformDefinitionBinding.bind(view) + } + + private fun setObservers() { + viewModel.acronymDefinition.observeNonNull(this) { + handleResult(it) + } + } + + private fun handleResult(state: RequestState) { + when (state) { + is RequestState.Success<*> -> handleSuccess(state.data as ShortformModel) + is RequestState.Empty -> handleEmpty() + is RequestState.Error -> handleError(state.type) + is RequestState.Loading -> handleLoading() + } + } + + private fun handleLoading() { + binding.shortformDefinitionLoading.visible() + binding.shortformDefinitionError.gone() + } + + private fun handleSuccess(shortform: ShortformModel) { + binding.shortformDefinitionError.gone() + binding.shortformDefinitionLoading.gone() + listAdapter.addAll(shortform.results) + } + + private fun handleEmpty() { + binding.shortformDefinitionErrorLabel.text = getString(R.string.shortform_definition_no_definition) + binding.shortformDefinitionError.visible() + binding.shortformDefinitionLoading.gone() + } + + private fun handleError(error: RequestError) { + binding.shortformDefinitionErrorLabel.text = getString(error.message) + binding.shortformDefinitionError.visible() + binding.shortformDefinitionLoading.gone() + } + + private fun setRecycler() { + binding.shortformDefinitionRecycler.apply { + layoutManager = LinearLayoutManager(context) + adapter = listAdapter.apply { + onItemClick = { + Toast.makeText(context, it.value, Toast.LENGTH_SHORT).show() + } + } + } + } } \ No newline at end of file diff --git a/shortform/src/main/java/dev/carlos/shortform/widgets/LongformAdapter.kt b/shortform/src/main/java/dev/carlos/shortform/widgets/LongformAdapter.kt new file mode 100644 index 0000000..6543abe --- /dev/null +++ b/shortform/src/main/java/dev/carlos/shortform/widgets/LongformAdapter.kt @@ -0,0 +1,60 @@ +package dev.carlos.shortform.widgets + +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.recyclerview.widget.RecyclerView +import dev.carlos.core.extensions.autoNotify +import dev.carlos.core.extensions.capitalize +import dev.carlos.shortform.R +import dev.carlos.shortform.data.models.ShortformModel +import dev.carlos.shortform.databinding.ItemLongformCardBinding +import kotlin.properties.Delegates + +class LongformAdapter : RecyclerView.Adapter() { + + private var items by Delegates.observable(emptyList()) { _, oldList, newList -> + autoNotify(oldList, newList) { old, new -> old.value == new.value } + notifyDataSetChanged() + } + + var onItemClick: (ShortformModel.LongformModel) -> Unit = { } + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder = + LongformViewHolder( + LayoutInflater + .from(parent.context) + .inflate(R.layout.item_longform_card, parent, false), + onItemClick + ) + + fun getItem(position: Int) = items[position] + + fun addAll(list: List) { + items = list + } + + override fun getItemCount() = items.size + + override fun onBindViewHolder(viewholder: RecyclerView.ViewHolder, position: Int) = + when (viewholder) { + is LongformViewHolder -> viewholder.bind(items[position]) + else -> throw NoWhenBranchMatchedException("Undefined viewholder") + } +} + +class LongformViewHolder( + private val view: View, + private val onItemClick: (ShortformModel.LongformModel) -> Unit +) : RecyclerView.ViewHolder(view) { + + private val binding = ItemLongformCardBinding.bind(view) + + fun bind(longform: ShortformModel.LongformModel) = with(view) { + binding.itemLongformName.text = longform.value.capitalize() + binding.itemLongformSince.text = view.resources.getString(R.string.item_longform_since, longform.since) + binding.itemLongformContainer.setOnClickListener { + onItemClick(longform) + } + } +} \ No newline at end of file diff --git a/shortform/src/main/res/layout/fragment_shortform_definition.xml b/shortform/src/main/res/layout/fragment_shortform_definition.xml new file mode 100644 index 0000000..0958fae --- /dev/null +++ b/shortform/src/main/res/layout/fragment_shortform_definition.xml @@ -0,0 +1,79 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/shortform/src/main/res/layout/item_longform_card.xml b/shortform/src/main/res/layout/item_longform_card.xml new file mode 100644 index 0000000..e580f44 --- /dev/null +++ b/shortform/src/main/res/layout/item_longform_card.xml @@ -0,0 +1,38 @@ + + + + + + + + diff --git a/shortform/src/main/res/navigation/shortform_navigation.xml b/shortform/src/main/res/navigation/shortform_navigation.xml new file mode 100644 index 0000000..2ed5d1b --- /dev/null +++ b/shortform/src/main/res/navigation/shortform_navigation.xml @@ -0,0 +1,16 @@ + + + + + + \ No newline at end of file diff --git a/shortform/src/main/res/values/strings.xml b/shortform/src/main/res/values/strings.xml index 5423e6c..be206d9 100644 --- a/shortform/src/main/res/values/strings.xml +++ b/shortform/src/main/res/values/strings.xml @@ -1,3 +1,5 @@ - acronyms + Desde %1$d + Buscar acrónimo + No existe una definición \ No newline at end of file