diff --git a/app/build.gradle b/app/build.gradle
index 2e827a0..d06a24f 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -40,41 +40,44 @@ android {
dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
- implementation 'androidx.core:core-ktx:1.3.2'
- implementation 'androidx.appcompat:appcompat:1.2.0'
- implementation 'com.google.android.material:material:1.3.0'
- implementation 'androidx.constraintlayout:constraintlayout:2.0.4'
+ implementation "androidx.core:core-ktx:$core_ktx"
+ implementation "androidx.appcompat:appcompat:$appcompat"
+ implementation "com.google.android.material:material:$material"
+ implementation "androidx.constraintlayout:constraintlayout:$constraint_layout"
testImplementation 'junit:junit:4.+'
- androidTestImplementation 'androidx.test.ext:junit:1.1.2'
- androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0'
+ androidTestImplementation "androidx.test.ext:junit:$junit"
+ androidTestImplementation "androidx.test.espresso:espresso-core:$espresso"
// Navigation
implementation "androidx.navigation:navigation-fragment-ktx:$navigationVersion"
implementation "androidx.navigation:navigation-ui-ktx:$navigationVersion"
// Koin
- implementation "org.koin:koin-android:2.0.1"
- implementation 'org.koin:koin-androidx-viewmodel:2.0.1'
- implementation 'org.koin:koin-androidx-scope:2.0.1'
+ implementation "org.koin:koin-android:$koin"
+ implementation "org.koin:koin-androidx-viewmodel:$koin"
+ implementation "org.koin:koin-androidx-scope:$koin"
//Retrofit
- implementation 'com.squareup.retrofit2:retrofit:2.6.1'
- implementation 'com.squareup.okhttp3:logging-interceptor:3.11.0'
-
- // Gson
- implementation 'com.squareup.retrofit2:converter-gson:2.6.1'
+ implementation "com.squareup.retrofit2:retrofit:$retrofit"
+ implementation "com.squareup.retrofit2:converter-gson:$retrofit"
+ implementation "com.squareup.okhttp3:logging-interceptor:$okhttp"
//Glide
- implementation 'com.github.bumptech.glide:glide:4.11.0'
- kapt 'com.github.bumptech.glide:compiler:4.11.0'
+ implementation "com.github.bumptech.glide:glide:$glide"
+ kapt "com.github.bumptech.glide:compiler:$glide"
//Hawk
- implementation 'com.orhanobut:hawk:2.0.1'
+ implementation "com.orhanobut:hawk:$hawk"
//Timber
- implementation 'com.jakewharton.timber:timber:4.7.1'
+ implementation "com.jakewharton.timber:timber:$timber"
//Calligraphy
- implementation 'io.github.inflationx:calligraphy3:3.1.1'
- implementation 'io.github.inflationx:viewpump:2.0.3'
+ implementation "io.github.inflationx:calligraphy3:$calligraphy"
+ implementation "io.github.inflationx:viewpump:$viewPump"
+
+ implementation "androidx.room:room-runtime:$room_version"
+ kapt "androidx.room:room-compiler:$room_version"
+ implementation "androidx.room:room-ktx:$room_version"
+
}
\ No newline at end of file
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 4c5e3c3..fe7b0ae 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -6,13 +6,12 @@
+ android:theme="@style/AppTheme.FullScreen">
diff --git a/app/src/main/java/com/base_android_template/App.kt b/app/src/main/java/com/base_android_template/App.kt
index c883b18..7ebce55 100644
--- a/app/src/main/java/com/base_android_template/App.kt
+++ b/app/src/main/java/com/base_android_template/App.kt
@@ -3,6 +3,7 @@ package com.base_android_template
import android.app.Application
import android.content.res.Configuration
import com.base_android_template.di.getAppModules
+import com.base_android_template.shared.HawkKeys
import com.base_android_template.shared.Locales
import com.base_android_template.utils.language.LocaleUtils
import com.orhanobut.hawk.Hawk
@@ -22,10 +23,10 @@ class App : Application() {
modules(getAppModules())
}
- initLocale()
-
Hawk.init(this).build()
+ initLocale()
+
initCalligraphy()
}
@@ -35,7 +36,7 @@ class App : Application() {
}
private fun initLocale() {
- val locale = Locale(Locales.ENGLISH)
+ val locale = Locale(Hawk.get(HawkKeys.HAWK_PREF_LOCALE, Locales.ENGLISH))
LocaleUtils.setLocale(locale)
LocaleUtils.updateConfig(this, baseContext.resources.configuration)
}
diff --git a/app/src/main/java/com/base_android_template/MainActivity.kt b/app/src/main/java/com/base_android_template/MainActivity.kt
index 964202b..0f16de5 100644
--- a/app/src/main/java/com/base_android_template/MainActivity.kt
+++ b/app/src/main/java/com/base_android_template/MainActivity.kt
@@ -1,12 +1,11 @@
package com.base_android_template
import android.os.Bundle
-import android.view.Menu
-import android.view.MenuItem
import androidx.appcompat.app.AppCompatActivity
-import com.base_android_template.shared.Locales
+import androidx.navigation.fragment.NavHostFragment
+import androidx.navigation.ui.NavigationUI
import com.base_android_template.utils.language.LocaleUtils
-import java.util.Locale
+import com.google.android.material.bottomnavigation.BottomNavigationView
class MainActivity : AppCompatActivity() {
@@ -18,26 +17,20 @@ class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
- }
- override fun onCreateOptionsMenu(menu: Menu?): Boolean {
- menuInflater.inflate(R.menu.language_menu, menu)
- return true
+ setUpNavigation()
}
- override fun onOptionsItemSelected(item: MenuItem): Boolean {
- return when (item.itemId) {
- R.id.english -> {
- LocaleUtils.setLocale(Locale(Locales.ENGLISH))
- recreate()
- true
- }
- R.id.romanian -> {
- LocaleUtils.setLocale(Locale(Locales.ROMANIAN))
- recreate()
- true
- }
- else -> super.onOptionsItemSelected(item)
+ private fun setUpNavigation() {
+ val bottomNavigationView = findViewById(R.id.bottom_navigation)
+ val navHostFragment = supportFragmentManager
+ .findFragmentById(R.id.main_nav_host_fragment) as NavHostFragment?
+ navHostFragment?.navController?.let {
+ NavigationUI.setupWithNavController(
+ bottomNavigationView,
+ it
+ )
}
}
+
}
\ No newline at end of file
diff --git a/app/src/main/java/com/base_android_template/api/GithubUsersApi.kt b/app/src/main/java/com/base_android_template/api/GithubUsersApi.kt
deleted file mode 100644
index ff6b8d4..0000000
--- a/app/src/main/java/com/base_android_template/api/GithubUsersApi.kt
+++ /dev/null
@@ -1,11 +0,0 @@
-package com.base_android_template.api
-
-import com.base_android_template.model.response.GithubUserResponse
-import com.base_android_template.shared.network.ApiResponse
-import retrofit2.http.GET
-
-interface GithubUsersApi {
-
- @GET("/users")
- suspend fun getGithubUsers(): ApiResponse, Error>
-}
\ No newline at end of file
diff --git a/app/src/main/java/com/base_android_template/base/BaseActivity.kt b/app/src/main/java/com/base_android_template/base/BaseActivity.kt
deleted file mode 100644
index 347ada5..0000000
--- a/app/src/main/java/com/base_android_template/base/BaseActivity.kt
+++ /dev/null
@@ -1,6 +0,0 @@
-package com.base_android_template.base
-
-import androidx.appcompat.app.AppCompatActivity
-
-class BaseActivity : AppCompatActivity() {
-}
\ No newline at end of file
diff --git a/app/src/main/java/com/base_android_template/base/BaseFragment.kt b/app/src/main/java/com/base_android_template/base/BaseFragment.kt
index 72f6c67..0bb9bd7 100644
--- a/app/src/main/java/com/base_android_template/base/BaseFragment.kt
+++ b/app/src/main/java/com/base_android_template/base/BaseFragment.kt
@@ -4,21 +4,51 @@ import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
+import android.widget.Toast
import androidx.annotation.LayoutRes
+import androidx.appcompat.app.AppCompatActivity
import androidx.databinding.DataBindingUtil
import androidx.databinding.ViewDataBinding
import androidx.fragment.app.Fragment
import androidx.lifecycle.Observer
import androidx.navigation.fragment.findNavController
import com.base_android_template.BR
+import com.base_android_template.shared.loading.UILoading
import com.base_android_template.shared.model.NavigationCommand
+import org.koin.android.ext.android.inject
+/**
+ * Represents the base class that will be extended by any Fragment in the app.
+ * It receives the corresponding ViewDataBinding and ViewModel generic types
+ * and the id of the layout to be inflated
+ *
+ * @param layoutResId Int. The id of the layout that defines the structure
+ * for the user interface of the fragment
+ */
abstract class BaseFragment(@LayoutRes private val layoutResId: Int) :
Fragment() {
private var binding: VB? = null
protected abstract val viewModel: VM
+ private val loadingDialog: UILoading by inject()
+ /**
+ * Receives the value from loading LiveData variable inside BaseViewModel
+ * and display the loading progress bar if value is true, and hide it otherwise
+ */
+ private val loadingObserver: Observer = Observer { showLoading ->
+ if (showLoading) {
+ loadingDialog.show()
+ return@Observer
+ }
+
+ loadingDialog.hide()
+ }
+
+ /**
+ * Receives the value from navigationCommand LiveData variable inside BaseViewModel
+ * and, depending on NavigationCommand, proceed the navigation
+ */
private val commandObserver = Observer { command ->
if (command != null) {
when (command) {
@@ -29,10 +59,45 @@ abstract class BaseFragment(@LayoutRes
findNavController().navigate(command.direction)
}
}
- viewModel.clearLastNavigationCommand()
}
}
+ /**
+ * Receives the value from message LiveData variable inside BaseViewModel
+ * and displays the messages in a Toast
+ */
+ private val messageObserver = Observer {
+ (activity as? AppCompatActivity)?.apply {
+ if (it.isNotEmpty()) {
+ Toast.makeText(this, it, Toast.LENGTH_LONG).show()
+ }
+ }
+ }
+
+ /**
+ * Receives the value from messageResId LiveData variable inside BaseViewModel
+ * and get the corresponding string to the id and displays the messages in a Toast
+ */
+ private val messageResIdObserver = Observer {
+ (activity as? AppCompatActivity)?.apply {
+ if (it != -1) {
+ if (getString(it) != "") {
+ Toast.makeText(this, it, Toast.LENGTH_LONG).show()
+ }
+ }
+ }
+ }
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+
+ context?.let { loadingDialog.init(it) }
+ }
+
+ /**
+ * Using DataBindingUtil, inflate the layout by creating the corresponding ViewDataBinding
+ * Set always used "viewModel" DataBinding variable
+ */
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
@@ -49,14 +114,21 @@ abstract class BaseFragment(@LayoutRes
viewModel.apply {
navigationCommand.observe(viewLifecycleOwner, commandObserver)
+ loading.observe(viewLifecycleOwner, loadingObserver)
+ message.observe(viewLifecycleOwner, messageObserver)
+ messageResId.observe(viewLifecycleOwner, messageResIdObserver)
}
}
+ /**
+ * Access the binding object from all subclasses
+ */
protected fun requireBinding(): VB =
binding ?: throw NullPointerException("View is in destroyed state and the Binding is null")
override fun onDestroyView() {
super.onDestroyView()
+ loadingDialog.cancel()
binding = null
}
diff --git a/app/src/main/java/com/base_android_template/base/BaseViewModel.kt b/app/src/main/java/com/base_android_template/base/BaseViewModel.kt
index 8726f74..87dc77e 100644
--- a/app/src/main/java/com/base_android_template/base/BaseViewModel.kt
+++ b/app/src/main/java/com/base_android_template/base/BaseViewModel.kt
@@ -5,23 +5,75 @@ import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.navigation.NavDirections
import com.base_android_template.shared.model.NavigationCommand
+import com.base_android_template.utils.SingleLiveEvent
+/**
+ * Represents the base class that will be extended by any ViewModel in the app.
+ * It contains public methods that can be called from all ViewModels in the hierarchy
+ * to send commands to the BaseFragment
+ *
+ */
open class BaseViewModel : ViewModel() {
- val navigationCommand: LiveData
+ val navigationCommand: SingleLiveEvent
get() = _navigationCommand
+ val loading: SingleLiveEvent
+ get() = _loading
+ val message: SingleLiveEvent
+ get() = _message
+ val messageResId: SingleLiveEvent
+ get() = _messageResId
- private val _navigationCommand = MutableLiveData()
+ private val _navigationCommand = SingleLiveEvent()
+ private val _loading = SingleLiveEvent()
+ private val _message = SingleLiveEvent()
+ private val _messageResId = SingleLiveEvent()
+ /**
+ * Call this method when want to navigate between fragments via NavDirections
+ *
+ * @param direction NavDirections. The generated method for navigation action
+ * that you've defined in the nav graph
+ */
fun postNavigationCommand(direction: NavDirections) {
_navigationCommand.postValue(NavigationCommand.PerformNavAction(direction))
}
+ /**
+ * Call this method when want to navigate up in fragments
+ *
+ */
fun postNavigateUpCommand() {
_navigationCommand.postValue(NavigationCommand.PerformNavUp)
}
- fun clearLastNavigationCommand() {
- _navigationCommand.value = null
+ /**
+ * Call this method when want to display a message in a Toast
+ *
+ * @param toDisplay String. The message to be displayed
+ */
+ fun postMessage(toDisplay: String) {
+ _message.postValue(toDisplay)
}
+
+ /**
+ * Call this method when want to display a message in a Toast
+ *
+ * @param id Int. The id of the string to be displayed
+ */
+ fun postMessageResId(id: Int) {
+ _messageResId.postValue(id)
+ }
+
+ /**
+ * Call this method when want to show/hide the loading progress bar
+ *
+ * @param loading Boolean. If:
+ * - true: show the loading progress bar
+ * - false: hide th loading progress bar
+ */
+ fun postLoading(loading: Boolean) {
+ _loading.postValue(loading)
+ }
+
}
\ No newline at end of file
diff --git a/app/src/main/java/com/base_android_template/di/ApiModule.kt b/app/src/main/java/com/base_android_template/di/ApiModule.kt
index e192b32..bd04cb3 100644
--- a/app/src/main/java/com/base_android_template/di/ApiModule.kt
+++ b/app/src/main/java/com/base_android_template/di/ApiModule.kt
@@ -1,12 +1,19 @@
package com.base_android_template.di
-import com.base_android_template.api.GithubUsersApi
+import com.base_android_template.remote.GithubUsersApi
+import com.base_android_template.remote.GithubUsersListRemote
+import com.base_android_template.remote.GithubUsersListRemoteImpl
import org.koin.dsl.module
import retrofit2.Retrofit
val apiModule = module {
- fun provideUserApi(retrofit: Retrofit): GithubUsersApi {
- return retrofit.create(GithubUsersApi::class.java)
+ fun provideUserApi(retrofit: Retrofit): GithubUsersApi =
+ retrofit.create(GithubUsersApi::class.java)
+
+ single {
+ GithubUsersListRemoteImpl(
+ githubUsersListService = get()
+ )
}
single { provideUserApi(get()) }
diff --git a/app/src/main/java/com/base_android_template/di/AppModule.kt b/app/src/main/java/com/base_android_template/di/AppModule.kt
index 145cc69..a205956 100644
--- a/app/src/main/java/com/base_android_template/di/AppModule.kt
+++ b/app/src/main/java/com/base_android_template/di/AppModule.kt
@@ -1,5 +1,6 @@
package com.base_android_template.di
+import com.base_android_template.feature.settings.settingsModule
import com.base_android_template.feature.github_users.githubUsersModule
-fun getAppModules() = createCoreModules() + githubUsersModule
\ No newline at end of file
+fun getAppModules() = createCoreModules() + githubUsersModule + settingsModule
\ No newline at end of file
diff --git a/app/src/main/java/com/base_android_template/di/CoreModules.kt b/app/src/main/java/com/base_android_template/di/CoreModules.kt
index 311a65e..f76d4a1 100644
--- a/app/src/main/java/com/base_android_template/di/CoreModules.kt
+++ b/app/src/main/java/com/base_android_template/di/CoreModules.kt
@@ -1,9 +1,10 @@
package com.base_android_template.di
import com.base_android_template.shared.BASE_URL
+import com.base_android_template.shared.loading.UILoading
+import com.base_android_template.shared.loading.UILoadingImplementation
import com.base_android_template.shared.provider.PreferencesProvider
import com.base_android_template.shared.provider.PreferencesProviderImpl
-import com.base_android_template.utils.network.NetworkResponseAdapterFactory
import com.google.gson.FieldNamingPolicy
import com.google.gson.GsonBuilder
import okhttp3.OkHttpClient
@@ -27,13 +28,15 @@ val coreModules = module {
factory {
Retrofit.Builder()
.baseUrl(BASE_URL)
- .addCallAdapterFactory(NetworkResponseAdapterFactory())
.addConverterFactory(GsonConverterFactory.create(get()))
.client(get())
.build()
}
factory { PreferencesProviderImpl() }
+
+ factory { UILoadingImplementation() }
}
-fun createCoreModules() = coreModules + apiModule + repositoryModule + useCaseModule
\ No newline at end of file
+fun createCoreModules() =
+ coreModules + apiModule + repositoryModule + useCaseModule + databaseModule
\ No newline at end of file
diff --git a/app/src/main/java/com/base_android_template/di/DatabaseModule.kt b/app/src/main/java/com/base_android_template/di/DatabaseModule.kt
new file mode 100644
index 0000000..7958701
--- /dev/null
+++ b/app/src/main/java/com/base_android_template/di/DatabaseModule.kt
@@ -0,0 +1,14 @@
+package com.base_android_template.di
+
+import android.content.Context
+import androidx.room.Room
+import com.base_android_template.persistance.database.GithubUsersDatabase
+import org.koin.dsl.module
+
+val databaseModule = module {
+ fun provideGithubUsersListDatabase(context: Context): GithubUsersDatabase =
+ Room.databaseBuilder(context, GithubUsersDatabase::class.java, "github_users_database")
+ .build()
+
+ single { provideGithubUsersListDatabase(get()) }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/base_android_template/di/RepositoryModule.kt b/app/src/main/java/com/base_android_template/di/RepositoryModule.kt
index 25fe265..c8cee85 100644
--- a/app/src/main/java/com/base_android_template/di/RepositoryModule.kt
+++ b/app/src/main/java/com/base_android_template/di/RepositoryModule.kt
@@ -5,5 +5,5 @@ import com.base_android_template.repository.GithubUsersRepositoryImpl
import org.koin.dsl.module
val repositoryModule = module {
- factory { GithubUsersRepositoryImpl(githubUsersApi = get()) }
+ single { GithubUsersRepositoryImpl(githubUsersListRemote = get()) }
}
\ No newline at end of file
diff --git a/app/src/main/java/com/base_android_template/di/UseCaseModule.kt b/app/src/main/java/com/base_android_template/di/UseCaseModule.kt
index bbdc795..50fc58b 100644
--- a/app/src/main/java/com/base_android_template/di/UseCaseModule.kt
+++ b/app/src/main/java/com/base_android_template/di/UseCaseModule.kt
@@ -1,9 +1,20 @@
package com.base_android_template.di
+import com.base_android_template.persistance.database.GithubUsersDatabase
+import com.base_android_template.repository.GithubUsersRepository
import com.base_android_template.usecase.GetGithubUsersUseCase
import com.base_android_template.usecase.GetGithubUsersUseCaseImpl
import org.koin.dsl.module
val useCaseModule = module {
- factory { GetGithubUsersUseCaseImpl(githubUsersRepository = get()) }
+
+ fun getGithubUsersUseCase(
+ githubUsersRepository: GithubUsersRepository,
+ githubUsersDatabase: GithubUsersDatabase,
+ ): GetGithubUsersUseCase = GetGithubUsersUseCaseImpl(
+ githubUsersRepository = githubUsersRepository,
+ githubUsersListDao = githubUsersDatabase.githubUsersListDao()
+ )
+
+ factory { getGithubUsersUseCase(get(), get()) }
}
\ No newline at end of file
diff --git a/app/src/main/java/com/base_android_template/feature/github_users/GithubUsersCallback.kt b/app/src/main/java/com/base_android_template/feature/github_users/GithubUsersCallback.kt
index 13653f1..62ee87e 100644
--- a/app/src/main/java/com/base_android_template/feature/github_users/GithubUsersCallback.kt
+++ b/app/src/main/java/com/base_android_template/feature/github_users/GithubUsersCallback.kt
@@ -1,17 +1,17 @@
package com.base_android_template.feature.github_users
import androidx.recyclerview.widget.DiffUtil
-import com.base_android_template.model.response.GithubUserResponse
+import com.base_android_template.model.entity.GithubUserEntity
-class GithubUsersCallback : DiffUtil.ItemCallback() {
+class GithubUsersCallback : DiffUtil.ItemCallback() {
override fun areItemsTheSame(
- oldItem: GithubUserResponse,
- newItem: GithubUserResponse
+ oldItem: GithubUserEntity,
+ newItem: GithubUserEntity
): Boolean = oldItem.id == newItem.id
override fun areContentsTheSame(
- oldItem: GithubUserResponse,
- newItem: GithubUserResponse
+ oldItem: GithubUserEntity,
+ newItem: GithubUserEntity
): Boolean = oldItem == newItem
}
\ No newline at end of file
diff --git a/app/src/main/java/com/base_android_template/feature/github_users/GithubUsersFragment.kt b/app/src/main/java/com/base_android_template/feature/github_users/GithubUsersFragment.kt
index 25190e4..a16c5e4 100644
--- a/app/src/main/java/com/base_android_template/feature/github_users/GithubUsersFragment.kt
+++ b/app/src/main/java/com/base_android_template/feature/github_users/GithubUsersFragment.kt
@@ -9,5 +9,4 @@ class GithubUsersFragment :
BaseFragment(R.layout.fragment_github_users) {
override val viewModel: GithubUsersViewModel by viewModel()
-
}
\ No newline at end of file
diff --git a/app/src/main/java/com/base_android_template/feature/github_users/GithubUsersListAdapter.kt b/app/src/main/java/com/base_android_template/feature/github_users/GithubUsersListAdapter.kt
index 42b92af..c077182 100644
--- a/app/src/main/java/com/base_android_template/feature/github_users/GithubUsersListAdapter.kt
+++ b/app/src/main/java/com/base_android_template/feature/github_users/GithubUsersListAdapter.kt
@@ -4,10 +4,10 @@ import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.recyclerview.widget.ListAdapter
import com.base_android_template.databinding.ItemGithubUserBinding
-import com.base_android_template.model.response.GithubUserResponse
+import com.base_android_template.model.entity.GithubUserEntity
class GithubUsersListAdapter :
- ListAdapter(GithubUsersCallback()) {
+ ListAdapter(GithubUsersCallback()) {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): GithubUsersListViewHolder {
val inflater = LayoutInflater.from(parent.context)
diff --git a/app/src/main/java/com/base_android_template/feature/github_users/GithubUsersListViewHolder.kt b/app/src/main/java/com/base_android_template/feature/github_users/GithubUsersListViewHolder.kt
index d378298..7db0150 100644
--- a/app/src/main/java/com/base_android_template/feature/github_users/GithubUsersListViewHolder.kt
+++ b/app/src/main/java/com/base_android_template/feature/github_users/GithubUsersListViewHolder.kt
@@ -2,13 +2,15 @@ package com.base_android_template.feature.github_users
import androidx.recyclerview.widget.RecyclerView
import com.base_android_template.databinding.ItemGithubUserBinding
-import com.base_android_template.model.response.GithubUserResponse
+import com.base_android_template.model.entity.GithubUserEntity
class GithubUsersListViewHolder(private val binding: ItemGithubUserBinding) :
RecyclerView.ViewHolder(binding.root) {
- fun bind(githubUserResponse: GithubUserResponse) {
- binding.item = githubUserResponse
- binding.executePendingBindings()
+ fun bind(githubUserItem: GithubUserEntity) {
+ binding.apply {
+ item = githubUserItem
+ executePendingBindings()
+ }
}
}
\ No newline at end of file
diff --git a/app/src/main/java/com/base_android_template/feature/github_users/GithubUsersViewModel.kt b/app/src/main/java/com/base_android_template/feature/github_users/GithubUsersViewModel.kt
index f524810..f012bf8 100644
--- a/app/src/main/java/com/base_android_template/feature/github_users/GithubUsersViewModel.kt
+++ b/app/src/main/java/com/base_android_template/feature/github_users/GithubUsersViewModel.kt
@@ -1,10 +1,11 @@
package com.base_android_template.feature.github_users
import androidx.lifecycle.viewModelScope
+import com.base_android_template.R
import com.base_android_template.base.BaseViewModel
import com.base_android_template.usecase.GetGithubUsersUseCase
+import com.base_android_template.usecase.GithubUsersError
import kotlinx.coroutines.launch
-import timber.log.Timber
class GithubUsersViewModel(
private val getGithubUsersUseCase: GetGithubUsersUseCase
@@ -14,19 +15,52 @@ class GithubUsersViewModel(
val githubUsersListAdapter = GithubUsersListAdapter()
init {
- getGithubUsers()
+ getLocalCartItems()
}
- private fun getGithubUsers() {
+ private fun getLocalCartItems() {
+ postLoading(true)
viewModelScope.launch {
- getGithubUsersUseCase.getGithubUsers().fold({
- githubUsersListAdapter.submitList(it)
- }, {
- Timber.d(
- GithubUsersViewModel::class.simpleName,
- "Error fetching Github users list"
- )
- })
+ getGithubUsersUseCase.getLocalGithubUsers().fold(
+ {
+ handleException(it)
+ },
+ {
+ githubUsersListAdapter.submitList(it)
+ postLoading(false)
+ }
+ )
+ }
+ }
+
+ private fun getRemoteGithubUsers() {
+ viewModelScope.launch {
+ getGithubUsersUseCase.getRemoteAndSaveLocalGithubUsers().fold(
+ {
+ postLoading(false)
+ handleException(it)
+ },
+ {
+ githubUsersListAdapter.submitList(it)
+ postLoading(false)
+ }
+ )
+ }
+ }
+
+ private fun handleException(githubUsersError: GithubUsersError) {
+ postLoading(false)
+ handleGithubUsersError(githubUsersError)
+ }
+
+ private fun handleGithubUsersError(githubUsersError: GithubUsersError) {
+ when (githubUsersError) {
+ is GithubUsersError.EmptyLocalGithubUsersListException -> {
+ getRemoteGithubUsers()
+ }
+ else -> {
+ postMessageResId(R.string.error_fetching_users_list)
+ }
}
}
}
\ No newline at end of file
diff --git a/app/src/main/java/com/base_android_template/feature/settings/SettingsFieldsState.kt b/app/src/main/java/com/base_android_template/feature/settings/SettingsFieldsState.kt
new file mode 100644
index 0000000..b5b4827
--- /dev/null
+++ b/app/src/main/java/com/base_android_template/feature/settings/SettingsFieldsState.kt
@@ -0,0 +1,18 @@
+package com.base_android_template.feature.settings
+
+import androidx.databinding.BaseObservable
+import androidx.databinding.Bindable
+import com.base_android_template.BR
+
+data class SettingsFieldsState(
+ var _englishChecked: Boolean = false
+) :
+ BaseObservable() {
+
+ var englishChecked: Boolean
+ @Bindable get() = _englishChecked
+ set(value) {
+ _englishChecked = value
+ notifyPropertyChanged(BR.englishChecked)
+ }
+}
diff --git a/app/src/main/java/com/base_android_template/feature/settings/SettingsForm.kt b/app/src/main/java/com/base_android_template/feature/settings/SettingsForm.kt
new file mode 100644
index 0000000..b3ab538
--- /dev/null
+++ b/app/src/main/java/com/base_android_template/feature/settings/SettingsForm.kt
@@ -0,0 +1,6 @@
+package com.base_android_template.feature.settings
+
+class SettingsForm {
+
+ val fields = SettingsFieldsState()
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/base_android_template/feature/settings/SettingsFragment.kt b/app/src/main/java/com/base_android_template/feature/settings/SettingsFragment.kt
new file mode 100644
index 0000000..76dd210
--- /dev/null
+++ b/app/src/main/java/com/base_android_template/feature/settings/SettingsFragment.kt
@@ -0,0 +1,24 @@
+package com.base_android_template.feature.settings
+
+import android.os.Bundle
+import com.base_android_template.R
+import com.base_android_template.base.BaseFragment
+import com.base_android_template.databinding.FragmentSettingsBinding
+import org.koin.androidx.viewmodel.ext.android.viewModel
+
+class SettingsFragment :
+ BaseFragment(R.layout.fragment_settings) {
+
+ override val viewModel: SettingsViewModel by viewModel()
+
+ override fun onActivityCreated(savedInstanceState: Bundle?) {
+ super.onActivityCreated(savedInstanceState)
+
+ viewModel.recreateActivity.observe(viewLifecycleOwner, {
+ if (it) {
+ activity?.recreate()
+ }
+ })
+ }
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/base_android_template/feature/settings/SettingsModule.kt b/app/src/main/java/com/base_android_template/feature/settings/SettingsModule.kt
new file mode 100644
index 0000000..e964f4c
--- /dev/null
+++ b/app/src/main/java/com/base_android_template/feature/settings/SettingsModule.kt
@@ -0,0 +1,8 @@
+package com.base_android_template.feature.settings
+
+import org.koin.androidx.viewmodel.dsl.viewModel
+import org.koin.dsl.module
+
+val settingsModule = module {
+ viewModel { SettingsViewModel(preferencesProvider = get()) }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/base_android_template/feature/settings/SettingsViewModel.kt b/app/src/main/java/com/base_android_template/feature/settings/SettingsViewModel.kt
new file mode 100644
index 0000000..2dd27ee
--- /dev/null
+++ b/app/src/main/java/com/base_android_template/feature/settings/SettingsViewModel.kt
@@ -0,0 +1,40 @@
+package com.base_android_template.feature.settings
+
+import android.widget.RadioGroup
+import androidx.core.view.get
+import androidx.lifecycle.LiveData
+import androidx.lifecycle.MutableLiveData
+import com.base_android_template.base.BaseViewModel
+import com.base_android_template.shared.Locales
+import com.base_android_template.shared.provider.PreferencesProvider
+import com.base_android_template.utils.language.LocaleUtils
+import java.util.Locale
+
+class SettingsViewModel(private val preferencesProvider: PreferencesProvider) : BaseViewModel() {
+
+ val settingsForm = SettingsForm()
+ val recreateActivity: LiveData get() = _recreateActivity
+
+ private val _recreateActivity = MutableLiveData()
+
+ init {
+ settingsForm.fields.englishChecked = preferencesProvider.getPrefLocale() == Locales.ENGLISH
+ }
+
+ val languageRadioGroupListener: RadioGroup.OnCheckedChangeListener =
+ RadioGroup.OnCheckedChangeListener { radioGroup, _ ->
+ when (radioGroup.checkedRadioButtonId) {
+ radioGroup[0].id -> changeLanguage(Locales.ENGLISH)
+ else -> changeLanguage(Locales.ROMANIAN)
+ }
+ }
+
+ private fun changeLanguage(language: String) {
+ LocaleUtils.setLocale(Locale(language))
+ preferencesProvider.setPrefLocale(language)
+ settingsForm.fields.englishChecked = language == Locales.ENGLISH
+ _recreateActivity.value = true
+ _recreateActivity.value = false
+ }
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/base_android_template/model/entity/GithubUserEntity.kt b/app/src/main/java/com/base_android_template/model/entity/GithubUserEntity.kt
new file mode 100644
index 0000000..8db17f6
--- /dev/null
+++ b/app/src/main/java/com/base_android_template/model/entity/GithubUserEntity.kt
@@ -0,0 +1,65 @@
+package com.base_android_template.model.entity
+
+import androidx.room.ColumnInfo
+import androidx.room.Entity
+import androidx.room.Index
+
+@Entity(
+ primaryKeys = ["id"],
+ indices = [Index("id")]
+)
+data class GithubUserEntity(
+ @ColumnInfo(name = "login")
+ val login: String,
+
+ @ColumnInfo(name = "id")
+ val id: Int,
+
+ @ColumnInfo(name = "node_id")
+ val nodeId: String,
+
+ @ColumnInfo(name = "avatar_url")
+ val avatarUrl: String,
+
+ @ColumnInfo(name = "gravatar_id")
+ val grAvatarId: String,
+
+ @ColumnInfo(name = "url")
+ val url: String,
+
+ @ColumnInfo(name = "html_url")
+ val htmlUrl: String,
+
+ @ColumnInfo(name = "followers_url")
+ val followersUrl: String,
+
+ @ColumnInfo(name = "following_url")
+ val followingUrl: String,
+
+ @ColumnInfo(name = "gists_url")
+ val gistsUrl: String,
+
+ @ColumnInfo(name = "starred_url")
+ val starredUrl: String,
+
+ @ColumnInfo(name = "subscriptions_url")
+ val subscriptionsUrl: String,
+
+ @ColumnInfo(name = "organizations_url")
+ val organizationsUrl: String,
+
+ @ColumnInfo(name = "repos_url")
+ val reposUrl: String,
+
+ @ColumnInfo(name = "events_url")
+ val eventsUrl: String,
+
+ @ColumnInfo(name = "received_events_url")
+ val receivedEventsUrl: String,
+
+ @ColumnInfo(name = "type")
+ val type: String,
+
+ @ColumnInfo(name = "site_admin")
+ val siteAdmin: String = ""
+)
\ No newline at end of file
diff --git a/app/src/main/java/com/base_android_template/model/response/GithubUserResponse.kt b/app/src/main/java/com/base_android_template/model/response/GithubUserResponse.kt
index eb04718..12aa628 100644
--- a/app/src/main/java/com/base_android_template/model/response/GithubUserResponse.kt
+++ b/app/src/main/java/com/base_android_template/model/response/GithubUserResponse.kt
@@ -4,55 +4,55 @@ import com.google.gson.annotations.SerializedName
data class GithubUserResponse(
@field:SerializedName("login")
- val login: String,
+ val login: String?,
@field:SerializedName("id")
val id: Int,
@field:SerializedName("node_id")
- val nodeId: String,
+ val nodeId: String?,
@field:SerializedName("avatar_url")
- val avatarUrl: String,
+ val avatarUrl: String?,
@field:SerializedName("gravatar_id")
- val grAvatarId: String,
+ val grAvatarId: String?,
@field:SerializedName("url")
- val url: String,
+ val url: String?,
@field:SerializedName("html_url")
- val htmlUrl: String,
+ val htmlUrl: String?,
@field:SerializedName("followers_url")
- val followersUrl: String,
+ val followersUrl: String?,
@field:SerializedName("following_url")
- val followingUrl: String,
+ val followingUrl: String?,
@field:SerializedName("gists_url")
- val gistsUrl: String,
+ val gistsUrl: String?,
@field:SerializedName("starred_url")
- val starredUrl: String,
+ val starredUrl: String?,
@field:SerializedName("subscriptions_url")
- val subscriptionsUrl: String,
+ val subscriptionsUrl: String?,
@field:SerializedName("organizations_url")
- val organizationsUrl: String,
+ val organizationsUrl: String?,
@field:SerializedName("repos_url")
- val reposUrl: String,
+ val reposUrl: String?,
@field:SerializedName("events_url")
- val eventsUrl: String,
+ val eventsUrl: String?,
@field:SerializedName("received_events_url")
- val receivedEventsUrl: String,
+ val receivedEventsUrl: String?,
@field:SerializedName("type")
- val type: String,
+ val type: String?,
@field:SerializedName("site_admin")
val siteAdmin: String
diff --git a/app/src/main/java/com/base_android_template/persistance/dao/GithubUsersListDao.kt b/app/src/main/java/com/base_android_template/persistance/dao/GithubUsersListDao.kt
new file mode 100644
index 0000000..7177986
--- /dev/null
+++ b/app/src/main/java/com/base_android_template/persistance/dao/GithubUsersListDao.kt
@@ -0,0 +1,20 @@
+package com.base_android_template.persistance.dao
+
+import androidx.room.Dao
+import androidx.room.Insert
+import androidx.room.OnConflictStrategy
+import androidx.room.Query
+import com.base_android_template.model.entity.GithubUserEntity
+
+@Dao
+interface GithubUsersListDao {
+
+ @Insert(onConflict = OnConflictStrategy.REPLACE)
+ suspend fun insertGithubUsers(usersList: List)
+
+ @Query("SELECT * FROM GithubUserEntity")
+ suspend fun getGithubUsers(): List?
+
+ @Query("DELETE FROM GithubUserEntity")
+ suspend fun deleteGithubUsers()
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/base_android_template/persistance/database/GithubUsersDatabase.kt b/app/src/main/java/com/base_android_template/persistance/database/GithubUsersDatabase.kt
new file mode 100644
index 0000000..40cee2a
--- /dev/null
+++ b/app/src/main/java/com/base_android_template/persistance/database/GithubUsersDatabase.kt
@@ -0,0 +1,12 @@
+package com.base_android_template.persistance.database
+
+import androidx.room.Database
+import androidx.room.RoomDatabase
+import com.base_android_template.model.entity.GithubUserEntity
+import com.base_android_template.persistance.dao.GithubUsersListDao
+
+@Database(entities = [GithubUserEntity::class], version = 1)
+abstract class GithubUsersDatabase : RoomDatabase() {
+
+ abstract fun githubUsersListDao(): GithubUsersListDao
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/base_android_template/remote/GithubUsersApi.kt b/app/src/main/java/com/base_android_template/remote/GithubUsersApi.kt
new file mode 100644
index 0000000..adca36e
--- /dev/null
+++ b/app/src/main/java/com/base_android_template/remote/GithubUsersApi.kt
@@ -0,0 +1,11 @@
+package com.base_android_template.remote
+
+import com.base_android_template.model.response.GithubUserResponse
+import retrofit2.Response
+import retrofit2.http.GET
+
+interface GithubUsersApi {
+
+ @GET("/users")
+ suspend fun getGithubUsers(): Response>
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/base_android_template/remote/GithubUsersListRemote.kt b/app/src/main/java/com/base_android_template/remote/GithubUsersListRemote.kt
new file mode 100644
index 0000000..96d8087
--- /dev/null
+++ b/app/src/main/java/com/base_android_template/remote/GithubUsersListRemote.kt
@@ -0,0 +1,11 @@
+package com.base_android_template.remote
+
+import com.base_android_template.model.response.GithubUserResponse
+import com.base_android_template.utils.Either
+import com.base_android_template.usecase.GithubUsersError
+
+interface GithubUsersListRemote {
+
+ suspend fun getGithubUsersList(): Either>
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/base_android_template/remote/GithubUsersListRemoteImpl.kt b/app/src/main/java/com/base_android_template/remote/GithubUsersListRemoteImpl.kt
new file mode 100644
index 0000000..46d4802
--- /dev/null
+++ b/app/src/main/java/com/base_android_template/remote/GithubUsersListRemoteImpl.kt
@@ -0,0 +1,39 @@
+package com.base_android_template.remote
+
+import com.base_android_template.model.response.GithubUserResponse
+import com.base_android_template.usecase.GithubUsersError
+import com.base_android_template.utils.Either
+import retrofit2.Response
+import java.io.IOException
+
+class GithubUsersListRemoteImpl(
+ private val githubUsersListService: GithubUsersApi
+) : GithubUsersListRemote {
+
+ override suspend fun getGithubUsersList(): Either> =
+ makeRequest {
+ githubUsersListService.getGithubUsers()
+ }
+
+ private suspend fun makeRequest(block: suspend () -> Response>): Either> {
+ return try {
+ val response = block.invoke()
+ handleResponse(response)
+ } catch (exception: IOException) {
+ Either.Failure(GithubUsersError.GeneralError)
+ }
+ }
+
+ private fun handleResponse(response: Response>): Either> {
+ return if (response.isSuccessful) {
+ handleSuccess(response)
+ } else {
+ Either.Failure(GithubUsersError.GeneralError)
+ }
+ }
+
+ private fun handleSuccess(response: Response>): Either.Success> {
+ val githubUsersList = response.body() ?: listOf()
+ return Either.Success(githubUsersList)
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/base_android_template/repository/GithubUsersRepository.kt b/app/src/main/java/com/base_android_template/repository/GithubUsersRepository.kt
index 87a288c..487eb58 100644
--- a/app/src/main/java/com/base_android_template/repository/GithubUsersRepository.kt
+++ b/app/src/main/java/com/base_android_template/repository/GithubUsersRepository.kt
@@ -1,9 +1,10 @@
package com.base_android_template.repository
import com.base_android_template.model.response.GithubUserResponse
-import com.base_android_template.shared.network.ApiResponse
+import com.base_android_template.utils.Either
+import com.base_android_template.usecase.GithubUsersError
interface GithubUsersRepository {
- suspend fun getGithubUsers(): ApiResponse, Error>
+ suspend fun getLocalGithubUsers(): Either>
}
\ No newline at end of file
diff --git a/app/src/main/java/com/base_android_template/repository/GithubUsersRepositoryImpl.kt b/app/src/main/java/com/base_android_template/repository/GithubUsersRepositoryImpl.kt
index ddf8ab6..8f3b968 100644
--- a/app/src/main/java/com/base_android_template/repository/GithubUsersRepositoryImpl.kt
+++ b/app/src/main/java/com/base_android_template/repository/GithubUsersRepositoryImpl.kt
@@ -1,13 +1,15 @@
package com.base_android_template.repository
-import com.base_android_template.api.GithubUsersApi
import com.base_android_template.model.response.GithubUserResponse
-import com.base_android_template.shared.network.ApiResponse
+import com.base_android_template.remote.GithubUsersListRemote
+import com.base_android_template.utils.Either
+import com.base_android_template.usecase.GithubUsersError
class GithubUsersRepositoryImpl(
- private val githubUsersApi: GithubUsersApi
+ private val githubUsersListRemote: GithubUsersListRemote
) :
GithubUsersRepository {
- override suspend fun getGithubUsers(): ApiResponse, Error> = githubUsersApi.getGithubUsers()
+ override suspend fun getLocalGithubUsers(): Either> =
+ githubUsersListRemote.getGithubUsersList()
}
\ No newline at end of file
diff --git a/app/src/main/java/com/base_android_template/shared/loading/UILoading.kt b/app/src/main/java/com/base_android_template/shared/loading/UILoading.kt
new file mode 100644
index 0000000..bbbb4ee
--- /dev/null
+++ b/app/src/main/java/com/base_android_template/shared/loading/UILoading.kt
@@ -0,0 +1,10 @@
+package com.base_android_template.shared.loading
+
+import android.content.Context
+
+interface UILoading {
+ fun init(context: Context)
+ fun show()
+ fun hide()
+ fun cancel()
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/base_android_template/shared/loading/UILoadingImpl.kt b/app/src/main/java/com/base_android_template/shared/loading/UILoadingImpl.kt
new file mode 100644
index 0000000..8cb698a
--- /dev/null
+++ b/app/src/main/java/com/base_android_template/shared/loading/UILoadingImpl.kt
@@ -0,0 +1,57 @@
+package com.base_android_template.shared.loading
+
+import android.app.Dialog
+import android.content.Context
+import android.graphics.Color
+import android.graphics.drawable.ColorDrawable
+import android.view.Window
+import com.base_android_template.R
+import timber.log.Timber
+
+class UILoadingImplementation : UILoading {
+
+ private lateinit var dialog: Dialog
+
+ override fun init(context: Context) {
+ buildDialog(context)
+ }
+
+ override fun show() {
+ try {
+ dialog.show()
+ } catch (e: Exception) {
+ Timber.e(e)
+ }
+ }
+
+ private fun buildDialog(context: Context) {
+ dialog = Dialog(context)
+
+ dialog.apply {
+ requestWindowFeature(Window.FEATURE_NO_TITLE)
+ setContentView(R.layout.dialog_loading)
+ setCancelable(false)
+ window?.setBackgroundDrawable(ColorDrawable(Color.TRANSPARENT))
+ }
+ }
+
+ override fun hide() {
+ if (!::dialog.isInitialized)
+ return
+
+ if (dialog.isShowing) {
+ try {
+ dialog.hide()
+ } catch (e: IllegalStateException) {
+ Timber.e(e)
+ }
+ }
+ }
+
+ override fun cancel() {
+ if (!::dialog.isInitialized)
+ return
+
+ dialog.cancel()
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/base_android_template/shared/model/NavigationCommand.kt b/app/src/main/java/com/base_android_template/shared/model/NavigationCommand.kt
index a219377..d37d393 100644
--- a/app/src/main/java/com/base_android_template/shared/model/NavigationCommand.kt
+++ b/app/src/main/java/com/base_android_template/shared/model/NavigationCommand.kt
@@ -3,6 +3,8 @@ package com.base_android_template.shared.model
import androidx.navigation.NavDirections
sealed class NavigationCommand {
+
object PerformNavUp : NavigationCommand()
+
data class PerformNavAction(val direction: NavDirections) : NavigationCommand()
}
\ No newline at end of file
diff --git a/app/src/main/java/com/base_android_template/shared/network/ApiResponse.kt b/app/src/main/java/com/base_android_template/shared/network/ApiResponse.kt
deleted file mode 100644
index 01285b4..0000000
--- a/app/src/main/java/com/base_android_template/shared/network/ApiResponse.kt
+++ /dev/null
@@ -1,36 +0,0 @@
-package com.base_android_template.shared.network
-
-import java.io.IOException
-
-sealed class ApiResponse {
- /**
- * Success response with body
- */
- data class Success(val body: T?) : ApiResponse()
-
- /**
- * Failure response with body
- */
- data class ServerError(val body: U, val code: Int) : ApiResponse()
-
- /**
- * Network error
- */
- data class NetworkError(val error: IOException) : ApiResponse()
-
- /**
- * For example, json parsing error
- */
- data class UnknownError(val error: Throwable?) : ApiResponse()
-
- fun fold(
- funSuccess: (T?) -> Any,
- funFailure: (Error) -> Any
- ): Any =
- when (this) {
- is Success -> funSuccess(body)
- is ServerError -> funFailure(Error.ServerError(body))
- is NetworkError -> funFailure(Error.NetworkError(error))
- is UnknownError -> funFailure(Error.UnknownError(error))
- }
-}
\ No newline at end of file
diff --git a/app/src/main/java/com/base_android_template/shared/network/Error.kt b/app/src/main/java/com/base_android_template/shared/network/Error.kt
deleted file mode 100644
index 86f05a7..0000000
--- a/app/src/main/java/com/base_android_template/shared/network/Error.kt
+++ /dev/null
@@ -1,12 +0,0 @@
-package com.base_android_template.shared.network
-
-import java.io.IOException
-
-sealed class Error {
- data class ServerError(val errorResponse: U) : Error()
-
- data class NetworkError(val error: IOException) : Error()
-
- data class UnknownError(val exception: Throwable?) : Error()
-}
-
diff --git a/app/src/main/java/com/base_android_template/usecase/GetGithubUsersUseCase.kt b/app/src/main/java/com/base_android_template/usecase/GetGithubUsersUseCase.kt
index 17fc1bd..0908ea2 100644
--- a/app/src/main/java/com/base_android_template/usecase/GetGithubUsersUseCase.kt
+++ b/app/src/main/java/com/base_android_template/usecase/GetGithubUsersUseCase.kt
@@ -1,9 +1,11 @@
package com.base_android_template.usecase
-import com.base_android_template.model.response.GithubUserResponse
-import com.base_android_template.shared.network.ApiResponse
+import com.base_android_template.model.entity.GithubUserEntity
+import com.base_android_template.utils.Either
interface GetGithubUsersUseCase {
- suspend fun getGithubUsers(): ApiResponse, Error>
+ suspend fun getRemoteAndSaveLocalGithubUsers(): Either>
+
+ suspend fun getLocalGithubUsers(): Either>
}
\ No newline at end of file
diff --git a/app/src/main/java/com/base_android_template/usecase/GetGithubUsersUseCaseImpl.kt b/app/src/main/java/com/base_android_template/usecase/GetGithubUsersUseCaseImpl.kt
index 41b392f..071bd5d 100644
--- a/app/src/main/java/com/base_android_template/usecase/GetGithubUsersUseCaseImpl.kt
+++ b/app/src/main/java/com/base_android_template/usecase/GetGithubUsersUseCaseImpl.kt
@@ -1,13 +1,41 @@
package com.base_android_template.usecase
-import com.base_android_template.model.response.GithubUserResponse
+import com.base_android_template.model.entity.GithubUserEntity
+import com.base_android_template.persistance.dao.GithubUsersListDao
import com.base_android_template.repository.GithubUsersRepository
-import com.base_android_template.shared.network.ApiResponse
+import com.base_android_template.utils.Either
+import com.base_android_template.utils.doOnSuccess
+import com.base_android_template.utils.extensions.mapToGithubUserEntity
+import com.base_android_template.utils.map
+import kotlinx.coroutines.coroutineScope
+import kotlinx.coroutines.launch
class GetGithubUsersUseCaseImpl internal constructor(
- private val githubUsersRepository: GithubUsersRepository
+ private val githubUsersRepository: GithubUsersRepository,
+ private val githubUsersListDao: GithubUsersListDao
) : GetGithubUsersUseCase {
- override suspend fun getGithubUsers(): ApiResponse, Error> =
- githubUsersRepository.getGithubUsers()
+ override suspend fun getRemoteAndSaveLocalGithubUsers(): Either> =
+ coroutineScope {
+ githubUsersRepository.getLocalGithubUsers()
+ .map { githubUsersList ->
+ githubUsersList.map { it.mapToGithubUserEntity() }
+ }
+ .doOnSuccess {
+ launch { updateLocalGithubUsersList(it) }
+ }
+ }
+
+ override suspend fun getLocalGithubUsers(): Either> {
+ val list = githubUsersListDao.getGithubUsers()
+
+ return if (list?.isNullOrEmpty() == true)
+ Either.Failure(GithubUsersError.EmptyLocalGithubUsersListException)
+ else Either.Success(list)
+ }
+
+ private suspend fun updateLocalGithubUsersList(list: List) {
+ githubUsersListDao.insertGithubUsers(list)
+ }
+
}
\ No newline at end of file
diff --git a/app/src/main/java/com/base_android_template/usecase/GithubUsersError.kt b/app/src/main/java/com/base_android_template/usecase/GithubUsersError.kt
new file mode 100644
index 0000000..ea53842
--- /dev/null
+++ b/app/src/main/java/com/base_android_template/usecase/GithubUsersError.kt
@@ -0,0 +1,6 @@
+package com.base_android_template.usecase
+
+sealed class GithubUsersError {
+ object EmptyLocalGithubUsersListException : GithubUsersError()
+ object GeneralError :GithubUsersError()
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/base_android_template/utils/Either.kt b/app/src/main/java/com/base_android_template/utils/Either.kt
new file mode 100644
index 0000000..b474071
--- /dev/null
+++ b/app/src/main/java/com/base_android_template/utils/Either.kt
@@ -0,0 +1,101 @@
+package com.base_android_template.utils
+
+/**
+ * Represents a value of one of two possible types (a disjoint union).
+ * Instances of [Either] are either an instance of [Failure] or [Success].
+ * FP Convention dictates that [Failure] is used for "failure"
+ * and [Success] is used for "success".
+ *
+ * @see Failure
+ * @see Success
+ * @see Credits to Fernando Cejas.
+ */
+sealed class Either {
+ /** * Represents the left side of [Either] class which by convention is a "Exception". */
+ data class Failure(val error: L) : Either()
+
+ /** * Represents the right side of [Either] class which by convention is a "Success". */
+ data class Success(val data: R) : Either()
+
+ /**
+ * Returns true if this is a Success, false otherwise.
+ * @see Success
+ */
+ val isSuccess get() = this is Success
+
+ /**
+ * Returns true if this is a Failure, false otherwise.
+ * @see Failure
+ */
+ val isFailure get() = this is Failure
+
+ /**
+ * Creates a Failure type.
+ * @see Failure
+ */
+ fun failure(a: L) = Failure(a)
+
+ /**
+ * Creates a Success type.
+ * @see Success
+ */
+ fun success(b: R) = Success(b)
+
+ /**
+ * Applies funFailure if this is a Failure or funSuccess if this is a Success.
+ * @see Failure
+ * @see Success
+ */
+ fun fold(funFailure: (L) -> Any, funSuccess: (R) -> Any): Any =
+ when (this) {
+ is Failure -> funFailure(error)
+ is Success -> funSuccess(data)
+ }
+}
+
+/**
+ * Composes 2 functions
+ * See Credits to Alex Hart.
+ */
+fun ((A) -> B).compose(function: (B) -> C): (A) -> C = {
+ function(this(it))
+}
+
+/**
+ * Success-biased flatMap() FP convention which means that Success is assumed to be the default case
+ * to operate on. If it is Failure, operations like map, flatMap, ... return the Failure value unchanged.
+ */
+fun Either.flatMap(function: (R) -> Either): Either =
+ when (this) {
+ is Either.Failure -> Either.Failure(error)
+ is Either.Success -> function(data)
+ }
+
+/**
+ * Success-biased map() FP convention which means that Success is assumed to be the default case
+ * to operate on. If it is Failure, operations like map, flatMap, ... return the Failure value unchanged.
+ */
+fun Either.map(function: (R) -> (T)): Either =
+ this.flatMap(function.compose(::success))
+
+/** Returns the value from this `Success` or the given argument if this is a `Failure`.
+ * Right(12).getOrElse(17) RETURNS 12 and Left(12).getOrElse(17) RETURNS 17.
+ * Just for unit tests
+ */
+fun Either.getOrElse(value: R): R =
+ when (this) {
+ is Either.Failure -> value
+ is Either.Success -> data
+ }
+
+/** Invokes function if Either is success.
+ * Returns itself.
+ */
+fun Either.doOnSuccess(block: (R) -> Any): Either =
+ when (this) {
+ is Either.Failure -> Either.Failure(error)
+ is Either.Success -> {
+ block(data)
+ Either.Success(data)
+ }
+ }
\ No newline at end of file
diff --git a/app/src/main/java/com/base_android_template/utils/SingleLiveEvent.kt b/app/src/main/java/com/base_android_template/utils/SingleLiveEvent.kt
new file mode 100644
index 0000000..53f2258
--- /dev/null
+++ b/app/src/main/java/com/base_android_template/utils/SingleLiveEvent.kt
@@ -0,0 +1,68 @@
+package com.base_android_template.utils
+
+/*
+ * Copyright 2017 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import android.util.Log
+import androidx.annotation.MainThread
+import androidx.lifecycle.LifecycleOwner
+import androidx.lifecycle.MutableLiveData
+import androidx.lifecycle.Observer
+import java.util.concurrent.atomic.AtomicBoolean
+
+/**
+ * A lifecycle-aware observable that sends only new updates after subscription, used for events like
+ * navigation and Snackbar messages.
+ *
+ *
+ * This avoids a common problem with events: on configuration change (like rotation) an update
+ * can be emitted if the observer is active. This LiveData only calls the observable if there's an
+ * explicit call to setValue() or call().
+ *
+ *
+ * Note that only one observer is going to be notified of changes.
+ */
+class SingleLiveEvent : MutableLiveData() {
+ private val pending = AtomicBoolean(false)
+ @MainThread
+ override fun observe(owner: LifecycleOwner, observer: Observer) {
+ if (hasActiveObservers()) {
+ Log.w(TAG, "Multiple observers registered but only one will be notified of changes.")
+ }
+ // Observe the internal MutableLiveData
+ super.observe(owner, Observer { t ->
+ if (pending.compareAndSet(true, false)) {
+ observer.onChanged(t)
+ }
+ })
+ }
+
+ @MainThread
+ override fun setValue(t: T?) {
+ pending.set(true)
+ super.setValue(t)
+ }
+ /**
+ * Used for cases where T is Void, to make calls cleaner.
+ */
+ @MainThread
+ fun call() {
+ value = null
+ }
+ companion object {
+ private val TAG = "SingleLiveEvent"
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/base_android_template/utils/binding_adapter/BindingAdapters.kt b/app/src/main/java/com/base_android_template/utils/binding_adapter/BindingAdapters.kt
index 8a5e1c2..4d0ae6c 100644
--- a/app/src/main/java/com/base_android_template/utils/binding_adapter/BindingAdapters.kt
+++ b/app/src/main/java/com/base_android_template/utils/binding_adapter/BindingAdapters.kt
@@ -1,6 +1,7 @@
package com.base_android_template.utils.binding_adapter
import android.widget.ImageView
+import android.widget.RadioGroup
import androidx.appcompat.content.res.AppCompatResources.getDrawable
import androidx.databinding.BindingAdapter
import androidx.recyclerview.widget.RecyclerView
@@ -9,8 +10,10 @@ import com.bumptech.glide.Glide
import com.bumptech.glide.load.engine.DiskCacheStrategy
import com.bumptech.glide.request.RequestOptions
-/*
- * Binding adapters are responsible for making the appropriate framework calls to set values for views directly inside layout.
+/**
+ * BindingAdapters class is responsible for making the appropriate framework calls to
+ * set values for views directly inside layout.
+ *
*/
object BindingAdapters {
@@ -51,4 +54,21 @@ object BindingAdapters {
.apply(options)
.into(imageView)
}
+
+ /**
+ * Set the RadioGroup.OnCheckedChangeListener on RadioGroup view
+ *
+ * @param radioGroup RadioGroup. The RadioGroup to which the OnCheckedChangeListener
+ * will be attached
+ * @param listener RadioGroup.OnCheckedChangeListener. The listener that will be
+ * invoked when the checked radio button changed in this group
+ */
+ @BindingAdapter("app:setRadioGroupListener")
+ @JvmStatic
+ fun setRadioGroupListener(
+ radioGroup: RadioGroup,
+ listener: RadioGroup.OnCheckedChangeListener
+ ) {
+ radioGroup.setOnCheckedChangeListener(listener)
+ }
}
\ No newline at end of file
diff --git a/app/src/main/java/com/base_android_template/utils/extensions/GithubUserResponseExtesion.kt b/app/src/main/java/com/base_android_template/utils/extensions/GithubUserResponseExtesion.kt
new file mode 100644
index 0000000..ce33efc
--- /dev/null
+++ b/app/src/main/java/com/base_android_template/utils/extensions/GithubUserResponseExtesion.kt
@@ -0,0 +1,26 @@
+package com.base_android_template.utils.extensions
+
+import com.base_android_template.model.entity.GithubUserEntity
+import com.base_android_template.model.response.GithubUserResponse
+
+fun GithubUserResponse.mapToGithubUserEntity(): GithubUserEntity =
+ GithubUserEntity(
+ login = this.login.orEmpty(),
+ id = this.id,
+ nodeId = this.nodeId.orEmpty(),
+ avatarUrl = this.avatarUrl.orEmpty(),
+ grAvatarId = this.grAvatarId.orEmpty(),
+ url = this.url.orEmpty(),
+ htmlUrl = this.htmlUrl.orEmpty(),
+ followersUrl = this.followersUrl.orEmpty(),
+ followingUrl = this.followingUrl.orEmpty(),
+ gistsUrl = this.gistsUrl.orEmpty(),
+ starredUrl = this.starredUrl.orEmpty(),
+ subscriptionsUrl = this.subscriptionsUrl.orEmpty(),
+ organizationsUrl = this.organizationsUrl.orEmpty(),
+ reposUrl = this.reposUrl.orEmpty(),
+ eventsUrl = this.eventsUrl.orEmpty(),
+ receivedEventsUrl = this.receivedEventsUrl.orEmpty(),
+ type = this.type.orEmpty(),
+ siteAdmin = this.siteAdmin
+ )
\ No newline at end of file
diff --git a/app/src/main/java/com/base_android_template/utils/language/LocaleUtils.kt b/app/src/main/java/com/base_android_template/utils/language/LocaleUtils.kt
index 8ba44a7..79d6f78 100644
--- a/app/src/main/java/com/base_android_template/utils/language/LocaleUtils.kt
+++ b/app/src/main/java/com/base_android_template/utils/language/LocaleUtils.kt
@@ -6,7 +6,7 @@ import android.content.res.Resources
import android.view.ContextThemeWrapper
import java.util.Locale
-/*
+/**
* Utility class to change app locale settings.
*/
diff --git a/app/src/main/java/com/base_android_template/utils/network/NetworkResponseAdapter.kt b/app/src/main/java/com/base_android_template/utils/network/NetworkResponseAdapter.kt
deleted file mode 100644
index 19f43c8..0000000
--- a/app/src/main/java/com/base_android_template/utils/network/NetworkResponseAdapter.kt
+++ /dev/null
@@ -1,23 +0,0 @@
-package com.base_android_template.utils.network
-
-import com.base_android_template.shared.network.ApiResponse
-import okhttp3.ResponseBody
-import retrofit2.Call
-import retrofit2.CallAdapter
-import retrofit2.Converter
-import java.lang.reflect.Type
-
-class NetworkResponseAdapter(
- private val successType: Type,
- private val errorBodyConverter: Converter
-) : CallAdapter>> {
-
- override fun responseType(): Type = successType
-
- override fun adapt(call: Call): Call> {
- return NetworkResponseCall(
- call,
- errorBodyConverter
- )
- }
-}
\ No newline at end of file
diff --git a/app/src/main/java/com/base_android_template/utils/network/NetworkResponseAdapterFactory.kt b/app/src/main/java/com/base_android_template/utils/network/NetworkResponseAdapterFactory.kt
deleted file mode 100644
index a4480b6..0000000
--- a/app/src/main/java/com/base_android_template/utils/network/NetworkResponseAdapterFactory.kt
+++ /dev/null
@@ -1,49 +0,0 @@
-package com.base_android_template.utils.network
-
-import com.base_android_template.shared.network.ApiResponse
-import retrofit2.Call
-import retrofit2.CallAdapter
-import retrofit2.Retrofit
-import java.lang.reflect.ParameterizedType
-import java.lang.reflect.Type
-
-class NetworkResponseAdapterFactory : CallAdapter.Factory() {
-
- override fun get(
- returnType: Type,
- annotations: Array,
- retrofit: Retrofit
- ): CallAdapter<*, *>? {
-
- // suspend functions wrap the response type in `Call`
- if (Call::class.java != getRawType(returnType)) {
- return null
- }
-
- // check first that the return type is `ParameterizedType`
- check(returnType is ParameterizedType) {
- "return type must be parameterized as Call> or Call>"
- }
-
- // get the response type inside the `Call` type
- val responseType = getParameterUpperBound(0, returnType)
- // if the response type is not ApiResponse then we can't handle this type, so we return null
- if (getRawType(responseType) != ApiResponse::class.java) {
- return null
- }
-
- // the response type is ApiResponse and should be parameterized
- check(responseType is ParameterizedType) { "Response must be parameterized as NetworkResponse or NetworkResponse" }
-
- val successBodyType = getParameterUpperBound(0, responseType)
- val errorBodyType = getParameterUpperBound(1, responseType)
-
- val errorBodyConverter =
- retrofit.nextResponseBodyConverter(null, errorBodyType, annotations)
-
- return NetworkResponseAdapter(
- successBodyType,
- errorBodyConverter
- )
- }
-}
\ No newline at end of file
diff --git a/app/src/main/java/com/base_android_template/utils/network/NetworkResponseCall.kt b/app/src/main/java/com/base_android_template/utils/network/NetworkResponseCall.kt
deleted file mode 100644
index b54cd49..0000000
--- a/app/src/main/java/com/base_android_template/utils/network/NetworkResponseCall.kt
+++ /dev/null
@@ -1,93 +0,0 @@
-package com.base_android_template.utils.network
-
-import com.base_android_template.shared.network.ApiResponse
-import okhttp3.Request
-import okhttp3.ResponseBody
-import retrofit2.Call
-import retrofit2.Callback
-import retrofit2.Converter
-import retrofit2.Response
-import java.io.EOFException
-import java.io.IOException
-
-internal class NetworkResponseCall(
- private val delegate: Call,
- private val errorConverter: Converter
-) : Call> {
-
- override fun enqueue(callback: Callback>) {
- return delegate.enqueue(object : Callback {
- override fun onResponse(call: Call, response: Response) {
- var error: E? = null
- response.errorBody()?.let {
- error = try {
- errorConverter.convert(it)
- } catch (c: EOFException) {
- null
- }
- }
- val body = response.body()
- val code = response.code()
-
- if (response.isSuccessful) {
- postSuccessfulResponse(body, callback)
- } else {
- postFailedResponse(error, callback, code)
- }
- }
-
- override fun onFailure(call: Call, throwable: Throwable) {
- val networkResponse = when (throwable) {
- is IOException -> ApiResponse.NetworkError(throwable)
- else -> ApiResponse.UnknownError(throwable)
- }
- callback.onResponse(this@NetworkResponseCall, Response.success(networkResponse))
- }
-
- })
- }
-
- private fun postFailedResponse(
- error: E?,
- callback: Callback>,
- code: Int
- ) {
- error?.let {
- callback.onResponse(
- this@NetworkResponseCall,
- Response.success(ApiResponse.ServerError(it, code))
- )
- } ?: callback.onResponse(
- this@NetworkResponseCall,
- Response.success(ApiResponse.UnknownError(null))
- )
- }
-
- private fun postSuccessfulResponse(
- body: S?,
- callback: Callback>
- ) {
- callback.onResponse(
- this@NetworkResponseCall,
- Response.success(ApiResponse.Success(body))
- )
- }
-
- override fun isExecuted() = delegate.isExecuted
-
- override fun clone() =
- NetworkResponseCall(
- delegate.clone(),
- errorConverter
- )
-
- override fun isCanceled() = delegate.isCanceled
-
- override fun cancel() = delegate.cancel()
-
- override fun execute(): Response> {
- throw UnsupportedOperationException("NetworkResponseCall doesn't support execute")
- }
-
- override fun request(): Request = delegate.request()
-}
\ No newline at end of file
diff --git a/app/src/main/res/color/bottom_nav_color.xml b/app/src/main/res/color/bottom_nav_color.xml
new file mode 100644
index 0000000..7eb7368
--- /dev/null
+++ b/app/src/main/res/color/bottom_nav_color.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/drawable/bg_shadow.xml b/app/src/main/res/drawable/bg_shadow.xml
new file mode 100644
index 0000000..4049149
--- /dev/null
+++ b/app/src/main/res/drawable/bg_shadow.xml
@@ -0,0 +1,24 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/drawable/ic_account_circle_24px.xml b/app/src/main/res/drawable/ic_account_circle_24px.xml
new file mode 100644
index 0000000..b845dc3
--- /dev/null
+++ b/app/src/main/res/drawable/ic_account_circle_24px.xml
@@ -0,0 +1,9 @@
+
+
+
diff --git a/app/src/main/res/drawable/ic_english.xml b/app/src/main/res/drawable/ic_english.xml
new file mode 100644
index 0000000..8d7bae4
--- /dev/null
+++ b/app/src/main/res/drawable/ic_english.xml
@@ -0,0 +1,90 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/drawable/ic_romanian.xml b/app/src/main/res/drawable/ic_romanian.xml
new file mode 100644
index 0000000..9a307ad
--- /dev/null
+++ b/app/src/main/res/drawable/ic_romanian.xml
@@ -0,0 +1,24 @@
+
+
+
+
+
diff --git a/app/src/main/res/drawable/ic_settings_24px.xml b/app/src/main/res/drawable/ic_settings_24px.xml
new file mode 100644
index 0000000..282c986
--- /dev/null
+++ b/app/src/main/res/drawable/ic_settings_24px.xml
@@ -0,0 +1,9 @@
+
+
+
diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml
index fdd0605..095d085 100644
--- a/app/src/main/res/layout/activity_main.xml
+++ b/app/src/main/res/layout/activity_main.xml
@@ -21,11 +21,25 @@
android:layout_width="@dimen/match_constraint"
android:layout_height="@dimen/match_constraint"
app:defaultNavHost="true"
+ app:layout_constraintBottom_toTopOf="@+id/bottom_navigation"
+ app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintTop_toTopOf="parent"
+ app:navGraph="@navigation/main_nav_graph" />
+
+
+ app:menu="@menu/bottom_navigation" />
\ No newline at end of file
diff --git a/app/src/main/res/layout/dialog_loading.xml b/app/src/main/res/layout/dialog_loading.xml
new file mode 100644
index 0000000..c7cc744
--- /dev/null
+++ b/app/src/main/res/layout/dialog_loading.xml
@@ -0,0 +1,8 @@
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/fragment_github_users.xml b/app/src/main/res/layout/fragment_github_users.xml
index 80efc03..bc57ee4 100644
--- a/app/src/main/res/layout/fragment_github_users.xml
+++ b/app/src/main/res/layout/fragment_github_users.xml
@@ -1,5 +1,7 @@
-
+
@@ -8,35 +10,80 @@
type="com.base_android_template.feature.github_users.GithubUsersViewModel" />
-
+ android:layout_height="match_parent"
+ app:layoutDescription="@xml/fragment_github_users_motion_scene">
+
+
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintTop_toBottomOf="@+id/welcome_title" />
+
+
+
+
+
+ app:layout_constraintTop_toBottomOf="@+id/top_separator"
+ tools:listitem="@layout/item_github_user" />
-
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/fragment_settings.xml b/app/src/main/res/layout/fragment_settings.xml
new file mode 100644
index 0000000..d9cbe5f
--- /dev/null
+++ b/app/src/main/res/layout/fragment_settings.xml
@@ -0,0 +1,81 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/item_github_user.xml b/app/src/main/res/layout/item_github_user.xml
index 0a4a892..73ca380 100644
--- a/app/src/main/res/layout/item_github_user.xml
+++ b/app/src/main/res/layout/item_github_user.xml
@@ -6,7 +6,7 @@
+ type="com.base_android_template.model.entity.GithubUserEntity" />
+ tools:text="Github User" />
diff --git a/app/src/main/res/menu/bottom_navigation.xml b/app/src/main/res/menu/bottom_navigation.xml
new file mode 100644
index 0000000..85e9296
--- /dev/null
+++ b/app/src/main/res/menu/bottom_navigation.xml
@@ -0,0 +1,15 @@
+
+
+
diff --git a/app/src/main/res/menu/language_menu.xml b/app/src/main/res/menu/language_menu.xml
deleted file mode 100644
index 0f8148b..0000000
--- a/app/src/main/res/menu/language_menu.xml
+++ /dev/null
@@ -1,12 +0,0 @@
-
-
\ No newline at end of file
diff --git a/app/src/main/res/navigation/main_nav_graph.xml b/app/src/main/res/navigation/main_nav_graph.xml
index 8cb6ffd..df2ec3f 100644
--- a/app/src/main/res/navigation/main_nav_graph.xml
+++ b/app/src/main/res/navigation/main_nav_graph.xml
@@ -3,12 +3,21 @@
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/nav_graph"
- app:startDestination="@id/githubUsersFragment">
+ app:startDestination="@id/githubUsers">
+ tools:layout="@layout/fragment_github_users" >
+
+
+
+
diff --git a/app/src/main/res/values-night/themes.xml b/app/src/main/res/values-night/themes.xml
index 685ba4b..ddd3781 100644
--- a/app/src/main/res/values-night/themes.xml
+++ b/app/src/main/res/values-night/themes.xml
@@ -1,12 +1,19 @@
-
-
-
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/values-ro/strings.xml b/app/src/main/res/values-ro/strings.xml
index 81b52b5..e5c75d9 100644
--- a/app/src/main/res/values-ro/strings.xml
+++ b/app/src/main/res/values-ro/strings.xml
@@ -1,6 +1,11 @@
+ Limba
Romana
Engleza
- Lista cu toti utilizatorii Github, in ordinea in care s-au inregistrat pe GitHub. Aceasta lista include conturi personale de utilizator si conturi de organizatie.
+ Useri
+ Setari
+ 👋 Salut, bine ai venit in aplicatia Base Android Template!
+ Pentru a demonstra apelurile catre server si operatiile pe baza de date Room, vom afisa in acest ecran o lista cu 30 de utilizatori Github preluati din baza de date locala. Daca baza de date nu contine utilizatori, lista va fi preluata de la server si apoi va fi salvata in baza de date locala.
+ Ceva nu a functionat! Lista cu utilizatori Github nu a putut fi preluata.
\ No newline at end of file
diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml
index 5d1ad9c..cf54358 100644
--- a/app/src/main/res/values/colors.xml
+++ b/app/src/main/res/values/colors.xml
@@ -1,8 +1,10 @@
- #FF90CAF9
#FF2196F3
#FF1976D2
+
#FF000000
#FFFFFFFF
+ #D8D8D8
+
\ No newline at end of file
diff --git a/app/src/main/res/values/dimens.xml b/app/src/main/res/values/dimens.xml
index c033fa1..9b83971 100644
--- a/app/src/main/res/values/dimens.xml
+++ b/app/src/main/res/values/dimens.xml
@@ -6,8 +6,10 @@
8dp
16dp
+ 24dp
+ 32dp
- 14sp
16sp
+ 24sp
\ No newline at end of file
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 73143c4..45ba5c1 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -1,8 +1,13 @@
Base-Android-Template
+ Language
Romanian
English
- Lists all users, in the order that they signed up on GitHub. This list includes personal user accounts and organization accounts.
+ Users
+ Settings
+ 👋 Hi, welcome to Base Android Template!
+ To prove server calls and Room database operations, we will display in this screen a list of 30 Github users fetched from the local database. If the database does not contain users, the list will be retrieved from the server and then saved in the local database.
+ Something went wrong! The Github users list could not be fetched.
\ No newline at end of file
diff --git a/app/src/main/res/values/themes.xml b/app/src/main/res/values/themes.xml
index 71d2c5e..64609f5 100644
--- a/app/src/main/res/values/themes.xml
+++ b/app/src/main/res/values/themes.xml
@@ -1,12 +1,18 @@
-
-
-
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/xml/fragment_github_users_motion_scene.xml b/app/src/main/res/xml/fragment_github_users_motion_scene.xml
new file mode 100644
index 0000000..beb27ed
--- /dev/null
+++ b/app/src/main/res/xml/fragment_github_users_motion_scene.xml
@@ -0,0 +1,138 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/build.gradle b/build.gradle
index 8076f01..5e77c09 100644
--- a/build.gradle
+++ b/build.gradle
@@ -1,8 +1,23 @@
// Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript {
- ext{
+ ext {
kotlin_version = "1.4.30"
- navigationVersion = "2.3.3"
+ core_ktx = "1.3.2"
+ appcompat = "1.2.0"
+ material = "1.3.0"
+ constraint_layout = "2.0.4"
+ junit = "1.1.2"
+ espresso = "3.3.0"
+ navigationVersion = "2.3.4"
+ room_version = "2.2.6"
+ calligraphy = "3.1.1"
+ viewPump = "2.0.3"
+ timber = "4.7.1"
+ hawk = "2.0.1"
+ glide = "4.11.0"
+ okhttp = "4.9.0"
+ retrofit = "2.7.2"
+ koin = "2.2.2"
}
repositories {