diff --git a/app/build.gradle b/app/build.gradle
index d08b275..c808dd7 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -2,6 +2,7 @@ apply from: '../versions.gradle'
apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-kapt'
+apply plugin: 'kotlin-parcelize'
android {
compileSdkVersion build_versions.target_sdk
@@ -15,7 +16,7 @@ android {
versionName "1.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
- buildConfigField "String", "BASE_ENDPOINT", '"http://www.nactem.ac.uk/software/acromine/"'
+ buildConfigField "String", "BASE_ENDPOINT", '"http://www.nactem.ac.uk"'
}
buildTypes {
@@ -57,4 +58,5 @@ android {
dependencies {
implementation project(":core")
+ implementation project(':shortform')
}
\ No newline at end of file
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index b2d252b..e4ecfed 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -6,7 +6,7 @@
diff --git a/app/src/main/java/dev/carlos/acronyms/MainApplication.kt b/app/src/main/java/dev/carlos/acronyms/MainApplication.kt
index ea7d95c..8c7e978 100644
--- a/app/src/main/java/dev/carlos/acronyms/MainApplication.kt
+++ b/app/src/main/java/dev/carlos/acronyms/MainApplication.kt
@@ -1,6 +1,7 @@
package dev.carlos.acronyms
import android.app.Application
+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
@@ -24,7 +25,8 @@ class MainApplication : Application() {
modules(
listOf(
- appModules
+ appModules,
+ shortformModule
)
)
}
diff --git a/app/src/main/java/dev/carlos/acronyms/views/MainActivity.kt b/app/src/main/java/dev/carlos/acronyms/views/MainActivity.kt
index 8d5b26b..415fd1c 100644
--- a/app/src/main/java/dev/carlos/acronyms/views/MainActivity.kt
+++ b/app/src/main/java/dev/carlos/acronyms/views/MainActivity.kt
@@ -5,15 +5,11 @@ import androidx.appcompat.app.AppCompatActivity
import androidx.navigation.findNavController
import androidx.navigation.ui.NavigationUI
import dev.carlos.acronyms.R
-import dev.carlos.acronyms.di.appModules
import dev.carlos.acronyms.viewmodel.NavigationViewmodel
-import dev.carlos.core.extensions.findNavHostFragment
import dev.carlos.core.extensions.observeNonNull
import dev.carlos.core.navigation.NavigationRouter
import org.koin.android.ext.android.inject
-import org.koin.android.ext.koin.androidContext
import org.koin.androidx.viewmodel.ext.android.viewModel
-import org.koin.core.context.startKoin
class MainActivity : AppCompatActivity() {
diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml
index a21b3e1..f35bcca 100644
--- a/app/src/main/res/layout/activity_main.xml
+++ b/app/src/main/res/layout/activity_main.xml
@@ -5,7 +5,8 @@
android:layout_height="match_parent"
>
-
+
+
+
diff --git a/app/src/main/res/xml/network_security_config.xml b/app/src/main/res/xml/network_security_config.xml
new file mode 100644
index 0000000..754ccf0
--- /dev/null
+++ b/app/src/main/res/xml/network_security_config.xml
@@ -0,0 +1,6 @@
+
+
+
+ www.nactem.ac.uk
+
+
diff --git a/base.gradle b/base.gradle
index 6b9694a..3ace927 100644
--- a/base.gradle
+++ b/base.gradle
@@ -2,6 +2,7 @@ apply from: '../versions.gradle'
apply plugin: 'com.android.library'
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-kapt'
+apply plugin: 'kotlin-parcelize'
android {
compileSdkVersion build_versions.target_sdk
diff --git a/core/build.gradle b/core/build.gradle
index 6372e2c..f47fd63 100644
--- a/core/build.gradle
+++ b/core/build.gradle
@@ -3,6 +3,7 @@ apply from: '../base.gradle'
apply plugin: 'com.android.library'
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-kapt'
+apply plugin: 'kotlin-parcelize'
dependencies {
api deps.google.kotlin.std_lib
@@ -15,6 +16,7 @@ dependencies {
api deps.google.androidx.navigation_fragment
api deps.google.androidx.navigation_ui
api deps.google.material.core
+ api deps.google.gson.core
api deps.core.okhttp.logging_interceptor
api deps.core.retrofit.runtime
api deps.core.retrofit.gson
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..39c676e
--- /dev/null
+++ b/core/src/main/java/dev/carlos/core/domain/network/NetworkErrors.kt
@@ -0,0 +1,10 @@
+package dev.carlos.core.domain.network
+
+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/domain/network/RequestState.kt b/core/src/main/java/dev/carlos/core/domain/network/RequestState.kt
new file mode 100644
index 0000000..b710558
--- /dev/null
+++ b/core/src/main/java/dev/carlos/core/domain/network/RequestState.kt
@@ -0,0 +1,8 @@
+package dev.carlos.core.domain.network
+
+sealed class RequestState {
+ class Success(val data: T) : RequestState()
+ object Loading : RequestState()
+ class Error(val type: RequestError) : RequestState()
+ object Empty : RequestState()
+}
diff --git a/core/src/main/java/dev/carlos/core/domain/network/RequestStatus.kt b/core/src/main/java/dev/carlos/core/domain/network/RequestStatus.kt
deleted file mode 100644
index 1c8cafc..0000000
--- a/core/src/main/java/dev/carlos/core/domain/network/RequestStatus.kt
+++ /dev/null
@@ -1,8 +0,0 @@
-package dev.carlos.core.domain.network
-
-sealed class RequestStatus {
- object Ready : RequestStatus()
- object Loading : RequestStatus()
- object Errored : RequestStatus()
- object Empty : RequestStatus()
-}
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/NumbersExtensions.kt b/core/src/main/java/dev/carlos/core/extensions/NumbersExtensions.kt
new file mode 100644
index 0000000..3a428da
--- /dev/null
+++ b/core/src/main/java/dev/carlos/core/extensions/NumbersExtensions.kt
@@ -0,0 +1,3 @@
+package dev.carlos.core.extensions
+
+fun Int.notEmpty() = this > 0
diff --git a/core/src/main/java/dev/carlos/core/extensions/SchedulerExtensions.kt b/core/src/main/java/dev/carlos/core/extensions/SchedulerExtensions.kt
new file mode 100644
index 0000000..7ac530c
--- /dev/null
+++ b/core/src/main/java/dev/carlos/core/extensions/SchedulerExtensions.kt
@@ -0,0 +1,19 @@
+package dev.carlos.core.extensions
+
+import dev.carlos.core.scheduler.Scheduler
+import io.reactivex.Completable
+import io.reactivex.Flowable
+import io.reactivex.Observable
+import io.reactivex.Single
+
+fun Observable.runOnIo(scheduler: Scheduler): Observable =
+ subscribeOn(scheduler.io()).observeOn(scheduler.ui())
+
+fun Single.runOnIo(scheduler: Scheduler): Single =
+ subscribeOn(scheduler.io()).observeOn(scheduler.ui())
+
+fun Flowable.runOnIo(scheduler: Scheduler): Flowable =
+ subscribeOn(scheduler.io()).observeOn(scheduler.ui())
+
+fun Completable.runOnIo(scheduler: Scheduler): Completable =
+ subscribeOn(scheduler.io()).observeOn(scheduler.ui())
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/java/dev/carlos/core/scheduler/Scheduler.kt b/core/src/main/java/dev/carlos/core/scheduler/Scheduler.kt
new file mode 100644
index 0000000..9a57637
--- /dev/null
+++ b/core/src/main/java/dev/carlos/core/scheduler/Scheduler.kt
@@ -0,0 +1,10 @@
+package dev.carlos.core.scheduler
+
+import io.reactivex.Scheduler
+
+interface Scheduler {
+ fun io(): Scheduler
+ fun computation(): Scheduler
+ fun newThread(): Scheduler
+ fun ui(): Scheduler
+}
diff --git a/core/src/main/java/dev/carlos/core/scheduler/SchedulerProvider.kt b/core/src/main/java/dev/carlos/core/scheduler/SchedulerProvider.kt
new file mode 100644
index 0000000..f04fb75
--- /dev/null
+++ b/core/src/main/java/dev/carlos/core/scheduler/SchedulerProvider.kt
@@ -0,0 +1,15 @@
+package dev.carlos.core.scheduler
+
+import io.reactivex.android.schedulers.AndroidSchedulers
+import io.reactivex.schedulers.Schedulers
+
+class SchedulerProvider : Scheduler {
+
+ override fun io() = Schedulers.io()
+
+ override fun computation() = Schedulers.computation()
+
+ override fun newThread() = Schedulers.newThread()
+
+ override fun ui(): io.reactivex.Scheduler = AndroidSchedulers.mainThread()
+}
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/core/src/main/res/drawable-v24/ic_launcher_foreground.xml b/core/src/main/res/drawable-v24/ic_launcher_foreground.xml
deleted file mode 100644
index 1448aba..0000000
--- a/core/src/main/res/drawable-v24/ic_launcher_foreground.xml
+++ /dev/null
@@ -1,35 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
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/drawable/ic_launcher_background.xml b/core/src/main/res/drawable/ic_launcher_background.xml
deleted file mode 100644
index 445d0d5..0000000
--- a/core/src/main/res/drawable/ic_launcher_background.xml
+++ /dev/null
@@ -1,204 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/core/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/core/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
deleted file mode 100644
index 03eed25..0000000
--- a/core/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
+++ /dev/null
@@ -1,5 +0,0 @@
-
-
-
-
-
\ No newline at end of file
diff --git a/core/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml b/core/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml
deleted file mode 100644
index 03eed25..0000000
--- a/core/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml
+++ /dev/null
@@ -1,5 +0,0 @@
-
-
-
-
-
\ No newline at end of file
diff --git a/core/src/main/res/mipmap-hdpi/ic_launcher.png b/core/src/main/res/mipmap-hdpi/ic_launcher.png
deleted file mode 100644
index a571e60..0000000
Binary files a/core/src/main/res/mipmap-hdpi/ic_launcher.png and /dev/null differ
diff --git a/core/src/main/res/mipmap-hdpi/ic_launcher_round.png b/core/src/main/res/mipmap-hdpi/ic_launcher_round.png
deleted file mode 100644
index 61da551..0000000
Binary files a/core/src/main/res/mipmap-hdpi/ic_launcher_round.png and /dev/null differ
diff --git a/core/src/main/res/mipmap-mdpi/ic_launcher.png b/core/src/main/res/mipmap-mdpi/ic_launcher.png
deleted file mode 100644
index c41dd28..0000000
Binary files a/core/src/main/res/mipmap-mdpi/ic_launcher.png and /dev/null differ
diff --git a/core/src/main/res/mipmap-mdpi/ic_launcher_round.png b/core/src/main/res/mipmap-mdpi/ic_launcher_round.png
deleted file mode 100644
index db5080a..0000000
Binary files a/core/src/main/res/mipmap-mdpi/ic_launcher_round.png and /dev/null differ
diff --git a/core/src/main/res/mipmap-xhdpi/ic_launcher.png b/core/src/main/res/mipmap-xhdpi/ic_launcher.png
deleted file mode 100644
index 6dba46d..0000000
Binary files a/core/src/main/res/mipmap-xhdpi/ic_launcher.png and /dev/null differ
diff --git a/core/src/main/res/mipmap-xhdpi/ic_launcher_round.png b/core/src/main/res/mipmap-xhdpi/ic_launcher_round.png
deleted file mode 100644
index da31a87..0000000
Binary files a/core/src/main/res/mipmap-xhdpi/ic_launcher_round.png and /dev/null differ
diff --git a/core/src/main/res/mipmap-xxhdpi/ic_launcher.png b/core/src/main/res/mipmap-xxhdpi/ic_launcher.png
deleted file mode 100644
index 15ac681..0000000
Binary files a/core/src/main/res/mipmap-xxhdpi/ic_launcher.png and /dev/null differ
diff --git a/core/src/main/res/mipmap-xxhdpi/ic_launcher_round.png b/core/src/main/res/mipmap-xxhdpi/ic_launcher_round.png
deleted file mode 100644
index b216f2d..0000000
Binary files a/core/src/main/res/mipmap-xxhdpi/ic_launcher_round.png and /dev/null differ
diff --git a/core/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/core/src/main/res/mipmap-xxxhdpi/ic_launcher.png
deleted file mode 100644
index f25a419..0000000
Binary files a/core/src/main/res/mipmap-xxxhdpi/ic_launcher.png and /dev/null differ
diff --git a/core/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png b/core/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png
deleted file mode 100644
index e96783c..0000000
Binary files a/core/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png and /dev/null differ
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/settings.gradle b/settings.gradle
index e89e7cc..51e8e82 100644
--- a/settings.gradle
+++ b/settings.gradle
@@ -1,2 +1,2 @@
rootProject.name = "Acronyms"
-include ':app', ':core'
+include ':app', ':core', ':shortform'
diff --git a/shortform/.gitignore b/shortform/.gitignore
new file mode 100644
index 0000000..42afabf
--- /dev/null
+++ b/shortform/.gitignore
@@ -0,0 +1 @@
+/build
\ No newline at end of file
diff --git a/shortform/build.gradle b/shortform/build.gradle
new file mode 100644
index 0000000..92d439c
--- /dev/null
+++ b/shortform/build.gradle
@@ -0,0 +1,16 @@
+apply from: '../versions.gradle'
+apply from: '../base.gradle'
+apply plugin: 'com.android.library'
+apply plugin: 'kotlin-android'
+apply plugin: 'kotlin-kapt'
+apply plugin: 'kotlin-parcelize'
+
+android {
+ defaultConfig {
+ buildConfigField "String", "DB_NAME", '"acronyms.db"'
+ }
+}
+
+dependencies {
+ implementation project(':core')
+}
diff --git a/shortform/proguard-rules.pro b/shortform/proguard-rules.pro
new file mode 100644
index 0000000..481bb43
--- /dev/null
+++ b/shortform/proguard-rules.pro
@@ -0,0 +1,21 @@
+# Add project specific ProGuard rules here.
+# You can control the set of applied configuration files using the
+# proguardFiles setting in build.gradle.
+#
+# For more details, see
+# http://developer.android.com/guide/developing/tools/proguard.html
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+# public *;
+#}
+
+# Uncomment this to preserve the line number information for
+# debugging stack traces.
+#-keepattributes SourceFile,LineNumberTable
+
+# If you keep the line number information, uncomment this to
+# hide the original source file name.
+#-renamesourcefileattribute SourceFile
\ No newline at end of file
diff --git a/shortform/src/main/AndroidManifest.xml b/shortform/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..7b5a50f
--- /dev/null
+++ b/shortform/src/main/AndroidManifest.xml
@@ -0,0 +1,2 @@
+
+
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/ShortformRemoteSource.kt b/shortform/src/main/java/dev/carlos/shortform/data/cloud/ShortformRemoteSource.kt
new file mode 100644
index 0000000..8c073d2
--- /dev/null
+++ b/shortform/src/main/java/dev/carlos/shortform/data/cloud/ShortformRemoteSource.kt
@@ -0,0 +1,8 @@
+package dev.carlos.shortform.data.cloud
+
+import dev.carlos.shortform.data.cloud.model.ShortformRemote
+import io.reactivex.Single
+
+interface ShortformRemoteSource {
+ fun getShortformDefinition(acronym: String): Single>
+}
diff --git a/shortform/src/main/java/dev/carlos/shortform/data/cloud/model/ShortformRemote.kt b/shortform/src/main/java/dev/carlos/shortform/data/cloud/model/ShortformRemote.kt
new file mode 100644
index 0000000..6fb68c8
--- /dev/null
+++ b/shortform/src/main/java/dev/carlos/shortform/data/cloud/model/ShortformRemote.kt
@@ -0,0 +1,18 @@
+package dev.carlos.shortform.data.cloud.model
+
+import android.os.Parcelable
+import com.google.gson.annotations.SerializedName
+import kotlinx.parcelize.Parcelize
+
+@Parcelize
+data class ShortformRemote(
+ @SerializedName("sf") val value: String,
+ @SerializedName("lfs") val results: List
+) : Parcelable {
+ @Parcelize
+ data class LongformRemote(
+ @SerializedName("lf") val value: String,
+ @SerializedName("freq") val corpusFrequency: Int,
+ @SerializedName("since") val since: Int
+ ) : Parcelable
+}
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/ShortformService.kt b/shortform/src/main/java/dev/carlos/shortform/data/cloud/retrofit/ShortformService.kt
new file mode 100644
index 0000000..f5dd805
--- /dev/null
+++ b/shortform/src/main/java/dev/carlos/shortform/data/cloud/retrofit/ShortformService.kt
@@ -0,0 +1,11 @@
+package dev.carlos.shortform.data.cloud.retrofit
+
+import dev.carlos.shortform.data.cloud.model.ShortformRemote
+import io.reactivex.Single
+import retrofit2.http.GET
+import retrofit2.http.Query
+
+interface ShortformService {
+ @GET("/software/acromine/dictionary.py")
+ fun getShortformDefinition(@Query("sf") acronym: String): Single>
+}
diff --git a/shortform/src/main/java/dev/carlos/shortform/data/models/Mappers.kt b/shortform/src/main/java/dev/carlos/shortform/data/models/Mappers.kt
new file mode 100644
index 0000000..6031961
--- /dev/null
+++ b/shortform/src/main/java/dev/carlos/shortform/data/models/Mappers.kt
@@ -0,0 +1,7 @@
+package dev.carlos.shortform.data.models
+
+import dev.carlos.shortform.data.cloud.model.ShortformRemote
+
+fun ShortformRemote.toShortformModel() = ShortformModel(this.value, this.results.map {
+ ShortformModel.LongformModel(it.value, it.corpusFrequency, it.since)
+})
diff --git a/shortform/src/main/java/dev/carlos/shortform/data/models/ShortformModel.kt b/shortform/src/main/java/dev/carlos/shortform/data/models/ShortformModel.kt
new file mode 100644
index 0000000..e36aa13
--- /dev/null
+++ b/shortform/src/main/java/dev/carlos/shortform/data/models/ShortformModel.kt
@@ -0,0 +1,12 @@
+package dev.carlos.shortform.data.models
+
+data class ShortformModel(
+ val value: String,
+ val results: List
+) {
+ data class LongformModel(
+ val value: String,
+ val corpusFrequency: Int,
+ val since: Int
+ )
+}
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/GetShortformDefinition.kt b/shortform/src/main/java/dev/carlos/shortform/domain/GetShortformDefinition.kt
new file mode 100644
index 0000000..fee307e
--- /dev/null
+++ b/shortform/src/main/java/dev/carlos/shortform/domain/GetShortformDefinition.kt
@@ -0,0 +1,15 @@
+package dev.carlos.shortform.domain
+
+import dev.carlos.shortform.data.models.ShortformModel
+import dev.carlos.core.extensions.runOnIo
+import dev.carlos.core.scheduler.Scheduler
+import io.reactivex.Single
+
+class GetShortformDefinition(
+ private val shortformRepository: ShortformRepository,
+ private val scheduler: Scheduler
+) {
+ fun getShortformDefinition(shortform: String): Single {
+ return shortformRepository.getShortformDefinition(shortform).runOnIo(scheduler)
+ }
+}
diff --git a/shortform/src/main/java/dev/carlos/shortform/domain/ShortformRepository.kt b/shortform/src/main/java/dev/carlos/shortform/domain/ShortformRepository.kt
new file mode 100644
index 0000000..76e6b0a
--- /dev/null
+++ b/shortform/src/main/java/dev/carlos/shortform/domain/ShortformRepository.kt
@@ -0,0 +1,9 @@
+package dev.carlos.shortform.domain
+
+import dev.carlos.shortform.data.models.ShortformModel
+import io.reactivex.Flowable
+import io.reactivex.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..79855aa
--- /dev/null
+++ b/shortform/src/main/java/dev/carlos/shortform/feature/ShortformFragment.kt
@@ -0,0 +1,112 @@
+package dev.carlos.shortform.feature
+
+import android.os.Bundle
+import android.view.KeyEvent
+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)
+ }
+
+ binding.shortformDefinitionSearchField.setOnKeyListener(object : View.OnKeyListener {
+ override fun onKey(v: View?, keyCode: Int, event: KeyEvent): Boolean {
+ if (event.action == KeyEvent.ACTION_DOWN &&
+ keyCode == KeyEvent.KEYCODE_ENTER
+ ) {
+ onSearch()
+ return true
+ }
+ return false
+ }
+ })
+ }
+
+ private fun onSearch() {
+ fetchAcronym(binding.shortformDefinitionSearchField.text.toString())
+ binding.shortformDefinitionRecycler.requestFocus()
+ }
+
+ private fun fetchAcronym(acronym: String) {
+ viewModel.fetchAcronymDefinition(acronym)
+ }
+
+ 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/viewmodels/ShortformViewmodel.kt b/shortform/src/main/java/dev/carlos/shortform/viewmodels/ShortformViewmodel.kt
new file mode 100644
index 0000000..35e36d7
--- /dev/null
+++ b/shortform/src/main/java/dev/carlos/shortform/viewmodels/ShortformViewmodel.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.GetShortformDefinition
+import retrofit2.HttpException
+import java.net.UnknownHostException
+
+class ShortformViewmodel(
+ private val getAcronymsDefinition: GetShortformDefinition
+) : RxViewModel() {
+
+ val acronymDefinition = MutableLiveData()
+
+ fun fetchAcronymDefinition(acronym: String) {
+ val disposable = getAcronymsDefinition.getShortformDefinition(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
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-night/themes.xml b/shortform/src/main/res/values-night/themes.xml
new file mode 100644
index 0000000..7558bbb
--- /dev/null
+++ b/shortform/src/main/res/values-night/themes.xml
@@ -0,0 +1,19 @@
+
+
+
+
\ No newline at end of file
diff --git a/shortform/src/main/res/values/colors.xml b/shortform/src/main/res/values/colors.xml
new file mode 100644
index 0000000..09837df
--- /dev/null
+++ b/shortform/src/main/res/values/colors.xml
@@ -0,0 +1,10 @@
+
+
+ #FFBB86FC
+ #FF6200EE
+ #FF3700B3
+ #FF03DAC5
+ #FF018786
+ #FF000000
+ #FFFFFFFF
+
\ No newline at end of file
diff --git a/shortform/src/main/res/values/strings.xml b/shortform/src/main/res/values/strings.xml
new file mode 100644
index 0000000..be206d9
--- /dev/null
+++ b/shortform/src/main/res/values/strings.xml
@@ -0,0 +1,5 @@
+
+ Desde %1$d
+ Buscar acrónimo
+ No existe una definición
+
\ No newline at end of file
diff --git a/shortform/src/main/res/values/themes.xml b/shortform/src/main/res/values/themes.xml
new file mode 100644
index 0000000..a6cea39
--- /dev/null
+++ b/shortform/src/main/res/values/themes.xml
@@ -0,0 +1,19 @@
+
+
+
+
\ No newline at end of file
diff --git a/versions.gradle b/versions.gradle
index 9aab55f..ed5da03 100644
--- a/versions.gradle
+++ b/versions.gradle
@@ -11,6 +11,7 @@ versions.androidx_lifecycle = "2.2.0"
versions.androidx_recycler_view = "1.2.0"
versions.androidx_navigation = "2.3.5"
versions.material_core = "1.3.0"
+versions.gson = "2.8.7"
versions.okhttp_interceptor = "4.9.1"
versions.retrofit = "2.9.0"
versions.timber = "4.7.1"
@@ -53,8 +54,12 @@ androidx.navigation_ui = "androidx.navigation:navigation-ui-ktx:$versions.androi
def material = [:]
material.core = "com.google.android.material:material:$versions.material_core"
+def gson = [:]
+gson.core = "com.google.code.gson:gson:$versions.gson"
+
def google = [:]
google.kotlin = kotlin
+google.gson = gson
google.androidx = androidx
google.material = material
deps.google = google