mirror of
https://github.com/melihaksoy/Android-Kotlin-Modulerized-CleanArchitecture.git
synced 2026-03-25 02:41:26 +01:00
Feature/styling (#36)
* Working with material styles * Replaced deprecated ViewModelProviders.of and applied base styles with material components * Various code style fixes and styles.xml improvements
This commit is contained in:
@@ -14,7 +14,7 @@ import javax.inject.Inject
|
||||
* Parent of fragments which has injections. Aim is to seperate [BaseFragment] functionality for fragments which
|
||||
* won't need any injection.
|
||||
*
|
||||
* Note that fragments that extends from [BaseDaggerFragment] should contribute android injector.
|
||||
* Note that fragments that extends from [BaseDaggerFragment] should contribute their injector.
|
||||
*
|
||||
* This class provides [viewModelFactory] which serves as factory for view models
|
||||
* in the project. It's injected by map of view models that this app is serving. Check [ViewModelFactory]
|
||||
@@ -22,16 +22,16 @@ import javax.inject.Inject
|
||||
*/
|
||||
abstract class BaseDaggerFragment<T : ViewDataBinding> : BaseFragment<T>(), HasAndroidInjector {
|
||||
|
||||
// region Properties
|
||||
//region Properties
|
||||
|
||||
@get:Inject
|
||||
internal var androidInjector: DispatchingAndroidInjector<Any>? = null
|
||||
|
||||
@Inject
|
||||
lateinit var viewModelFactory: ViewModelFactory
|
||||
// endregion
|
||||
//endregion
|
||||
|
||||
// region Functions
|
||||
//region Functions
|
||||
|
||||
override fun onAttach(context: Context) {
|
||||
AndroidSupportInjection.inject(this)
|
||||
@@ -39,5 +39,5 @@ abstract class BaseDaggerFragment<T : ViewDataBinding> : BaseFragment<T>(), HasA
|
||||
}
|
||||
|
||||
override fun androidInjector(): AndroidInjector<Any>? = androidInjector
|
||||
// endregion
|
||||
//endregion
|
||||
}
|
||||
|
||||
@@ -20,12 +20,18 @@ import com.melih.repository.interactors.base.Reason
|
||||
*/
|
||||
abstract class BaseFragment<T : ViewDataBinding> : Fragment() {
|
||||
|
||||
// region Properties
|
||||
//region Abstractions
|
||||
|
||||
@LayoutRes
|
||||
abstract fun getLayoutId(): Int
|
||||
//endregion
|
||||
|
||||
//region Properties
|
||||
|
||||
protected lateinit var binding: T
|
||||
// endregion
|
||||
//endregion
|
||||
|
||||
// region Functions
|
||||
//region Functions
|
||||
|
||||
override fun onCreateView(
|
||||
inflater: LayoutInflater,
|
||||
@@ -46,8 +52,5 @@ abstract class BaseFragment<T : ViewDataBinding> : Fragment() {
|
||||
block()
|
||||
}.show()
|
||||
}
|
||||
|
||||
@LayoutRes
|
||||
abstract fun getLayoutId(): Int
|
||||
// endregion
|
||||
//endregion
|
||||
}
|
||||
|
||||
@@ -37,12 +37,12 @@ const val INITIAL_PAGE = 0
|
||||
@UseExperimental(ExperimentalCoroutinesApi::class)
|
||||
abstract class BasePagingDataSource<T> : PageKeyedDataSource<Int, T>() {
|
||||
|
||||
// region Abstractions
|
||||
//region Abstractions
|
||||
|
||||
abstract fun loadDataForPage(page: Int): Flow<Result<List<T>>> // Load next page(s)
|
||||
// endregion
|
||||
//endregion
|
||||
|
||||
// region Properties
|
||||
//region Properties
|
||||
|
||||
private val _stateData = MutableLiveData<State>()
|
||||
private val _reasonData = MutableLiveData<Reason>()
|
||||
@@ -59,9 +59,9 @@ abstract class BasePagingDataSource<T> : PageKeyedDataSource<Int, T>() {
|
||||
*/
|
||||
val reasonData: LiveData<Reason>
|
||||
get() = _reasonData
|
||||
// endregion
|
||||
//endregion
|
||||
|
||||
// region Functions
|
||||
//region Functions
|
||||
|
||||
override fun loadInitial(params: LoadInitialParams<Int>, callback: LoadInitialCallback<Int, T>) {
|
||||
// Looping through channel as we'll receive any state, error or data here
|
||||
@@ -136,5 +136,5 @@ abstract class BasePagingDataSource<T> : PageKeyedDataSource<Int, T>() {
|
||||
coroutineScope.cancel()
|
||||
super.invalidate()
|
||||
}
|
||||
// endregion
|
||||
//endregion
|
||||
}
|
||||
|
||||
@@ -16,20 +16,20 @@ import androidx.paging.DataSource
|
||||
*/
|
||||
abstract class BasePagingFactory<T> : DataSource.Factory<Int, T>() {
|
||||
|
||||
// region Abstractions
|
||||
//region Abstractions
|
||||
|
||||
abstract fun createSource(): BasePagingDataSource<T>
|
||||
// endregion
|
||||
//endregion
|
||||
|
||||
// region Properties
|
||||
//region Properties
|
||||
|
||||
private val _currentSource = MutableLiveData<BasePagingDataSource<T>>()
|
||||
|
||||
val currentSource: LiveData<BasePagingDataSource<T>>
|
||||
get() = _currentSource
|
||||
// endregion
|
||||
//endregion
|
||||
|
||||
// region Functions
|
||||
//region Functions
|
||||
|
||||
override fun create(): DataSource<Int, T> = createSource().apply { _currentSource.postValue(this) }
|
||||
|
||||
@@ -38,5 +38,5 @@ abstract class BasePagingFactory<T> : DataSource.Factory<Int, T>() {
|
||||
* by calling [BasePagingDataSource.invalidate]
|
||||
*/
|
||||
fun invalidateDataSource() = currentSource.value?.invalidate()
|
||||
// endregion
|
||||
//endregion
|
||||
}
|
||||
|
||||
@@ -15,6 +15,8 @@ abstract class BasePagingListAdapter<T>(
|
||||
private val clickListener: (T) -> Unit
|
||||
) : PagedListAdapter<T, BaseViewHolder<T>>(callback) {
|
||||
|
||||
//region Abstractions
|
||||
|
||||
/**
|
||||
* This method will be called to create view holder to obfuscate layout inflation creation / process
|
||||
*
|
||||
@@ -27,6 +29,9 @@ abstract class BasePagingListAdapter<T>(
|
||||
parent: ViewGroup,
|
||||
viewType: Int
|
||||
): BaseViewHolder<T>
|
||||
//endregion
|
||||
|
||||
//region Functions
|
||||
|
||||
/**
|
||||
* [createViewHolder] will provide holders, no need to override this
|
||||
@@ -51,6 +56,7 @@ abstract class BasePagingListAdapter<T>(
|
||||
holder.bind(item)
|
||||
}
|
||||
}
|
||||
//endregion
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -58,6 +64,8 @@ abstract class BasePagingListAdapter<T>(
|
||||
*/
|
||||
abstract class BaseViewHolder<T>(binding: ViewDataBinding) : RecyclerView.ViewHolder(binding.root) {
|
||||
|
||||
//region Functions
|
||||
|
||||
/**
|
||||
* Items are delivered to [bind] via [BaseListAdapter.onBindViewHolder]
|
||||
*
|
||||
@@ -65,4 +73,5 @@ abstract class BaseViewHolder<T>(binding: ViewDataBinding) : RecyclerView.ViewHo
|
||||
* @param position position from adapter
|
||||
*/
|
||||
abstract fun bind(item: T)
|
||||
//endregion
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package com.melih.core.base.viewmodel
|
||||
|
||||
import androidx.lifecycle.LiveData
|
||||
import androidx.lifecycle.Transformations
|
||||
import androidx.lifecycle.Transformations.switchMap
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.paging.PagedList
|
||||
import androidx.paging.toLiveData
|
||||
@@ -20,19 +20,19 @@ import com.melih.repository.interactors.base.State
|
||||
*/
|
||||
abstract class BasePagingViewModel<T> : ViewModel() {
|
||||
|
||||
// region Abstractions
|
||||
//region Abstractions
|
||||
|
||||
abstract val factory: BasePagingFactory<T>
|
||||
abstract val config: PagedList.Config
|
||||
// endregion
|
||||
//endregion
|
||||
|
||||
// region Properties
|
||||
//region Properties
|
||||
|
||||
/**
|
||||
* Observe [stateData] to get notified of state of data
|
||||
*/
|
||||
val stateData: LiveData<State> by lazy {
|
||||
Transformations.switchMap(factory.currentSource) {
|
||||
switchMap(factory.currentSource) {
|
||||
it.stateData
|
||||
}
|
||||
}
|
||||
@@ -41,7 +41,7 @@ abstract class BasePagingViewModel<T> : ViewModel() {
|
||||
* Observe [errorData] to get notified if an error occurs
|
||||
*/
|
||||
val errorData: LiveData<Reason> by lazy {
|
||||
Transformations.switchMap(factory.currentSource) {
|
||||
switchMap(factory.currentSource) {
|
||||
it.reasonData
|
||||
}
|
||||
}
|
||||
@@ -52,9 +52,9 @@ abstract class BasePagingViewModel<T> : ViewModel() {
|
||||
val pagedList: LiveData<PagedList<T>> by lazy {
|
||||
factory.toLiveData(config)
|
||||
}
|
||||
// endregion
|
||||
//endregion
|
||||
|
||||
// region Functions
|
||||
//region Functions
|
||||
|
||||
fun refresh() {
|
||||
factory.currentSource.value?.invalidate()
|
||||
@@ -66,5 +66,5 @@ abstract class BasePagingViewModel<T> : ViewModel() {
|
||||
fun retry() {
|
||||
factory.currentSource.value?.invalidate()
|
||||
}
|
||||
// endregion
|
||||
//endregion
|
||||
}
|
||||
|
||||
@@ -15,10 +15,10 @@ import kotlinx.coroutines.launch
|
||||
*/
|
||||
abstract class BaseViewModel<T> : ViewModel() {
|
||||
|
||||
// region Abstractions
|
||||
//region Abstractions
|
||||
|
||||
abstract suspend fun loadData()
|
||||
// endregion
|
||||
//endregion
|
||||
|
||||
init {
|
||||
viewModelScope.launch {
|
||||
@@ -26,7 +26,7 @@ abstract class BaseViewModel<T> : ViewModel() {
|
||||
}
|
||||
}
|
||||
|
||||
// region Properties
|
||||
//region Properties
|
||||
|
||||
private val _successData = MutableLiveData<T>()
|
||||
private val _stateData = MutableLiveData<State>()
|
||||
@@ -49,9 +49,9 @@ abstract class BaseViewModel<T> : ViewModel() {
|
||||
*/
|
||||
val errorData: LiveData<Reason>
|
||||
get() = _errorData
|
||||
// endregion
|
||||
//endregion
|
||||
|
||||
// region Functions
|
||||
//region Functions
|
||||
|
||||
/**
|
||||
* Default success handler which assigns given [data] to [successData]
|
||||
@@ -97,5 +97,5 @@ abstract class BaseViewModel<T> : ViewModel() {
|
||||
loadData()
|
||||
}
|
||||
}
|
||||
// endregion
|
||||
//endregion
|
||||
}
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
package com.melih.core.extensions
|
||||
|
||||
import androidx.recyclerview.widget.DiffUtil
|
||||
|
||||
/**
|
||||
* Get [diff callback][DiffUtil.ItemCallback] for given type based on provided checker.
|
||||
* It uses [itemCheck] for both [DiffUtil.ItemCallback.areItemsTheSame] and [DiffUtil.ItemCallback.areContentsTheSame].
|
||||
*/
|
||||
inline fun <T> createDiffCallback(crossinline itemCheck: (oldItem: T, newItem: T) -> Boolean) = createDiffCallback(itemCheck, itemCheck)
|
||||
|
||||
/**
|
||||
* Get [diff callback][DiffUtil.ItemCallback] for given type based on provided checker
|
||||
*/
|
||||
inline fun <T> createDiffCallback(
|
||||
crossinline itemCheck: (oldItem: T, newItem: T) -> Boolean,
|
||||
crossinline contentCheck: (oldItem: T, newItem: T) -> Boolean
|
||||
) = object : DiffUtil.ItemCallback<T>() {
|
||||
|
||||
//region Functions
|
||||
|
||||
override fun areItemsTheSame(oldItem: T, newItem: T): Boolean =
|
||||
itemCheck(oldItem, newItem)
|
||||
|
||||
override fun areContentsTheSame(oldItem: T, newItem: T): Boolean =
|
||||
contentCheck(oldItem, newItem)
|
||||
//endregion
|
||||
}
|
||||
@@ -3,9 +3,6 @@ package com.melih.core.extensions
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.lifecycle.LiveData
|
||||
import androidx.lifecycle.Observer
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.ViewModelProvider
|
||||
import androidx.lifecycle.ViewModelProviders
|
||||
|
||||
/**
|
||||
* Reduces required boilerplate code to observe a live data
|
||||
@@ -16,13 +13,3 @@ import androidx.lifecycle.ViewModelProviders
|
||||
fun <T> Fragment.observe(data: LiveData<T>, block: (T) -> Unit) {
|
||||
data.observe(this, Observer(block))
|
||||
}
|
||||
|
||||
/**
|
||||
* Method for getting viewModel from factory and run a block over it if required for easy access
|
||||
*
|
||||
* crossinline for unwanted returns
|
||||
*/
|
||||
inline fun <reified T : ViewModel> ViewModelProvider.Factory.createFor(
|
||||
fragment: Fragment,
|
||||
crossinline block: T.() -> Unit = {}
|
||||
): T = ViewModelProviders.of(fragment, this)[T::class.java].apply(block)
|
||||
|
||||
@@ -1,14 +0,0 @@
|
||||
package com.melih.core.extensions
|
||||
|
||||
import androidx.recyclerview.widget.DiffUtil
|
||||
|
||||
/**
|
||||
* Get [diff callback][DiffUtil.ItemCallback] for given type based on provided checker
|
||||
*/
|
||||
inline fun <T> getDiffCallbackForType(crossinline itemCheck: (oldItem: T, newItem: T) -> Boolean) = object : DiffUtil.ItemCallback<T>() {
|
||||
override fun areItemsTheSame(oldItem: T, newItem: T): Boolean =
|
||||
itemCheck(oldItem, newItem)
|
||||
|
||||
override fun areContentsTheSame(oldItem: T, newItem: T): Boolean =
|
||||
itemCheck(oldItem, newItem)
|
||||
}
|
||||
@@ -1,7 +1,11 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<color name="colorPrimary">#008577</color>
|
||||
<color name="colorPrimaryDark">#00574B</color>
|
||||
<color name="colorAccent">#D81B60</color>
|
||||
<color name="lightGray">#8F8F8F</color>
|
||||
<color name="primaryColor">#f9a825</color>
|
||||
<color name="primaryLightColor">#ffd95a</color>
|
||||
<color name="primaryDarkColor">#c17900</color>
|
||||
<color name="secondaryColor">#757575</color>
|
||||
<color name="secondaryLightColor">#a4a4a4</color>
|
||||
<color name="secondaryDarkColor">#494949</color>
|
||||
<color name="primaryTextColor">#000000</color>
|
||||
<color name="secondaryTextColor">#686868</color>
|
||||
</resources>
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<dimen name="padding_standard">8dp</dimen>
|
||||
<dimen name="padding_large">16dp</dimen>
|
||||
<dimen name="corner_radius_light">4dp</dimen>
|
||||
<dimen name="corner_radius_standard">11dp</dimen>
|
||||
</resources>
|
||||
@@ -1,35 +1,40 @@
|
||||
<resources>
|
||||
|
||||
<!-- Base application theme. -->
|
||||
<style name="AppTheme" parent="Theme.AppCompat.Light.NoActionBar">
|
||||
<!-- Customize your theme here. -->
|
||||
<item name="colorPrimary">@color/colorPrimary</item>
|
||||
<item name="colorPrimaryDark">@color/colorPrimaryDark</item>
|
||||
<item name="colorAccent">@color/colorAccent</item>
|
||||
</style>
|
||||
<!-- Base application theme. -->
|
||||
<style name="AppTheme" parent="Theme.MaterialComponents.Light.NoActionBar">
|
||||
<item name="colorPrimary">@color/primaryColor</item>
|
||||
<item name="colorPrimaryDark">@color/primaryDarkColor</item>
|
||||
<item name="colorSecondary">@color/secondaryColor</item>
|
||||
<item name="colorAccent">@color/secondaryDarkColor</item>
|
||||
|
||||
<!--Styles-->
|
||||
<item name="android:textColorPrimary">@color/primaryTextColor</item>
|
||||
<item name="android:textColorSecondary">@color/secondaryTextColor</item>
|
||||
|
||||
<style name="TitleTextStyle">
|
||||
<item name="android:singleLine">true</item>
|
||||
</style>
|
||||
<item name="toolbarStyle">@style/AppTheme.ToolbarStyle</item>
|
||||
<item name="materialCardViewStyle">@style/AppTheme.CardViewStyle</item>
|
||||
</style>
|
||||
|
||||
<style name="ShortDescriptionTextStyle">
|
||||
<item name="android:ellipsize">end</item>
|
||||
<item name="android:maxLines">@integer/common_max_lines</item>
|
||||
<item name="android:gravity">center|left</item>
|
||||
</style>
|
||||
<!-- Widgets -->
|
||||
|
||||
<style name="DescriptionTextStyle">
|
||||
<style name="AppTheme.ToolbarStyle" parent="Widget.MaterialComponents.Toolbar">
|
||||
<item name="android:background">@color/primaryColor</item>
|
||||
</style>
|
||||
|
||||
</style>
|
||||
<style name="AppTheme.CardViewStyle" parent="Widget.MaterialComponents.CardView">
|
||||
<item name="android:background">@null</item>
|
||||
</style>
|
||||
|
||||
<!--Text appearances-->
|
||||
<!--Styles-->
|
||||
<style name="AppTheme.BaseTextViewStyle" parent="Widget.MaterialComponents.TextView" />
|
||||
|
||||
<style name="TitleTextAppearance" parent="TextAppearance.AppCompat.Title" />
|
||||
|
||||
<style name="DescriptionTextAppearance" parent="TextAppearance.AppCompat.Body1">
|
||||
<item name="android:textColor">@color/lightGray</item>
|
||||
</style>
|
||||
<style name="AppTheme.TextViewStyle.Title" parent="AppTheme.BaseTextViewStyle">
|
||||
<item name="android:textAppearance">@style/TextAppearance.MaterialComponents.Headline5</item>
|
||||
<item name="android:singleLine">true</item>
|
||||
</style>
|
||||
|
||||
<style name="AppTheme.TextViewStyle.Description" parent="AppTheme.BaseTextViewStyle">
|
||||
<item name="android:ellipsize">end</item>
|
||||
<item name="android:maxLines">@integer/common_max_lines</item>
|
||||
<item name="android:textAppearance">@style/TextAppearance.MaterialComponents.Caption</item>
|
||||
</style>
|
||||
</resources>
|
||||
|
||||
Reference in New Issue
Block a user