diff --git a/.idea/codeStyles/Project.xml b/.idea/codeStyles/Project.xml
new file mode 100644
index 0000000..882ab2a
--- /dev/null
+++ b/.idea/codeStyles/Project.xml
@@ -0,0 +1,143 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ xmlns:android
+ ^$
+
+
+
+
+
+
+
+
+ xmlns:.*
+ ^$
+
+
+ BY_NAME
+
+
+
+
+
+
+ .*:id
+ http://schemas.android.com/apk/res/android
+
+
+
+
+
+
+
+
+ .*:name
+ http://schemas.android.com/apk/res/android
+
+
+
+
+
+
+
+
+ name
+ ^$
+
+
+
+
+
+
+
+
+ style
+ ^$
+
+
+
+
+
+
+
+
+ .*
+ ^$
+
+
+ BY_NAME
+
+
+
+
+
+
+ .*
+ http://schemas.android.com/apk/res/android
+
+
+ ANDROID_ATTRIBUTE_ORDER
+
+
+
+
+
+
+ .*
+ .*
+
+
+ BY_NAME
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/codeStyles/codeStyleConfig.xml b/.idea/codeStyles/codeStyleConfig.xml
new file mode 100644
index 0000000..a55e7a1
--- /dev/null
+++ b/.idea/codeStyles/codeStyleConfig.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/deploymentTargetDropDown.xml b/.idea/deploymentTargetDropDown.xml
index 71c4a7c..ea20fc2 100644
--- a/.idea/deploymentTargetDropDown.xml
+++ b/.idea/deploymentTargetDropDown.xml
@@ -1,26 +1,17 @@
-
-
+
-
+
-
-
+
+
-
-
-
-
-
-
-
-
-
-
+
+
\ No newline at end of file
diff --git a/.idea/kotlinScripting.xml b/.idea/kotlinScripting.xml
new file mode 100644
index 0000000..ea39e51
--- /dev/null
+++ b/.idea/kotlinScripting.xml
@@ -0,0 +1,9 @@
+
+
+
+
+ 2147483647
+ true
+
+
+
\ No newline at end of file
diff --git a/.idea/misc.xml b/.idea/misc.xml
index 7bb6b40..b9847bb 100644
--- a/.idea/misc.xml
+++ b/.idea/misc.xml
@@ -6,6 +6,7 @@
+
diff --git a/app/src/main/java/io/github/mohamedisoliman/pixapay/DSL.kt b/app/src/main/java/io/github/mohamedisoliman/pixapay/DSL.kt
new file mode 100644
index 0000000..ade695b
--- /dev/null
+++ b/app/src/main/java/io/github/mohamedisoliman/pixapay/DSL.kt
@@ -0,0 +1,53 @@
+package io.github.mohamedisoliman.pixapay
+
+import io.github.mohamedisoliman.pixapay.data.entities.ImageModel
+
+
+@DslMarker
+annotation class ImageDSL
+
+
+fun image(block: ImageBuilder.() -> Unit): ImageModel = ImageBuilder().apply(block).build()
+
+@ImageDSL
+class ImageBuilder {
+ var imageId: Long = -1
+ var userName: String = ""
+ var url: String = ""
+ var likes: String = ""
+ var downloads: String = ""
+ var comments: String = ""
+ private var tags: MutableList = mutableListOf()
+ var largeImageURL: String? = ""
+
+ fun tag(block: ImageBuilder.() -> String) {
+ tags.add(block())
+ }
+
+ fun build(): ImageModel = ImageModel(
+ imageId = imageId,
+ userName = userName,
+ url = url,
+ likes = likes,
+ downloads = downloads,
+ comments = comments,
+ tags = tags,
+ largeImageURL = largeImageURL
+ )
+}
+
+fun main() {
+ val image = image {
+ comments = "20"
+ downloads = "30"
+ tag { "Nature" }
+ tag { "People" }
+ tag {
+ comments = "40"
+ "Travel"
+
+ }
+ }
+
+ print(image)
+}
\ No newline at end of file
diff --git a/app/src/main/java/io/github/mohamedisoliman/pixapay/MVI.kt b/app/src/main/java/io/github/mohamedisoliman/pixapay/MVI.kt
new file mode 100644
index 0000000..d422e0a
--- /dev/null
+++ b/app/src/main/java/io/github/mohamedisoliman/pixapay/MVI.kt
@@ -0,0 +1,5 @@
+package io.github.mohamedisoliman.pixapay
+
+interface UiEvent
+
+interface UiState
\ No newline at end of file
diff --git a/app/src/main/java/io/github/mohamedisoliman/pixapay/data/ImagesRepository.kt b/app/src/main/java/io/github/mohamedisoliman/pixapay/data/ImagesRepository.kt
index 58e49e0..26695f4 100644
--- a/app/src/main/java/io/github/mohamedisoliman/pixapay/data/ImagesRepository.kt
+++ b/app/src/main/java/io/github/mohamedisoliman/pixapay/data/ImagesRepository.kt
@@ -3,6 +3,7 @@ package io.github.mohamedisoliman.pixapay.data
import io.github.mohamedisoliman.pixapay.data.entities.PixabayImage
import io.github.mohamedisoliman.pixapay.data.local.LocalPixabayStoreContract
import io.github.mohamedisoliman.pixapay.data.remote.RemotePixabayContract
+import io.github.mohamedisoliman.pixapay.domain.search.ImagesRepositoryContract
import kotlinx.coroutines.flow.*
import timber.log.Timber
import javax.inject.Inject
diff --git a/app/src/main/java/io/github/mohamedisoliman/pixapay/data/entities/ImageModel.kt b/app/src/main/java/io/github/mohamedisoliman/pixapay/data/entities/ImageModel.kt
index 31ff0d7..25e1ceb 100644
--- a/app/src/main/java/io/github/mohamedisoliman/pixapay/data/entities/ImageModel.kt
+++ b/app/src/main/java/io/github/mohamedisoliman/pixapay/data/entities/ImageModel.kt
@@ -2,11 +2,11 @@ package io.github.mohamedisoliman.pixapay.data.entities
data class ImageModel(
val imageId: Long = -1,
- val userName: String,
- val url: String,
- val likes: String,
- val downloads: String,
- val comments: String,
- val tags: List,
- val largeImageURL: String?,
+ val userName: String = "",
+ val url: String = "",
+ val likes: String = "",
+ val downloads: String = "",
+ val comments: String = "",
+ val tags: List = emptyList(),
+ val largeImageURL: String? = "",
)
\ No newline at end of file
diff --git a/app/src/main/java/io/github/mohamedisoliman/pixapay/di/AppModule.kt b/app/src/main/java/io/github/mohamedisoliman/pixapay/di/AppModule.kt
index 91c1d7b..182f039 100644
--- a/app/src/main/java/io/github/mohamedisoliman/pixapay/di/AppModule.kt
+++ b/app/src/main/java/io/github/mohamedisoliman/pixapay/di/AppModule.kt
@@ -7,7 +7,7 @@ import dagger.hilt.InstallIn
import dagger.hilt.android.qualifiers.ApplicationContext
import dagger.hilt.components.SingletonComponent
import io.github.mohamedisoliman.pixapay.data.ImagesRepository
-import io.github.mohamedisoliman.pixapay.data.ImagesRepositoryContract
+import io.github.mohamedisoliman.pixapay.domain.search.ImagesRepositoryContract
import io.github.mohamedisoliman.pixapay.data.local.*
import io.github.mohamedisoliman.pixapay.data.remote.RemotePixabayContract
import io.github.mohamedisoliman.pixapay.data.remote.pixabayApi
diff --git a/app/src/main/java/io/github/mohamedisoliman/pixapay/di/SearchViewModelDi.kt b/app/src/main/java/io/github/mohamedisoliman/pixapay/di/SearchViewModelDi.kt
new file mode 100644
index 0000000..c4aeb03
--- /dev/null
+++ b/app/src/main/java/io/github/mohamedisoliman/pixapay/di/SearchViewModelDi.kt
@@ -0,0 +1,26 @@
+package io.github.mohamedisoliman.pixapay.di
+
+import dagger.Module
+import dagger.Provides
+import dagger.hilt.InstallIn
+import dagger.hilt.android.components.ViewModelComponent
+import dagger.hilt.android.scopes.ViewModelScoped
+import io.github.mohamedisoliman.pixapay.domain.search.SearchState
+import io.github.mohamedisoliman.pixapay.ui.search.SearchScreenEvent
+import kotlinx.coroutines.flow.MutableSharedFlow
+import kotlinx.coroutines.flow.MutableStateFlow
+
+
+@Module
+@InstallIn(ViewModelComponent::class)
+internal object ViewModelSearchModule {
+
+ @Provides
+ @ViewModelScoped
+ fun providesEventsFlow(): MutableSharedFlow = MutableSharedFlow()
+
+
+ @Provides
+ @ViewModelScoped
+ fun providesStatesFlow(): MutableStateFlow = MutableStateFlow(SearchState.IDLE())
+}
\ No newline at end of file
diff --git a/app/src/main/java/io/github/mohamedisoliman/pixapay/domain/SearchUsecase.kt b/app/src/main/java/io/github/mohamedisoliman/pixapay/domain/SearchUsecase.kt
deleted file mode 100644
index 34aa301..0000000
--- a/app/src/main/java/io/github/mohamedisoliman/pixapay/domain/SearchUsecase.kt
+++ /dev/null
@@ -1,28 +0,0 @@
-package io.github.mohamedisoliman.pixapay.domain
-
-import io.github.mohamedisoliman.pixapay.data.ImagesRepositoryContract
-import io.github.mohamedisoliman.pixapay.data.entities.toImageModel
-import io.github.mohamedisoliman.pixapay.data.entities.ImageModel
-import kotlinx.coroutines.flow.*
-import javax.inject.Inject
-
-class SearchUsecase @Inject constructor(
- private val imagesRepository: ImagesRepositoryContract,
-) {
-
- operator fun invoke(query: String): Flow {
- return imagesRepository.search(query)
- .map { list -> list.map { it.toImageModel() } }
- .map { if (it.isEmpty()) SearchState.Empty else SearchState.Success(it) }
- .onStart { emit(SearchState.Loading) }
- .catch { emit(SearchState.Error(it)) }
- }
-
-}
-
-sealed class SearchState {
- object Empty : SearchState()
- class Success(val images: List) : SearchState()
- class Error(val throwable: Throwable) : SearchState()
- object Loading : SearchState()
-}
\ No newline at end of file
diff --git a/app/src/main/java/io/github/mohamedisoliman/pixapay/data/ImagesRepositoryContract.kt b/app/src/main/java/io/github/mohamedisoliman/pixapay/domain/search/ImagesRepositoryContract.kt
similarity index 78%
rename from app/src/main/java/io/github/mohamedisoliman/pixapay/data/ImagesRepositoryContract.kt
rename to app/src/main/java/io/github/mohamedisoliman/pixapay/domain/search/ImagesRepositoryContract.kt
index 879062d..00208e4 100644
--- a/app/src/main/java/io/github/mohamedisoliman/pixapay/data/ImagesRepositoryContract.kt
+++ b/app/src/main/java/io/github/mohamedisoliman/pixapay/domain/search/ImagesRepositoryContract.kt
@@ -1,4 +1,4 @@
-package io.github.mohamedisoliman.pixapay.data
+package io.github.mohamedisoliman.pixapay.domain.search
import io.github.mohamedisoliman.pixapay.data.entities.PixabayImage
import kotlinx.coroutines.flow.Flow
diff --git a/app/src/main/java/io/github/mohamedisoliman/pixapay/domain/search/SearchState.kt b/app/src/main/java/io/github/mohamedisoliman/pixapay/domain/search/SearchState.kt
new file mode 100644
index 0000000..01e9e5e
--- /dev/null
+++ b/app/src/main/java/io/github/mohamedisoliman/pixapay/domain/search/SearchState.kt
@@ -0,0 +1,29 @@
+package io.github.mohamedisoliman.pixapay.domain.search
+
+import io.github.mohamedisoliman.pixapay.UiState
+import io.github.mohamedisoliman.pixapay.data.entities.ImageModel
+
+sealed class SearchState(
+ val isLoading: Boolean = false,
+ val result: List? = null,
+ val searchText: String? = null,
+ val throwable: Throwable? = null,
+) : UiState {
+
+ class IDLE(searchText: String? = "") : SearchState(searchText = searchText)
+
+ class EmptyResult(searchText: String?) : SearchState(searchText = searchText)
+
+ class Loading(searchText: String?) : SearchState(searchText = searchText, isLoading = true)
+
+ class Success(
+ result: List?,
+ searchText: String?,
+ ) : SearchState(result = result, searchText = searchText)
+
+ class Error(
+ searchText: String? = null,
+ throwable: Throwable? = null,
+ ) : SearchState(searchText = searchText, throwable = throwable)
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/io/github/mohamedisoliman/pixapay/domain/search/SearchUsecase.kt b/app/src/main/java/io/github/mohamedisoliman/pixapay/domain/search/SearchUsecase.kt
new file mode 100644
index 0000000..b24a078
--- /dev/null
+++ b/app/src/main/java/io/github/mohamedisoliman/pixapay/domain/search/SearchUsecase.kt
@@ -0,0 +1,27 @@
+package io.github.mohamedisoliman.pixapay.domain.search
+
+import io.github.mohamedisoliman.pixapay.data.entities.toImageModel
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.catch
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.onStart
+import javax.inject.Inject
+
+class SearchUsecase @Inject constructor(
+ private val imagesRepository: ImagesRepositoryContract,
+) {
+
+ operator fun invoke(query: String): Flow {
+ return imagesRepository.search(query)
+ .map { list -> list.map { it.toImageModel() } }
+ .map {
+ if (it.isEmpty())
+ SearchState.EmptyResult(query)
+ else
+ SearchState.Success(searchText = query, result = it)
+ }.onStart { emit(SearchState.Loading(query)) }
+ .catch { emit(SearchState.Error(searchText = query, it)) }
+ }
+
+}
+
diff --git a/app/src/main/java/io/github/mohamedisoliman/pixapay/ui/navigation.kt b/app/src/main/java/io/github/mohamedisoliman/pixapay/ui/navigation.kt
index ecf6990..b581c5e 100644
--- a/app/src/main/java/io/github/mohamedisoliman/pixapay/ui/navigation.kt
+++ b/app/src/main/java/io/github/mohamedisoliman/pixapay/ui/navigation.kt
@@ -14,8 +14,10 @@ import io.github.mohamedisoliman.pixapay.R
import io.github.mohamedisoliman.pixapay.ui.image_details.ImageDetailsScreen
import io.github.mohamedisoliman.pixapay.ui.search.SearchImagesViewModel
import io.github.mohamedisoliman.pixapay.ui.search.SearchScreen
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+@OptIn(ExperimentalCoroutinesApi::class)
@Composable
fun AppNavigation(
modifier: Modifier = Modifier,
@@ -23,6 +25,9 @@ fun AppNavigation(
) {
val viewModel: SearchImagesViewModel = hiltViewModel()
+ viewModel.navigateToDetails = {
+ navController.navigate("${Screen.ImageDetails.route}/${it}")
+ }
NavHost(
navController = navController,
@@ -31,9 +36,7 @@ fun AppNavigation(
) {
composable(Screen.Search.route) {
- SearchScreen(viewModel) {
- navController.navigate("${Screen.ImageDetails.route}/${it}")
- }
+ SearchScreen(viewModel)
}
composable(
diff --git a/app/src/main/java/io/github/mohamedisoliman/pixapay/ui/search/SearchImagesViewModel.kt b/app/src/main/java/io/github/mohamedisoliman/pixapay/ui/search/SearchImagesViewModel.kt
index eb3785e..9810bde 100644
--- a/app/src/main/java/io/github/mohamedisoliman/pixapay/ui/search/SearchImagesViewModel.kt
+++ b/app/src/main/java/io/github/mohamedisoliman/pixapay/ui/search/SearchImagesViewModel.kt
@@ -3,47 +3,75 @@ package io.github.mohamedisoliman.pixapay.ui.search
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import dagger.hilt.android.lifecycle.HiltViewModel
-import io.github.mohamedisoliman.pixapay.domain.SearchState
-import io.github.mohamedisoliman.pixapay.domain.SearchState.*
-import io.github.mohamedisoliman.pixapay.domain.SearchUsecase
import io.github.mohamedisoliman.pixapay.data.entities.ImageModel
+import io.github.mohamedisoliman.pixapay.domain.search.SearchState
+import io.github.mohamedisoliman.pixapay.domain.search.SearchUsecase
import kotlinx.coroutines.flow.*
+import kotlinx.coroutines.launch
import javax.inject.Inject
@HiltViewModel
class SearchImagesViewModel @Inject constructor(
- val searchUsecase: SearchUsecase,
+ private val searchUsecase: SearchUsecase,
+ private val _states: MutableStateFlow,
) : ViewModel() {
- private var _searchViewState = MutableStateFlow(Empty)
- val searchViewState: StateFlow = _searchViewState
-
- private var _searchQueryState = MutableStateFlow("fruits")
- val searchQueryState: StateFlow = _searchQueryState
+ val states = _states.asStateFlow()
+ private val _queryState = MutableStateFlow("fruits")
+ val query by lazy { _queryState.asStateFlow() }
+ private val _searchClick = MutableSharedFlow()
+ lateinit var navigateToDetails: (Long) -> Unit
init {
+ subscribeToEvents()
onSearchClicked()
}
- fun search(query: String) {
- searchUsecase(query = query)
- .onEach { _searchViewState.value = it }
+
+ private fun subscribeToEvents() {
+ merge(
+ searchCurrentQuery(),
+ searchWhileTyping(),
+ updateUiSearchText()
+ )
+ .flatMapMerge { it.toUsecase() }
+ .onEach { _states.value = it }
.launchIn(viewModelScope)
+
}
- fun onSearchChange(searchText: String) {
- _searchQueryState.value = searchText
+ fun onSearchClicked() {
+ viewModelScope.launch { _searchClick.emit(_queryState.value) }
}
- fun findImage(id: Long): ImageModel? =
- (_searchViewState.value as? Success)?.images?.find { it.imageId == id }
+ fun onSearchQueryChange(query: String) {
+ viewModelScope.launch { _queryState.emit(query) }
+ }
+
+ fun findImage(id: Long): ImageModel? = _states.value.result?.find { it.imageId == id }
- fun onSearchClicked() {
- search(_searchQueryState.value)
+ private fun SearchScreenEvent.toUsecase(): Flow = when (this) {
+ is SearchClicked -> searchUsecase(this.query)
+ is SearchQueryChanged -> searchUsecase(this.query)
+ is SearchQueryUpdated -> flowOf(SearchState.IDLE(searchText = this.query))
}
+ private fun updateUiSearchText() =
+ _queryState.asSharedFlow()
+ .map { SearchQueryUpdated(it) }
+
+ private fun searchWhileTyping() =
+ _queryState.asSharedFlow()
+ .map { SearchQueryChanged(it) }
+ .debounce(700)
+
+ private fun searchCurrentQuery() =
+ _searchClick.asSharedFlow()
+ .map { SearchClicked(it) }
+ .onStart { SearchClicked("fruits") }
+
}
diff --git a/app/src/main/java/io/github/mohamedisoliman/pixapay/ui/search/SearchScreen.kt b/app/src/main/java/io/github/mohamedisoliman/pixapay/ui/search/SearchScreen.kt
index 814c7d0..f04ce87 100644
--- a/app/src/main/java/io/github/mohamedisoliman/pixapay/ui/search/SearchScreen.kt
+++ b/app/src/main/java/io/github/mohamedisoliman/pixapay/ui/search/SearchScreen.kt
@@ -29,41 +29,79 @@ import androidx.compose.ui.unit.dp
import coil.annotation.ExperimentalCoilApi
import coil.compose.rememberImagePainter
import io.github.mohamedisoliman.pixapay.R
-import io.github.mohamedisoliman.pixapay.domain.SearchState
-import io.github.mohamedisoliman.pixapay.domain.SearchState.*
+import io.github.mohamedisoliman.pixapay.UiState
+import io.github.mohamedisoliman.pixapay.data.entities.ImageModel
+import io.github.mohamedisoliman.pixapay.domain.search.SearchState
import io.github.mohamedisoliman.pixapay.ui.common.ImageChips
import io.github.mohamedisoliman.pixapay.ui.common.isPortrait
import io.github.mohamedisoliman.pixapay.ui.common.toUiModel
-import io.github.mohamedisoliman.pixapay.data.entities.ImageModel
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.FlowPreview
@Preview
@Composable
fun PreviewSearch() {
- SearchScreenContent(searchState = Empty)
+ SearchScreenContent()
}
+@OptIn(FlowPreview::class)
+@ExperimentalCoroutinesApi
@Composable
-fun SearchScreen(viewModel: SearchImagesViewModel, onNavigateClicked: (Long) -> Unit = { }) {
- val viewState by viewModel.searchViewState.collectAsState()
- val query by viewModel.searchQueryState.collectAsState()
+fun SearchScreen(viewModel: SearchImagesViewModel) {
+ val viewState by viewModel.states.collectAsState()
+ var showDialog by remember { mutableStateOf(false) }
+ var imageId by remember { mutableStateOf(-1L) }
SearchScreenContent(
- onImageClicked = onNavigateClicked,
- searchText = query,
- searchState = viewState,
- onSearchChange = { viewModel.onSearchChange(it) },
- onSearchClicked = { viewModel.onSearchClicked() }
+ searchText = viewModel.query.value,
+ searchMainView = {
+ viewState.StateToMainView(showDialog = {
+ showDialog = it
+ }, imageId = {
+ imageId = it
+ })
+
+ },
+ onSearchChange = { viewModel.onSearchQueryChange(it) },
+ onSearchClicked = { viewModel.onSearchClicked() },
+ isLoading = viewState.isLoading
)
+
+ ConfirmationDialog(showDialog = showDialog, onConfirm = {
+ showDialog = false
+ viewModel.navigateToDetails(imageId)
+ }) {
+ showDialog = false
+ }
+}
+
+@Composable
+private fun UiState.StateToMainView(
+ imageId: (Long) -> Unit,
+ showDialog: (Boolean) -> Unit,
+) {
+ when (this) {
+ is SearchState.EmptyResult -> EmptyView()
+ is SearchState.Error -> this.throwable?.let { ErrorView(it) }
+ is SearchState.Success -> this.result?.let { list ->
+ ImageListView(list) {
+ imageId(it)
+ showDialog(true)
+ }
+ }
+ else -> {
+ }
+ }
}
@Composable
fun ConfirmationDialog(
- showDialog: Boolean,
+ showDialog: Boolean = false,
onConfirm: () -> Unit,
onDismiss: () -> Unit,
) {
- if (showDialog) {
+ if (showDialog)
AlertDialog(
modifier = Modifier.fillMaxWidth(),
title = { Text(stringResource(R.string.dialog_title_open_image_details)) },
@@ -86,55 +124,37 @@ fun ConfirmationDialog(
}
}
)
- }
}
@OptIn(ExperimentalComposeUiApi::class)
@Composable
private fun SearchScreenContent(
searchText: String = "",
+ isLoading: Boolean = false,
onSearchChange: (String) -> Unit = {},
onSearchClicked: () -> Unit = {},
- searchState: SearchState = Empty,
- onImageClicked: (Long) -> Unit = {},
+ searchMainView: @Composable () -> Unit = {},
) {
- var showDialog by remember { mutableStateOf(false) }
- var imageId by remember { mutableStateOf(-1L) }
-
Box(
modifier = Modifier.padding(top = 8.dp, start = 8.dp, end = 8.dp),
contentAlignment = Alignment.TopCenter
) {
- when (searchState) {
- is Success -> ImageListView(searchState.images) {
- imageId = it
- showDialog = true
- }
- is Empty -> EmptyView(modifier = Modifier.align(Alignment.Center))
- is Error -> ErrorView(searchState.throwable)
- }
+ searchMainView()
SearchTopbar(
searchText = searchText,
onSearchChange = onSearchChange,
onSearchClicked = onSearchClicked,
- isLoading = searchState is Loading,
+ isLoading = isLoading,
)
}
- ConfirmationDialog(showDialog = showDialog, onConfirm = {
- showDialog = false
- onImageClicked(imageId)
- }) {
- showDialog = false
- }
-
}
@Composable
-private fun EmptyView(modifier: Modifier) {
+fun EmptyView(modifier: Modifier = Modifier) {
PlaceHolderView(
modifier = modifier,
icon = Icons.Outlined.Pets,
@@ -188,7 +208,7 @@ fun ErrorView(
@OptIn(ExperimentalFoundationApi::class)
@Composable
-private fun ImageListView(
+fun ImageListView(
images: List,
onImageClicked: (Long) -> Unit,
) {
diff --git a/app/src/main/java/io/github/mohamedisoliman/pixapay/ui/search/SearchScreenEvent.kt b/app/src/main/java/io/github/mohamedisoliman/pixapay/ui/search/SearchScreenEvent.kt
new file mode 100644
index 0000000..1fa7735
--- /dev/null
+++ b/app/src/main/java/io/github/mohamedisoliman/pixapay/ui/search/SearchScreenEvent.kt
@@ -0,0 +1,15 @@
+package io.github.mohamedisoliman.pixapay.ui.search
+
+import io.github.mohamedisoliman.pixapay.UiEvent
+
+
+sealed class SearchScreenEvent : UiEvent
+
+data class SearchClicked(val query: String) : SearchScreenEvent()
+
+data class SearchQueryChanged(val query: String) : SearchScreenEvent()
+
+data class SearchQueryUpdated(val query: String) : SearchScreenEvent()
+
+
+
diff --git a/app/src/test/java/io/github/mohamedisoliman/pixapay/domain/SearchUsecaseTest.kt b/app/src/test/java/io/github/mohamedisoliman/pixapay/domain/SearchUsecaseTest.kt
index b877e69..7c73f23 100644
--- a/app/src/test/java/io/github/mohamedisoliman/pixapay/domain/SearchUsecaseTest.kt
+++ b/app/src/test/java/io/github/mohamedisoliman/pixapay/domain/SearchUsecaseTest.kt
@@ -1,7 +1,9 @@
package io.github.mohamedisoliman.pixapay.domain
-import io.github.mohamedisoliman.pixapay.data.ImagesRepositoryContract
+import io.github.mohamedisoliman.pixapay.domain.search.ImagesRepositoryContract
import io.github.mohamedisoliman.pixapay.data.entities.PixabayImage
+import io.github.mohamedisoliman.pixapay.domain.search.SearchState
+import io.github.mohamedisoliman.pixapay.domain.search.SearchUsecase
import io.mockk.mockk
import kotlinx.coroutines.flow.*
import kotlinx.coroutines.runBlocking
@@ -12,13 +14,13 @@ class SearchUsecaseTest {
@Test
- fun `search() Then start Loading`() = runBlocking {
+ fun `search() THEN start with Loading state`() = runBlocking {
val hit = mockk()
val repository = mockRepository(flowOf(listOf(hit, hit, hit)))
val result = SearchUsecase(repository).invoke("flowers").first()
- assert((result is SearchState.Loading))
+ assert(result is SearchState.Loading)
}
@Test
@@ -39,7 +41,7 @@ class SearchUsecaseTest {
val result = SearchUsecase(repository).invoke("flowers").last()
- assert((result is SearchState.Empty))
+ assert((result is SearchState.EmptyResult))
}
@Test
@@ -51,7 +53,7 @@ class SearchUsecaseTest {
val result = SearchUsecase(repository).invoke("flowers").last()
- assert((result is SearchState.Success) && result.images.size == 3)
+ assert((result is SearchState.Success) && result.result?.size == 3)
}