diff --git a/iosApp/iosApp.xcodeproj/project.pbxproj b/iosApp/iosApp.xcodeproj/project.pbxproj index af51353..e16c14e 100644 --- a/iosApp/iosApp.xcodeproj/project.pbxproj +++ b/iosApp/iosApp.xcodeproj/project.pbxproj @@ -353,7 +353,7 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CODE_SIGN_ENTITLEMENTS = iosApp/iosApp.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; - "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Manual; CURRENT_PROJECT_VERSION = 3; DEVELOPMENT_ASSET_PATHS = "\"iosApp/Preview Content\""; @@ -373,7 +373,7 @@ "PRODUCT_BUNDLE_IDENTIFIER[sdk=iphoneos*]" = org.mixdrinks.app9LJW42P73L; PRODUCT_NAME = "${APP_NAME}"; PROVISIONING_PROFILE_SPECIFIER = ""; - "PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = "After add domains"; + "PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = "After add domains (Development)"; SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; SUPPORTS_MACCATALYST = NO; SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO; diff --git a/iosApp/iosApp/ContentView.swift b/iosApp/iosApp/ContentView.swift index 7bb7815..7f7ec2d 100644 --- a/iosApp/iosApp/ContentView.swift +++ b/iosApp/iosApp/ContentView.swift @@ -5,6 +5,7 @@ import shared struct ComposeView: UIViewControllerRepresentable { func makeUIViewController(context: Context) -> UIViewController { let controller = Main_iosKt.MainViewController() + controller.navigationController?.interactivePopGestureRecognizer?.isEnabled = false return controller } diff --git a/shared/src/commonMain/kotlin/org/mixdrinks/app/MixDrinksApp.kt b/shared/src/commonMain/kotlin/org/mixdrinks/app/MixDrinksApp.kt index 73787ad..8248fb1 100644 --- a/shared/src/commonMain/kotlin/org/mixdrinks/app/MixDrinksApp.kt +++ b/shared/src/commonMain/kotlin/org/mixdrinks/app/MixDrinksApp.kt @@ -3,13 +3,16 @@ package org.mixdrinks.app import androidx.compose.runtime.Composable import androidx.compose.runtime.remember import com.arkivanov.decompose.DefaultComponentContext +import org.mixdrinks.ui.ComponentsFactory +import org.mixdrinks.ui.Graph import org.mixdrinks.ui.RootComponent import org.mixdrinks.ui.RootContent @Composable internal fun MixDrinksApp(contextComponent: DefaultComponentContext, deepLink: String?) { val rootComponent = remember { - RootComponent(contextComponent) + val graph = Graph() + RootComponent(contextComponent, graph, ComponentsFactory(graph)) } RootContent(rootComponent, deepLink) diff --git a/shared/src/commonMain/kotlin/org/mixdrinks/data/CocktailsProvider.kt b/shared/src/commonMain/kotlin/org/mixdrinks/data/CocktailsProvider.kt index fcc1fca..228b9dd 100644 --- a/shared/src/commonMain/kotlin/org/mixdrinks/data/CocktailsProvider.kt +++ b/shared/src/commonMain/kotlin/org/mixdrinks/data/CocktailsProvider.kt @@ -5,11 +5,11 @@ import kotlinx.coroutines.flow.map import org.mixdrinks.domain.CocktailSelector import org.mixdrinks.domain.ImageUrlCreators import org.mixdrinks.dto.CocktailId -import org.mixdrinks.dto.SnapshotDto +import org.mixdrinks.dto.TagDto import org.mixdrinks.ui.list.FilterObserver internal class CocktailsProvider( - private val snapshot: suspend () -> SnapshotDto, + private val snapshotRepository: SnapshotRepository, private val filterRepository: FilterObserver, private val cocktailSelector: suspend () -> CocktailSelector, private val tagsRepository: TagsRepository, @@ -19,7 +19,7 @@ internal class CocktailsProvider( val id: CocktailId, val url: String, val name: String, - val tags: List, + val tags: List, ) suspend fun getCocktails(): Flow> { @@ -27,13 +27,14 @@ internal class CocktailsProvider( val notEmptyFilter = it.filter { filterGroup -> filterGroup.value.isNotEmpty() } if (notEmptyFilter.isEmpty()) { - snapshot().cocktails + snapshotRepository.get().cocktails } else { val notEmptyFilterIds = notEmptyFilter .mapValues { filterGroupIdListEntry -> filterGroupIdListEntry.value.map { it.filterId } } val ids = cocktailSelector().getCocktailIds(notEmptyFilterIds) - snapshot().cocktails.filter { cocktailDto -> ids.contains(cocktailDto.id) } + snapshotRepository.get().cocktails + .filter { cocktailDto -> ids.contains(cocktailDto.id) } } .map { cocktailDto -> Cocktail( @@ -43,7 +44,7 @@ internal class CocktailsProvider( ImageUrlCreators.Size.SIZE_400 ), name = cocktailDto.name, - tags = tagsRepository.getTags(cocktailDto.tags).map { it.name } + tags = tagsRepository.getTags(cocktailDto.tags) ) } } diff --git a/shared/src/commonMain/kotlin/org/mixdrinks/data/DetailGoods.kt b/shared/src/commonMain/kotlin/org/mixdrinks/data/DetailGoods.kt index 877cf55..f9488f0 100644 --- a/shared/src/commonMain/kotlin/org/mixdrinks/data/DetailGoods.kt +++ b/shared/src/commonMain/kotlin/org/mixdrinks/data/DetailGoods.kt @@ -23,10 +23,6 @@ data class ItemsType( TOOL -> FilterGroups.TOOLS } } - - companion object { - fun fromString(value: String) = Type.values().first { it.toString() == value } - } } } diff --git a/shared/src/commonMain/kotlin/org/mixdrinks/data/TagsRepository.kt b/shared/src/commonMain/kotlin/org/mixdrinks/data/TagsRepository.kt index eb32cf3..e9d185c 100644 --- a/shared/src/commonMain/kotlin/org/mixdrinks/data/TagsRepository.kt +++ b/shared/src/commonMain/kotlin/org/mixdrinks/data/TagsRepository.kt @@ -1,14 +1,13 @@ package org.mixdrinks.data -import org.mixdrinks.dto.SnapshotDto import org.mixdrinks.dto.TagDto import org.mixdrinks.dto.TagId -class TagsRepository( - private val snapshot: suspend () -> SnapshotDto, +internal class TagsRepository( + private val snapshotRepository: SnapshotRepository, ) { suspend fun getTags(tagId: List): List { - return snapshot().tags.filter { tagDto -> tagId.contains(tagDto.id) } + return snapshotRepository.get().tags.filter { tagDto -> tagId.contains(tagDto.id) } } } diff --git a/shared/src/commonMain/kotlin/org/mixdrinks/ui/ComponentsFactory.kt b/shared/src/commonMain/kotlin/org/mixdrinks/ui/ComponentsFactory.kt new file mode 100644 index 0000000..b5bd937 --- /dev/null +++ b/shared/src/commonMain/kotlin/org/mixdrinks/ui/ComponentsFactory.kt @@ -0,0 +1,173 @@ +package org.mixdrinks.ui + +import com.arkivanov.decompose.ComponentContext +import org.mixdrinks.data.CocktailsProvider +import org.mixdrinks.data.FutureCocktailSelector +import org.mixdrinks.data.ItemsType +import org.mixdrinks.data.TagsRepository +import org.mixdrinks.domain.CocktailSelector +import org.mixdrinks.dto.CocktailId +import org.mixdrinks.dto.FilterId +import org.mixdrinks.ui.details.DetailsComponent +import org.mixdrinks.ui.details.FullCocktailRepository +import org.mixdrinks.ui.details.goods.GoodsRepository +import org.mixdrinks.ui.filters.main.FilterComponent +import org.mixdrinks.ui.filters.search.ItemRepository +import org.mixdrinks.ui.filters.search.SearchItemComponent +import org.mixdrinks.ui.items.ItemDetailComponent +import org.mixdrinks.ui.items.ItemGoodsRepository +import org.mixdrinks.ui.list.CocktailListMapper +import org.mixdrinks.ui.list.SelectedFilterProvider +import org.mixdrinks.ui.list.main.ListComponent +import org.mixdrinks.ui.list.predefine.PreDefineCocktailsComponent +import org.mixdrinks.ui.list.predefine.PreDefineFilterStorage +import org.mixdrinks.ui.navigation.Navigator +import org.mixdrinks.ui.tag.CommonTag +import org.mixdrinks.ui.tag.CommonTagCocktailsComponent +import org.mixdrinks.ui.tag.CommonTagNameProvider + +internal class ComponentsFactory( + private val graph: Graph +) { + + fun cocktailListComponent( + componentContext: ComponentContext, + navigator: Navigator + ): ListComponent = + ListComponent( + componentContext = componentContext, + cocktailsProvider = CocktailsProvider( + graph.snapshotRepository, + graph.mutableFilterStorage, + suspend { + CocktailSelector(graph.mutableFilterStorage.getFilterGroups() + .map { it.toFilterGroup() }) + }, + TagsRepository(graph.snapshotRepository), + ), + selectedFilterProvider = SelectedFilterProvider(suspend { graph.snapshotRepository.get() }, + suspend { graph.mutableFilterStorage }), + navigator = navigator, + mutableFilterStorage = graph.mutableFilterStorage, + cocktailListMapper = CocktailListMapper(), + ) + + fun cocktailDetailsComponent( + componentContext: ComponentContext, + cocktailId: CocktailId, + navigator: Navigator, + ): DetailsComponent { + return DetailsComponent(componentContext, + FullCocktailRepository(graph.snapshotRepository), + cocktailId, + navigator, + GoodsRepository { graph.snapshotRepository.get() }) + } + + fun filterScreenComponent( + componentContext: ComponentContext, + navigator: Navigator + ): FilterComponent { + return FilterComponent( + componentContext, + graph.mutableFilterStorage, + createFutureCocktailSelector(), + navigator, + ) + } + + fun searchItemScreen( + component: ComponentContext, + searchItemType: SearchItemComponent.SearchItemType, + navigator: Navigator, + ): SearchItemComponent { + val itemRepository = + ItemRepository(graph.snapshotRepository, createFutureCocktailSelector()) + return SearchItemComponent( + component, + searchItemType, + graph.mutableFilterStorage, + itemRepository, + navigator, + ) + } + + suspend fun commonTagCocktailsScreen( + component: ComponentContext, + commonTag: CommonTag, + navigator: Navigator, + ): CommonTagCocktailsComponent { + return CommonTagCocktailsComponent( + componentContext = component, + commonTagNameProvider = CommonTagNameProvider( + snapshotRepository = graph.snapshotRepository, + ), + cocktailsProvider = CocktailsProvider( + snapshotRepository = graph.snapshotRepository, + filterRepository = PreDefineFilterStorage( + commonTag.type.filterGroups.id, + FilterId(commonTag.id) + ), + cocktailSelector = { + CocktailSelector(graph.mutableFilterStorage.getFilterGroups() + .map { it.toFilterGroup() }) + }, + tagsRepository = TagsRepository(graph.snapshotRepository), + ), + commonTag = commonTag, + navigator = navigator, + commonCocktailListMapper = CocktailListMapper(), + ) + } + + fun detailGoodsScreen( + componentContext: ComponentContext, navigator: Navigator, id: Int, type: String, + ): ItemDetailComponent { + return ItemDetailComponent( + componentContext, + ItemGoodsRepository(graph.snapshotRepository), + navigator, + ItemsType(id, ItemsType.Type.valueOf(type)), + createPredefinedCocktailsComponent( + componentContext, + ItemsType(id, ItemsType.Type.valueOf(type)), + navigator, + ), + ) + } + + fun createPredefinedCocktailsComponent( + componentContext: ComponentContext, + itemsType: ItemsType, + navigator: Navigator, + ): PreDefineCocktailsComponent { + return PreDefineCocktailsComponent( + componentContext = componentContext, + cocktailsProvider = CocktailsProvider( + snapshotRepository = graph.snapshotRepository, + filterRepository = PreDefineFilterStorage( + itemsType.type.getFilterGroup().id, + FilterId(itemsType.id) + ), + cocktailSelector = suspend { + CocktailSelector(graph.mutableFilterStorage.getFilterGroups() + .map { it.toFilterGroup() }) + }, + tagsRepository = TagsRepository(graph.snapshotRepository) + ), + navigator = navigator, + cocktailsMapper = CocktailListMapper(), + ) + } + + private fun createFutureCocktailSelector(): FutureCocktailSelector { + return FutureCocktailSelector( + snapshot = { graph.snapshotRepository.get() }, + cocktailSelector = { + CocktailSelector(graph.mutableFilterStorage.getFilterGroups() + .map { it.toFilterGroup() }) + }, + mutableFilterStorage = { graph.mutableFilterStorage }, + ) + } +} diff --git a/shared/src/commonMain/kotlin/org/mixdrinks/ui/RootComponent.kt b/shared/src/commonMain/kotlin/org/mixdrinks/ui/RootComponent.kt index 5cfe752..c4a1229 100644 --- a/shared/src/commonMain/kotlin/org/mixdrinks/ui/RootComponent.kt +++ b/shared/src/commonMain/kotlin/org/mixdrinks/ui/RootComponent.kt @@ -14,34 +14,23 @@ import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.coroutineScope import kotlinx.coroutines.launch import kotlinx.serialization.json.Json -import org.mixdrinks.data.CocktailsProvider -import org.mixdrinks.data.FutureCocktailSelector -import org.mixdrinks.data.ItemsType import org.mixdrinks.data.MixDrinksService import org.mixdrinks.data.SnapshotRepository -import org.mixdrinks.data.TagsRepository -import org.mixdrinks.domain.CocktailSelector import org.mixdrinks.domain.FilterPathParser import org.mixdrinks.dto.CocktailId -import org.mixdrinks.dto.FilterId import org.mixdrinks.ui.details.DetailsComponent -import org.mixdrinks.ui.details.FullCocktailRepository -import org.mixdrinks.ui.details.goods.GoodsRepository import org.mixdrinks.ui.filters.main.FilterComponent -import org.mixdrinks.ui.filters.search.ItemRepository import org.mixdrinks.ui.filters.search.SearchItemComponent -import org.mixdrinks.ui.goods.ItemDetailComponent -import org.mixdrinks.ui.goods.ItemGoodsRepository -import org.mixdrinks.ui.list.SelectedFilterProvider +import org.mixdrinks.ui.items.ItemDetailComponent import org.mixdrinks.ui.list.main.ListComponent import org.mixdrinks.ui.list.main.MutableFilterStorage -import org.mixdrinks.ui.list.predefine.PreDefineCocktailsComponent -import org.mixdrinks.ui.list.predefine.PreDefineFilterStorage import org.mixdrinks.ui.navigation.DeepLinkParser import org.mixdrinks.ui.navigation.Navigator +import org.mixdrinks.ui.tag.CommonTag +import org.mixdrinks.ui.tag.CommonTagCocktailsComponent import org.mixdrinks.ui.widgets.undomain.launch -internal object Graph { +internal class Graph { private val settings: Settings = Settings() @@ -50,11 +39,15 @@ internal object Graph { ignoreUnknownKeys = true } - private val ktorfit = Ktorfit.Builder().httpClient(HttpClient { - install(ContentNegotiation) { - json(json) - } - }).baseUrl("https://api.mixdrinks.org/").build().create() + private val ktorfit = Ktorfit.Builder() + .httpClient(HttpClient { + install(ContentNegotiation) { + json(json) + } + }) + .baseUrl("https://api.mixdrinks.org/") + .build() + .create() val snapshotRepository: SnapshotRepository = SnapshotRepository(ktorfit, settings, json) @@ -63,6 +56,8 @@ internal object Graph { internal class RootComponent( componentContext: ComponentContext, + private val graph: Graph, + private val componentsFactory: ComponentsFactory, ) : ComponentContext by componentContext { private val navigation = StackNavigation() @@ -79,7 +74,7 @@ internal class RootComponent( val stack: Value> = _stack private val deepLinkParser = DeepLinkParser( - suspend { Graph.snapshotRepository.get() }, + suspend { graph.snapshotRepository.get() }, FilterPathParser(), ) @@ -92,7 +87,7 @@ internal class RootComponent( ) is DeepLinkParser.DeepLinkAction.Filters -> { - Graph.mutableFilterStorage.selectMany(deepLinkAction.selectedFilters) + graph.mutableFilterStorage.selectMany(deepLinkAction.selectedFilters) Navigator.Config.ListConfig() } } @@ -110,126 +105,46 @@ internal class RootComponent( navigator.back() } - fun buildPreDefineCocktailsComponent( - componentContext: ComponentContext, - itemsType: ItemsType, - ): PreDefineCocktailsComponent { - return PreDefineCocktailsComponent( - componentContext = componentContext, - cocktailsProvider = CocktailsProvider( - snapshot = suspend { Graph.snapshotRepository.get() }, - filterRepository = PreDefineFilterStorage( - itemsType.type.getFilterGroup().id, - FilterId(itemsType.id) - ), - cocktailSelector = suspend { - CocktailSelector(Graph.mutableFilterStorage.getFilterGroups() - .map { it.toFilterGroup() }) - }, - tagsRepository = TagsRepository(suspend { Graph.snapshotRepository.get() }) - ), - navigator = navigator, - ) - } - private fun createChild(config: Navigator.Config, componentContext: ComponentContext): Child = when (config) { - is Navigator.Config.ListConfig -> Child.List(listScreen(componentContext)) + is Navigator.Config.ListConfig -> Child.List( + componentsFactory.cocktailListComponent(componentContext, navigator) + ) + is Navigator.Config.DetailsConfig -> Child.Details( - detailsScreen( + componentsFactory.cocktailDetailsComponent( componentContext, - config + CocktailId(config.id), + navigator ) ) - is Navigator.Config.FilterConfig -> Child.Filters(filterScreen(componentContext)) + is Navigator.Config.FilterConfig -> Child.Filters( + componentsFactory.filterScreenComponent(componentContext, navigator) + ) + is Navigator.Config.SearchItemConfig -> Child.ItemSearch( - searchItemScreen( - componentContext, config.searchItemType + componentsFactory.searchItemScreen( + componentContext, + config.searchItemType, + navigator ) ) is Navigator.Config.ItemConfig -> Child.Item( - detailGoodsScreen( - componentContext, config.id, config.typeGoods + componentsFactory.detailGoodsScreen( + componentContext, navigator, config.id, config.typeGoods ) ) - } - private fun listScreen(componentContext: ComponentContext): ListComponent = ListComponent( - componentContext = componentContext, - cocktailsProvider = CocktailsProvider( - suspend { Graph.snapshotRepository.get() }, - Graph.mutableFilterStorage, - suspend { - CocktailSelector(Graph.mutableFilterStorage.getFilterGroups() - .map { it.toFilterGroup() }) - }, - TagsRepository(suspend { Graph.snapshotRepository.get() }), - ), - selectedFilterProvider = SelectedFilterProvider(suspend { Graph.snapshotRepository.get() }, - suspend { Graph.mutableFilterStorage }), - navigator = navigator, - mutableFilterStorage = Graph.mutableFilterStorage, - ) - - private fun detailGoodsScreen( - componentContext: ComponentContext, id: Int, type: String - ): ItemDetailComponent { - return ItemDetailComponent( - componentContext, - ItemGoodsRepository { Graph.snapshotRepository.get() }, - navigator, - ItemsType(id, ItemsType.Type.fromString(type)), - this, - ) - } - - private fun detailsScreen( - componentContext: ComponentContext, config: Navigator.Config.DetailsConfig - ): DetailsComponent { - return DetailsComponent(componentContext, - FullCocktailRepository { Graph.snapshotRepository.get() }, - CocktailId(config.id), - navigator, - GoodsRepository { Graph.snapshotRepository.get() }) - } - - private fun filterScreen(componentContext: ComponentContext): FilterComponent { - return FilterComponent( - componentContext, - Graph.mutableFilterStorage, - getFutureCocktail(), - navigator, - ) - } - - private fun getFutureCocktail(): FutureCocktailSelector { - return FutureCocktailSelector( - snapshot = { Graph.snapshotRepository.get() }, - cocktailSelector = { - CocktailSelector(Graph.mutableFilterStorage.getFilterGroups() - .map { it.toFilterGroup() }) - }, - mutableFilterStorage = { Graph.mutableFilterStorage }, - ) - } - - private fun searchItemScreen( - component: ComponentContext, - searchItemType: SearchItemComponent.SearchItemType, - ): SearchItemComponent { - val itemRepository = ItemRepository( - suspend { Graph.snapshotRepository.get() }, getFutureCocktail() - ) - return SearchItemComponent( - component, - searchItemType, - Graph.mutableFilterStorage, - itemRepository, - navigation, - ) - } + is Navigator.Config.CommonTagConfig -> Child.CommonTagCocktails( + suspend { + componentsFactory.commonTagCocktailsScreen( + componentContext, CommonTag(config.id, config.type), navigator + ) + } + ) + } sealed class Child { class List(val component: ListComponent) : Child() @@ -237,5 +152,6 @@ internal class RootComponent( class Filters(val component: FilterComponent) : Child() class Item(val component: ItemDetailComponent) : Child() class ItemSearch(val component: SearchItemComponent) : Child() + class CommonTagCocktails(val component: suspend () -> CommonTagCocktailsComponent) : Child() } } diff --git a/shared/src/commonMain/kotlin/org/mixdrinks/ui/RootContent.kt b/shared/src/commonMain/kotlin/org/mixdrinks/ui/RootContent.kt index e359720..e4405ba 100644 --- a/shared/src/commonMain/kotlin/org/mixdrinks/ui/RootContent.kt +++ b/shared/src/commonMain/kotlin/org/mixdrinks/ui/RootContent.kt @@ -17,8 +17,9 @@ import com.arkivanov.decompose.extensions.compose.jetbrains.stack.animation.stac import org.mixdrinks.ui.details.DetailView import org.mixdrinks.ui.filters.main.FilterView import org.mixdrinks.ui.filters.search.SearchItemView -import org.mixdrinks.ui.goods.ItemDetailsView +import org.mixdrinks.ui.items.ItemDetailsView import org.mixdrinks.ui.list.main.MutableCocktailList +import org.mixdrinks.ui.tag.TagCocktails @Composable internal fun RootContent(component: RootComponent, deepLink: String?) { @@ -32,6 +33,8 @@ internal fun RootContent(component: RootComponent, deepLink: String?) { } else { Offset.Infinite } + + println("Start $lastTouch") }, onDragEnd = { lastTouch = Offset.Infinite @@ -57,6 +60,7 @@ internal fun RootContent(component: RootComponent, deepLink: String?) { is RootComponent.Child.Details -> DetailView(child.component) is RootComponent.Child.Filters -> FilterView(child.component) is RootComponent.Child.ItemSearch -> SearchItemView(child.component) + is RootComponent.Child.CommonTagCocktails -> TagCocktails(child.component) } } ) @@ -67,4 +71,4 @@ internal fun RootContent(component: RootComponent, deepLink: String?) { } } -private const val CLOSE_ANIMATION_DURACIOTN_TRIGGER = 300 +private const val CLOSE_ANIMATION_DURACIOTN_TRIGGER = 100 diff --git a/shared/src/commonMain/kotlin/org/mixdrinks/ui/details/DetailsComponent.kt b/shared/src/commonMain/kotlin/org/mixdrinks/ui/details/DetailsComponent.kt index edc8a8e..df90a11 100644 --- a/shared/src/commonMain/kotlin/org/mixdrinks/ui/details/DetailsComponent.kt +++ b/shared/src/commonMain/kotlin/org/mixdrinks/ui/details/DetailsComponent.kt @@ -3,8 +3,6 @@ package org.mixdrinks.ui.details import androidx.compose.ui.text.capitalize import androidx.compose.ui.text.intl.Locale import com.arkivanov.decompose.ComponentContext -import com.arkivanov.decompose.router.stack.pop -import com.arkivanov.decompose.router.stack.push import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.flow @@ -19,7 +17,6 @@ import org.mixdrinks.dto.GoodId import org.mixdrinks.dto.TagId import org.mixdrinks.dto.TasteId import org.mixdrinks.dto.ToolId -import org.mixdrinks.ui.RootComponent import org.mixdrinks.ui.details.goods.GoodsRepository import org.mixdrinks.ui.details.goods.GoodsSubComponent import org.mixdrinks.ui.navigation.Navigator @@ -90,13 +87,12 @@ internal class DetailsComponent( navigator.back() } - @Suppress("EmptyFunctionBlock", "UnusedPrivateMember") fun onTagClick(tagId: TagId) { + navigator.navigateToTagCocktails(tagId) } - @Suppress("EmptyFunctionBlock", "UnusedPrivateMember") fun onTasteClick(tasteId: TasteId) { - + navigator.navigationToTasteCocktails(tasteId) } fun onGoodClick(goodId: GoodId) { diff --git a/shared/src/commonMain/kotlin/org/mixdrinks/ui/details/FullCocktailRepository.kt b/shared/src/commonMain/kotlin/org/mixdrinks/ui/details/FullCocktailRepository.kt index c2dd3b3..3f6f990 100644 --- a/shared/src/commonMain/kotlin/org/mixdrinks/ui/details/FullCocktailRepository.kt +++ b/shared/src/commonMain/kotlin/org/mixdrinks/ui/details/FullCocktailRepository.kt @@ -1,31 +1,32 @@ package org.mixdrinks.ui.details import org.mixdrinks.data.FullCocktail +import org.mixdrinks.data.SnapshotRepository import org.mixdrinks.dto.CocktailId -import org.mixdrinks.dto.SnapshotDto internal class FullCocktailRepository( - private val snapshot: suspend () -> SnapshotDto, + private val snapshotRepository: SnapshotRepository, ) { suspend fun getFullCocktail(cocktailId: CocktailId): FullCocktail? { - val cocktail = snapshot().cocktails.find { it.id == cocktailId } ?: return null + val snapshot = snapshotRepository.get() + val cocktail = snapshot.cocktails.find { it.id == cocktailId } ?: return null - val tools = snapshot().tools.filter { cocktail.tools.contains(it.id) } + val tools = snapshot.tools.filter { cocktail.tools.contains(it.id) } .map { FullCocktail.Tool( toolId = it.id, name = it.name, ) } - val tastes = snapshot().tastes.filter { cocktail.tastes.contains(it.id) } + val tastes = snapshot.tastes.filter { cocktail.tastes.contains(it.id) } .map { FullCocktail.Taste( id = it.id, name = it.name, ) } - val tags = snapshot().tags.filter { cocktail.tags.contains(it.id) } + val tags = snapshot.tags.filter { cocktail.tags.contains(it.id) } .map { FullCocktail.Tag( id = it.id, @@ -33,7 +34,7 @@ internal class FullCocktailRepository( ) } - val glassware = snapshot().glassware.find { it.id == cocktail.glassware } + val glassware = snapshot.glassware.find { it.id == cocktail.glassware } ?: error("Cannot found glassware for cocktail") return FullCocktail( diff --git a/shared/src/commonMain/kotlin/org/mixdrinks/ui/details/goods/GoodsView.kt b/shared/src/commonMain/kotlin/org/mixdrinks/ui/details/goods/GoodsView.kt index 444ab81..3ad72cf 100644 --- a/shared/src/commonMain/kotlin/org/mixdrinks/ui/details/goods/GoodsView.kt +++ b/shared/src/commonMain/kotlin/org/mixdrinks/ui/details/goods/GoodsView.kt @@ -1,7 +1,6 @@ package org.mixdrinks.ui.details.goods import androidx.compose.foundation.Image -import androidx.compose.foundation.border import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column diff --git a/shared/src/commonMain/kotlin/org/mixdrinks/ui/filters/search/ItemRepository.kt b/shared/src/commonMain/kotlin/org/mixdrinks/ui/filters/search/ItemRepository.kt index fc87b35..c45548e 100644 --- a/shared/src/commonMain/kotlin/org/mixdrinks/ui/filters/search/ItemRepository.kt +++ b/shared/src/commonMain/kotlin/org/mixdrinks/ui/filters/search/ItemRepository.kt @@ -1,6 +1,7 @@ package org.mixdrinks.ui.filters.search import org.mixdrinks.data.FutureCocktailSelector +import org.mixdrinks.data.SnapshotRepository import org.mixdrinks.domain.FilterGroups import org.mixdrinks.dto.FilterId import org.mixdrinks.dto.GoodId @@ -8,7 +9,7 @@ import org.mixdrinks.dto.SnapshotDto import org.mixdrinks.dto.ToolId internal class ItemRepository( - private val snapshot: suspend () -> SnapshotDto, + private val snapshotRepository: SnapshotRepository, private val futureCocktailSelector: FutureCocktailSelector, ) { @@ -20,7 +21,7 @@ internal class ItemRepository( } private suspend fun getTools(): List { - return snapshot().tools + return snapshotRepository.get().tools .map { ItemDto( id = ItemId.Tool(it.id), @@ -34,7 +35,7 @@ internal class ItemRepository( } private suspend fun getGoods(): List { - return snapshot().goods + return snapshotRepository.get().goods .map { ItemDto( id = ItemId.Good(it.id), diff --git a/shared/src/commonMain/kotlin/org/mixdrinks/ui/filters/search/SearchItemComponent.kt b/shared/src/commonMain/kotlin/org/mixdrinks/ui/filters/search/SearchItemComponent.kt index 0d11df3..1ef2824 100644 --- a/shared/src/commonMain/kotlin/org/mixdrinks/ui/filters/search/SearchItemComponent.kt +++ b/shared/src/commonMain/kotlin/org/mixdrinks/ui/filters/search/SearchItemComponent.kt @@ -2,8 +2,6 @@ package org.mixdrinks.ui.filters.search import androidx.compose.runtime.Immutable import com.arkivanov.decompose.ComponentContext -import com.arkivanov.decompose.router.stack.StackNavigation -import com.arkivanov.decompose.router.stack.pop import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow @@ -11,12 +9,11 @@ import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.flowOn import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.transform -import org.mixdrinks.ui.list.main.MutableFilterStorage import org.mixdrinks.domain.FilterGroups import org.mixdrinks.domain.ImageUrlCreators import org.mixdrinks.dto.FilterGroupId import org.mixdrinks.dto.FilterId -import org.mixdrinks.ui.RootComponent +import org.mixdrinks.ui.list.main.MutableFilterStorage import org.mixdrinks.ui.navigation.Navigator import org.mixdrinks.ui.widgets.undomain.UiState import org.mixdrinks.ui.widgets.undomain.launch @@ -27,7 +24,7 @@ internal class SearchItemComponent( private val searchItemType: SearchItemType, private val mutableFilterStorage: MutableFilterStorage, private val itemRepository: ItemRepository, - private val navigation: StackNavigation, + private val navigator: Navigator, ) : ComponentContext by componentContext { private val _textState = MutableStateFlow("") @@ -97,7 +94,7 @@ internal class SearchItemComponent( } fun close() { - navigation.pop() + navigator.back() } fun onSearchQueryChanged(query: String) = launch { @@ -105,7 +102,11 @@ internal class SearchItemComponent( } fun onItemClicked(id: ItemRepository.ItemId, isSelected: Boolean) = launch { - mutableFilterStorage.onValueChange(searchItemType.filterGroupId, FilterId(id.value), isSelected) + mutableFilterStorage.onValueChange( + searchItemType.filterGroupId, + FilterId(id.value), + isSelected + ) } @Immutable diff --git a/shared/src/commonMain/kotlin/org/mixdrinks/ui/goods/ItemDetailComponent.kt b/shared/src/commonMain/kotlin/org/mixdrinks/ui/items/ItemDetailComponent.kt similarity index 79% rename from shared/src/commonMain/kotlin/org/mixdrinks/ui/goods/ItemDetailComponent.kt rename to shared/src/commonMain/kotlin/org/mixdrinks/ui/items/ItemDetailComponent.kt index c96a131..e2c6fcc 100644 --- a/shared/src/commonMain/kotlin/org/mixdrinks/ui/goods/ItemDetailComponent.kt +++ b/shared/src/commonMain/kotlin/org/mixdrinks/ui/items/ItemDetailComponent.kt @@ -1,4 +1,4 @@ -package org.mixdrinks.ui.goods +package org.mixdrinks.ui.items import com.arkivanov.decompose.ComponentContext import kotlinx.coroutines.Dispatchers @@ -22,7 +22,7 @@ internal class ItemDetailComponent( private val goodsRepository: ItemGoodsRepository, private val navigator: Navigator, private val itemsType: ItemsType, - public val rootComponent: RootComponent, + public val predefineCocktailComponent: PreDefineCocktailsComponent, ) : ComponentContext by componentContext { val state: StateFlow> = when (itemsType.type) { @@ -35,7 +35,7 @@ internal class ItemDetailComponent( } ItemsType.Type.GLASSWARE -> flow { - emit(goodsRepository.getGallsswareDetails(GlasswareId(itemsType.id))) + emit(goodsRepository.getGlasswareDetails(GlasswareId(itemsType.id))) } } .map { good: DetailGoodsUiModel -> @@ -47,11 +47,4 @@ internal class ItemDetailComponent( fun close() { navigator.back() } - - fun getPredefineCocktailComponent(): PreDefineCocktailsComponent { - return rootComponent.buildPreDefineCocktailsComponent( - componentContext = componentContext, - itemsType = itemsType, - ) - } } diff --git a/shared/src/commonMain/kotlin/org/mixdrinks/ui/goods/ItemDetailsView.kt b/shared/src/commonMain/kotlin/org/mixdrinks/ui/items/ItemDetailsView.kt similarity index 93% rename from shared/src/commonMain/kotlin/org/mixdrinks/ui/goods/ItemDetailsView.kt rename to shared/src/commonMain/kotlin/org/mixdrinks/ui/items/ItemDetailsView.kt index d13a8e5..e132c82 100644 --- a/shared/src/commonMain/kotlin/org/mixdrinks/ui/goods/ItemDetailsView.kt +++ b/shared/src/commonMain/kotlin/org/mixdrinks/ui/items/ItemDetailsView.kt @@ -1,4 +1,4 @@ -package org.mixdrinks.ui.goods +package org.mixdrinks.ui.items import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.Image @@ -52,7 +52,7 @@ internal fun ItemViewContent( good: DetailGoodsUiModel, component: ItemDetailComponent ) { - val predefineComponent = remember(good) { component.getPredefineCocktailComponent() } + val predefineComponent = remember(good) { component.predefineCocktailComponent } val cocktails by predefineComponent.state.collectAsState() LazyColumn { stickyHeader { @@ -96,7 +96,11 @@ internal fun ItemViewContent( text = "Коктейлі з ${good.name}", ) } - cocktailListInserter(cocktails, predefineComponent::onCocktailClick) + cocktailListInserter( + cocktails, + predefineComponent::navigateToDetails, + predefineComponent::navigateToTagCocktails + ) } } diff --git a/shared/src/commonMain/kotlin/org/mixdrinks/ui/goods/ItemGoodsRepository.kt b/shared/src/commonMain/kotlin/org/mixdrinks/ui/items/ItemGoodsRepository.kt similarity index 74% rename from shared/src/commonMain/kotlin/org/mixdrinks/ui/goods/ItemGoodsRepository.kt rename to shared/src/commonMain/kotlin/org/mixdrinks/ui/items/ItemGoodsRepository.kt index fbad659..493fdd7 100644 --- a/shared/src/commonMain/kotlin/org/mixdrinks/ui/goods/ItemGoodsRepository.kt +++ b/shared/src/commonMain/kotlin/org/mixdrinks/ui/items/ItemGoodsRepository.kt @@ -1,18 +1,18 @@ -package org.mixdrinks.ui.goods +package org.mixdrinks.ui.items import org.mixdrinks.data.DetailGoodsUiModel +import org.mixdrinks.data.SnapshotRepository import org.mixdrinks.domain.ImageUrlCreators import org.mixdrinks.dto.GlasswareId import org.mixdrinks.dto.GoodId -import org.mixdrinks.dto.SnapshotDto import org.mixdrinks.dto.ToolId internal class ItemGoodsRepository( - private val snapshot: suspend () -> SnapshotDto, + private val snapshot: SnapshotRepository, ) { suspend fun getGoodDetails(goodId: GoodId): DetailGoodsUiModel { - val good = snapshot().goods.find { it.id.id == goodId.id } + val good = snapshot.get().goods.find { it.id.id == goodId.id } ?: error("Goods ${goodId.id} not found") return DetailGoodsUiModel( good.id.id, good.name, good.about, @@ -24,7 +24,7 @@ internal class ItemGoodsRepository( } suspend fun getToolDetails(toolId: ToolId): DetailGoodsUiModel { - val tool = snapshot().tools.find { it.id.id == toolId.id } + val tool = snapshot.get().tools.find { it.id.id == toolId.id } ?: error("Tool ${toolId.id} not found") return DetailGoodsUiModel( tool.id.id, tool.name, tool.about, @@ -35,8 +35,8 @@ internal class ItemGoodsRepository( ) } - suspend fun getGallsswareDetails(glasswareId: GlasswareId): DetailGoodsUiModel { - val glassware = snapshot().glassware.find { it.id == glasswareId } + suspend fun getGlasswareDetails(glasswareId: GlasswareId): DetailGoodsUiModel { + val glassware = snapshot.get().glassware.find { it.id == glasswareId } ?: error("Glassware $glasswareId not found") return DetailGoodsUiModel( glassware.id.value, glassware.name, glassware.about, diff --git a/shared/src/commonMain/kotlin/org/mixdrinks/ui/list/CocktailList.kt b/shared/src/commonMain/kotlin/org/mixdrinks/ui/list/CocktailList.kt index eae2d37..bf9d413 100644 --- a/shared/src/commonMain/kotlin/org/mixdrinks/ui/list/CocktailList.kt +++ b/shared/src/commonMain/kotlin/org/mixdrinks/ui/list/CocktailList.kt @@ -25,6 +25,7 @@ import com.seiko.imageloader.rememberAsyncImagePainter import org.mixdrinks.app.styles.MixDrinksColors import org.mixdrinks.app.styles.MixDrinksTextStyles import org.mixdrinks.dto.CocktailId +import org.mixdrinks.dto.TagId import org.mixdrinks.ui.tag.Tag @OptIn(ExperimentalFoundationApi::class) @@ -32,10 +33,11 @@ import org.mixdrinks.ui.tag.Tag internal fun CocktailList( cocktails: CocktailsListState.Cocktails, onClick: (CocktailId) -> Unit, + onTagClick: (TagId) -> Unit, ) { LazyColumn { items(cocktails.list, key = { cocktail -> cocktail.id.id }) { cocktail -> - Cocktail(Modifier.animateItemPlacement(), cocktail, onClick) + Cocktail(Modifier.animateItemPlacement(), cocktail, onClick, onTagClick) } } } @@ -43,10 +45,11 @@ internal fun CocktailList( internal fun LazyListScope.cocktailListInserter( cocktails: CocktailsListState.Cocktails, onClick: (CocktailId) -> Unit, + onTagClick: (TagId) -> Unit, ) { cocktails.list.forEach { item(key = it.id.id) { - Cocktail(Modifier, it, onClick) + Cocktail(Modifier, it, onClick, onTagClick) } } } @@ -56,6 +59,7 @@ internal fun Cocktail( modifier: Modifier, cocktail: CocktailsListState.Cocktails.Cocktail, onClick: (CocktailId) -> Unit, + onTagClick : (TagId) -> Unit, ) { Card( modifier = modifier @@ -85,7 +89,7 @@ internal fun Cocktail( horizontalArrangement = Arrangement.spacedBy(4.dp) ) { items(cocktail.tags) { - Tag(it) {} + Tag(it, onTagClick) } } } diff --git a/shared/src/commonMain/kotlin/org/mixdrinks/ui/list/CocktailListMapper.kt b/shared/src/commonMain/kotlin/org/mixdrinks/ui/list/CocktailListMapper.kt new file mode 100644 index 0000000..5a4ca6d --- /dev/null +++ b/shared/src/commonMain/kotlin/org/mixdrinks/ui/list/CocktailListMapper.kt @@ -0,0 +1,26 @@ +package org.mixdrinks.ui.list + +import androidx.compose.ui.text.capitalize +import androidx.compose.ui.text.intl.Locale +import org.mixdrinks.data.CocktailsProvider +import org.mixdrinks.domain.ImageUrlCreators + +internal class CocktailListMapper { + + fun map(cocktails: List): List { + return cocktails.map { cocktail -> + CocktailsListState.Cocktails.Cocktail( + id = cocktail.id, + url = ImageUrlCreators.createUrl( + cocktail.id, ImageUrlCreators.Size.SIZE_400 + ), + name = cocktail.name, + tags = cocktail.tags.map { + CocktailsListState.TagUIModel( + it.id, it.name.capitalize(Locale.current) + ) + }, + ) + } + } +} diff --git a/shared/src/commonMain/kotlin/org/mixdrinks/ui/list/CocktailsListState.kt b/shared/src/commonMain/kotlin/org/mixdrinks/ui/list/CocktailsListState.kt index 2e204e4..8589aba 100644 --- a/shared/src/commonMain/kotlin/org/mixdrinks/ui/list/CocktailsListState.kt +++ b/shared/src/commonMain/kotlin/org/mixdrinks/ui/list/CocktailsListState.kt @@ -2,6 +2,7 @@ package org.mixdrinks.ui.list import androidx.compose.runtime.Immutable import org.mixdrinks.dto.CocktailId +import org.mixdrinks.dto.TagId import org.mixdrinks.ui.filters.FilterItemUiModel @Immutable @@ -15,10 +16,16 @@ internal sealed class CocktailsListState { val id: CocktailId, val url: String, val name: String, - val tags: List, + val tags: List, ) } + @Immutable + data class TagUIModel( + val id: TagId, + val name: String, + ) + @Immutable data class PlaceHolder( val filters: List, diff --git a/shared/src/commonMain/kotlin/org/mixdrinks/ui/list/main/ListComponent.kt b/shared/src/commonMain/kotlin/org/mixdrinks/ui/list/main/ListComponent.kt index 454f06d..9b2c75c 100644 --- a/shared/src/commonMain/kotlin/org/mixdrinks/ui/list/main/ListComponent.kt +++ b/shared/src/commonMain/kotlin/org/mixdrinks/ui/list/main/ListComponent.kt @@ -1,7 +1,5 @@ package org.mixdrinks.ui.list.main -import androidx.compose.ui.text.capitalize -import androidx.compose.ui.text.intl.Locale import com.arkivanov.decompose.ComponentContext import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers @@ -13,11 +11,11 @@ import kotlinx.coroutines.flow.flowOn import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.stateIn import org.mixdrinks.data.CocktailsProvider -import org.mixdrinks.domain.ImageUrlCreators -import org.mixdrinks.dto.CocktailId import org.mixdrinks.ui.filters.FilterItemUiModel +import org.mixdrinks.ui.list.CocktailListMapper import org.mixdrinks.ui.list.CocktailsListState import org.mixdrinks.ui.list.SelectedFilterProvider +import org.mixdrinks.ui.navigation.INavigator import org.mixdrinks.ui.navigation.Navigator import org.mixdrinks.ui.widgets.undomain.UiState import org.mixdrinks.ui.widgets.undomain.stateInWhileSubscribe @@ -28,7 +26,9 @@ internal class ListComponent( private val selectedFilterProvider: SelectedFilterProvider, private val navigator: Navigator, private val mutableFilterStorage: MutableFilterStorage, -) : ComponentContext by componentContext { + private val cocktailListMapper: CocktailListMapper, +) : ComponentContext by componentContext, + INavigator by navigator { val state: StateFlow> = flow { emitAll(cocktailsProvider.getCocktails().map { cocktails -> @@ -51,16 +51,7 @@ internal class ListComponent( if (cocktails.isEmpty()) { CocktailsListState.PlaceHolder(selectedFilterProvider.getSelectedFiltersWithData()) } else { - CocktailsListState.Cocktails(cocktails.map { cocktail -> - CocktailsListState.Cocktails.Cocktail( - id = cocktail.id, - url = ImageUrlCreators.createUrl( - cocktail.id, ImageUrlCreators.Size.SIZE_400 - ), - name = cocktail.name, - tags = cocktail.tags.map { it.capitalize(Locale.current) }, - ) - }) + CocktailsListState.Cocktails(cocktailListMapper.map(cocktails)) } ) } @@ -71,12 +62,4 @@ internal class ListComponent( ) { mutableFilterStorage.onFilterStateChange(filterGroupId, isSelect) } - - fun openFilters() { - navigator.navigateToFilters() - } - - fun onCocktailClick(cocktailId: CocktailId) { - navigator.navigateToDetails(cocktailId.id) - } } diff --git a/shared/src/commonMain/kotlin/org/mixdrinks/ui/list/main/MutableCocktailList.kt b/shared/src/commonMain/kotlin/org/mixdrinks/ui/list/main/MutableCocktailList.kt index 1a35f4b..9ac1145 100644 --- a/shared/src/commonMain/kotlin/org/mixdrinks/ui/list/main/MutableCocktailList.kt +++ b/shared/src/commonMain/kotlin/org/mixdrinks/ui/list/main/MutableCocktailList.kt @@ -50,7 +50,7 @@ internal fun MutableCocktailList(component: ListComponent) { Box( modifier = Modifier .clickable { - component.openFilters() + component.navigateToFilters() } .align(Alignment.CenterEnd) .size(52.dp) @@ -80,17 +80,26 @@ internal fun MutableCocktailList(component: ListComponent) { } } - ContentHolder( - stateflow = component.state, - ) { - when (it) { - is CocktailsListState.PlaceHolder -> { - PlaceHolder(it, component) - } + Content(component) + } +} - is CocktailsListState.Cocktails -> { - CocktailList(it, component::onCocktailClick) - } +@Composable +internal fun Content(component: ListComponent) { + ContentHolder( + stateflow = component.state, + ) { + when (it) { + is CocktailsListState.PlaceHolder -> { + PlaceHolder(it, component) + } + + is CocktailsListState.Cocktails -> { + CocktailList( + it, + component::navigateToDetails, + component::navigateToTagCocktails + ) } } } diff --git a/shared/src/commonMain/kotlin/org/mixdrinks/ui/list/predefine/PreDefineCocktailsComponent.kt b/shared/src/commonMain/kotlin/org/mixdrinks/ui/list/predefine/PreDefineCocktailsComponent.kt index 30d3242..962c4e3 100644 --- a/shared/src/commonMain/kotlin/org/mixdrinks/ui/list/predefine/PreDefineCocktailsComponent.kt +++ b/shared/src/commonMain/kotlin/org/mixdrinks/ui/list/predefine/PreDefineCocktailsComponent.kt @@ -1,7 +1,5 @@ package org.mixdrinks.ui.list.predefine -import androidx.compose.ui.text.capitalize -import androidx.compose.ui.text.intl.Locale import com.arkivanov.decompose.ComponentContext import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers @@ -14,20 +12,22 @@ import kotlinx.coroutines.flow.flowOn import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.stateIn import org.mixdrinks.data.CocktailsProvider -import org.mixdrinks.domain.ImageUrlCreators import org.mixdrinks.dto.CocktailId +import org.mixdrinks.ui.list.CocktailListMapper import org.mixdrinks.ui.list.CocktailsListState +import org.mixdrinks.ui.navigation.INavigator import org.mixdrinks.ui.navigation.Navigator internal class PreDefineCocktailsComponent( private val componentContext: ComponentContext, private val cocktailsProvider: CocktailsProvider, private val navigator: Navigator, -) : ComponentContext by componentContext { + private val cocktailsMapper: CocktailListMapper, +) : ComponentContext by componentContext, INavigator by navigator { val state: StateFlow = flow { emitAll(cocktailsProvider.getCocktails().map { cocktails -> - map(cocktails) + CocktailsListState.Cocktails(cocktailsMapper.map(cocktails)) }) } .flowOn(Dispatchers.Default) @@ -37,22 +37,4 @@ internal class PreDefineCocktailsComponent( SharingStarted.WhileSubscribed(), CocktailsListState.Cocktails(emptyList()) ) - - private fun map(cocktails: List): CocktailsListState.Cocktails { - return CocktailsListState.Cocktails(cocktails.map { cocktail -> - CocktailsListState.Cocktails.Cocktail( - id = cocktail.id, - url = ImageUrlCreators.createUrl( - cocktail.id, ImageUrlCreators.Size.SIZE_400 - ), - name = cocktail.name, - tags = cocktail.tags.map { it.capitalize(Locale.current) }, - ) - } - ) - } - - fun onCocktailClick(cocktailId: CocktailId) { - navigator.navigateToDetails(cocktailId.id) - } } diff --git a/shared/src/commonMain/kotlin/org/mixdrinks/ui/navigation/INavigator.kt b/shared/src/commonMain/kotlin/org/mixdrinks/ui/navigation/INavigator.kt new file mode 100644 index 0000000..b6855d3 --- /dev/null +++ b/shared/src/commonMain/kotlin/org/mixdrinks/ui/navigation/INavigator.kt @@ -0,0 +1,29 @@ +package org.mixdrinks.ui.navigation + +import org.mixdrinks.data.ItemsType +import org.mixdrinks.dto.CocktailId +import org.mixdrinks.dto.TagId +import org.mixdrinks.dto.TasteId +import org.mixdrinks.ui.filters.search.SearchItemComponent + +internal interface INavigator { + + fun back() + + fun navigateToItem(itemsType: ItemsType.Type, id: Int) + + fun navigateToDetails(id: Int) + + fun navigateToDetails(id: CocktailId) + + fun navigateToSearchItem(searchItemType: SearchItemComponent.SearchItemType) + + fun navigateToFilters() + + fun navigateToTagCocktails(tagId: TagId) + + fun navigationToTasteCocktails(tasteId: TasteId) + + fun openFromDeepLink(config: Navigator.Config) + +} diff --git a/shared/src/commonMain/kotlin/org/mixdrinks/ui/navigation/Navigator.kt b/shared/src/commonMain/kotlin/org/mixdrinks/ui/navigation/Navigator.kt index efcfe82..df3f138 100644 --- a/shared/src/commonMain/kotlin/org/mixdrinks/ui/navigation/Navigator.kt +++ b/shared/src/commonMain/kotlin/org/mixdrinks/ui/navigation/Navigator.kt @@ -6,12 +6,16 @@ import com.arkivanov.decompose.router.stack.push import com.arkivanov.essenty.parcelable.Parcelable import com.arkivanov.essenty.parcelable.Parcelize import org.mixdrinks.data.ItemsType +import org.mixdrinks.dto.CocktailId +import org.mixdrinks.dto.TagId +import org.mixdrinks.dto.TasteId import org.mixdrinks.ui.filters.search.SearchItemComponent +import org.mixdrinks.ui.tag.CommonTag import kotlin.native.concurrent.ThreadLocal internal class Navigator( private val stackNavigation: StackNavigation -) { +) : INavigator { sealed class Config(open val operationIndex: Int) : Parcelable { @Parcelize @@ -57,36 +61,56 @@ internal class Navigator( ) } + @Parcelize + data class CommonTagConfig( + val id: Int, + val type: CommonTag.Type, + override val operationIndex: Int + ) : Config(operationIndex) { + constructor(id: Int, type: CommonTag.Type) : this(id, type, operation++) + } + @ThreadLocal companion object { private var operation: Int = 0 } } - fun back() { + override fun back() { stackNavigation.pop() } - fun navigateToItem(itemsType: ItemsType.Type, id: Int) { + override fun navigateToItem(itemsType: ItemsType.Type, id: Int) { stackNavigation.push( - Config.ItemConfig(id, itemsType.toString()) + Config.ItemConfig(id, itemsType.name) ) } - fun navigateToDetails(id: Int) { + override fun navigateToDetails(id: Int) { stackNavigation.push(Config.DetailsConfig(id)) } - fun navigateToSearchItem(searchItemType: SearchItemComponent.SearchItemType) { + override fun navigateToDetails(id: CocktailId) { + stackNavigation.push(Config.DetailsConfig(id.id)) + } + + override fun navigateToSearchItem(searchItemType: SearchItemComponent.SearchItemType) { stackNavigation.push(Config.SearchItemConfig(searchItemType)) } - fun navigateToFilters() { + override fun navigateToFilters() { stackNavigation.push(Config.FilterConfig()) } - fun openFromDeepLink(config: Config) { - stackNavigation.push(config) + override fun navigateToTagCocktails(tagId: TagId) { + stackNavigation.push(Config.CommonTagConfig(tagId.id, CommonTag.Type.TAG)) + } + + override fun navigationToTasteCocktails(tasteId: TasteId) { + stackNavigation.push(Config.CommonTagConfig(tasteId.id, CommonTag.Type.TASTE)) } + override fun openFromDeepLink(config: Config) { + stackNavigation.push(config) + } } diff --git a/shared/src/commonMain/kotlin/org/mixdrinks/ui/tag/CommonTag.kt b/shared/src/commonMain/kotlin/org/mixdrinks/ui/tag/CommonTag.kt new file mode 100644 index 0000000..6848401 --- /dev/null +++ b/shared/src/commonMain/kotlin/org/mixdrinks/ui/tag/CommonTag.kt @@ -0,0 +1,14 @@ +package org.mixdrinks.ui.tag + +import org.mixdrinks.domain.FilterGroups + +internal data class CommonTag( + val id: Int, + val type: Type, +) { + + enum class Type(val filterGroups: FilterGroups) { + TAG(FilterGroups.TAGS), TASTE(FilterGroups.TASTE) + } + +} diff --git a/shared/src/commonMain/kotlin/org/mixdrinks/ui/tag/CommonTagCocktailsComponent.kt b/shared/src/commonMain/kotlin/org/mixdrinks/ui/tag/CommonTagCocktailsComponent.kt new file mode 100644 index 0000000..0c398fb --- /dev/null +++ b/shared/src/commonMain/kotlin/org/mixdrinks/ui/tag/CommonTagCocktailsComponent.kt @@ -0,0 +1,55 @@ +package org.mixdrinks.ui.tag + +import com.arkivanov.decompose.ComponentContext +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.flow.SharingStarted +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.distinctUntilChanged +import kotlinx.coroutines.flow.emitAll +import kotlinx.coroutines.flow.flow +import kotlinx.coroutines.flow.flowOn +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.stateIn +import org.mixdrinks.data.CocktailsProvider +import org.mixdrinks.ui.list.CocktailListMapper +import org.mixdrinks.ui.list.CocktailsListState +import org.mixdrinks.ui.navigation.INavigator +import org.mixdrinks.ui.navigation.Navigator +import org.mixdrinks.ui.widgets.undomain.scope + +internal class CommonTagCocktailsComponent( + private val componentContext: ComponentContext, + private val commonTagNameProvider: CommonTagNameProvider, + private val cocktailsProvider: CocktailsProvider, + private val commonTag: CommonTag, + private val navigator: Navigator, + private val commonCocktailListMapper: CocktailListMapper, +) : ComponentContext by componentContext, + INavigator by navigator { + + val name: StateFlow = flow { + emit(commonTagNameProvider.getName(commonTag) ?: "") + } + .map { + "Коктейлі $it" + } + .stateIn( + scope = componentContext.scope, + started = SharingStarted.WhileSubscribed(), + initialValue = "" + ) + + val state: StateFlow = flow { + emitAll(cocktailsProvider.getCocktails().map { cocktails -> + CocktailsListState.Cocktails(commonCocktailListMapper.map(cocktails)) + }) + } + .flowOn(Dispatchers.Default) + .distinctUntilChanged() + .stateIn( + CoroutineScope(Dispatchers.Main), + SharingStarted.WhileSubscribed(), + CocktailsListState.Cocktails(emptyList()) + ) +} diff --git a/shared/src/commonMain/kotlin/org/mixdrinks/ui/tag/CommonTagNameProvider.kt b/shared/src/commonMain/kotlin/org/mixdrinks/ui/tag/CommonTagNameProvider.kt new file mode 100644 index 0000000..23fcb7b --- /dev/null +++ b/shared/src/commonMain/kotlin/org/mixdrinks/ui/tag/CommonTagNameProvider.kt @@ -0,0 +1,22 @@ +package org.mixdrinks.ui.tag + +import org.mixdrinks.data.SnapshotRepository + +internal class CommonTagNameProvider( + private val snapshotRepository: SnapshotRepository, +) { + + suspend fun getName(commonTag: CommonTag): String? { + return when (commonTag.type) { + CommonTag.Type.TAG -> { + snapshotRepository.get() + .tags.find { it.id.id == commonTag.id }?.name + } + + CommonTag.Type.TASTE -> { + snapshotRepository.get() + .tastes.find { it.id.id == commonTag.id }?.name + } + } + } +} diff --git a/shared/src/commonMain/kotlin/org/mixdrinks/ui/tag/Tag.kt b/shared/src/commonMain/kotlin/org/mixdrinks/ui/tag/Tag.kt index 721e3b6..2874f12 100644 --- a/shared/src/commonMain/kotlin/org/mixdrinks/ui/tag/Tag.kt +++ b/shared/src/commonMain/kotlin/org/mixdrinks/ui/tag/Tag.kt @@ -13,6 +13,13 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.unit.dp import org.mixdrinks.app.styles.MixDrinksColors import org.mixdrinks.app.styles.MixDrinksTextStyles +import org.mixdrinks.dto.TagId +import org.mixdrinks.ui.list.CocktailsListState + +@Composable +internal fun Tag(tag: CocktailsListState.TagUIModel, onClick: (TagId) -> Unit) { + Tag(name = tag.name, onClick = { onClick(tag.id) }) +} @Composable internal fun Tag(name: String, onClick: () -> Unit) { diff --git a/shared/src/commonMain/kotlin/org/mixdrinks/ui/tag/TagCocktails.kt b/shared/src/commonMain/kotlin/org/mixdrinks/ui/tag/TagCocktails.kt new file mode 100644 index 0000000..0dc70c6 --- /dev/null +++ b/shared/src/commonMain/kotlin/org/mixdrinks/ui/tag/TagCocktails.kt @@ -0,0 +1,36 @@ +package org.mixdrinks.ui.tag + +import androidx.compose.foundation.ExperimentalFoundationApi +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.runtime.Composable +import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.getValue +import org.mixdrinks.ui.list.cocktailListInserter +import org.mixdrinks.ui.widgets.Header +import org.mixdrinks.ui.widgets.undomain.ComponentWidget + +@OptIn(ExperimentalFoundationApi::class) +@Composable +internal fun TagCocktails( + componentProvider: suspend () -> CommonTagCocktailsComponent +) { + ComponentWidget(componentProvider) { component -> + val name by component.name.collectAsState() + val cocktails by component.state.collectAsState() + + LazyColumn { + stickyHeader { + Header( + name = name, + component::back + ) + } + + cocktailListInserter( + cocktails, + component::navigateToDetails, + component::navigateToTagCocktails + ) + } + } +} diff --git a/shared/src/commonMain/kotlin/org/mixdrinks/ui/widgets/Header.kt b/shared/src/commonMain/kotlin/org/mixdrinks/ui/widgets/Header.kt new file mode 100644 index 0000000..b703245 --- /dev/null +++ b/shared/src/commonMain/kotlin/org/mixdrinks/ui/widgets/Header.kt @@ -0,0 +1,56 @@ +package org.mixdrinks.ui.widgets + +import androidx.compose.foundation.Image +import androidx.compose.foundation.background +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.material.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.unit.dp +import org.jetbrains.compose.resources.ExperimentalResourceApi +import org.jetbrains.compose.resources.painterResource +import org.mixdrinks.app.styles.MixDrinksColors +import org.mixdrinks.app.styles.MixDrinksTextStyles + +@OptIn(ExperimentalResourceApi::class) +@Composable +fun Header(name: String, onBackClick: () -> Unit) { + Row( + modifier = Modifier + .background(MixDrinksColors.Main) + .fillMaxWidth() + .height(52.dp), + ) { + Box( + modifier = Modifier.size(52.dp) + .clickable { + onBackClick() + } + ) { + Image( + modifier = Modifier + .align(Alignment.Center) + .size(32.dp) + .padding(start = 12.dp), + painter = painterResource("ic_arrow_back.xml"), + contentDescription = "Назад" + ) + } + Text( + modifier = Modifier.padding(start = 4.dp) + .align(Alignment.CenterVertically), + color = MixDrinksColors.White, + text = name, + style = MixDrinksTextStyles.H2, + softWrap = false, + maxLines = 1, + ) + } +} diff --git a/shared/src/commonMain/kotlin/org/mixdrinks/ui/widgets/undomain/ComponentScope.kt b/shared/src/commonMain/kotlin/org/mixdrinks/ui/widgets/undomain/ComponentScope.kt index f909692..7faa841 100644 --- a/shared/src/commonMain/kotlin/org/mixdrinks/ui/widgets/undomain/ComponentScope.kt +++ b/shared/src/commonMain/kotlin/org/mixdrinks/ui/widgets/undomain/ComponentScope.kt @@ -9,11 +9,13 @@ import kotlinx.coroutines.cancel import kotlinx.coroutines.launch internal fun ComponentContext.launch(block: suspend () -> Unit) { - val scope = CoroutineScope(Dispatchers.Default + SupervisorJob()) lifecycle.doOnDestroy { - scope.cancel() + this.scope.cancel() } - scope.launch { + this.scope.launch { block() } } + +internal val ComponentContext.scope: CoroutineScope + get() = CoroutineScope(Dispatchers.Default + SupervisorJob()) diff --git a/shared/src/commonMain/kotlin/org/mixdrinks/ui/widgets/undomain/ComponentWidget.kt b/shared/src/commonMain/kotlin/org/mixdrinks/ui/widgets/undomain/ComponentWidget.kt new file mode 100644 index 0000000..6d56ec9 --- /dev/null +++ b/shared/src/commonMain/kotlin/org/mixdrinks/ui/widgets/undomain/ComponentWidget.kt @@ -0,0 +1,26 @@ +package org.mixdrinks.ui.widgets.undomain + +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.getValue +import androidx.compose.runtime.remember +import kotlinx.coroutines.flow.MutableStateFlow + +@Composable +fun ComponentWidget( + provider: suspend () -> T, + content: @Composable (T) -> Unit +) { + val state = remember { MutableStateFlow(null) } + + LaunchedEffect("") { + state.emit(provider()) + } + + val stateTest by state.collectAsState() + + stateTest?.let { + content(it) + } +}