mirror of
https://github.com/melihaksoy/Android-Kotlin-Modulerized-CleanArchitecture.git
synced 2026-04-26 18:58:32 +02:00
Milestones/ms1 (#16)
* Closes #11 * Closes #13 * Closes #17 * Closes #18 * Closes #19 * Closes #6 * Closes #3 * Closes #12 * Closes #15
This commit is contained in:
committed by
Melih Aksoy
parent
11889446cb
commit
625776609d
14
features/launches/src/main/AndroidManifest.xml
Normal file
14
features/launches/src/main/AndroidManifest.xml
Normal file
@@ -0,0 +1,14 @@
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="com.melih.list">
|
||||
|
||||
<application>
|
||||
<activity
|
||||
android:name="com.melih.list.ui.LaunchesActivity"
|
||||
android:theme="@style/AppTheme">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
</application>
|
||||
</manifest>
|
||||
@@ -0,0 +1,27 @@
|
||||
package com.melih.list.di
|
||||
|
||||
import com.melih.list.di.modules.LaunchesBinds
|
||||
import com.melih.list.di.modules.LaunchesProvides
|
||||
import com.melih.list.di.scopes.LaunchesFragmentScope
|
||||
import com.melih.list.ui.LaunchesFragment
|
||||
import dagger.Module
|
||||
import dagger.android.ContributesAndroidInjector
|
||||
|
||||
/**
|
||||
* Contributes fragments & view models in this module
|
||||
*/
|
||||
@Module
|
||||
abstract class LaunchesContributor {
|
||||
|
||||
// region Contributes
|
||||
|
||||
@ContributesAndroidInjector(
|
||||
modules = [
|
||||
LaunchesProvides::class,
|
||||
LaunchesBinds::class
|
||||
]
|
||||
)
|
||||
@LaunchesFragmentScope
|
||||
abstract fun launchesFragment(): LaunchesFragment
|
||||
// endregion
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
package com.melih.list.di
|
||||
|
||||
import com.melih.list.di.scopes.LaunchesScope
|
||||
import com.melih.list.ui.LaunchesActivity
|
||||
import dagger.Module
|
||||
import dagger.android.ContributesAndroidInjector
|
||||
|
||||
/**
|
||||
* Contributes fragments & view models in this module
|
||||
*/
|
||||
@Module
|
||||
abstract class LaunchesFeatureModule {
|
||||
|
||||
// region Contributes
|
||||
|
||||
@ContributesAndroidInjector(
|
||||
modules = [
|
||||
LaunchesContributor::class
|
||||
]
|
||||
)
|
||||
@LaunchesScope
|
||||
abstract fun launchesActivity(): LaunchesActivity
|
||||
// endregion
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
package com.melih.list.di.modules
|
||||
|
||||
import androidx.lifecycle.ViewModel
|
||||
import com.melih.core.di.keys.ViewModelKey
|
||||
import com.melih.list.ui.vm.LaunchesViewModel
|
||||
import dagger.Binds
|
||||
import dagger.Module
|
||||
import dagger.multibindings.IntoMap
|
||||
|
||||
@Module
|
||||
abstract class LaunchesBinds {
|
||||
|
||||
// region ViewModels
|
||||
|
||||
@Binds
|
||||
@IntoMap
|
||||
@ViewModelKey(LaunchesViewModel::class)
|
||||
abstract fun listViewModel(listViewModel: LaunchesViewModel): ViewModel
|
||||
// endregion
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
package com.melih.list.di.modules
|
||||
|
||||
import androidx.paging.Config
|
||||
import com.melih.repository.interactors.DEFAULT_LAUNCHES_AMOUNT
|
||||
import com.melih.repository.interactors.GetLaunches
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
|
||||
@Module
|
||||
class LaunchesProvides {
|
||||
|
||||
/**
|
||||
* Provides lauches, using default value of 15
|
||||
*/
|
||||
@Provides
|
||||
fun provideGetLaunchesParams() = GetLaunches.Params(page = 0)
|
||||
|
||||
@Provides
|
||||
fun getPagingConfig() = Config(
|
||||
DEFAULT_LAUNCHES_AMOUNT,
|
||||
prefetchDistance = 2,
|
||||
enablePlaceholders = false
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
package com.melih.list.di.scopes
|
||||
|
||||
import javax.inject.Scope
|
||||
|
||||
@Scope
|
||||
@Retention(AnnotationRetention.BINARY)
|
||||
annotation class LaunchesFragmentScope
|
||||
@@ -0,0 +1,7 @@
|
||||
package com.melih.list.di.scopes
|
||||
|
||||
import javax.inject.Scope
|
||||
|
||||
@Scope
|
||||
@Retention(AnnotationRetention.BINARY)
|
||||
annotation class LaunchesScope
|
||||
@@ -0,0 +1,26 @@
|
||||
package com.melih.list.ui
|
||||
|
||||
import android.os.Bundle
|
||||
import androidx.navigation.fragment.NavHostFragment
|
||||
import com.melih.core.base.lifecycle.BaseActivity
|
||||
import com.melih.list.R
|
||||
import com.melih.list.databinding.LaunchesActivityBinding
|
||||
|
||||
class LaunchesActivity : BaseActivity<LaunchesActivityBinding>() {
|
||||
|
||||
// region Functions
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
|
||||
setSupportActionBar(binding.toolbar)
|
||||
}
|
||||
|
||||
override fun getLayoutId(): Int = R.layout.activity_launches
|
||||
|
||||
override fun createNavHostFragment() =
|
||||
NavHostFragment.create(R.navigation.nav_launches)
|
||||
|
||||
override fun addNavHostTo(): Int = R.id.container
|
||||
// endregion
|
||||
}
|
||||
@@ -0,0 +1,97 @@
|
||||
package com.melih.list.ui
|
||||
|
||||
import android.os.Bundle
|
||||
import android.view.View
|
||||
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout
|
||||
import com.melih.core.actions.Actions
|
||||
import com.melih.core.base.lifecycle.BaseDaggerFragment
|
||||
import com.melih.core.extensions.createFor
|
||||
import com.melih.core.extensions.observe
|
||||
import com.melih.list.R
|
||||
import com.melih.list.databinding.ListBinding
|
||||
import com.melih.list.ui.adapters.LaunchesAdapter
|
||||
import com.melih.list.ui.vm.LaunchesViewModel
|
||||
import com.melih.repository.entities.LaunchEntity
|
||||
import com.melih.repository.interactors.base.State
|
||||
|
||||
class LaunchesFragment : BaseDaggerFragment<ListBinding>(), SwipeRefreshLayout.OnRefreshListener {
|
||||
|
||||
// region Properties
|
||||
|
||||
private val viewModel: LaunchesViewModel
|
||||
get() = viewModelFactory.createFor(this)
|
||||
|
||||
private val launchesAdapter = LaunchesAdapter(::onItemSelected)
|
||||
// endregion
|
||||
|
||||
// region Functions
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
//setHasOptionsMenu(true)
|
||||
|
||||
binding.rocketList.adapter = launchesAdapter
|
||||
binding.swipeRefreshLayout.setOnRefreshListener(this)
|
||||
|
||||
observeDataChanges()
|
||||
}
|
||||
|
||||
//override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
|
||||
// inflater.inflate(R.menu.menu_rocket_list, menu)
|
||||
//
|
||||
// with(menu.findItem(R.id.search)) {
|
||||
// onExpandOrCollapse(::onSearchExpand, ::onSearchCollapse)
|
||||
// setSearchQueryListener(actionView as SearchView)
|
||||
// }
|
||||
//
|
||||
// super.onCreateOptionsMenu(menu, inflater)
|
||||
//}
|
||||
|
||||
private fun observeDataChanges() {
|
||||
|
||||
// Observing state to show loading
|
||||
observe(viewModel.stateData) {
|
||||
binding.swipeRefreshLayout.isRefreshing = it is State.Loading
|
||||
}
|
||||
|
||||
// Observing error to show toast with retry action
|
||||
observe(viewModel.errorData) {
|
||||
showSnackbarWithAction(it) {
|
||||
viewModel.retry()
|
||||
}
|
||||
}
|
||||
|
||||
observe(viewModel.pagedList) {
|
||||
launchesAdapter.submitList(it)
|
||||
}
|
||||
|
||||
//observe(viewModel.filteredItems) {
|
||||
// launchesAdapter.submitList(it)
|
||||
//}
|
||||
}
|
||||
|
||||
private fun onItemSelected(item: LaunchEntity?) {
|
||||
startActivity(Actions.openDetailFor(item?.id ?: -1L))
|
||||
}
|
||||
|
||||
//private fun onSearchExpand() {
|
||||
// binding.swipeRefreshLayout.isEnabled = false
|
||||
//}
|
||||
|
||||
//private fun onSearchCollapse() {
|
||||
// binding.swipeRefreshLayout.isEnabled = true
|
||||
//}
|
||||
|
||||
//private fun setSearchQueryListener(searchView: SearchView) {
|
||||
// searchView.setOnQueryChangedListener {
|
||||
// viewModel.filterItemListBy(it)
|
||||
// }
|
||||
//}
|
||||
|
||||
override fun onRefresh() {
|
||||
viewModel.refresh()
|
||||
}
|
||||
|
||||
override fun getLayoutId(): Int = R.layout.fragment_launches
|
||||
// endregion
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
package com.melih.list.ui.adapters
|
||||
|
||||
import android.view.LayoutInflater
|
||||
import android.view.ViewGroup
|
||||
import androidx.recyclerview.widget.DiffUtil
|
||||
import com.melih.core.base.recycler.BasePagingListAdapter
|
||||
import com.melih.core.base.recycler.BaseViewHolder
|
||||
import com.melih.list.databinding.LaunchRowBinding
|
||||
import com.melih.repository.entities.LaunchEntity
|
||||
|
||||
class LaunchesAdapter(itemClickListener: (LaunchEntity?) -> Unit) : BasePagingListAdapter<LaunchEntity>(
|
||||
object : DiffUtil.ItemCallback<LaunchEntity>() {
|
||||
override fun areItemsTheSame(oldItem: LaunchEntity, newItem: LaunchEntity): Boolean =
|
||||
oldItem.id == newItem.id
|
||||
|
||||
override fun areContentsTheSame(oldItem: LaunchEntity, newItem: LaunchEntity): Boolean =
|
||||
oldItem.name == newItem.name
|
||||
|
||||
},
|
||||
itemClickListener
|
||||
) {
|
||||
override fun createViewHolder(
|
||||
inflater: LayoutInflater,
|
||||
parent: ViewGroup,
|
||||
viewType: Int
|
||||
): BaseViewHolder<LaunchEntity> =
|
||||
LaunchesViewHolder(LaunchRowBinding.inflate(inflater, parent, false))
|
||||
|
||||
}
|
||||
|
||||
class LaunchesViewHolder(private val binding: LaunchRowBinding) : BaseViewHolder<LaunchEntity>(binding) {
|
||||
|
||||
override fun bind(item: LaunchEntity?) {
|
||||
binding.entity = item
|
||||
|
||||
val missions = item?.missions
|
||||
binding.tvDescription.text = if (!missions.isNullOrEmpty()) missions[0].description else ""
|
||||
|
||||
binding.executePendingBindings()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
package com.melih.list.ui.paging
|
||||
|
||||
import com.melih.core.base.paging.BasePagingDataSource
|
||||
import com.melih.repository.entities.LaunchEntity
|
||||
import com.melih.repository.interactors.GetLaunches
|
||||
import com.melih.repository.interactors.base.Result
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import javax.inject.Inject
|
||||
|
||||
class LaunchesPagingSource @Inject constructor(
|
||||
private val getLaunches: GetLaunches,
|
||||
private val getLaunchesParams: GetLaunches.Params
|
||||
) : BasePagingDataSource<LaunchEntity>() {
|
||||
|
||||
//region Functions
|
||||
|
||||
@UseExperimental(ExperimentalCoroutinesApi::class)
|
||||
override fun loadDataForPage(page: Int): Flow<Result<List<LaunchEntity>>> =
|
||||
getLaunches(
|
||||
getLaunchesParams.copy(
|
||||
page = page
|
||||
)
|
||||
)
|
||||
//endregion
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
package com.melih.list.ui.paging
|
||||
|
||||
import com.melih.core.base.paging.BasePagingDataSource
|
||||
import com.melih.core.base.paging.BasePagingFactory
|
||||
import com.melih.repository.entities.LaunchEntity
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Provider
|
||||
|
||||
class LaunchesPagingSourceFactory @Inject constructor(
|
||||
private val sourceProvider: Provider<LaunchesPagingSource>
|
||||
) : BasePagingFactory<LaunchEntity>() {
|
||||
|
||||
override fun createSource(): BasePagingDataSource<LaunchEntity> = sourceProvider.get()
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
package com.melih.list.ui.vm
|
||||
|
||||
import androidx.paging.PagedList
|
||||
import com.melih.core.base.paging.BasePagingFactory
|
||||
import com.melih.core.base.viewmodel.BasePagingViewModel
|
||||
import com.melih.list.ui.paging.LaunchesPagingSourceFactory
|
||||
import com.melih.repository.entities.LaunchEntity
|
||||
import javax.inject.Inject
|
||||
|
||||
class LaunchesViewModel @Inject constructor(
|
||||
private val launchesPagingSourceFactory: LaunchesPagingSourceFactory,
|
||||
private val launchesPagingConfig: PagedList.Config
|
||||
) : BasePagingViewModel<LaunchEntity>() {
|
||||
|
||||
// region Properties
|
||||
|
||||
override val factory: BasePagingFactory<LaunchEntity>
|
||||
get() = launchesPagingSourceFactory
|
||||
|
||||
override val config: PagedList.Config
|
||||
get() = launchesPagingConfig
|
||||
|
||||
//private val _filteredItems = MediatorLiveData<PagedList<LaunchEntity>>()
|
||||
|
||||
//val filteredItems: LiveData<PagedList<LaunchEntity>>
|
||||
// get() = _filteredItems
|
||||
// endregion
|
||||
|
||||
//init {
|
||||
// _filteredItems.addSource(pagedList, _filteredItems::setValue)
|
||||
//}
|
||||
|
||||
// region Functions
|
||||
|
||||
//fun filterItemListBy(query: String?) {
|
||||
//
|
||||
// _filteredItems.value = if (!query.isNullOrBlank()) {
|
||||
// pagedList.value
|
||||
// ?.snapshot() as PagedList<LaunchEntity>
|
||||
// } else {
|
||||
// pagedList.value
|
||||
// }
|
||||
//}
|
||||
// endregion
|
||||
}
|
||||
22
features/launches/src/main/res/anim/item_enter.xml
Normal file
22
features/launches/src/main/res/anim/item_enter.xml
Normal file
@@ -0,0 +1,22 @@
|
||||
<set xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:duration="@integer/anim_duration">
|
||||
|
||||
<translate
|
||||
android:fromYDelta="-20%"
|
||||
android:interpolator="@android:anim/decelerate_interpolator"
|
||||
android:toYDelta="0" />
|
||||
|
||||
<alpha
|
||||
android:fromAlpha="0"
|
||||
android:interpolator="@android:anim/decelerate_interpolator"
|
||||
android:toAlpha="1" />
|
||||
|
||||
<scale
|
||||
android:fromXScale="105%"
|
||||
android:fromYScale="105%"
|
||||
android:interpolator="@android:anim/decelerate_interpolator"
|
||||
android:pivotX="50%"
|
||||
android:pivotY="50%"
|
||||
android:toXScale="100%"
|
||||
android:toYScale="100%" />
|
||||
</set>
|
||||
@@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<layoutAnimation xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:animation="@anim/item_enter"
|
||||
android:animationOrder="normal"
|
||||
android:delay="15%" />
|
||||
22
features/launches/src/main/res/layout/activity_launches.xml
Normal file
22
features/launches/src/main/res/layout/activity_launches.xml
Normal file
@@ -0,0 +1,22 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<layout xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
|
||||
<data class="LaunchesActivityBinding" />
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical">
|
||||
|
||||
<androidx.appcompat.widget.Toolbar
|
||||
android:id="@+id/toolbar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="?android:actionBarSize"
|
||||
android:background="@color/colorPrimary" />
|
||||
|
||||
<FrameLayout
|
||||
android:id="@+id/container"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent" />
|
||||
</LinearLayout>
|
||||
</layout>
|
||||
37
features/launches/src/main/res/layout/fragment_launches.xml
Normal file
37
features/launches/src/main/res/layout/fragment_launches.xml
Normal file
@@ -0,0 +1,37 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<layout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools">
|
||||
|
||||
<data class="ListBinding">
|
||||
|
||||
<variable
|
||||
name="viewModel"
|
||||
type="com.melih.list.ui.vm.LaunchesViewModel" />
|
||||
</data>
|
||||
|
||||
<androidx.coordinatorlayout.widget.CoordinatorLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<androidx.swiperefreshlayout.widget.SwipeRefreshLayout
|
||||
android:id="@+id/swipeRefreshLayout"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
app:layout_behavior="com.melih.core.utils.SnackbarBehaviour">
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/rocketList"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
android:layoutAnimation="@anim/layout_item_enter"
|
||||
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
tools:listitem="@layout/row_launch" />
|
||||
</androidx.swiperefreshlayout.widget.SwipeRefreshLayout>
|
||||
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
||||
</layout>
|
||||
77
features/launches/src/main/res/layout/row_launch.xml
Normal file
77
features/launches/src/main/res/layout/row_launch.xml
Normal file
@@ -0,0 +1,77 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<layout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools">
|
||||
|
||||
<data class="LaunchRowBinding">
|
||||
|
||||
<variable
|
||||
name="entity"
|
||||
type="com.melih.repository.entities.LaunchEntity" />
|
||||
</data>
|
||||
|
||||
<androidx.cardview.widget.CardView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="@dimen/padding_standard"
|
||||
android:layout_marginLeft="@dimen/padding_standard"
|
||||
android:layout_marginTop="@dimen/padding_standard"
|
||||
android:layout_marginEnd="@dimen/padding_standard"
|
||||
android:layout_marginRight="@dimen/padding_standard"
|
||||
app:cardCornerRadius="@dimen/corner_radius_standard"
|
||||
app:cardElevation="10dp">
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="160dp">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/imgRocket"
|
||||
imageUrl="@{entity.rocket.imageURL}"
|
||||
android:layout_width="120dp"
|
||||
android:layout_height="120dp"
|
||||
android:layout_marginStart="@dimen/padding_standard"
|
||||
android:layout_marginLeft="@dimen/padding_standard"
|
||||
android:contentDescription="@string/cd_rocket_image"
|
||||
android:scaleType="centerCrop"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
tools:srcCompat="@tools:sample/avatars[14]" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvDescription"
|
||||
style="@style/ShortDescriptionTextStyle"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
android:layout_marginStart="@dimen/padding_standard"
|
||||
android:layout_marginLeft="@dimen/padding_standard"
|
||||
android:layout_marginEnd="@dimen/padding_standard"
|
||||
android:layout_marginRight="@dimen/padding_standard"
|
||||
android:textAppearance="@style/DescriptionTextAppearance"
|
||||
app:layout_constraintBottom_toBottomOf="@+id/imgRocket"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toEndOf="@+id/imgRocket"
|
||||
app:layout_constraintTop_toBottomOf="@+id/tvTitle"
|
||||
tools:text="@sample/launches.json/launches/missions/description" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvTitle"
|
||||
style="@style/TitleTextStyle"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="@dimen/padding_standard"
|
||||
android:layout_marginLeft="@dimen/padding_standard"
|
||||
android:layout_marginEnd="@dimen/padding_standard"
|
||||
android:layout_marginRight="@dimen/padding_standard"
|
||||
android:text="@{entity.name}"
|
||||
android:textAppearance="@style/TitleTextAppearance"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toEndOf="@+id/imgRocket"
|
||||
app:layout_constraintTop_toTopOf="@+id/imgRocket"
|
||||
tools:text="@sample/launches.json/launches/name" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
</androidx.cardview.widget.CardView>
|
||||
</layout>
|
||||
11
features/launches/src/main/res/menu/menu_rocket_list.xml
Normal file
11
features/launches/src/main/res/menu/menu_rocket_list.xml
Normal file
@@ -0,0 +1,11 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<menu xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||
<item
|
||||
android:id="@+id/search"
|
||||
android:icon="@android:drawable/ic_menu_search"
|
||||
android:title="@string/search"
|
||||
app:actionViewClass="androidx.appcompat.widget.SearchView"
|
||||
app:showAsAction="always|collapseActionView" />
|
||||
|
||||
</menu>
|
||||
13
features/launches/src/main/res/navigation/nav_launches.xml
Normal file
13
features/launches/src/main/res/navigation/nav_launches.xml
Normal file
@@ -0,0 +1,13 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<navigation xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:id="@+id/nav_launches"
|
||||
app:startDestination="@id/launchesFragment">
|
||||
|
||||
<fragment
|
||||
android:id="@+id/launchesFragment"
|
||||
android:name="com.melih.list.ui.LaunchesFragment"
|
||||
android:label="LaunchesFragment"
|
||||
tools:layout="@layout/fragment_launches" />
|
||||
</navigation>
|
||||
4
features/launches/src/main/res/values/integers.xml
Normal file
4
features/launches/src/main/res/values/integers.xml
Normal file
@@ -0,0 +1,4 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<integer name="anim_duration">350</integer>
|
||||
</resources>
|
||||
4
features/launches/src/main/res/values/strings.xml
Normal file
4
features/launches/src/main/res/values/strings.xml
Normal file
@@ -0,0 +1,4 @@
|
||||
<resources>
|
||||
<string name="cd_rocket_image">Image of the rocket</string>
|
||||
<string name="search">Search</string>
|
||||
</resources>
|
||||
@@ -0,0 +1,27 @@
|
||||
@file:UseExperimental(ExperimentalCoroutinesApi::class)
|
||||
|
||||
package com.melih.list
|
||||
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.test.TestCoroutineDispatcher
|
||||
import kotlinx.coroutines.test.resetMain
|
||||
import kotlinx.coroutines.test.setMain
|
||||
import org.junit.jupiter.api.AfterEach
|
||||
import org.junit.jupiter.api.BeforeEach
|
||||
|
||||
abstract class BaseTestWithMainThread {
|
||||
|
||||
protected val dispatcher = TestCoroutineDispatcher()
|
||||
|
||||
@BeforeEach
|
||||
fun setUp() {
|
||||
Dispatchers.setMain(dispatcher)
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
fun tearDown() {
|
||||
Dispatchers.resetMain()
|
||||
dispatcher.cleanupTestCoroutines()
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user