├── .editorconfig
├── .github
├── default-ruleset.json
├── pull_request_template.md
└── workflows
│ ├── an-check-pr-buildable.yml
│ ├── ios-develop.yml
│ ├── ios-release.yml
│ └── ios-test.yml
├── .gitignore
├── Package.swift
├── README.md
├── android
├── app
│ ├── .gitignore
│ ├── build.gradle.kts
│ └── src
│ │ └── main
│ │ ├── AndroidManifest.xml
│ │ ├── kotlin
│ │ └── kmp
│ │ │ └── android
│ │ │ ├── MainActivity.kt
│ │ │ ├── di
│ │ │ └── RootModule.kt
│ │ │ ├── navigation
│ │ │ └── NavBarFeature.kt
│ │ │ └── ui
│ │ │ └── Root.kt
│ │ └── res
│ │ ├── drawable-v24
│ │ └── ic_launcher_foreground.xml
│ │ ├── drawable
│ │ └── ic_launcher_background.xml
│ │ ├── mipmap-anydpi-v26
│ │ ├── ic_launcher.xml
│ │ └── ic_launcher_round.xml
│ │ ├── mipmap-hdpi
│ │ ├── ic_launcher.png
│ │ └── ic_launcher_round.png
│ │ ├── mipmap-mdpi
│ │ ├── ic_launcher.png
│ │ └── ic_launcher_round.png
│ │ ├── mipmap-xhdpi
│ │ ├── ic_launcher.png
│ │ └── ic_launcher_round.png
│ │ ├── mipmap-xxhdpi
│ │ ├── ic_launcher.png
│ │ └── ic_launcher_round.png
│ │ ├── mipmap-xxxhdpi
│ │ ├── ic_launcher.png
│ │ └── ic_launcher_round.png
│ │ ├── values-night
│ │ └── colors.xml
│ │ └── values
│ │ ├── colors.xml
│ │ └── themes.xml
├── books
│ ├── .gitignore
│ ├── build.gradle.kts
│ └── src
│ │ └── main
│ │ ├── AndroidManifest.xml
│ │ └── java
│ │ └── kmp
│ │ └── android
│ │ └── books
│ │ ├── di
│ │ └── Module.kt
│ │ ├── navigation
│ │ ├── BooksGraph.kt
│ │ └── BooksNavigation.kt
│ │ ├── ui
│ │ └── BooksList.kt
│ │ └── vm
│ │ └── BooksViewModel.kt
├── login
│ ├── .gitignore
│ ├── build.gradle.kts
│ └── src
│ │ └── main
│ │ ├── AndroidManifest.xml
│ │ └── kotlin
│ │ └── kmp
│ │ └── android
│ │ └── login
│ │ ├── di
│ │ └── LoginModule.kt
│ │ ├── navigation
│ │ ├── LoginDestination.kt
│ │ └── LoginNavigation.kt
│ │ ├── ui
│ │ └── AuthScreen.kt
│ │ └── vm
│ │ └── AuthViewModel.kt
├── profile
│ ├── .gitignore
│ ├── build.gradle.kts
│ └── src
│ │ └── main
│ │ ├── AndroidManifest.xml
│ │ └── java
│ │ └── kmp
│ │ └── android
│ │ └── profile
│ │ ├── di
│ │ └── ProfileModule.kt
│ │ ├── navigation
│ │ ├── ProfileGraph.kt
│ │ └── ProfileNavigation.kt
│ │ ├── ui
│ │ ├── BookList.kt
│ │ ├── EditProfileScreen.kt
│ │ ├── ProfileInfo.kt
│ │ ├── ProfileScreen.kt
│ │ └── UserEdit.kt
│ │ └── vm
│ │ └── ProfileViewModel.kt
├── recipes
│ ├── .gitignore
│ ├── build.gradle.kts
│ └── src
│ │ └── main
│ │ ├── AndroidManifest.xml
│ │ └── java
│ │ └── kmp
│ │ └── android
│ │ └── recipes
│ │ ├── domain
│ │ └── model
│ │ │ └── RecipesTarget.kt
│ │ ├── navigation
│ │ ├── RecipesGraph.kt
│ │ └── RecipesNavigation.kt
│ │ └── ui
│ │ ├── CanvasClock.kt
│ │ ├── ListTransition.kt
│ │ ├── RecipesList.kt
│ │ └── RopePhysics.kt
├── shared
│ ├── .gitignore
│ ├── build.gradle.kts
│ └── src
│ │ └── main
│ │ ├── AndroidManifest.xml
│ │ ├── kotlin
│ │ └── kmp
│ │ │ └── android
│ │ │ └── shared
│ │ │ ├── core
│ │ │ ├── system
│ │ │ │ ├── BaseActivity.kt
│ │ │ │ └── BaseViewModel.kt
│ │ │ ├── ui
│ │ │ │ └── util
│ │ │ │ │ ├── BackDispatcher.kt
│ │ │ │ │ └── PermissionRequest.kt
│ │ │ └── util
│ │ │ │ └── ViewModel.kt
│ │ │ ├── device
│ │ │ └── LocationControllerImpl.kt
│ │ │ ├── di
│ │ │ └── Module.kt
│ │ │ ├── domain
│ │ │ ├── controller
│ │ │ │ └── LocationController.kt
│ │ │ └── usecase
│ │ │ │ └── GetLocationFlowUseCaseImpl.kt
│ │ │ ├── extension
│ │ │ ├── Error.kt
│ │ │ └── Modifier.kt
│ │ │ ├── navigation
│ │ │ └── Destination.kt
│ │ │ ├── style
│ │ │ ├── Theme.kt
│ │ │ └── Values.kt
│ │ │ ├── ui
│ │ │ ├── Screen.kt
│ │ │ └── User.kt
│ │ │ └── vm
│ │ │ └── BaseIntentViewModel.kt
│ │ └── res
│ │ ├── values-en
│ │ └── generated_strings.xml
│ │ ├── values-sk
│ │ └── generated_strings.xml
│ │ └── values
│ │ └── generated_strings.xml
└── users
│ ├── .gitignore
│ ├── build.gradle.kts
│ └── src
│ └── main
│ ├── AndroidManifest.xml
│ └── java
│ └── kmp
│ └── android
│ └── users
│ ├── data
│ └── UsersPagingSource.kt
│ ├── di
│ └── Module.kt
│ ├── navigation
│ ├── UsersGraph.kt
│ └── UsersNavigation.kt
│ ├── ui
│ ├── UserDetail.kt
│ └── UserList.kt
│ └── vm
│ └── UsersViewModel.kt
├── build-logic
├── convention
│ ├── build.gradle.kts
│ └── src
│ │ └── main
│ │ └── kotlin
│ │ ├── config
│ │ ├── BuildVariantsConfig.kt
│ │ ├── ComposeConfig.kt
│ │ ├── KmmConfig.kt
│ │ ├── KotlinAndroidConfig.kt
│ │ ├── SigningConfig.kt
│ │ ├── TestConfig.kt
│ │ ├── TwineConfig.kt
│ │ └── VariantsConfig.kt
│ │ ├── constants
│ │ ├── Application.kt
│ │ └── ProjectConstants.kt
│ │ ├── extensions
│ │ ├── DependencyExtensions.kt
│ │ ├── ExtensionConstants.kt
│ │ ├── PojectExtensions.kt
│ │ ├── PropertyExtensions.kt
│ │ └── VariantDimensionExtensions.kt
│ │ └── plugin
│ │ ├── AndroidApplicationComposeConventionPlugin.kt
│ │ ├── AndroidApplicationConventionPlugin.kt
│ │ ├── AndroidLibraryComposeConventionPlugin.kt
│ │ ├── AndroidLibraryConventionPlugin.kt
│ │ ├── KmmLibraryConventionPlugin.kt
│ │ └── KotlinConventionPlugin.kt
├── gradle.properties
└── settings.gradle.kts
├── build.gradle.kts
├── gradle.properties
├── gradle
├── libs.versions.toml
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
├── ios
├── .gitattributes
├── .gitignore
├── .swiftlint.yml
├── Application
│ ├── AppDelegate.swift
│ ├── AppFlowController.swift
│ ├── AppIcon.xcassets
│ │ ├── AppIcon-Alpha.appiconset
│ │ │ ├── Contents.json
│ │ │ └── icon_appstore.png
│ │ ├── AppIcon-Beta.appiconset
│ │ │ ├── Contents.json
│ │ │ └── icon_appstore.png
│ │ ├── AppIcon.appiconset
│ │ │ ├── Contents.json
│ │ │ └── icon_appstore.png
│ │ └── Contents.json
│ ├── DependencyInjection
│ │ ├── .swiftpm
│ │ │ └── xcode
│ │ │ │ └── xcshareddata
│ │ │ │ └── xcschemes
│ │ │ │ └── DependencyInjection.xcscheme
│ │ ├── Package.swift
│ │ └── Sources
│ │ │ ├── DependencyInjection
│ │ │ ├── KMPDependency.swift
│ │ │ ├── KMPUseCases.swift
│ │ │ ├── Providers.swift
│ │ │ ├── Repositories.swift
│ │ │ └── UseCases.swift
│ │ │ └── DependencyInjectionMocks
│ │ │ └── UseCaseMocks.swift
│ ├── Entitlements
│ │ ├── Alpha.entitlements
│ │ ├── Beta.entitlements
│ │ └── Prod.entitlements
│ ├── GoogleService
│ │ ├── GoogleService-Info-Alpha.plist
│ │ ├── GoogleService-Info-Beta.plist
│ │ └── GoogleService-Info-Prod.plist
│ ├── Info
│ │ ├── Base.lproj
│ │ │ └── InfoPlist.strings
│ │ ├── Info+Proxyman.plist
│ │ ├── Info.plist
│ │ ├── PrivacyInfo.xcprivacy
│ │ ├── cs.lproj
│ │ │ └── InfoPlist.strings
│ │ ├── en.lproj
│ │ │ └── InfoPlist.strings
│ │ └── sk.lproj
│ │ │ └── InfoPlist.strings
│ ├── LaunchScreen.storyboard
│ └── MainFlowController.swift
├── CI
│ └── FastlaneRunner
│ │ ├── .gitignore
│ │ ├── Package.resolved
│ │ ├── Package.swift
│ │ └── Sources
│ │ └── FastlaneRunner
│ │ ├── Configuration
│ │ ├── AppStoreConnectAPIKey.swift
│ │ ├── BuildConfiguration.swift
│ │ └── KeychainConfiguration.swift
│ │ ├── Lanes
│ │ ├── Build.swift
│ │ ├── Tag.swift
│ │ ├── Test.swift
│ │ └── UploadTestFlight.swift
│ │ └── main.swift
├── DataLayer
│ ├── Providers
│ │ ├── AnalyticsProvider
│ │ │ ├── .gitignore
│ │ │ ├── Package.swift
│ │ │ └── Sources
│ │ │ │ ├── AnalyticsProvider
│ │ │ │ ├── AnalyticsProvider.swift
│ │ │ │ └── FirebaseAnalyticsProvider.swift
│ │ │ │ └── AnalyticsProviderMocks
│ │ │ │ └── AnalyticsProviderMock.swift
│ │ ├── DatabaseProvider
│ │ │ ├── .gitignore
│ │ │ ├── Package.swift
│ │ │ └── Sources
│ │ │ │ ├── DatabaseProvider
│ │ │ │ ├── DatabaseProvider.swift
│ │ │ │ ├── DatabaseProviderError.swift
│ │ │ │ ├── Object+Extensions.swift
│ │ │ │ ├── RealmDatabaseProvider.swift
│ │ │ │ └── UpdateModel.swift
│ │ │ │ └── DatabaseProviderMocks
│ │ │ │ └── DatabaseProviderMock.swift
│ │ ├── GraphQLProvider
│ │ │ ├── .gitignore
│ │ │ ├── Package.resolved
│ │ │ ├── Package.swift
│ │ │ └── Sources
│ │ │ │ ├── GraphQLProvider
│ │ │ │ ├── ApolloClient+Async.swift
│ │ │ │ ├── ApolloGraphQLProvider.swift
│ │ │ │ └── GraphQLProvider.swift
│ │ │ │ └── GraphQLProviderMocks
│ │ │ │ └── GraphQLProviderMock.swift
│ │ ├── KeychainProvider
│ │ │ ├── .gitignore
│ │ │ ├── Package.swift
│ │ │ └── Sources
│ │ │ │ ├── KeychainProvider
│ │ │ │ ├── Bundle+Extensions.swift
│ │ │ │ ├── KeychainProvider.swift
│ │ │ │ ├── KeychainProviderError.swift
│ │ │ │ └── SystemKeychainProvider.swift
│ │ │ │ └── KeychainProviderMocks
│ │ │ │ └── KeychainProviderMock.swift
│ │ ├── LocationProvider
│ │ │ ├── .gitignore
│ │ │ ├── Package.swift
│ │ │ └── Sources
│ │ │ │ ├── LocationProvider
│ │ │ │ ├── LocationProvider.swift
│ │ │ │ └── SystemLocationProvider.swift
│ │ │ │ └── LocationProviderMocks
│ │ │ │ └── LocationProviderMock.swift
│ │ ├── NetworkProvider
│ │ │ ├── .gitignore
│ │ │ ├── Package.swift
│ │ │ └── Sources
│ │ │ │ ├── NetworkProvider
│ │ │ │ ├── Encoding
│ │ │ │ │ ├── JSONEncoding.swift
│ │ │ │ │ ├── ParameterEncoding.swift
│ │ │ │ │ └── URLEncoding.swift
│ │ │ │ ├── Logger+Extensions.swift
│ │ │ │ ├── NetworkEndpoint.swift
│ │ │ │ ├── NetworkMethod.swift
│ │ │ │ ├── NetworkProvider.swift
│ │ │ │ ├── NetworkProviderError.swift
│ │ │ │ ├── NetworkStatusCode.swift
│ │ │ │ ├── NetworkTask.swift
│ │ │ │ ├── Pages.swift
│ │ │ │ ├── Representable.swift
│ │ │ │ └── SystemNetworkProvider.swift
│ │ │ │ └── NetworkProviderMocks
│ │ │ │ └── NetworkProviderMock.swift
│ │ ├── PushNotificationsProvider
│ │ │ ├── .gitignore
│ │ │ ├── Package.swift
│ │ │ └── Sources
│ │ │ │ ├── PushNotificationsProvider
│ │ │ │ ├── FirebasePushNotificationsProvider.swift
│ │ │ │ └── PushNotificationsProvider.swift
│ │ │ │ └── PushNotificationsProviderMocks
│ │ │ │ └── PushNotificationsProviderMocks.swift
│ │ ├── RemoteConfigProvider
│ │ │ ├── .gitignore
│ │ │ ├── Package.swift
│ │ │ └── Sources
│ │ │ │ ├── RemoteConfigProvider
│ │ │ │ ├── FirebaseRemoteConfigProvider.swift
│ │ │ │ └── RemoteConfigProvider.swift
│ │ │ │ └── RemoteConfigProviderMocks
│ │ │ │ └── RemoteConfigProviderMock.swift
│ │ ├── StoreKitProvider
│ │ │ ├── .gitignore
│ │ │ ├── Package.swift
│ │ │ └── Sources
│ │ │ │ └── StoreKitProvider
│ │ │ │ ├── AppleStoreKitProvider.swift
│ │ │ │ ├── StoreKitProvider.swift
│ │ │ │ └── StoreKitProviderError.swift
│ │ └── UserDefaultsProvider
│ │ │ ├── .gitignore
│ │ │ ├── Package.swift
│ │ │ └── Sources
│ │ │ ├── UserDefaultsProvider
│ │ │ ├── Bundle+Extensions.swift
│ │ │ ├── SystemUserDefaultsProvider.swift
│ │ │ ├── UserDefaultsProvider.swift
│ │ │ └── UserDefaultsProviderError.swift
│ │ │ └── UserDefaultsProviderMocks
│ │ │ └── UserDefaultsProviderMock.swift
│ └── Toolkits
│ │ ├── AnalyticsToolkit
│ │ ├── .gitignore
│ │ ├── .swiftpm
│ │ │ └── xcode
│ │ │ │ └── xcshareddata
│ │ │ │ └── xcschemes
│ │ │ │ └── AnalyticsToolkit.xcscheme
│ │ ├── Package.swift
│ │ ├── Sources
│ │ │ └── AnalyticsToolkit
│ │ │ │ └── Repositories
│ │ │ │ └── AnalyticsRepository.swift
│ │ └── Tests
│ │ │ └── AnalyticsToolkitTests
│ │ │ └── AnalyticsRepositoryTests.swift
│ │ ├── AuthToolkit
│ │ ├── .gitignore
│ │ ├── .swiftpm
│ │ │ └── xcode
│ │ │ │ └── xcshareddata
│ │ │ │ └── xcschemes
│ │ │ │ └── AuthToolkit.xcscheme
│ │ ├── Package.swift
│ │ ├── Sources
│ │ │ └── AuthToolkit
│ │ │ │ ├── NetworkEndpoints
│ │ │ │ └── AuthAPI.swift
│ │ │ │ ├── NetworkModels
│ │ │ │ ├── NETAuthToken.swift
│ │ │ │ ├── NETLoginData.swift
│ │ │ │ └── NETRegistrationData.swift
│ │ │ │ └── Repositories
│ │ │ │ └── AuthRepository.swift
│ │ └── Tests
│ │ │ └── AuthToolkitTests
│ │ │ ├── AuthRepositoryTests.swift
│ │ │ └── NetworkStubs
│ │ │ └── NETAuthToken.json
│ │ ├── LocationToolkit
│ │ ├── .gitignore
│ │ ├── .swiftpm
│ │ │ └── xcode
│ │ │ │ └── xcshareddata
│ │ │ │ └── xcschemes
│ │ │ │ └── LocationToolkit.xcscheme
│ │ ├── Package.swift
│ │ ├── Sources
│ │ │ └── LocationToolkit
│ │ │ │ └── Repositories
│ │ │ │ └── LocationRepository.swift
│ │ └── Tests
│ │ │ └── LocationToolkitTests
│ │ │ └── LocationRepositoryTests.swift
│ │ ├── PushNotificationsToolkit
│ │ ├── .gitignore
│ │ ├── .swiftpm
│ │ │ └── xcode
│ │ │ │ └── xcshareddata
│ │ │ │ └── xcschemes
│ │ │ │ └── PushNotificationsToolkit.xcscheme
│ │ ├── Package.swift
│ │ ├── Sources
│ │ │ └── PushNotificationsToolkit
│ │ │ │ ├── NetworkModels
│ │ │ │ └── NETPushNotification.swift
│ │ │ │ └── Repositories
│ │ │ │ └── PushNotificationsRepository.swift
│ │ └── Tests
│ │ │ └── PushNotificationsToolkitTests
│ │ │ ├── NetworkStubs
│ │ │ └── NETPushNotification.json
│ │ │ └── PushNotificationsRepositoryTests.swift
│ │ ├── RemoteConfigToolkit
│ │ ├── .gitignore
│ │ ├── .swiftpm
│ │ │ └── xcode
│ │ │ │ └── xcshareddata
│ │ │ │ └── xcschemes
│ │ │ │ └── RemoteConfigToolkit.xcscheme
│ │ ├── Package.swift
│ │ ├── Sources
│ │ │ └── RemoteConfigToolkit
│ │ │ │ └── Repositories
│ │ │ │ └── RemoteConfigRepository.swift
│ │ └── Tests
│ │ │ └── RemoteConfigToolkitTests
│ │ │ └── RemoteConfigRepositoryTests.swift
│ │ ├── RocketToolkit
│ │ ├── .gitignore
│ │ ├── .swiftpm
│ │ │ └── xcode
│ │ │ │ └── xcshareddata
│ │ │ │ └── xcschemes
│ │ │ │ └── RocketToolkit.xcscheme
│ │ ├── Package.swift
│ │ ├── Sources
│ │ │ ├── RocketToolkit
│ │ │ │ ├── NetworkModels
│ │ │ │ │ ├── Apollo
│ │ │ │ │ │ └── Schema
│ │ │ │ │ │ │ └── SchemaConfiguration.swift
│ │ │ │ │ └── RocketLaunch+Conversion.swift
│ │ │ │ ├── NetworkQueries
│ │ │ │ │ └── RocketLaunchList.graphql
│ │ │ │ └── Repositories
│ │ │ │ │ └── RocketLaunchRepository.swift
│ │ │ └── RocketToolkitMocks
│ │ │ │ └── .gitkeep
│ │ ├── Tests
│ │ │ └── RocketToolkitTests
│ │ │ │ └── RocketLaunchRepositoryTests.swift
│ │ └── apollo-codegen-config.json
│ │ ├── StoreKitToolkit
│ │ ├── .gitignore
│ │ ├── Package.swift
│ │ └── Sources
│ │ │ └── StoreKitToolkit
│ │ │ └── Repositories
│ │ │ └── StoreKitRepository.swift
│ │ └── UserToolkit
│ │ ├── .gitignore
│ │ ├── .swiftpm
│ │ └── xcode
│ │ │ └── xcshareddata
│ │ │ └── xcschemes
│ │ │ └── UserToolkit.xcscheme
│ │ ├── Package.swift
│ │ ├── Sources
│ │ └── UserToolkit
│ │ │ ├── DatabaseModels
│ │ │ └── DBUser.swift
│ │ │ ├── NetworkEndpoints
│ │ │ └── UserAPI.swift
│ │ │ ├── NetworkModels
│ │ │ └── NETUser.swift
│ │ │ └── Repositories
│ │ │ └── UserRepository.swift
│ │ └── Tests
│ │ └── UserToolkitTests
│ │ ├── NetworkStubs
│ │ └── User
│ │ │ ├── NETUser.json
│ │ │ └── NETUserList.json
│ │ └── UserRepositoryTests.swift
├── DevStack.xcodeproj
│ ├── project.pbxproj
│ ├── project.xcworkspace
│ │ ├── contents.xcworkspacedata
│ │ └── xcshareddata
│ │ │ ├── IDEWorkspaceChecks.plist
│ │ │ └── swiftpm
│ │ │ └── Package.resolved
│ └── xcshareddata
│ │ └── xcschemes
│ │ ├── DevStack Widget.xcscheme
│ │ ├── DevStack.xcscheme
│ │ ├── DevStack_Alpha Widget.xcscheme
│ │ ├── DevStack_Alpha.xcscheme
│ │ ├── DevStack_Beta Widget.xcscheme
│ │ └── DevStack_Beta.xcscheme
├── DevStack.xcworkspace
│ ├── contents.xcworkspacedata
│ └── xcshareddata
│ │ ├── IDEWorkspaceChecks.plist
│ │ ├── WorkspaceSettings.xcsettings
│ │ └── swiftpm
│ │ └── Package.resolved
├── DomainLayer
│ ├── SharedDomain
│ │ ├── .gitignore
│ │ ├── .swiftpm
│ │ │ └── xcode
│ │ │ │ └── xcshareddata
│ │ │ │ └── xcschemes
│ │ │ │ └── SharedDomain.xcscheme
│ │ ├── Package.resolved
│ │ ├── Package.swift
│ │ ├── Sources
│ │ │ ├── SharedDomain
│ │ │ │ ├── Analytics
│ │ │ │ │ ├── Models
│ │ │ │ │ │ ├── AnalyticsEvent.swift
│ │ │ │ │ │ ├── LoginEvent.swift
│ │ │ │ │ │ └── UserEvent.swift
│ │ │ │ │ ├── Repositories
│ │ │ │ │ │ └── AnalyticsRepository.swift
│ │ │ │ │ └── UseCases
│ │ │ │ │ │ └── TrackAnalyticsEventUseCase.swift
│ │ │ │ ├── Auth
│ │ │ │ │ ├── Errors
│ │ │ │ │ │ └── AuthError.swift
│ │ │ │ │ ├── Models
│ │ │ │ │ │ ├── AuthToken.swift
│ │ │ │ │ │ ├── LoginData.swift
│ │ │ │ │ │ └── RegistrationData.swift
│ │ │ │ │ ├── Repositories
│ │ │ │ │ │ └── AuthRepository.swift
│ │ │ │ │ └── UseCases
│ │ │ │ │ │ ├── GetProfileIdUseCase.swift
│ │ │ │ │ │ ├── IsUserLoggedUseCase.swift
│ │ │ │ │ │ ├── LoginUseCase.swift
│ │ │ │ │ │ ├── LogoutUseCase.swift
│ │ │ │ │ │ └── RegistrationUseCase.swift
│ │ │ │ ├── Common
│ │ │ │ │ └── Extensions
│ │ │ │ │ │ ├── ErrorResult+Extensions.swift
│ │ │ │ │ │ ├── Interoperability+Extensions.swift
│ │ │ │ │ │ └── SharedUseCase+Extensions.swift
│ │ │ │ ├── Location
│ │ │ │ │ ├── Repositories
│ │ │ │ │ │ └── LocationRepository.swift
│ │ │ │ │ └── UseCases
│ │ │ │ │ │ └── GetCurrentLocationUseCase.swift
│ │ │ │ ├── Profile
│ │ │ │ │ └── UseCases
│ │ │ │ │ │ ├── GetProfileUseCase.swift
│ │ │ │ │ │ └── UpdateProfileCounterUseCase.swift
│ │ │ │ ├── PushNotifications
│ │ │ │ │ ├── Models
│ │ │ │ │ │ └── PushNotification.swift
│ │ │ │ │ ├── Repositories
│ │ │ │ │ │ └── PushNotificationsRepository.swift
│ │ │ │ │ └── UseCases
│ │ │ │ │ │ ├── HandlePushNotificationUseCase.swift
│ │ │ │ │ │ └── RegisterForPushNotificationsUseCase.swift
│ │ │ │ ├── RemoteConfig
│ │ │ │ │ ├── Models
│ │ │ │ │ │ └── RemoteConfigCoding.swift
│ │ │ │ │ ├── Repositories
│ │ │ │ │ │ └── RemoteConfigRepository.swift
│ │ │ │ │ └── UseCases
│ │ │ │ │ │ └── GetRemoteConfigValueUseCase.swift
│ │ │ │ ├── Rocket
│ │ │ │ │ ├── Models
│ │ │ │ │ │ └── RocketLaunch.swift
│ │ │ │ │ ├── Repositories
│ │ │ │ │ │ └── RocketLaunchRepository.swift
│ │ │ │ │ └── UseCases
│ │ │ │ │ │ └── GetRocketLaunchesUseCase.swift
│ │ │ │ ├── SourceType.swift
│ │ │ │ ├── StoreKit
│ │ │ │ │ ├── Repositories
│ │ │ │ │ │ └── StoreKitRepository.swift
│ │ │ │ │ └── UseCases
│ │ │ │ │ │ └── RequestFeedbackUseCase.swift
│ │ │ │ ├── User
│ │ │ │ │ ├── Models
│ │ │ │ │ │ └── User.swift
│ │ │ │ │ ├── Repositories
│ │ │ │ │ │ └── UserRepository.swift
│ │ │ │ │ └── UseCases
│ │ │ │ │ │ ├── GetUserUseCase.swift
│ │ │ │ │ │ ├── GetUsersUseCase.swift
│ │ │ │ │ │ └── UpdateUserUseCase.swift
│ │ │ │ └── Validation
│ │ │ │ │ ├── Errors
│ │ │ │ │ └── ValidationError.swift
│ │ │ │ │ └── UseCases
│ │ │ │ │ ├── ValidateEmailUseCase.swift
│ │ │ │ │ └── ValidatePasswordUseCase.swift
│ │ │ └── SharedDomainMocks
│ │ │ │ ├── KMPUtilities
│ │ │ │ ├── TestError.swift
│ │ │ │ ├── UseCaseFlowNoParamsMock.swift
│ │ │ │ ├── UseCaseResultMock.swift
│ │ │ │ └── UseCaseResultNoParamsMock.swift
│ │ │ │ ├── Mocks
│ │ │ │ └── DevstackKmpSharedMocks.swift
│ │ │ │ └── Stubs
│ │ │ │ ├── Auth
│ │ │ │ ├── AuthToken+Stubs.swift
│ │ │ │ ├── LoginData+Stubs.swift
│ │ │ │ └── RegistrationData+Stubs.swift
│ │ │ │ ├── Book
│ │ │ │ └── Book+Stubs.swift
│ │ │ │ ├── PushNotification
│ │ │ │ └── PushNotification+Stubs.swift
│ │ │ │ ├── Rocket
│ │ │ │ └── RocketLaunch+Stubs.swift
│ │ │ │ └── User
│ │ │ │ └── User+Stubs.swift
│ │ └── Tests
│ │ │ └── SharedDomainTests
│ │ │ ├── Analytics
│ │ │ └── TrackAnalyticsEventUseCaseTests.swift
│ │ │ ├── ArrayOfTuples+Equatable.swift
│ │ │ ├── Auth
│ │ │ ├── GetProfileIdUseCaseTests.swift
│ │ │ ├── IsUserLoggedUseCaseTests.swift
│ │ │ ├── LoginUseCaseTests.swift
│ │ │ ├── LogoutUseCaseTests.swift
│ │ │ └── RegistrationUseCaseTests.swift
│ │ │ ├── Location
│ │ │ └── GetCurrentLocationUseCaseTests.swift
│ │ │ ├── Profile
│ │ │ ├── GetProfileUseCaseTests.swift
│ │ │ └── UpdateProfileCounterUseCaseTests.swift
│ │ │ ├── PushNotification
│ │ │ ├── HandlePushNotificationUseCaseTests.swift
│ │ │ └── RegisterForPushNotificationsUseCaseTests.swift
│ │ │ ├── RemoteConfig
│ │ │ └── GetRemoteConfigValueUseCaseTests.swift
│ │ │ ├── Rocket
│ │ │ └── GetRocketLaunchesUseCaseTests.swift
│ │ │ ├── User
│ │ │ ├── GetUserUseCaseTests.swift
│ │ │ ├── GetUsersUseCaseTests.swift
│ │ │ └── UpdateUserUseCaseTests.swift
│ │ │ └── Validation
│ │ │ ├── ValidateEmailUseCaseTests.swift
│ │ │ └── ValidatePasswordUseCaseTests.swift
│ └── Utilities
│ │ ├── .gitignore
│ │ ├── .swiftpm
│ │ └── xcode
│ │ │ └── xcshareddata
│ │ │ └── xcschemes
│ │ │ └── Utilities.xcscheme
│ │ ├── Package.swift
│ │ └── Sources
│ │ └── Utilities
│ │ ├── AsyncSemaphore.swift
│ │ ├── Environment.swift
│ │ ├── Extensions
│ │ ├── ArrayOfTuples+Equatable.swift
│ │ ├── Data+Extensions.swift
│ │ ├── Decodable+Extensions.swift
│ │ ├── Encodable+Extensions.swift
│ │ ├── Logger+Extensions.swift
│ │ └── String+Extensions.swift
│ │ ├── Formatter.swift
│ │ ├── KMPUtilities.swift
│ │ ├── NetworkingConstants.swift
│ │ └── Pages.swift
├── PresentationLayer
│ ├── MapToolkit
│ │ ├── .gitignore
│ │ ├── Package.swift
│ │ └── Sources
│ │ │ └── MapToolkit
│ │ │ └── Views
│ │ │ └── GoogleMapsView.swift
│ ├── Onboarding
│ │ ├── .gitignore
│ │ ├── .swiftpm
│ │ │ └── xcode
│ │ │ │ └── xcshareddata
│ │ │ │ └── xcschemes
│ │ │ │ └── Onboarding.xcscheme
│ │ ├── Package.swift
│ │ ├── Sources
│ │ │ └── Onboarding
│ │ │ │ ├── Errors
│ │ │ │ ├── AuthError.swift
│ │ │ │ └── ValidationError.swift
│ │ │ │ ├── Login
│ │ │ │ ├── LoginView.swift
│ │ │ │ └── LoginViewModel.swift
│ │ │ │ ├── OnboardingFlowController.swift
│ │ │ │ ├── Registration
│ │ │ │ ├── RegistrationView.swift
│ │ │ │ └── RegistrationViewModel.swift
│ │ │ │ └── Views
│ │ │ │ ├── EmailAndPasswordFields.swift
│ │ │ │ └── PrimaryAndSecondaryButtons.swift
│ │ └── Tests
│ │ │ └── OnboardingTests
│ │ │ ├── LoginViewModelTests.swift
│ │ │ └── RegistrationViewModelTests.swift
│ ├── Profile
│ │ ├── .gitignore
│ │ ├── .swiftpm
│ │ │ └── xcode
│ │ │ │ └── xcshareddata
│ │ │ │ └── xcschemes
│ │ │ │ └── Profile.xcscheme
│ │ ├── Package.swift
│ │ ├── Sources
│ │ │ └── Profile
│ │ │ │ ├── Main
│ │ │ │ ├── ProfileView.swift
│ │ │ │ └── ProfileViewModel.swift
│ │ │ │ └── ProfileFlowController.swift
│ │ └── Tests
│ │ │ └── ProfileTests
│ │ │ └── ProfileViewModelTests.swift
│ ├── Recipes
│ │ ├── .gitignore
│ │ ├── .swiftpm
│ │ │ └── xcode
│ │ │ │ └── xcshareddata
│ │ │ │ └── xcschemes
│ │ │ │ └── Recipes.xcscheme
│ │ ├── Package.swift
│ │ ├── Sources
│ │ │ └── Recipes
│ │ │ │ ├── Books
│ │ │ │ ├── BooksView.swift
│ │ │ │ └── BooksViewModel.swift
│ │ │ │ ├── Counter
│ │ │ │ ├── CounterView.swift
│ │ │ │ └── CounterViewModel.swift
│ │ │ │ ├── Images
│ │ │ │ ├── ImagesView.swift
│ │ │ │ └── ImagesViewModel.swift
│ │ │ │ ├── Main
│ │ │ │ ├── RecipesView.swift
│ │ │ │ └── RecipesViewModel.swift
│ │ │ │ ├── Maps
│ │ │ │ ├── MapsView.swift
│ │ │ │ └── MapsViewModel.swift
│ │ │ │ ├── Media
│ │ │ │ ├── MediaView.swift
│ │ │ │ └── MediaViewModel.swift
│ │ │ │ ├── RecipesFlowController.swift
│ │ │ │ ├── RocketLaunches
│ │ │ │ ├── RocketLaunchesView.swift
│ │ │ │ └── RocketLaunchesViewModel.swift
│ │ │ │ ├── Skeleton
│ │ │ │ ├── SkeletonView.swift
│ │ │ │ └── SkeletonViewModel.swift
│ │ │ │ ├── SlidingButton
│ │ │ │ ├── SlidingButtonView.swift
│ │ │ │ └── SlidingButtonViewModel.swift
│ │ │ │ └── TipKit
│ │ │ │ ├── ExampleTipKitView.swift
│ │ │ │ ├── ExampleTipKitViewModel.swift
│ │ │ │ └── Views
│ │ │ │ ├── ActionTip.swift
│ │ │ │ ├── InlineTip.swift
│ │ │ │ ├── PopoverTip.swift
│ │ │ │ └── RuleTip.swift
│ │ └── Tests
│ │ │ └── RecipesTests
│ │ │ ├── BooksViewModelTests.swift
│ │ │ ├── CounterViewModelTests.swift
│ │ │ ├── RecipesViewModelTests.swift
│ │ │ └── RocketLaunchesViewModelTests.swift
│ ├── UIToolkit
│ │ ├── .gitignore
│ │ ├── .swiftpm
│ │ │ └── xcode
│ │ │ │ └── xcshareddata
│ │ │ │ └── xcschemes
│ │ │ │ └── UIToolkit.xcscheme
│ │ ├── Package.swift
│ │ ├── Plugins
│ │ │ └── TwinePlugin
│ │ │ │ └── TwinePlugin.swift
│ │ └── Sources
│ │ │ └── UIToolkit
│ │ │ ├── BaseViews
│ │ │ ├── SwiftUI
│ │ │ │ ├── Button
│ │ │ │ │ ├── PrimaryButtonStyle.swift
│ │ │ │ │ ├── SecondaryButtonStyle.swift
│ │ │ │ │ └── SlidingButton
│ │ │ │ │ │ ├── DraggingComponent.swift
│ │ │ │ │ │ └── SlidingButton.swift
│ │ │ │ ├── Image
│ │ │ │ │ └── RemoteImage.swift
│ │ │ │ ├── MediaPicker
│ │ │ │ │ └── MediaPickerView.swift
│ │ │ │ ├── ProgressView
│ │ │ │ │ └── PrimaryProgressView.swift
│ │ │ │ ├── ScrollView
│ │ │ │ │ └── PagingScrollView.swift
│ │ │ │ ├── Shimmer
│ │ │ │ │ └── Shimmer.swift
│ │ │ │ ├── SizeReader
│ │ │ │ │ └── ChildSizeReader.swift
│ │ │ │ ├── Snackbar
│ │ │ │ │ ├── InfoErrorSnackHost.swift
│ │ │ │ │ ├── SnackHost.swift
│ │ │ │ │ └── SnackState.swift
│ │ │ │ ├── Text
│ │ │ │ │ └── HeadlineText.swift
│ │ │ │ ├── TextField
│ │ │ │ │ ├── PrimaryTextField.swift
│ │ │ │ │ └── PrimaryTextFieldStyle.swift
│ │ │ │ ├── ToastView
│ │ │ │ │ ├── ToastView.swift
│ │ │ │ │ └── ToastViewModifier.swift
│ │ │ │ ├── UIHostingController
│ │ │ │ │ └── BaseHostingController.swift
│ │ │ │ ├── UserView
│ │ │ │ │ └── UserView.swift
│ │ │ │ └── Whisper
│ │ │ │ │ └── Whisper.swift
│ │ │ └── UIKit
│ │ │ │ ├── UIViewController
│ │ │ │ ├── BaseNavigationController.swift
│ │ │ │ ├── BaseViewController+Alerts.swift
│ │ │ │ ├── BaseViewController.swift
│ │ │ │ ├── FullscreenImageViewController.swift
│ │ │ │ ├── ImagePickerViewController.swift
│ │ │ │ ├── SafariViewController.swift
│ │ │ │ └── WebViewController.swift
│ │ │ │ └── Whisper
│ │ │ │ └── WhisperView.swift
│ │ │ ├── Environment
│ │ │ └── LoadingKey.swift
│ │ │ ├── Extensions
│ │ │ ├── BundleToken.swift
│ │ │ ├── CLLocationCoordinate2D+Extensions.swift
│ │ │ ├── Date+Extensions.swift
│ │ │ ├── Double+Extensions.swift
│ │ │ ├── EnvironmentValues+Extensions.swift
│ │ │ ├── Int+Extensions.swift
│ │ │ ├── Measurement+Extensions.swift
│ │ │ ├── NSObject+Extensions.swift
│ │ │ ├── NavigationLink+Extensions.swift
│ │ │ ├── String+Extensions.swift
│ │ │ ├── UIImage+Extensions.swift
│ │ │ ├── UIViewController+Extensions.swift
│ │ │ └── View+Extensions.swift
│ │ │ ├── Resources
│ │ │ ├── Colors.xcassets
│ │ │ │ ├── Contents.json
│ │ │ │ ├── Error.colorset
│ │ │ │ │ └── Contents.json
│ │ │ │ ├── Info.colorset
│ │ │ │ │ └── Contents.json
│ │ │ │ ├── MateeBlue.colorset
│ │ │ │ │ └── Contents.json
│ │ │ │ ├── MateeYellow.colorset
│ │ │ │ │ └── Contents.json
│ │ │ │ └── Success.colorset
│ │ │ │ │ └── Contents.json
│ │ │ └── Images.xcassets
│ │ │ │ ├── BrandLogo.imageset
│ │ │ │ ├── Contents.json
│ │ │ │ ├── logo-dark@1x.png
│ │ │ │ ├── logo-dark@2x.png
│ │ │ │ ├── logo-dark@3x.png
│ │ │ │ ├── logo-light@1x.png
│ │ │ │ ├── logo-light@2x.png
│ │ │ │ └── logo-light@3x.png
│ │ │ │ ├── Contents.json
│ │ │ │ ├── Icons
│ │ │ │ ├── CheckboxOff.imageset
│ │ │ │ │ ├── Contents.json
│ │ │ │ │ ├── checkbox_off.png
│ │ │ │ │ ├── checkbox_off@2x.png
│ │ │ │ │ └── checkbox_off@3x.png
│ │ │ │ ├── CheckboxOn.imageset
│ │ │ │ │ ├── Contents.json
│ │ │ │ │ ├── checkboox_on.png
│ │ │ │ │ ├── checkboox_on@2x.png
│ │ │ │ │ └── checkboox_on@3x.png
│ │ │ │ └── Contents.json
│ │ │ │ └── TabBar
│ │ │ │ ├── Contents.json
│ │ │ │ ├── ProfileTabBar.imageset
│ │ │ │ ├── Contents.json
│ │ │ │ ├── ic_profile@1x.png
│ │ │ │ ├── ic_profile@2x.png
│ │ │ │ └── ic_profile@3x.png
│ │ │ │ ├── RecipesTabBar.imageset
│ │ │ │ ├── Contents.json
│ │ │ │ ├── ic_dashboard@1x.png
│ │ │ │ ├── ic_dashboard@2x.png
│ │ │ │ └── ic_dashboard@3x.png
│ │ │ │ └── UsersTabBar.imageset
│ │ │ │ ├── Contents.json
│ │ │ │ ├── ic_dashboard@1x.png
│ │ │ │ ├── ic_dashboard@2x.png
│ │ │ │ └── ic_dashboard@3x.png
│ │ │ ├── Utilities
│ │ │ ├── Alerts
│ │ │ │ ├── AlertAction.swift
│ │ │ │ ├── AlertData+SwiftUI.swift
│ │ │ │ ├── AlertData+UIKit.swift
│ │ │ │ ├── AlertData.swift
│ │ │ │ ├── ToastData.swift
│ │ │ │ └── WhisperData.swift
│ │ │ ├── AppTheme.swift
│ │ │ ├── BaseViewModel.swift
│ │ │ ├── Countries.swift
│ │ │ ├── FlowController.swift
│ │ │ ├── FlowControllerMock.swift
│ │ │ ├── MokoFix.swift
│ │ │ ├── PagingHandler.swift
│ │ │ ├── Plurals.swift
│ │ │ ├── PreferenceKEy
│ │ │ │ ├── SizePreferenceKey.swift
│ │ │ │ └── ViewOffsetKey.swift
│ │ │ └── ViewModel.swift
│ │ │ ├── swiftgen-strings.stencil
│ │ │ ├── swiftgen-xcassets.stencil
│ │ │ └── swiftgen.yml
│ └── Users
│ │ ├── .gitignore
│ │ ├── .swiftpm
│ │ └── xcode
│ │ │ └── xcshareddata
│ │ │ └── xcschemes
│ │ │ └── Users.xcscheme
│ │ ├── Package.swift
│ │ ├── Sources
│ │ └── Users
│ │ │ ├── Detail
│ │ │ ├── UserDetailView.swift
│ │ │ └── UserDetailViewModel.swift
│ │ │ ├── Main
│ │ │ ├── UsersView.swift
│ │ │ └── UsersViewModel.swift
│ │ │ └── UsersFlowController.swift
│ │ └── Tests
│ │ └── UsersTests
│ │ ├── UserDetailViewModelTests.swift
│ │ └── UsersViewModelTests.swift
├── README.md
├── Widget
│ ├── Assets.xcassets
│ │ ├── AccentColor.colorset
│ │ │ └── Contents.json
│ │ ├── AppIcon.appiconset
│ │ │ └── Contents.json
│ │ ├── Contents.json
│ │ └── WidgetBackground.colorset
│ │ │ └── Contents.json
│ ├── Entitlements
│ │ ├── Alpha.entitlements
│ │ ├── Beta.entitlements
│ │ └── Prod.entitlements
│ ├── Info.plist
│ ├── Widget.swift
│ └── WidgetView.swift
├── scripts
│ ├── IDETemplateMacros.plist
│ ├── apollo.sh
│ ├── build-kmp.sh
│ ├── copy-moko-resources.sh
│ ├── generate-error-messages.sh
│ ├── rename.sh
│ ├── setup.sh
│ ├── swiftlint-analyze.sh
│ └── twine.sh
└── signing
│ ├── Development_Matee.cer
│ └── Development_Matee.p12
├── other
├── CHANGELOG.md
├── keystore
│ ├── debug.jks
│ └── release.jks
├── refactor_project.sh
└── tools
│ ├── mainframer.sh
│ ├── mainframer_starter.sh
│ └── run_bundler.sh
├── settings.gradle.kts
├── shared
├── .gitignore
├── build.gradle.kts
└── src
│ ├── androidMain
│ ├── AndroidManifest.xml
│ └── kotlin
│ │ └── kmp
│ │ └── shared
│ │ ├── base
│ │ └── error
│ │ │ └── ErrorMessageProviderImpl.kt
│ │ ├── di
│ │ └── KoinAndroid.kt
│ │ ├── infrastructure
│ │ └── local
│ │ │ └── DriverFactory.kt
│ │ └── system
│ │ ├── ConfigImpl.kt
│ │ └── Log.kt
│ ├── androidUnitTest
│ └── kotlin
│ │ └── konsistTest
│ │ ├── android
│ │ └── compose
│ │ │ └── ComposeTest.kt
│ │ ├── common
│ │ └── GeneralTest.kt
│ │ ├── di
│ │ └── KoinTest.kt
│ │ ├── domain
│ │ ├── model
│ │ │ └── DomainModelsTest.kt
│ │ └── usecase
│ │ │ └── UseCaseTest.kt
│ │ ├── infrastructure
│ │ └── InfrastructureTest.kt
│ │ └── repository
│ │ └── RepositoryTest.kt
│ ├── commonMain
│ ├── kotlin
│ │ └── kmp
│ │ │ └── shared
│ │ │ ├── base
│ │ │ ├── Result.kt
│ │ │ ├── error
│ │ │ │ ├── ErrorMessageProvider.kt
│ │ │ │ ├── domain
│ │ │ │ │ ├── AuthError.kt
│ │ │ │ │ ├── BackendError.kt
│ │ │ │ │ └── CommonError.kt
│ │ │ │ └── util
│ │ │ │ │ └── Network.kt
│ │ │ ├── usecase
│ │ │ │ ├── UseCaseFlow.kt
│ │ │ │ └── UseCaseResult.kt
│ │ │ └── util
│ │ │ │ └── extension
│ │ │ │ └── Result.kt
│ │ │ ├── data
│ │ │ ├── repository
│ │ │ │ ├── AuthRepositoryImpl.kt
│ │ │ │ ├── BookRepositoryImpl.kt
│ │ │ │ └── UserRepositoryImpl.kt
│ │ │ ├── source
│ │ │ │ ├── AuthSource.kt
│ │ │ │ ├── BookLocalSource.kt
│ │ │ │ └── UserSource.kt
│ │ │ └── store
│ │ │ │ └── UserStore.kt
│ │ │ ├── di
│ │ │ └── Module.kt
│ │ │ ├── domain
│ │ │ ├── model
│ │ │ │ ├── Book.kt
│ │ │ │ ├── User.kt
│ │ │ │ └── UserPagingResult.kt
│ │ │ ├── repository
│ │ │ │ ├── AuthRepository.kt
│ │ │ │ ├── BookRepository.kt
│ │ │ │ └── UserRepository.kt
│ │ │ └── usecase
│ │ │ │ ├── DeleteAuthDataUseCaseImpl.kt
│ │ │ │ ├── LoginUseCaseImpl.kt
│ │ │ │ ├── RegisterUseCaseImpl.kt
│ │ │ │ ├── book
│ │ │ │ ├── GetBooksUseCaseImpl.kt
│ │ │ │ └── RefreshBooksUseCaseImpl.kt
│ │ │ │ └── user
│ │ │ │ ├── GetLocalUsersUseCaseImpl.kt
│ │ │ │ ├── GetLoggedInUserUseCaseImpl.kt
│ │ │ │ ├── GetRemoteUsersUseCaseImpl.kt
│ │ │ │ ├── GetUserUseCaseImpl.kt
│ │ │ │ ├── IsUserLoggedInUseCaseImpl.kt
│ │ │ │ ├── RefreshUsersUseCaseImpl.kt
│ │ │ │ ├── ReplaceUserCacheWithUseCaseImpl.kt
│ │ │ │ ├── UpdateLocalUserCacheUseCaseImpl.kt
│ │ │ │ ├── UpdateUserUseCaseImpl.kt
│ │ │ │ └── UserCacheChangeFlowUseCaseImpl.kt
│ │ │ ├── extension
│ │ │ ├── Book.kt
│ │ │ ├── Store.kt
│ │ │ ├── User.kt
│ │ │ └── UserPaging.kt
│ │ │ ├── infrastructure
│ │ │ ├── local
│ │ │ │ ├── AuthDao.kt
│ │ │ │ └── Database.kt
│ │ │ ├── model
│ │ │ │ ├── LoginDto.kt
│ │ │ │ ├── RegistrationDto.kt
│ │ │ │ ├── UserDto.kt
│ │ │ │ └── UserPagingDto.kt
│ │ │ ├── remote
│ │ │ │ ├── AuthService.kt
│ │ │ │ ├── HttpClient.kt
│ │ │ │ └── UserService.kt
│ │ │ └── source
│ │ │ │ ├── AuthSourceImpl.kt
│ │ │ │ ├── BookLocalSourceImpl.kt
│ │ │ │ ├── UserLocalSourceImpl.kt
│ │ │ │ └── UserRemoteSourceImpl.kt
│ │ │ └── system
│ │ │ ├── Config.kt
│ │ │ └── Logger.kt
│ ├── resources
│ │ └── MR
│ │ │ ├── base
│ │ │ └── strings.xml
│ │ │ ├── cs
│ │ │ └── strings.xml
│ │ │ └── sk
│ │ │ └── strings.xml
│ └── sqldelight
│ │ └── kmp
│ │ └── shared
│ │ └── infrastructure
│ │ └── local
│ │ ├── Book.sq
│ │ ├── User.sq
│ │ └── UserCache.sq
│ └── iosMain
│ └── kotlin
│ ├── dev
│ └── icerock
│ │ └── moko
│ │ └── resources
│ │ └── utils
│ │ └── MokoResourcesPreviewWorkaround.kt
│ └── kmp
│ └── shared
│ ├── KotlinDateTime.kt
│ ├── SwiftCoroutines.kt
│ ├── base
│ └── error
│ │ └── ErrorMessageProviderImpl.kt
│ ├── di
│ └── KoinIOS.kt
│ ├── infrastructure
│ └── local
│ │ └── DriverFactory.kt
│ ├── system
│ ├── ConfigImpl.kt
│ └── Log.kt
│ └── utils
│ └── FlowTestHelper.kt
└── twine
├── errors.txt
└── strings.txt
/.editorconfig:
--------------------------------------------------------------------------------
1 | [*.{kt,kts}]
2 | ktlint_code_style = official
3 |
4 | # Indentation
5 | indent_size = 4
6 | indent_style = space
7 |
8 | # Trailing Comma
9 | ij_kotlin_allow_trailing_comma = true
10 | ij_kotlin_allow_trailing_comma_on_call_site = true
11 | trailing-comma-on-call-site = true
12 | trailing-comma-on-declaration-site = true
--------------------------------------------------------------------------------
/.github/pull_request_template.md:
--------------------------------------------------------------------------------
1 | # :pencil: Description
2 | - FIXME
3 |
4 | # :bulb: What’s new?
5 | - FIXME
6 |
7 | # :no_mouth: What’s missing?
8 | - FIXME
9 |
10 | # :books: References
11 | - FIXME
12 |
--------------------------------------------------------------------------------
/.github/workflows/ios-test.yml:
--------------------------------------------------------------------------------
1 | name: iOS - Test develop
2 |
3 | on:
4 | pull_request:
5 | branches:
6 | - develop
7 |
8 | jobs:
9 | test:
10 | name: Run tests
11 | runs-on: [self-hosted, ios-build]
12 | timeout-minutes: 15
13 | steps:
14 | - name: Checkout repo
15 | uses: actions/checkout@v3
16 | continue-on-error: true
17 | with:
18 | clean: false
19 | - name: Build KMP
20 | working-directory: ios
21 | run: |
22 | ./scripts/generate-error-messages.sh
23 | ./scripts/build-kmp.sh debug true true false
24 | - name: Setup tools
25 | working-directory: ios
26 | run: ./scripts/setup.sh
27 | - name: Run tests
28 | working-directory: ios
29 | run: swift run --package-path CI/FastlaneRunner fastlaneRunner lane testAlphaLane
30 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | *.iml
2 | *.ipr
3 | *.iws
4 |
5 | .gradle
6 | /local.properties
7 | .idea/*
8 | !.idea/copyright
9 | /build
10 | /captures
11 | .externalNativeBuild
12 | **/build/*
13 | .kotlintest
14 |
15 | # linux specific files
16 | .directory
17 | *.*~
18 |
19 | # windows specific files
20 | Thumbs.db
21 | thumbs.db
22 |
23 | # mac specific files
24 | .DS_Store
25 |
26 | **/src/debug/res/**/ic_launcher.png
27 | **/src/debug/res/**/ic_launcher_round.png
28 | **/src/uat/res/**/ic_launcher.png
29 | **/src/uat/res/**/ic_launcher_round.png
30 | ios/.idea
31 |
--------------------------------------------------------------------------------
/Package.swift:
--------------------------------------------------------------------------------
1 | // swift-tools-version:5.3
2 | import PackageDescription
3 |
4 | let package = Package(
5 | name: "DevstackKmpShared",
6 | platforms: [
7 | .iOS(.v11)
8 | ],
9 | products: [
10 | .library(
11 | name: "DevstackKmpShared",
12 | targets: ["DevstackKmpShared"]
13 | ),
14 | ],
15 | targets: [
16 | .binaryTarget(
17 | name: "DevstackKmpShared",
18 | path: "./shared/swiftpackage/DevstackKmpShared.xcframework"
19 | ),
20 | ]
21 | )
22 |
--------------------------------------------------------------------------------
/android/app/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/android/app/build.gradle.kts:
--------------------------------------------------------------------------------
1 | @Suppress("DSL_SCOPE_VIOLATION") // Remove after upgrading to gradle 8.1
2 | plugins {
3 | alias(libs.plugins.devstack.android.application.compose)
4 | }
5 |
6 | android {
7 | namespace = "kmp.android"
8 | }
9 |
10 | dependencies {
11 | implementation(project(":shared"))
12 | implementation(project(":android:shared"))
13 | implementation(project(":android:login"))
14 | implementation(project(":android:users"))
15 | implementation(project(":android:profile"))
16 | implementation(project(":android:recipes"))
17 | implementation(project(":android:books"))
18 | }
19 |
--------------------------------------------------------------------------------
/android/app/src/main/kotlin/kmp/android/navigation/NavBarFeature.kt:
--------------------------------------------------------------------------------
1 | package kmp.android.navigation
2 |
3 | import androidx.annotation.StringRes
4 | import kmp.android.books.navigation.BooksGraph
5 | import kmp.android.profile.navigation.ProfileGraph
6 | import kmp.android.recipes.navigation.RecipesGraph
7 | import kmp.android.shared.R
8 | import kmp.android.users.navigation.UsersGraph
9 |
10 | enum class NavBarFeature(val route: String, @StringRes val titleRes: Int) {
11 | Users(UsersGraph.rootPath, R.string.bottom_bar_item_1),
12 | Profile(ProfileGraph.rootPath, R.string.bottom_bar_item_2),
13 | Recipes(RecipesGraph.rootPath, R.string.bottom_bar_item_3),
14 | Books(BooksGraph.rootPath, R.string.bottom_bar_item_4),
15 | }
16 |
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MateeDevs/devstack-native-app/26a21470027d26623d46c97b6e2e299c1604fb02/android/app/src/main/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MateeDevs/devstack-native-app/26a21470027d26623d46c97b6e2e299c1604fb02/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MateeDevs/devstack-native-app/26a21470027d26623d46c97b6e2e299c1604fb02/android/app/src/main/res/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MateeDevs/devstack-native-app/26a21470027d26623d46c97b6e2e299c1604fb02/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MateeDevs/devstack-native-app/26a21470027d26623d46c97b6e2e299c1604fb02/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MateeDevs/devstack-native-app/26a21470027d26623d46c97b6e2e299c1604fb02/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MateeDevs/devstack-native-app/26a21470027d26623d46c97b6e2e299c1604fb02/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MateeDevs/devstack-native-app/26a21470027d26623d46c97b6e2e299c1604fb02/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MateeDevs/devstack-native-app/26a21470027d26623d46c97b6e2e299c1604fb02/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MateeDevs/devstack-native-app/26a21470027d26623d46c97b6e2e299c1604fb02/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/android/app/src/main/res/values-night/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | @android:color/transparent
4 |
--------------------------------------------------------------------------------
/android/app/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #30000000
4 |
--------------------------------------------------------------------------------
/android/app/src/main/res/values/themes.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
7 |
--------------------------------------------------------------------------------
/android/books/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/android/books/build.gradle.kts:
--------------------------------------------------------------------------------
1 | @Suppress("DSL_SCOPE_VIOLATION") // Remove after upgrading to gradle 8.1
2 | plugins {
3 | alias(libs.plugins.devstack.android.library.compose)
4 | }
5 |
6 | android {
7 | namespace = "kmp.android.books"
8 | }
9 |
10 | dependencies {
11 | implementation(project(":shared"))
12 | implementation(project(":android:shared"))
13 | }
14 |
--------------------------------------------------------------------------------
/android/books/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/android/books/src/main/java/kmp/android/books/di/Module.kt:
--------------------------------------------------------------------------------
1 | package kmp.android.books.di
2 |
3 | import kmp.android.books.vm.BooksViewModel
4 | import org.koin.androidx.viewmodel.dsl.viewModel
5 | import org.koin.dsl.module
6 |
7 | val booksModule = module {
8 | viewModel { BooksViewModel(get(), get()) }
9 | }
10 |
--------------------------------------------------------------------------------
/android/books/src/main/java/kmp/android/books/navigation/BooksNavigation.kt:
--------------------------------------------------------------------------------
1 | package kmp.android.books.navigation
2 |
3 | import androidx.navigation.NavGraphBuilder
4 | import androidx.navigation.NavHostController
5 | import androidx.navigation.compose.navigation
6 | import kmp.android.books.ui.bookListRoute
7 |
8 | fun NavGraphBuilder.booksNavGraph(
9 | navHostController: NavHostController,
10 | ) {
11 | navigation(
12 | startDestination = BooksGraph.List.route,
13 | route = BooksGraph.rootPath,
14 | ) {
15 | bookListRoute()
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/android/login/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/android/login/build.gradle.kts:
--------------------------------------------------------------------------------
1 | @Suppress("DSL_SCOPE_VIOLATION") // Remove after upgrading to gradle 8.1
2 | plugins {
3 | alias(libs.plugins.devstack.android.library.compose)
4 | }
5 |
6 | android {
7 | namespace = "kmp.android.login"
8 | }
9 |
10 | dependencies {
11 | implementation(project(":shared"))
12 | implementation(project(":android:shared"))
13 | }
14 |
--------------------------------------------------------------------------------
/android/login/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/android/login/src/main/kotlin/kmp/android/login/di/LoginModule.kt:
--------------------------------------------------------------------------------
1 | package kmp.android.login.di
2 |
3 | import kmp.android.login.vm.AuthViewModel
4 | import org.koin.androidx.viewmodel.dsl.viewModel
5 | import org.koin.dsl.module
6 |
7 | val loginModule = module {
8 | viewModel { AuthViewModel(get(), get()) }
9 | }
10 |
--------------------------------------------------------------------------------
/android/login/src/main/kotlin/kmp/android/login/navigation/LoginDestination.kt:
--------------------------------------------------------------------------------
1 | package kmp.android.login.navigation
2 |
3 | import kmp.android.shared.navigation.Destination
4 |
5 | data object LoginDestination : Destination(parent = null) {
6 |
7 | override val routeDefinition: String = "login"
8 | }
9 |
--------------------------------------------------------------------------------
/android/login/src/main/kotlin/kmp/android/login/navigation/LoginNavigation.kt:
--------------------------------------------------------------------------------
1 | package kmp.android.login.navigation
2 |
3 | import androidx.navigation.NavGraphBuilder
4 | import androidx.navigation.NavHostController
5 | import kmp.android.login.ui.authRoute
6 |
7 | fun NavGraphBuilder.loginNavGraph(
8 | navHostController: NavHostController,
9 | navigateToUsers: () -> Unit,
10 | ) {
11 | authRoute(
12 | navHostController = navHostController,
13 | navigateToUsers = navigateToUsers,
14 | )
15 | }
16 |
--------------------------------------------------------------------------------
/android/profile/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/android/profile/build.gradle.kts:
--------------------------------------------------------------------------------
1 | @Suppress("DSL_SCOPE_VIOLATION") // Remove after upgrading to gradle 8.1
2 | plugins {
3 | alias(libs.plugins.devstack.android.library.compose)
4 | }
5 |
6 | android {
7 | namespace = "kmp.android.profile"
8 | }
9 |
10 | dependencies {
11 | implementation(project(":shared"))
12 | implementation(project(":android:shared"))
13 | }
14 |
--------------------------------------------------------------------------------
/android/profile/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/android/profile/src/main/java/kmp/android/profile/di/ProfileModule.kt:
--------------------------------------------------------------------------------
1 | package kmp.android.profile.di
2 |
3 | import kmp.android.profile.vm.ProfileViewModel
4 | import org.koin.androidx.viewmodel.dsl.viewModel
5 | import org.koin.dsl.module
6 |
7 | val profileModule = module {
8 | viewModel { ProfileViewModel(get(), get(), get(), get(), get(), get()) }
9 | }
10 |
--------------------------------------------------------------------------------
/android/profile/src/main/java/kmp/android/profile/navigation/ProfileGraph.kt:
--------------------------------------------------------------------------------
1 | package kmp.android.profile.navigation
2 |
3 | import kmp.android.shared.navigation.Destination
4 | import kmp.android.shared.navigation.FeatureGraph
5 |
6 | object ProfileGraph : FeatureGraph(parent = null) {
7 |
8 | override val path: String = "profile"
9 |
10 | object Home : Destination(parent = this) {
11 | override val routeDefinition: String = "home"
12 | }
13 |
14 | object Edit : Destination(parent = this) {
15 | override val routeDefinition: String = "edit"
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/android/profile/src/main/java/kmp/android/profile/navigation/ProfileNavigation.kt:
--------------------------------------------------------------------------------
1 | package kmp.android.profile.navigation
2 |
3 | import androidx.navigation.NavGraphBuilder
4 | import androidx.navigation.NavHostController
5 | import androidx.navigation.navigation
6 | import kmp.android.profile.ui.editProfileRoute
7 | import kmp.android.profile.ui.navigateToEditProfile
8 | import kmp.android.profile.ui.profileRoute
9 |
10 | fun NavGraphBuilder.profileNavGraph(
11 | navHostController: NavHostController,
12 | navigateToLogin: () -> Unit,
13 | ) {
14 | navigation(
15 | startDestination = ProfileGraph.Home.route,
16 | route = ProfileGraph.rootPath,
17 | ) {
18 | profileRoute(
19 | navigateToEditProfile = { navHostController.navigateToEditProfile() },
20 | navigateToLogin = navigateToLogin,
21 | )
22 |
23 | editProfileRoute()
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/android/recipes/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/android/recipes/build.gradle.kts:
--------------------------------------------------------------------------------
1 | @Suppress("DSL_SCOPE_VIOLATION") // Remove after upgrading to gradle 8.1
2 | plugins {
3 | alias(libs.plugins.devstack.android.library.compose)
4 | }
5 |
6 | android {
7 | namespace = "kmp.android.recipes"
8 | }
9 |
10 | dependencies {
11 | implementation(project(":shared"))
12 | implementation(project(":android:shared"))
13 |
14 | implementation(libs.compose.materialIconsExtended)
15 | }
16 |
--------------------------------------------------------------------------------
/android/recipes/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/android/recipes/src/main/java/kmp/android/recipes/domain/model/RecipesTarget.kt:
--------------------------------------------------------------------------------
1 | package kmp.android.recipes.domain.model
2 |
3 | import androidx.annotation.StringRes
4 | import kmp.android.shared.R
5 |
6 | enum class RecipesTarget(@StringRes val titleRes: Int, @StringRes val descriptionRes: Int) {
7 | Rope(
8 | titleRes = R.string.recipes_view_rope_name,
9 | descriptionRes = R.string.recipes_view_rope_description,
10 | ),
11 |
12 | CanvasClock(
13 | titleRes = R.string.recipes_view_clock_name,
14 | descriptionRes = R.string.recipes_view_clock_description,
15 | ),
16 |
17 | ListTransition(
18 | titleRes = R.string.recipes_view_list_name,
19 | descriptionRes = R.string.recipes_view_list_description,
20 | ),
21 | }
22 |
--------------------------------------------------------------------------------
/android/recipes/src/main/java/kmp/android/recipes/navigation/RecipesGraph.kt:
--------------------------------------------------------------------------------
1 | package kmp.android.recipes.navigation
2 |
3 | import kmp.android.shared.navigation.Destination
4 | import kmp.android.shared.navigation.FeatureGraph
5 |
6 | object RecipesGraph : FeatureGraph(parent = null) {
7 |
8 | override val path: String = "recipes"
9 |
10 | object List : Destination(this) {
11 | override val routeDefinition: String = "list"
12 | }
13 |
14 | object Rope : Destination(this) {
15 | override val routeDefinition: String = "rope"
16 | }
17 |
18 | object CanvasClock : Destination(this) {
19 | override val routeDefinition: String = "clock"
20 | }
21 |
22 | object ListTransition : Destination(this) {
23 | override val routeDefinition: String = "transition-list"
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/android/shared/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/android/shared/build.gradle.kts:
--------------------------------------------------------------------------------
1 | @Suppress("DSL_SCOPE_VIOLATION") // Remove after upgrading to gradle 8.1
2 | plugins {
3 | alias(libs.plugins.devstack.android.library.compose)
4 | }
5 |
6 | android {
7 | namespace = "kmp.android.shared"
8 | }
9 |
10 | dependencies {
11 | implementation(project(":shared"))
12 |
13 | implementation(libs.googlePlayServices.location)
14 | }
15 |
--------------------------------------------------------------------------------
/android/shared/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/android/shared/src/main/kotlin/kmp/android/shared/core/system/BaseActivity.kt:
--------------------------------------------------------------------------------
1 | package kmp.android.shared.core.system
2 |
3 | import androidx.activity.ComponentActivity
4 |
5 | public abstract class BaseActivity : ComponentActivity()
6 |
--------------------------------------------------------------------------------
/android/shared/src/main/kotlin/kmp/android/shared/di/Module.kt:
--------------------------------------------------------------------------------
1 | package kmp.android.shared.di
2 |
3 | import android.content.Context
4 | import com.google.android.gms.location.LocationServices
5 | import kmp.android.shared.device.LocationControllerImpl
6 | import kmp.android.shared.domain.controller.LocationController
7 | import kmp.android.shared.domain.usecase.GetLocationFlowUseCase
8 | import kmp.android.shared.domain.usecase.GetLocationFlowUseCaseImpl
9 | import org.koin.dsl.module
10 |
11 | val androidSharedModule = module {
12 | single {
13 | LocationControllerImpl(
14 | context = get(),
15 | locationProvider = LocationServices.getFusedLocationProviderClient(get()),
16 | )
17 | }
18 |
19 | factory { GetLocationFlowUseCaseImpl(get()) }
20 | }
21 |
--------------------------------------------------------------------------------
/android/shared/src/main/kotlin/kmp/android/shared/domain/controller/LocationController.kt:
--------------------------------------------------------------------------------
1 | package kmp.android.shared.domain.controller
2 |
3 | import android.location.Location
4 | import kotlinx.coroutines.flow.Flow
5 |
6 | interface LocationController {
7 | val lastLocation: Location?
8 | val locationFlow: Flow
9 | }
10 |
--------------------------------------------------------------------------------
/android/shared/src/main/kotlin/kmp/android/shared/domain/usecase/GetLocationFlowUseCaseImpl.kt:
--------------------------------------------------------------------------------
1 | package kmp.android.shared.domain.usecase
2 |
3 | import android.location.Location
4 | import kmp.android.shared.domain.controller.LocationController
5 | import kmp.shared.base.Result
6 | import kmp.shared.base.usecase.UseCaseResultNoParams
7 | import kotlinx.coroutines.flow.Flow
8 |
9 | interface GetLocationFlowUseCase : UseCaseResultNoParams>
10 |
11 | internal class GetLocationFlowUseCaseImpl(
12 | private val locationController: LocationController,
13 | ) : GetLocationFlowUseCase {
14 | override suspend fun invoke(): Result> =
15 | Result.Success(locationController.locationFlow)
16 | }
17 |
--------------------------------------------------------------------------------
/android/shared/src/main/kotlin/kmp/android/shared/extension/Error.kt:
--------------------------------------------------------------------------------
1 | package kmp.android.shared.extension
2 |
3 | import android.annotation.SuppressLint
4 | import androidx.compose.material.SnackbarHostState
5 | import androidx.compose.runtime.Composable
6 | import androidx.compose.runtime.LaunchedEffect
7 | import kmp.shared.base.ErrorResult
8 | import kmp.shared.base.error.ErrorMessageProvider
9 | import kmp.shared.base.error.getMessage
10 | import kotlinx.coroutines.flow.Flow
11 | import org.koin.androidx.compose.get
12 |
13 | @SuppressLint("ComposableNaming")
14 | @Suppress("konsist.every internal or public compose function has a modifier")
15 | @Composable
16 | infix fun Flow.showIn(snackHost: SnackbarHostState) {
17 | val errorMessageProvider: ErrorMessageProvider = get()
18 | LaunchedEffect(this, snackHost) {
19 | collect { error ->
20 | snackHost.showSnackbar(errorMessageProvider.getMessage(error))
21 | }
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/android/users/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/android/users/build.gradle.kts:
--------------------------------------------------------------------------------
1 | @Suppress("DSL_SCOPE_VIOLATION") // Remove after upgrading to gradle 8.1
2 | plugins {
3 | alias(libs.plugins.devstack.android.library.compose)
4 | }
5 |
6 | android {
7 | namespace = "kmp.android.users"
8 | }
9 |
10 | dependencies {
11 | implementation(project(":shared"))
12 | implementation(project(":android:shared"))
13 |
14 | implementation(libs.bundles.paging)
15 | }
16 |
--------------------------------------------------------------------------------
/android/users/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/android/users/src/main/java/kmp/android/users/di/Module.kt:
--------------------------------------------------------------------------------
1 | package kmp.android.users.di
2 |
3 | import kmp.android.users.data.UserPagingMediator
4 | import kmp.android.users.data.UsersPagingSource
5 | import kmp.android.users.vm.UsersViewModel
6 | import org.koin.androidx.viewmodel.dsl.viewModel
7 | import org.koin.dsl.module
8 |
9 | val usersModule = module {
10 | single { UserPagingMediator(get(), get(), get()) }
11 | factory { UsersPagingSource(get(), get()) }
12 | viewModel { UsersViewModel({ get() }, get(), get()) }
13 | }
14 |
--------------------------------------------------------------------------------
/android/users/src/main/java/kmp/android/users/navigation/UsersNavigation.kt:
--------------------------------------------------------------------------------
1 | package kmp.android.users.navigation
2 |
3 | import androidx.navigation.NavGraphBuilder
4 | import androidx.navigation.NavHostController
5 | import androidx.navigation.compose.navigation
6 | import kmp.android.users.ui.navigateToUserDetail
7 | import kmp.android.users.ui.userDetailRoute
8 | import kmp.android.users.ui.userListRoute
9 |
10 | fun NavGraphBuilder.usersNavGraph(
11 | navHostController: NavHostController,
12 | ) {
13 | navigation(
14 | startDestination = UsersGraph.List.route,
15 | route = UsersGraph.rootPath,
16 | ) {
17 | userListRoute(
18 | navigateToUserDetail = { navHostController.navigateToUserDetail(it) },
19 | )
20 |
21 | userDetailRoute()
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/build-logic/convention/src/main/kotlin/config/BuildVariantsConfig.kt:
--------------------------------------------------------------------------------
1 | package config
2 |
3 | import com.android.build.api.dsl.BuildType
4 | import com.android.build.api.dsl.CommonExtension
5 | import constants.ProjectConstants
6 | import org.gradle.api.plugins.ExtensionAware
7 | import org.gradle.kotlin.dsl.extra
8 |
9 | internal fun CommonExtension<*, T, *, *, *>.configureBuildVariants() {
10 | buildTypes {
11 | debug {
12 | splits.abi.isEnable = false
13 | splits.density.isEnable = false
14 | (this as ExtensionAware).extra["alwaysUpdateBuildId"] = false
15 | }
16 | create(ProjectConstants.Variant.alpha) {
17 | initWith(getByName("release"))
18 | }
19 | release {
20 | isMinifyEnabled = false
21 | isShrinkResources = false
22 | }
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/build-logic/convention/src/main/kotlin/config/KotlinAndroidConfig.kt:
--------------------------------------------------------------------------------
1 | package config
2 |
3 | import com.android.build.api.dsl.CommonExtension
4 | import constants.ProjectConstants
5 | import extensions.libs
6 | import org.gradle.api.Project
7 |
8 | internal fun Project.configureKotlinAndroid(
9 | commonExtension: CommonExtension<*, *, *, *, *>,
10 | ) = with(commonExtension) {
11 | compileSdk = libs.versions.sdk.compile.get().toInt()
12 |
13 | defaultConfig {
14 | minSdk = libs.versions.sdk.min.get().toInt()
15 | }
16 |
17 | compileOptions {
18 | sourceCompatibility = ProjectConstants.javaVersion
19 | targetCompatibility = ProjectConstants.javaVersion
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/build-logic/convention/src/main/kotlin/config/TestConfig.kt:
--------------------------------------------------------------------------------
1 | package config
2 |
3 | import extensions.libs
4 | import extensions.testImplementation
5 | import org.gradle.api.Project
6 | import org.gradle.kotlin.dsl.dependencies
7 |
8 | internal fun Project.configureTests() {
9 | dependencies {
10 | testImplementation(libs.junit)
11 | testImplementation(libs.konsist)
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/build-logic/convention/src/main/kotlin/constants/Application.kt:
--------------------------------------------------------------------------------
1 | package constants
2 |
3 | object Application {
4 | const val id = "cz.matee.devstack.kmp.android"
5 | const val appName = "MateeCoreApp"
6 |
7 | object Version {
8 | val name: String get() = "1.0.0"
9 | val code: Int get() = 1
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/build-logic/convention/src/main/kotlin/constants/ProjectConstants.kt:
--------------------------------------------------------------------------------
1 | package constants
2 |
3 | import org.gradle.api.JavaVersion
4 |
5 | object ProjectConstants {
6 | const val shared = ":shared"
7 | const val iosShared = "DevstackKmpShared"
8 | val javaVersion = JavaVersion.VERSION_17
9 |
10 | object Android {
11 | private const val root = ":android"
12 | const val shared = "$root:shared"
13 | const val login = "$root:login"
14 | const val profile = "$root:profile"
15 | const val users = "$root:users"
16 | const val recipes = "$root:recipes"
17 | const val books = "$root:books"
18 | }
19 |
20 | object Variant {
21 | const val debug = "debug"
22 | const val alpha = "alpha"
23 | const val release = "release"
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/build-logic/convention/src/main/kotlin/extensions/ExtensionConstants.kt:
--------------------------------------------------------------------------------
1 | package extensions
2 |
3 | object ExtensionConstants {
4 | const val IMPLEMENTATION = "implementation"
5 | const val TEST_IMPLEMENTATION = "testImplementation"
6 | const val ANDROID_TEST_IMPLEMENTATION = "androidTestImplementation"
7 | const val KAPT = "kapt"
8 | const val KTLINT_RULESET = "ktlintRuleset"
9 | const val ALPHA_IMPLEMENTATION = "alphaImplementation"
10 | const val DEBUG_IMPLEMENTATION = "debugImplementation"
11 | const val BETA_IMPLEMENTATION = "betaImplementation"
12 | const val API = "api"
13 | const val KSP = "ksp"
14 | }
15 |
--------------------------------------------------------------------------------
/build-logic/convention/src/main/kotlin/extensions/VariantDimensionExtensions.kt:
--------------------------------------------------------------------------------
1 | package extensions
2 |
3 | import com.android.build.api.dsl.VariantDimension
4 |
5 | fun VariantDimension.stringResource(key: String, value: String) {
6 | resValue("string", key, value)
7 | }
8 |
--------------------------------------------------------------------------------
/build-logic/convention/src/main/kotlin/plugin/AndroidApplicationComposeConventionPlugin.kt:
--------------------------------------------------------------------------------
1 | package plugin
2 |
3 | import config.configureComposeCompiler
4 | import config.configureComposeDependencies
5 | import config.configureComposeLint
6 | import extensions.androidApp
7 | import org.gradle.api.Plugin
8 | import org.gradle.api.Project
9 | import org.gradle.kotlin.dsl.apply
10 |
11 | @Suppress("unused")
12 | class AndroidApplicationComposeConventionPlugin : Plugin {
13 |
14 | override fun apply(target: Project) {
15 | with(target) {
16 | apply()
17 |
18 | androidApp {
19 | configureComposeCompiler(this)
20 | }
21 | configureComposeDependencies()
22 | configureComposeLint()
23 | }
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/build-logic/convention/src/main/kotlin/plugin/AndroidLibraryComposeConventionPlugin.kt:
--------------------------------------------------------------------------------
1 | package plugin
2 |
3 | import com.android.build.api.dsl.LibraryExtension
4 | import config.configureComposeCompiler
5 | import config.configureComposeDependencies
6 | import org.gradle.api.Plugin
7 | import org.gradle.api.Project
8 | import org.gradle.kotlin.dsl.apply
9 | import org.gradle.kotlin.dsl.configure
10 |
11 | @Suppress("unused")
12 | class AndroidLibraryComposeConventionPlugin : Plugin {
13 |
14 | override fun apply(target: Project) {
15 | with(target) {
16 | apply()
17 |
18 | extensions.configure {
19 | configureComposeCompiler(this)
20 | }
21 |
22 | configureComposeDependencies()
23 | }
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/build-logic/gradle.properties:
--------------------------------------------------------------------------------
1 | org.gradle.parallel=true
2 | org.gradle.caching=true
3 | org.gradle.configureondemand=true
--------------------------------------------------------------------------------
/build-logic/settings.gradle.kts:
--------------------------------------------------------------------------------
1 | pluginManagement {
2 | repositories {
3 | gradlePluginPortal()
4 | mavenCentral()
5 | google()
6 | }
7 | }
8 |
9 | dependencyResolutionManagement {
10 | repositories {
11 | gradlePluginPortal()
12 | mavenCentral()
13 | google()
14 | maven("https://plugins.gradle.org/m2/")
15 | }
16 | versionCatalogs {
17 | create("libs") {
18 | from(files("../gradle/libs.versions.toml"))
19 | }
20 | }
21 | }
22 |
23 | rootProject.name = "build-logic"
24 | include(":convention")
25 |
--------------------------------------------------------------------------------
/gradle.properties:
--------------------------------------------------------------------------------
1 | # KOTLIN
2 | kotlin.code.style=official
3 | # JVM
4 | org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8
5 | # ANDROID
6 | android.useAndroidX=true
7 | android.enableJetifier=true
8 | # MP
9 | kotlin.mpp.androidSourceSetLayoutVersion=2
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MateeDevs/devstack-native-app/26a21470027d26623d46c97b6e2e299c1604fb02/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Mon Apr 12 11:15:46 CEST 2021
2 | distributionBase=GRADLE_USER_HOME
3 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.0-all.zip
4 | distributionPath=wrapper/dists
5 | zipStorePath=wrapper/dists
6 | zipStoreBase=GRADLE_USER_HOME
7 |
--------------------------------------------------------------------------------
/ios/.gitattributes:
--------------------------------------------------------------------------------
1 | *.pbxproj merge=union
2 |
--------------------------------------------------------------------------------
/ios/Application/AppIcon.xcassets/AppIcon-Alpha.appiconset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "icon_appstore.png",
5 | "idiom" : "universal",
6 | "platform" : "ios",
7 | "size" : "1024x1024"
8 | }
9 | ],
10 | "info" : {
11 | "author" : "xcode",
12 | "version" : 1
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/ios/Application/AppIcon.xcassets/AppIcon-Alpha.appiconset/icon_appstore.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MateeDevs/devstack-native-app/26a21470027d26623d46c97b6e2e299c1604fb02/ios/Application/AppIcon.xcassets/AppIcon-Alpha.appiconset/icon_appstore.png
--------------------------------------------------------------------------------
/ios/Application/AppIcon.xcassets/AppIcon-Beta.appiconset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "icon_appstore.png",
5 | "idiom" : "universal",
6 | "platform" : "ios",
7 | "size" : "1024x1024"
8 | }
9 | ],
10 | "info" : {
11 | "author" : "xcode",
12 | "version" : 1
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/ios/Application/AppIcon.xcassets/AppIcon-Beta.appiconset/icon_appstore.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MateeDevs/devstack-native-app/26a21470027d26623d46c97b6e2e299c1604fb02/ios/Application/AppIcon.xcassets/AppIcon-Beta.appiconset/icon_appstore.png
--------------------------------------------------------------------------------
/ios/Application/AppIcon.xcassets/AppIcon.appiconset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "icon_appstore.png",
5 | "idiom" : "universal",
6 | "platform" : "ios",
7 | "size" : "1024x1024"
8 | }
9 | ],
10 | "info" : {
11 | "author" : "xcode",
12 | "version" : 1
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/ios/Application/AppIcon.xcassets/AppIcon.appiconset/icon_appstore.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MateeDevs/devstack-native-app/26a21470027d26623d46c97b6e2e299c1604fb02/ios/Application/AppIcon.xcassets/AppIcon.appiconset/icon_appstore.png
--------------------------------------------------------------------------------
/ios/Application/AppIcon.xcassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/ios/Application/DependencyInjection/Sources/DependencyInjection/KMPUseCases.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Created by Petr Chmelar on 06.10.2023
3 | // Copyright © 2023 Matee. All rights reserved.
4 | //
5 |
6 | import DevstackKmpShared
7 | import Factory
8 | import SharedDomain
9 |
10 | public extension Container {
11 | // Koin
12 | private var kmp: Factory { self { KMPKoinDependency() }.singleton }
13 |
14 | // Books
15 | var getBooksUseCase: Factory { self { self.kmp().get(GetBooksUseCase.self) } }
16 | var refreshBooksUseCase: Factory { self { self.kmp().get(RefreshBooksUseCase.self) } }
17 | }
18 |
--------------------------------------------------------------------------------
/ios/Application/Entitlements/Alpha.entitlements:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | aps-environment
6 | development
7 | com.apple.security.application-groups
8 |
9 | group.cz.matee.devstack.alpha
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/ios/Application/Entitlements/Beta.entitlements:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | aps-environment
6 | development
7 | com.apple.security.application-groups
8 |
9 | group.cz.matee.devstack.beta
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/ios/Application/Entitlements/Prod.entitlements:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | aps-environment
6 | development
7 | com.apple.security.application-groups
8 |
9 | group.cz.matee.devstack
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/ios/Application/Info/Base.lproj/InfoPlist.strings:
--------------------------------------------------------------------------------
1 | /*
2 | InfoPlist.strings
3 | DevStack
4 |
5 | Created by Petr Chmelar on 09/02/2020.
6 | Copyright © 2020 Matee. All rights reserved.
7 | */
8 |
9 | "NSCameraUsageDescription" = "You need to grant access to the camera so you can take a photo.";
10 | "NSPhotoLibraryUsageDescription" = "You need to grant access to your photo library so you can pick a photo.";
11 | "NSPhotoLibraryAddUsageDescription" = "You need to grant access to add photos to your photo library.";
12 | "NSLocationWhenInUseUsageDescription" = "You need to grant access to location services in order to fully benefit from all the features.";
13 |
--------------------------------------------------------------------------------
/ios/Application/Info/PrivacyInfo.xcprivacy:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | NSPrivacyAccessedAPITypes
6 |
7 |
8 | NSPrivacyAccessedAPIType
9 | NSPrivacyAccessedAPICategoryUserDefaults
10 | NSPrivacyAccessedAPITypeReasons
11 |
12 | CA92.1
13 |
14 |
15 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/ios/Application/Info/cs.lproj/InfoPlist.strings:
--------------------------------------------------------------------------------
1 | /*
2 | InfoPlist.strings
3 | DevStack
4 |
5 | Created by Petr Chmelar on 09/02/2020.
6 | Copyright © 2020 Matee. All rights reserved.
7 | */
8 |
9 | "NSCameraUsageDescription" = "Abyste mohli pořídit fotografii, musíte udělit přístup k fotoaparátu.";
10 | "NSPhotoLibraryUsageDescription" = "Chcete-li vybrat fotografii, musíte udělit přístup do vaší knihovny fotografií.";
11 | "NSPhotoLibraryAddUsageDescription" = "Chcete-li přidat fotky do své knihovny fotografií, musíte udělit přístup.";
12 | "NSLocationWhenInUseUsageDescription" = "Abyste mohli plně využívat všech funkcí, musíte udělit přístup k lokalizačním službám.";
13 |
--------------------------------------------------------------------------------
/ios/Application/Info/en.lproj/InfoPlist.strings:
--------------------------------------------------------------------------------
1 | /*
2 | InfoPlist.strings
3 | DevStack
4 |
5 | Created by Petr Chmelar on 09/02/2020.
6 | Copyright © 2020 Matee. All rights reserved.
7 | */
8 |
9 | "NSCameraUsageDescription" = "You need to grant access to the camera so you can take a photo.";
10 | "NSPhotoLibraryUsageDescription" = "You need to grant access to your photo library so you can pick a photo.";
11 | "NSPhotoLibraryAddUsageDescription" = "You need to grant access to add photos to your photo library.";
12 | "NSLocationWhenInUseUsageDescription" = "You need to grant access to location services in order to fully benefit from all the features.";
13 |
--------------------------------------------------------------------------------
/ios/Application/Info/sk.lproj/InfoPlist.strings:
--------------------------------------------------------------------------------
1 | /*
2 | InfoPlist.strings
3 | DevStack
4 |
5 | Created by Petr Chmelar on 09/02/2020.
6 | Copyright © 2020 Matee. All rights reserved.
7 | */
8 |
9 | "NSCameraUsageDescription" = "Aby ste mohli odfotografovať, musíte udeliť prístup k fotoaparátu.";
10 | "NSPhotoLibraryUsageDescription" = "Ak chcete vybrať fotografiu, musíte udeliť prístup do vašej knižnice fotografií.";
11 | "NSPhotoLibraryAddUsageDescription" = "Ak chcete pridať fotky do svojej knižnice fotografií, musíte udeliť prístup.";
12 | "NSLocationWhenInUseUsageDescription" = "Aby ste mohli plne využívať všetkých funkcií, musíte udeliť prístup k lokalizačným službám.";
13 |
--------------------------------------------------------------------------------
/ios/CI/FastlaneRunner/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | /.build
3 | /Packages
4 | /*.xcodeproj
5 | xcuserdata/
6 | DerivedData/
7 | .swiftpm/config/registries.json
8 | .swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata
9 | .netrc
10 |
--------------------------------------------------------------------------------
/ios/CI/FastlaneRunner/Package.resolved:
--------------------------------------------------------------------------------
1 | {
2 | "pins" : [
3 | {
4 | "identity" : "fastlane",
5 | "kind" : "remoteSourceControl",
6 | "location" : "https://github.com/fastlane/fastlane",
7 | "state" : {
8 | "revision" : "8e456d608937ea51ba9306ee501e583da0eeca9a",
9 | "version" : "2.211.0"
10 | }
11 | },
12 | {
13 | "identity" : "swiftshell",
14 | "kind" : "remoteSourceControl",
15 | "location" : "https://github.com/kareman/SwiftShell",
16 | "state" : {
17 | "revision" : "99680b2efc7c7dbcace1da0b3979d266f02e213c",
18 | "version" : "5.1.0"
19 | }
20 | }
21 | ],
22 | "version" : 2
23 | }
24 |
--------------------------------------------------------------------------------
/ios/CI/FastlaneRunner/Sources/FastlaneRunner/Configuration/AppStoreConnectAPIKey.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Created by Petr Chmelar on 07.02.2023
3 | // Copyright © 2023 Matee. All rights reserved.
4 | //
5 |
6 | import Foundation
7 |
8 | struct AppStoreConnectAPIKey {
9 | let id: String
10 | let issuerId: String
11 | let path: String
12 | let certificatePath: String
13 | }
14 |
15 | extension AppStoreConnectAPIKey {
16 | static let matee = AppStoreConnectAPIKey(
17 | id: "CQT3ZM53U8",
18 | issuerId: "e85e5ea1-4316-4e79-a474-858ab98197e0",
19 | path: "\(FileManager.default.currentDirectoryPath)/AuthKey_Matee.p8",
20 | certificatePath: "signing/Development_Matee.p12"
21 | )
22 | }
23 |
--------------------------------------------------------------------------------
/ios/CI/FastlaneRunner/Sources/FastlaneRunner/Configuration/KeychainConfiguration.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Created by Petr Chmelar on 07.02.2023
3 | // Copyright © 2023 Matee. All rights reserved.
4 | //
5 |
6 | struct KeychainConfiguration {
7 | let name: String
8 | let path: String
9 | let password: String
10 | }
11 |
12 | extension KeychainConfiguration {
13 | static let fastlane = KeychainConfiguration(
14 | name: "fastlane",
15 | path: "~/Library/Keychains/fastlane-db",
16 | password: randomString()
17 | )
18 |
19 | static func randomString(length: Int = 16) -> String {
20 | let letters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
21 | return String((0.. [String: String] {
11 | var inputParameters: [String: String] = [:]
12 | for argument in CommandLine.arguments {
13 | let splitted = argument.split(separator: ":")
14 | guard splitted.count > 1 else { continue }
15 | inputParameters[String(splitted[0])] = String(splitted[1])
16 | }
17 | return inputParameters
18 | }
19 | }
20 |
21 | Main().run(with: Fastfile())
22 |
--------------------------------------------------------------------------------
/ios/DataLayer/Providers/AnalyticsProvider/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | /.build
3 | /Packages
4 | /*.xcodeproj
5 | xcuserdata/
6 | DerivedData/
7 | .swiftpm/config/registries.json
8 | .swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata
9 | .netrc
10 |
--------------------------------------------------------------------------------
/ios/DataLayer/Providers/AnalyticsProvider/Sources/AnalyticsProvider/AnalyticsProvider.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Created by Petr Chmelar on 26.09.2021
3 | // Copyright © 2021 Matee. All rights reserved.
4 | //
5 |
6 | public protocol AnalyticsProvider {
7 | /// Track a given event
8 | func track(_ name: String, params: [String: AnyHashable])
9 | }
10 |
--------------------------------------------------------------------------------
/ios/DataLayer/Providers/AnalyticsProvider/Sources/AnalyticsProvider/FirebaseAnalyticsProvider.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Created by Petr Chmelar on 26.09.2021
3 | // Copyright © 2021 Matee. All rights reserved.
4 | //
5 |
6 | import Firebase
7 | import FirebaseAnalytics
8 |
9 | public struct FirebaseAnalyticsProvider {
10 | public init() {
11 | // Start Firebase if not yet started
12 | if FirebaseApp.app() == nil {
13 | FirebaseApp.configure()
14 | }
15 | }
16 | }
17 |
18 | extension FirebaseAnalyticsProvider: AnalyticsProvider {
19 |
20 | public func track(_ name: String, params: [String: AnyHashable]) {
21 | Analytics.logEvent(name, parameters: params)
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/ios/DataLayer/Providers/DatabaseProvider/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | /.build
3 | /Packages
4 | /*.xcodeproj
5 | xcuserdata/
6 | DerivedData/
7 | .swiftpm/config/registries.json
8 | .swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata
9 | .netrc
10 |
--------------------------------------------------------------------------------
/ios/DataLayer/Providers/DatabaseProvider/Sources/DatabaseProvider/DatabaseProviderError.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Created by Petr Chmelar on 16.05.2022
3 | // Copyright © 2022 Matee. All rights reserved.
4 | //
5 |
6 | public enum DatabaseProviderError: Error {
7 | case typeNotRepresentable
8 | case objectNotFound
9 | }
10 |
--------------------------------------------------------------------------------
/ios/DataLayer/Providers/DatabaseProvider/Sources/DatabaseProvider/UpdateModel.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Created by Petr Chmelar on 29.03.2021
3 | // Copyright © 2021 Matee. All rights reserved.
4 | //
5 |
6 | import RealmSwift
7 |
8 | public enum UpdateModel {
9 | case apiModel
10 | case fullModel
11 |
12 | func value(for object: Object) -> Any {
13 | switch self {
14 | case .apiModel: object.apiModel
15 | case .fullModel: object.fullModel
16 | }
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/ios/DataLayer/Providers/GraphQLProvider/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | /.build
3 | /Packages
4 | /*.xcodeproj
5 | xcuserdata/
6 | DerivedData/
7 | .swiftpm/config/registries.json
8 | .swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata
9 | .netrc
10 | apollo-ios-cli
11 |
--------------------------------------------------------------------------------
/ios/DataLayer/Providers/GraphQLProvider/Package.resolved:
--------------------------------------------------------------------------------
1 | {
2 | "pins" : [
3 | {
4 | "identity" : "apollo-ios",
5 | "kind" : "remoteSourceControl",
6 | "location" : "https://github.com/apollographql/apollo-ios",
7 | "state" : {
8 | "revision" : "7e28eb75e9970edaba346a3c1eab1f8fc479a04d",
9 | "version" : "1.7.1"
10 | }
11 | },
12 | {
13 | "identity" : "sqlite.swift",
14 | "kind" : "remoteSourceControl",
15 | "location" : "https://github.com/stephencelis/SQLite.swift.git",
16 | "state" : {
17 | "revision" : "7a2e3cd27de56f6d396e84f63beefd0267b55ccb",
18 | "version" : "0.14.1"
19 | }
20 | }
21 | ],
22 | "version" : 2
23 | }
24 |
--------------------------------------------------------------------------------
/ios/DataLayer/Providers/KeychainProvider/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | /.build
3 | /Packages
4 | /*.xcodeproj
5 | xcuserdata/
6 | DerivedData/
7 | .swiftpm/config/registries.json
8 | .swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata
9 | .netrc
10 |
--------------------------------------------------------------------------------
/ios/DataLayer/Providers/KeychainProvider/Sources/KeychainProvider/Bundle+Extensions.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Created by Petr Chmelar on 28.05.2022
3 | // Copyright © 2022 Matee. All rights reserved.
4 | //
5 |
6 | import Foundation
7 |
8 | public extension Bundle {
9 | /// Return the main bundle when in the app or an app extension.
10 | /// Taken from: https://stackoverflow.com/questions/26189060
11 | static var app: Bundle {
12 | var components = main.bundleURL.path.split(separator: "/")
13 | var bundle: Bundle?
14 |
15 | if let index = components.lastIndex(where: { $0.hasSuffix(".app") }) {
16 | components.removeLast((components.count - 1) - index)
17 | bundle = Bundle(path: components.joined(separator: "/"))
18 | }
19 |
20 | return bundle ?? main
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/ios/DataLayer/Providers/KeychainProvider/Sources/KeychainProvider/KeychainProvider.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Created by Petr Chmelar on 01/08/2018.
3 | // Copyright © 2018 Matee. All rights reserved.
4 | //
5 |
6 | public enum KeychainCoding: String, CaseIterable {
7 | case authToken
8 | case userId
9 | }
10 |
11 | public protocol KeychainProvider {
12 |
13 | /// Try to read a value for the given key
14 | func read(_ key: KeychainCoding) throws -> String
15 |
16 | /// Create or update the given key with a given value
17 | func update(_ key: KeychainCoding, value: String) throws
18 |
19 | /// Delete value for the given key
20 | func delete(_ key: KeychainCoding) throws
21 |
22 | /// Delete all records
23 | func deleteAll() throws
24 | }
25 |
--------------------------------------------------------------------------------
/ios/DataLayer/Providers/KeychainProvider/Sources/KeychainProvider/KeychainProviderError.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Created by Petr Chmelar on 28.05.2022
3 | // Copyright © 2022 Matee. All rights reserved.
4 | //
5 |
6 | public enum KeychainProviderError: Error {
7 | case invalidBundleIdentifier
8 | case valueForKeyNotFound
9 | }
10 |
--------------------------------------------------------------------------------
/ios/DataLayer/Providers/LocationProvider/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | /.build
3 | /Packages
4 | /*.xcodeproj
5 | xcuserdata/
6 | DerivedData/
7 | .swiftpm/config/registries.json
8 | .swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata
9 | .netrc
10 |
--------------------------------------------------------------------------------
/ios/DataLayer/Providers/LocationProvider/Sources/LocationProvider/LocationProvider.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Created by Petr Chmelar on 20.05.2022
3 | // Copyright © 2022 Matee. All rights reserved.
4 | //
5 |
6 | import CoreLocation
7 |
8 | public protocol LocationProvider {
9 |
10 | /// Check whether the location services are enabled and authorized
11 | func isLocationEnabled() -> Bool
12 |
13 | /// Observe current location
14 | func getCurrentLocation(withAccuracy accuracy: CLLocationAccuracy) -> AsyncStream
15 | }
16 |
--------------------------------------------------------------------------------
/ios/DataLayer/Providers/NetworkProvider/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | /.build
3 | /Packages
4 | /*.xcodeproj
5 | xcuserdata/
6 | DerivedData/
7 | .swiftpm/config/registries.json
8 | .swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata
9 | .netrc
10 |
--------------------------------------------------------------------------------
/ios/DataLayer/Providers/NetworkProvider/Sources/NetworkProvider/Encoding/ParameterEncoding.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Created by Petr Chmelar on 03.04.2022
3 | // Copyright © 2022 Matee. All rights reserved.
4 | //
5 |
6 | import Foundation
7 |
8 | /// A type used to define how a set of parameters are applied to a `URLRequest`.
9 | public protocol ParameterEncoding {
10 | /// Creates a `URLRequest` by encoding parameters and applying them on the passed request.
11 | ///
12 | /// - Parameters:
13 | /// - urlRequest: `URLRequest` value onto which parameters will be encoded.
14 | /// - parameters: `[String: Any]` to encode onto the request.
15 | ///
16 | /// - Returns: The encoded `URLRequest`.
17 | /// - Throws: Any `Error` produced during parameter encoding.
18 | func encode(_ urlRequest: URLRequest, with parameters: [String: Any]) throws -> URLRequest
19 | }
20 |
--------------------------------------------------------------------------------
/ios/DataLayer/Providers/NetworkProvider/Sources/NetworkProvider/NetworkEndpoint.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Created by Petr Chmelar on 03.04.2022
3 | // Copyright © 2022 Matee. All rights reserved.
4 | //
5 |
6 | import Foundation
7 |
8 | public protocol NetworkEndpoint {
9 |
10 | /// The endpoint's base `URL`.
11 | var baseURL: URL { get }
12 |
13 | /// The path to be appended to `baseURL` to form the full `URL`.
14 | var path: String { get }
15 |
16 | /// The HTTP method used in the request.
17 | var method: NetworkMethod { get }
18 |
19 | /// The headers to be used in the request.
20 | var headers: [String: String]? { get }
21 |
22 | /// The type of HTTP task to be performed.
23 | var task: NetworkTask { get }
24 | }
25 |
--------------------------------------------------------------------------------
/ios/DataLayer/Providers/NetworkProvider/Sources/NetworkProvider/NetworkMethod.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Created by Petr Chmelar on 03.04.2022
3 | // Copyright © 2022 Matee. All rights reserved.
4 | //
5 |
6 | public enum NetworkMethod: String {
7 | case delete = "DELETE"
8 | case get = "GET"
9 | case patch = "PATCH"
10 | case post = "POST"
11 | case put = "PUT"
12 | }
13 |
--------------------------------------------------------------------------------
/ios/DataLayer/Providers/NetworkProvider/Sources/NetworkProvider/NetworkProviderError.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Created by Petr Chmelar on 13.05.2022
3 | // Copyright © 2022 Matee. All rights reserved.
4 | //
5 |
6 | public enum NetworkProviderError: Error {
7 | case requestFailed(statusCode: NetworkStatusCode, message: String)
8 | }
9 |
--------------------------------------------------------------------------------
/ios/DataLayer/Providers/NetworkProvider/Sources/NetworkProvider/NetworkStatusCode.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Created by Petr Chmelar on 13.05.2022
3 | // Copyright © 2022 Matee. All rights reserved.
4 | //
5 |
6 | public enum NetworkStatusCode: Int {
7 | case badRequest = 400
8 | case unathorized = 401
9 | case forbidden = 403
10 | case notFound = 404
11 | case methodNotAllowed = 405
12 | case conflict = 409
13 | case internalServerError = 500
14 | case unknown = 0
15 | }
16 |
--------------------------------------------------------------------------------
/ios/DataLayer/Providers/NetworkProvider/Sources/NetworkProvider/NetworkTask.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Created by Petr Chmelar on 03.04.2022
3 | // Copyright © 2022 Matee. All rights reserved.
4 | //
5 |
6 | import Foundation
7 |
8 | public enum NetworkTask {
9 | /// A request with no additional data.
10 | case requestPlain
11 |
12 | /// A requests body set with encoded parameters.
13 | case requestParameters(parameters: [String: Any], encoding: ParameterEncoding)
14 | }
15 |
--------------------------------------------------------------------------------
/ios/DataLayer/Providers/NetworkProvider/Sources/NetworkProvider/Representable.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Created by Tomáš Batěk on 24.07.2023
3 | // Copyright © 2023 Matee. All rights reserved.
4 | //
5 |
6 | import Foundation
7 |
8 | public protocol DomainRepresentable {
9 | associatedtype DomainModel
10 |
11 | var domainModel: DomainModel { get throws }
12 | }
13 |
14 | public protocol NetworkRepresentable {
15 | associatedtype NetworkModel
16 |
17 | var networkModel: NetworkModel { get throws }
18 | }
19 |
--------------------------------------------------------------------------------
/ios/DataLayer/Providers/PushNotificationsProvider/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | /.build
3 | /Packages
4 | /*.xcodeproj
5 | xcuserdata/
6 | DerivedData/
7 | .swiftpm/config/registries.json
8 | .swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata
9 | .netrc
10 |
--------------------------------------------------------------------------------
/ios/DataLayer/Providers/PushNotificationsProvider/Sources/PushNotificationsProvider/PushNotificationsProvider.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Created by Petr Chmelar on 22.02.2021.
3 | // Copyright © 2021 Matee. All rights reserved.
4 | //
5 |
6 | import UserNotifications
7 |
8 | public protocol PushNotificationsProvider {
9 | /// Request user's authorization for push notifications
10 | func requestAuthorization(options: UNAuthorizationOptions, completionHandler: @escaping (Bool, Error?) -> Void)
11 | }
12 |
--------------------------------------------------------------------------------
/ios/DataLayer/Providers/RemoteConfigProvider/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | /.build
3 | /Packages
4 | /*.xcodeproj
5 | xcuserdata/
6 | DerivedData/
7 | .swiftpm/config/registries.json
8 | .swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata
9 | .netrc
10 |
--------------------------------------------------------------------------------
/ios/DataLayer/Providers/RemoteConfigProvider/Sources/RemoteConfigProvider/RemoteConfigProvider.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Created by Petr Chmelar on 04.03.2021
3 | // Copyright © 2021 Matee. All rights reserved.
4 | //
5 |
6 | public protocol RemoteConfigProvider {
7 | /// Try to read a value for the given key
8 | func read(_ key: String) async throws -> Bool
9 | }
10 |
--------------------------------------------------------------------------------
/ios/DataLayer/Providers/StoreKitProvider/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | /.build
3 | /Packages
4 | xcuserdata/
5 | DerivedData/
6 | .swiftpm/configuration/registries.json
7 | .swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata
8 | .netrc
9 |
--------------------------------------------------------------------------------
/ios/DataLayer/Providers/StoreKitProvider/Package.swift:
--------------------------------------------------------------------------------
1 | // swift-tools-version: 5.9
2 | // The swift-tools-version declares the minimum version of Swift required to build this package.
3 |
4 | import PackageDescription
5 |
6 | let package = Package(
7 | name: "StoreKitProvider",
8 | platforms: [.iOS(.v15)],
9 | products: [
10 | // Products define the executables and libraries a package produces, making them visible to other packages.
11 | .library(
12 | name: "StoreKitProvider",
13 | targets: ["StoreKitProvider"]),
14 | ],
15 | targets: [
16 | // Targets are the basic building blocks of a package, defining a module or a test suite.
17 | // Targets can depend on other targets in this package and products from dependencies.
18 | .target(
19 | name: "StoreKitProvider")
20 | ]
21 | )
22 |
--------------------------------------------------------------------------------
/ios/DataLayer/Providers/StoreKitProvider/Sources/StoreKitProvider/AppleStoreKitProvider.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Created by David Kadlček on 11.01.2024
3 | // Copyright © 2024 Matee. All rights reserved.
4 | //
5 |
6 | import Foundation
7 | import StoreKit
8 |
9 | public final class AppleStoreKitProvider: StoreKitProvider {
10 | public init() {}
11 |
12 | public func requestReview() throws {
13 | if let scene = UIApplication.shared.connectedScenes.first(where: { $0.activationState == .foregroundActive }) as? UIWindowScene {
14 | DispatchQueue.main.async {
15 | SKStoreReviewController.requestReview(in: scene)
16 | }
17 | } else {
18 | throw StoreKitProviderError.invalidWindow
19 | }
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/ios/DataLayer/Providers/StoreKitProvider/Sources/StoreKitProvider/StoreKitProvider.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Created by David Kadlček on 11.01.2024
3 | // Copyright © 2024 Matee. All rights reserved.
4 | //
5 |
6 | import Foundation
7 |
8 | public protocol StoreKitProvider {
9 | func requestReview() throws
10 | }
11 |
--------------------------------------------------------------------------------
/ios/DataLayer/Providers/StoreKitProvider/Sources/StoreKitProvider/StoreKitProviderError.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Created by David Kadlček on 11.01.2024
3 | // Copyright © 2024 Matee. All rights reserved.
4 | //
5 |
6 | import Foundation
7 |
8 | public enum StoreKitProviderError: Error {
9 | case invalidWindow
10 | }
11 |
--------------------------------------------------------------------------------
/ios/DataLayer/Providers/UserDefaultsProvider/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | /.build
3 | /Packages
4 | /*.xcodeproj
5 | xcuserdata/
6 | DerivedData/
7 | .swiftpm/config/registries.json
8 | .swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata
9 | .netrc
10 |
--------------------------------------------------------------------------------
/ios/DataLayer/Providers/UserDefaultsProvider/Sources/UserDefaultsProvider/Bundle+Extensions.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Created by Petr Chmelar on 28.05.2022
3 | // Copyright © 2022 Matee. All rights reserved.
4 | //
5 |
6 | import Foundation
7 |
8 | public extension Bundle {
9 | /// Return the main bundle when in the app or an app extension.
10 | /// Taken from: https://stackoverflow.com/questions/26189060
11 | static var app: Bundle {
12 | var components = main.bundleURL.path.split(separator: "/")
13 | var bundle: Bundle?
14 |
15 | if let index = components.lastIndex(where: { $0.hasSuffix(".app") }) {
16 | components.removeLast((components.count - 1) - index)
17 | bundle = Bundle(path: components.joined(separator: "/"))
18 | }
19 |
20 | return bundle ?? main
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/ios/DataLayer/Providers/UserDefaultsProvider/Sources/UserDefaultsProvider/UserDefaultsProvider.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Created by Petr Chmelar on 14/10/2019.
3 | // Copyright © 2019 Matee. All rights reserved.
4 | //
5 |
6 | public enum UserDefaultsCoding: String, CaseIterable {
7 | case hasRunBefore
8 | }
9 |
10 | public protocol UserDefaultsProvider {
11 |
12 | /// Try to read a value for the given key
13 | func read(_ key: UserDefaultsCoding) throws -> T
14 |
15 | /// Create or update the given key with a given value
16 | func update(_ key: UserDefaultsCoding, value: T) throws
17 |
18 | /// Delete value for the given key
19 | func delete(_ key: UserDefaultsCoding) throws
20 |
21 | /// Delete all records
22 | func deleteAll() throws
23 | }
24 |
--------------------------------------------------------------------------------
/ios/DataLayer/Providers/UserDefaultsProvider/Sources/UserDefaultsProvider/UserDefaultsProviderError.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Created by Petr Chmelar on 28.05.2022
3 | // Copyright © 2022 Matee. All rights reserved.
4 | //
5 |
6 | public enum UserDefaultsProviderError: Error {
7 | case invalidBundleIdentifier
8 | case valueForKeyNotFound
9 | }
10 |
--------------------------------------------------------------------------------
/ios/DataLayer/Toolkits/AnalyticsToolkit/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | /.build
3 | /Packages
4 | /*.xcodeproj
5 | xcuserdata/
6 | DerivedData/
7 | .swiftpm/config/registries.json
8 | .swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata
9 | .netrc
10 |
--------------------------------------------------------------------------------
/ios/DataLayer/Toolkits/AnalyticsToolkit/Sources/AnalyticsToolkit/Repositories/AnalyticsRepository.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Created by Petr Chmelar on 26.09.2021
3 | // Copyright © 2021 Matee. All rights reserved.
4 | //
5 |
6 | import AnalyticsProvider
7 | import SharedDomain
8 |
9 | public struct AnalyticsRepositoryImpl: AnalyticsRepository {
10 |
11 | private let analytics: AnalyticsProvider
12 |
13 | public init(analyticsProvider: AnalyticsProvider) {
14 | analytics = analyticsProvider
15 | }
16 |
17 | public func create(_ event: AnalyticsEvent) {
18 | analytics.track(event.name, params: event.params)
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/ios/DataLayer/Toolkits/AuthToolkit/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | /.build
3 | /Packages
4 | /*.xcodeproj
5 | xcuserdata/
6 | DerivedData/
7 | .swiftpm/config/registries.json
8 | .swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata
9 | .netrc
10 |
--------------------------------------------------------------------------------
/ios/DataLayer/Toolkits/AuthToolkit/Sources/AuthToolkit/NetworkModels/NETAuthToken.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Created by Petr Chmelar on 17.02.2021.
3 | // Copyright © 2021 Matee. All rights reserved.
4 | //
5 |
6 | import SharedDomain
7 |
8 | struct NETAuthToken: Decodable {
9 | let userId: String
10 | let email: String
11 | let token: String
12 | }
13 |
14 | // Conversion from NetworkModel to DomainModel
15 | extension NETAuthToken {
16 | var domainModel: AuthToken {
17 | AuthToken(
18 | userId: userId,
19 | token: token
20 | )
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/ios/DataLayer/Toolkits/AuthToolkit/Sources/AuthToolkit/NetworkModels/NETLoginData.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Created by Petr Chmelar on 17.02.2021.
3 | // Copyright © 2021 Matee. All rights reserved.
4 | //
5 |
6 | import SharedDomain
7 |
8 | struct NETLoginData: Encodable {
9 | let email: String
10 | let pass: String
11 | }
12 |
13 | // Conversion from DomainModel to NetworkModel
14 | extension LoginData {
15 | var networkModel: NETLoginData {
16 | NETLoginData(
17 | email: email,
18 | pass: password
19 | )
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/ios/DataLayer/Toolkits/AuthToolkit/Sources/AuthToolkit/NetworkModels/NETRegistrationData.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Created by Petr Chmelar on 17.02.2021.
3 | // Copyright © 2021 Matee. All rights reserved.
4 | //
5 |
6 | import SharedDomain
7 |
8 | struct NETRegistrationData: Encodable {
9 | let email: String
10 | let pass: String
11 | let firstName: String
12 | let lastName: String
13 | }
14 |
15 | // Conversion from DomainModel to NetworkModel
16 | extension RegistrationData {
17 | var networkModel: NETRegistrationData {
18 | NETRegistrationData(
19 | email: email,
20 | pass: password,
21 | firstName: firstName,
22 | lastName: lastName
23 | )
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/ios/DataLayer/Toolkits/AuthToolkit/Tests/AuthToolkitTests/NetworkStubs/NETAuthToken.json:
--------------------------------------------------------------------------------
1 | {
2 | "userId": "5c1a3d7b4a74580016faadf8",
3 | "email": "petr.chmelar@matee.cz",
4 | "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiI1YzFhM2Q3YjRhNzQ1ODAwMTZmYWFkZjgiLCJpYXQiOjE1NDUyMjM1OTcsImV4cCI6MTU0NTgyODM5N30.Gr5GmFDDm2u_hFvltMlwwwmT_JIc_134HNC6seRe90U"
5 | }
6 |
--------------------------------------------------------------------------------
/ios/DataLayer/Toolkits/LocationToolkit/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | /.build
3 | /Packages
4 | /*.xcodeproj
5 | xcuserdata/
6 | DerivedData/
7 | .swiftpm/config/registries.json
8 | .swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata
9 | .netrc
10 |
--------------------------------------------------------------------------------
/ios/DataLayer/Toolkits/LocationToolkit/Sources/LocationToolkit/Repositories/LocationRepository.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Created by Viktor Kaderabek on 10/08/2018.
3 | // Copyright © 2018 Matee. All rights reserved.
4 | //
5 |
6 | import CoreLocation
7 | import LocationProvider
8 | import SharedDomain
9 |
10 | public final class LocationRepositoryImpl: LocationRepository {
11 |
12 | private let location: LocationProvider
13 |
14 | public init(locationProvider: LocationProvider) {
15 | location = locationProvider
16 | }
17 |
18 | public func readIsLocationEnabled() -> Bool {
19 | location.isLocationEnabled()
20 | }
21 |
22 | public func readCurrentLocation(withAccuracy accuracy: CLLocationAccuracy) -> AsyncStream {
23 | location.getCurrentLocation(withAccuracy: accuracy)
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/ios/DataLayer/Toolkits/PushNotificationsToolkit/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | /.build
3 | /Packages
4 | /*.xcodeproj
5 | xcuserdata/
6 | DerivedData/
7 | .swiftpm/config/registries.json
8 | .swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata
9 | .netrc
10 |
--------------------------------------------------------------------------------
/ios/DataLayer/Toolkits/PushNotificationsToolkit/Tests/PushNotificationsToolkitTests/NetworkStubs/NETPushNotification.json:
--------------------------------------------------------------------------------
1 | {
2 | "aps": {
3 | "alert": {
4 | "title": "User updated",
5 | "body": "Click for more details"
6 | }
7 | },
8 | "type": "2",
9 | "entity_id": "5c1a3d7b4a74580016faadf8"
10 | }
11 |
--------------------------------------------------------------------------------
/ios/DataLayer/Toolkits/RemoteConfigToolkit/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | /.build
3 | /Packages
4 | /*.xcodeproj
5 | xcuserdata/
6 | DerivedData/
7 | .swiftpm/config/registries.json
8 | .swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata
9 | .netrc
10 |
--------------------------------------------------------------------------------
/ios/DataLayer/Toolkits/RemoteConfigToolkit/Sources/RemoteConfigToolkit/Repositories/RemoteConfigRepository.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Created by Petr Chmelar on 04.03.2021
3 | // Copyright © 2021 Matee. All rights reserved.
4 | //
5 |
6 | import RemoteConfigProvider
7 | import SharedDomain
8 |
9 | public struct RemoteConfigRepositoryImpl: RemoteConfigRepository {
10 |
11 | private let remoteConfig: RemoteConfigProvider
12 |
13 | public init(remoteConfigProvider: RemoteConfigProvider) {
14 | remoteConfig = remoteConfigProvider
15 | }
16 |
17 | public func read(_ key: RemoteConfigCoding) async throws -> Bool {
18 | try await remoteConfig.read(key.rawValue)
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/ios/DataLayer/Toolkits/RocketToolkit/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | /.build
3 | /Packages
4 | /*.xcodeproj
5 | xcuserdata/
6 | DerivedData/
7 | .swiftpm/config/registries.json
8 | .swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata
9 | .netrc
10 |
--------------------------------------------------------------------------------
/ios/DataLayer/Toolkits/RocketToolkit/Sources/RocketToolkit/NetworkModels/Apollo/Schema/SchemaConfiguration.swift:
--------------------------------------------------------------------------------
1 | // @generated
2 | // This file was automatically generated and can be edited to
3 | // provide custom configuration for a generated GraphQL schema.
4 | //
5 | // Any changes to this file will not be overwritten by future
6 | // code generation execution.
7 |
8 | import ApolloAPI
9 |
10 | enum SchemaConfiguration: ApolloAPI.SchemaConfiguration {
11 | static func cacheKeyInfo(for type: Object, object: ObjectData) -> CacheKeyInfo? {
12 | // Implement this function to configure cache key resolution for your schema types.
13 | return nil
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/ios/DataLayer/Toolkits/RocketToolkit/Sources/RocketToolkit/NetworkModels/RocketLaunch+Conversion.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Created by Petr Chmelar on 01.06.2022
3 | // Copyright © 2022 Matee. All rights reserved.
4 | //
5 |
6 | import ApolloAPI
7 | import SharedDomain
8 |
9 | // Conversion from NetworkModel to DomainModel
10 | extension Rocket.RocketLaunchListQuery.Data.Launches.Launch {
11 | var domainModel: RocketLaunch {
12 | .init(
13 | id: id,
14 | site: site ?? ""
15 | )
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/ios/DataLayer/Toolkits/RocketToolkit/Sources/RocketToolkit/NetworkQueries/RocketLaunchList.graphql:
--------------------------------------------------------------------------------
1 | query RocketLaunchList {
2 | launches {
3 | cursor
4 | hasMore
5 | launches {
6 | id
7 | site
8 | }
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/ios/DataLayer/Toolkits/RocketToolkit/Sources/RocketToolkitMocks/.gitkeep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MateeDevs/devstack-native-app/26a21470027d26623d46c97b6e2e299c1604fb02/ios/DataLayer/Toolkits/RocketToolkit/Sources/RocketToolkitMocks/.gitkeep
--------------------------------------------------------------------------------
/ios/DataLayer/Toolkits/StoreKitToolkit/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | /.build
3 | /Packages
4 | xcuserdata/
5 | DerivedData/
6 | .swiftpm/configuration/registries.json
7 | .swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata
8 | .netrc
9 |
--------------------------------------------------------------------------------
/ios/DataLayer/Toolkits/StoreKitToolkit/Sources/StoreKitToolkit/Repositories/StoreKitRepository.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Created by David Kadlček on 11.01.2024
3 | // Copyright © 2024 Matee. All rights reserved.
4 | //
5 |
6 | import Foundation
7 | import SharedDomain
8 | import StoreKitProvider
9 |
10 | public struct StoreKitRepositoryImpl: StoreKitRepository {
11 |
12 | private let storeKitProvider: StoreKitProvider
13 |
14 | public init(
15 | storeKitProvider: StoreKitProvider
16 | ) {
17 | self.storeKitProvider = storeKitProvider
18 | }
19 |
20 | public func requestFeedback() throws {
21 | try storeKitProvider.requestReview()
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/ios/DataLayer/Toolkits/UserToolkit/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | /.build
3 | /Packages
4 | /*.xcodeproj
5 | xcuserdata/
6 | DerivedData/
7 | .swiftpm/config/registries.json
8 | .swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata
9 | .netrc
10 |
--------------------------------------------------------------------------------
/ios/DataLayer/Toolkits/UserToolkit/Tests/UserToolkitTests/NetworkStubs/User/NETUser.json:
--------------------------------------------------------------------------------
1 | {
2 | "id": "5c1a3d7b4a74580016faadf8",
3 | "email": "petr.chmelar@matee.cz",
4 | "firstName": "Petr",
5 | "lastName": "Chmelar",
6 | "phone": "112567",
7 | "bio": "iOS dev"
8 | }
9 |
--------------------------------------------------------------------------------
/ios/DataLayer/Toolkits/UserToolkit/Tests/UserToolkitTests/NetworkStubs/User/NETUserList.json:
--------------------------------------------------------------------------------
1 | {
2 | "page": 0,
3 | "limit": 10,
4 | "lastPage": 0,
5 | "totalCount": 2,
6 | "data": [
7 | {
8 | "id": "5c1a3d7b4a74580016faadf8",
9 | "email": "petr.chmelar@matee.cz",
10 | "firstName": "Petr",
11 | "lastName": "Chmelar"
12 | },
13 | {
14 | "id": "5c50224464662000177f69a9",
15 | "email": "user1@matee.cz",
16 | "firstName": "User1",
17 | "lastName": "Matee"
18 | }
19 | ]
20 | }
21 |
--------------------------------------------------------------------------------
/ios/DevStack.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/ios/DevStack.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/ios/DevStack.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/ios/DevStack.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEWorkspaceSharedSettings_AutocreateContextsIfNeeded
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/ios/DomainLayer/SharedDomain/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | /.build
3 | /Packages
4 | /*.xcodeproj
5 | xcuserdata/
6 | DerivedData/
7 | .swiftpm/config/registries.json
8 | .swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata
9 | .netrc
10 |
--------------------------------------------------------------------------------
/ios/DomainLayer/SharedDomain/Sources/SharedDomain/Analytics/Models/AnalyticsEvent.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Created by Petr Chmelar on 28.09.2021
3 | // Copyright © 2021 Matee. All rights reserved.
4 | //
5 |
6 | public protocol Trackable {
7 | var analyticsEvent: AnalyticsEvent { get }
8 | }
9 |
10 | public struct AnalyticsEvent: Equatable {
11 | public let name: String
12 | public let params: [String: AnyHashable]
13 |
14 | public init(
15 | name: String,
16 | params: [String: AnyHashable] = [:]
17 | ) {
18 | self.name = name
19 | self.params = params
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/ios/DomainLayer/SharedDomain/Sources/SharedDomain/Analytics/Models/LoginEvent.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Created by Petr Chmelar on 26.09.2021
3 | // Copyright © 2021 Matee. All rights reserved.
4 | //
5 |
6 | public enum LoginEvent {
7 | case screenAppear
8 | case loginButtonTap
9 | case registerButtonTap
10 | }
11 |
12 | extension LoginEvent: Trackable {
13 | public var analyticsEvent: AnalyticsEvent {
14 | switch self {
15 | case .screenAppear: .init(name: "login_screen")
16 | case .loginButtonTap: .init(name: "login_button_tap")
17 | case .registerButtonTap: .init(name: "register_button_tap")
18 | }
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/ios/DomainLayer/SharedDomain/Sources/SharedDomain/Analytics/Models/UserEvent.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Created by Petr Chmelar on 28.09.2021
3 | // Copyright © 2021 Matee. All rights reserved.
4 | //
5 |
6 | public enum UserEvent {
7 | case userDetail(id: String)
8 | }
9 |
10 | extension UserEvent: Trackable {
11 | public var analyticsEvent: AnalyticsEvent {
12 | switch self {
13 | case .userDetail(let id): .init(name: "user_detail", params: ["id": id])
14 | }
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/ios/DomainLayer/SharedDomain/Sources/SharedDomain/Analytics/Repositories/AnalyticsRepository.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Created by Petr Chmelar on 26.09.2021
3 | // Copyright © 2021 Matee. All rights reserved.
4 | //
5 |
6 | import Spyable
7 |
8 | @Spyable
9 | public protocol AnalyticsRepository {
10 | func create(_ event: AnalyticsEvent)
11 | }
12 |
--------------------------------------------------------------------------------
/ios/DomainLayer/SharedDomain/Sources/SharedDomain/Analytics/UseCases/TrackAnalyticsEventUseCase.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Created by Petr Chmelar on 27.09.2021
3 | // Copyright © 2021 Matee. All rights reserved.
4 | //
5 |
6 | import Spyable
7 |
8 | @Spyable
9 | public protocol TrackAnalyticsEventUseCase {
10 | func execute(_ event: AnalyticsEvent)
11 | }
12 |
13 | public struct TrackAnalyticsEventUseCaseImpl: TrackAnalyticsEventUseCase {
14 |
15 | private let analyticsRepository: AnalyticsRepository
16 |
17 | public init(analyticsRepository: AnalyticsRepository) {
18 | self.analyticsRepository = analyticsRepository
19 | }
20 |
21 | public func execute(_ event: AnalyticsEvent) {
22 | analyticsRepository.create(event)
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/ios/DomainLayer/SharedDomain/Sources/SharedDomain/Auth/Errors/AuthError.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Created by Petr Chmelar on 16.05.2022
3 | // Copyright © 2022 Matee. All rights reserved.
4 | //
5 |
6 | public enum AuthError: Error, Equatable {
7 | case registration(Registration)
8 |
9 | public enum Registration {
10 | case userAlreadyExists
11 | case failed
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/ios/DomainLayer/SharedDomain/Sources/SharedDomain/Auth/Models/AuthToken.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Created by Petr Chmelar on 22.02.2021.
3 | // Copyright © 2021 Matee. All rights reserved.
4 | //
5 |
6 | public struct AuthToken: Equatable {
7 | public let userId: String
8 | public let token: String
9 |
10 | public init(
11 | userId: String,
12 | token: String
13 | ) {
14 | self.userId = userId
15 | self.token = token
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/ios/DomainLayer/SharedDomain/Sources/SharedDomain/Auth/Models/LoginData.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Created by Petr Chmelar on 17.02.2021.
3 | // Copyright © 2021 Matee. All rights reserved.
4 | //
5 |
6 | public struct LoginData: Equatable {
7 | public let email: String
8 | public let password: String
9 |
10 | public init(
11 | email: String,
12 | password: String
13 | ) {
14 | self.email = email
15 | self.password = password
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/ios/DomainLayer/SharedDomain/Sources/SharedDomain/Auth/Models/RegistrationData.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Created by Petr Chmelar on 17.02.2021.
3 | // Copyright © 2021 Matee. All rights reserved.
4 | //
5 |
6 | public struct RegistrationData: Equatable {
7 | public let email: String
8 | public let password: String
9 | public let firstName: String
10 | public let lastName: String
11 |
12 | public init(
13 | email: String,
14 | password: String,
15 | firstName: String,
16 | lastName: String
17 | ) {
18 | self.email = email
19 | self.password = password
20 | self.firstName = firstName
21 | self.lastName = lastName
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/ios/DomainLayer/SharedDomain/Sources/SharedDomain/Auth/Repositories/AuthRepository.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Created by Petr Chmelar on 05.03.2021
3 | // Copyright © 2021 Matee. All rights reserved.
4 | //
5 |
6 | import Spyable
7 |
8 | @Spyable
9 | public protocol AuthRepository {
10 | func login(_ data: LoginData) async throws
11 | func registration(_ data: RegistrationData) async throws
12 | func readProfileId() throws -> String
13 | func logout() throws
14 | }
15 |
--------------------------------------------------------------------------------
/ios/DomainLayer/SharedDomain/Sources/SharedDomain/Auth/UseCases/GetProfileIdUseCase.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Created by Petr Chmelar on 22.02.2021.
3 | // Copyright © 2021 Matee. All rights reserved.
4 | //
5 |
6 | import Spyable
7 |
8 | @Spyable
9 | public protocol GetProfileIdUseCase {
10 | func execute() throws -> String
11 | }
12 |
13 | public struct GetProfileIdUseCaseImpl: GetProfileIdUseCase {
14 |
15 | private let authRepository: AuthRepository
16 |
17 | public init(authRepository: AuthRepository) {
18 | self.authRepository = authRepository
19 | }
20 |
21 | public func execute() throws -> String {
22 | try authRepository.readProfileId()
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/ios/DomainLayer/SharedDomain/Sources/SharedDomain/Auth/UseCases/IsUserLoggedUseCase.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Created by Petr Chmelar on 20.05.2022
3 | // Copyright © 2022 Matee. All rights reserved.
4 | //
5 |
6 | import Spyable
7 |
8 | @Spyable
9 | public protocol IsUserLoggedUseCase {
10 | func execute() -> Bool
11 | }
12 |
13 | public struct IsUserLoggedUseCaseImpl: IsUserLoggedUseCase {
14 |
15 | private let getProfileIdUseCase: GetProfileIdUseCase
16 |
17 | public init(getProfileIdUseCase: GetProfileIdUseCase) {
18 | self.getProfileIdUseCase = getProfileIdUseCase
19 | }
20 |
21 | public func execute() -> Bool {
22 | do {
23 | _ = try getProfileIdUseCase.execute()
24 | return true
25 | } catch { return false }
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/ios/DomainLayer/SharedDomain/Sources/SharedDomain/Auth/UseCases/LogoutUseCase.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Created by Petr Chmelar on 22.02.2021.
3 | // Copyright © 2021 Matee. All rights reserved.
4 | //
5 |
6 | import Spyable
7 |
8 | @Spyable
9 | public protocol LogoutUseCase {
10 | func execute() throws
11 | }
12 |
13 | public struct LogoutUseCaseImpl: LogoutUseCase {
14 |
15 | private let authRepository: AuthRepository
16 |
17 | public init(authRepository: AuthRepository) {
18 | self.authRepository = authRepository
19 | }
20 |
21 | public func execute() throws {
22 | try authRepository.logout()
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/ios/DomainLayer/SharedDomain/Sources/SharedDomain/Location/Repositories/LocationRepository.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Created by Petr Chmelar on 05.03.2021
3 | // Copyright © 2021 Matee. All rights reserved.
4 | //
5 |
6 | import CoreLocation
7 | import Spyable
8 |
9 | @Spyable
10 | public protocol LocationRepository {
11 | func readIsLocationEnabled() -> Bool
12 | func readCurrentLocation(withAccuracy accuracy: CLLocationAccuracy) -> AsyncStream
13 | }
14 |
--------------------------------------------------------------------------------
/ios/DomainLayer/SharedDomain/Sources/SharedDomain/Location/UseCases/GetCurrentLocationUseCase.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Created by Petr Chmelar on 22.02.2021.
3 | // Copyright © 2021 Matee. All rights reserved.
4 | //
5 |
6 | import CoreLocation
7 | import Spyable
8 |
9 | @Spyable
10 | public protocol GetCurrentLocationUseCase {
11 | func execute() -> AsyncStream
12 | }
13 |
14 | public struct GetCurrentLocationUseCaseImpl: GetCurrentLocationUseCase {
15 |
16 | private let locationRepository: LocationRepository
17 |
18 | public init(locationRepository: LocationRepository) {
19 | self.locationRepository = locationRepository
20 | }
21 |
22 | public func execute() -> AsyncStream {
23 | locationRepository.readCurrentLocation(withAccuracy: kCLLocationAccuracyThreeKilometers)
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/ios/DomainLayer/SharedDomain/Sources/SharedDomain/PushNotifications/Models/PushNotification.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Created by Petr Chmelar on 05/04/2020.
3 | // Copyright © 2020 Matee. All rights reserved.
4 | //
5 |
6 | public enum PushNotificationType: Int {
7 | case info = 1
8 | case userDetail = 2
9 | }
10 |
11 | public struct PushNotification: Equatable {
12 | public let title: String
13 | public let body: String
14 | public let type: PushNotificationType
15 | public let entityId: String
16 |
17 | public init(
18 | title: String,
19 | body: String,
20 | type: PushNotificationType,
21 | entityId: String
22 | ) {
23 | self.title = title
24 | self.body = body
25 | self.type = type
26 | self.entityId = entityId
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/ios/DomainLayer/SharedDomain/Sources/SharedDomain/PushNotifications/Repositories/PushNotificationsRepository.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Created by Petr Chmelar on 09.03.2021
3 | // Copyright © 2021 Matee. All rights reserved.
4 | //
5 |
6 | import Spyable
7 | import UserNotifications
8 |
9 | @Spyable
10 | public protocol PushNotificationsRepository {
11 | func decode(_ notificationData: [AnyHashable: Any]) throws -> PushNotification
12 | func register(options: UNAuthorizationOptions, completionHandler: @escaping (Bool, Error?) -> Void)
13 | }
14 |
--------------------------------------------------------------------------------
/ios/DomainLayer/SharedDomain/Sources/SharedDomain/PushNotifications/UseCases/HandlePushNotificationUseCase.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Created by Petr Chmelar on 22.02.2021.
3 | // Copyright © 2021 Matee. All rights reserved.
4 | //
5 |
6 | import Spyable
7 |
8 | @Spyable
9 | public protocol HandlePushNotificationUseCase {
10 | func execute(_ notificationData: [AnyHashable: Any]) throws -> PushNotification
11 | }
12 |
13 | public struct HandlePushNotificationUseCaseImpl: HandlePushNotificationUseCase {
14 |
15 | private let pushNotificationsRepository: PushNotificationsRepository
16 |
17 | public init(pushNotificationsRepository: PushNotificationsRepository) {
18 | self.pushNotificationsRepository = pushNotificationsRepository
19 | }
20 |
21 | public func execute(_ notificationData: [AnyHashable: Any]) throws -> PushNotification {
22 | try pushNotificationsRepository.decode(notificationData)
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/ios/DomainLayer/SharedDomain/Sources/SharedDomain/RemoteConfig/Models/RemoteConfigCoding.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Created by Petr Chmelar on 05.03.2021
3 | // Copyright © 2021 Matee. All rights reserved.
4 | //
5 |
6 | public enum RemoteConfigCoding: String {
7 | case profileLabelIsVisible = "profile_label_is_visible"
8 | }
9 |
--------------------------------------------------------------------------------
/ios/DomainLayer/SharedDomain/Sources/SharedDomain/RemoteConfig/Repositories/RemoteConfigRepository.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Created by Petr Chmelar on 08.03.2021
3 | // Copyright © 2021 Matee. All rights reserved.
4 | //
5 |
6 | import Spyable
7 |
8 | @Spyable
9 | public protocol RemoteConfigRepository {
10 | func read(_ key: RemoteConfigCoding) async throws -> Bool
11 | }
12 |
--------------------------------------------------------------------------------
/ios/DomainLayer/SharedDomain/Sources/SharedDomain/RemoteConfig/UseCases/GetRemoteConfigValueUseCase.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Created by Petr Chmelar on 04.03.2021
3 | // Copyright © 2021 Matee. All rights reserved.
4 | //
5 |
6 | import Spyable
7 |
8 | @Spyable
9 | public protocol GetRemoteConfigValueUseCase {
10 | func execute(_ key: RemoteConfigCoding) async throws -> Bool
11 | }
12 |
13 | public struct GetRemoteConfigValueUseCaseImpl: GetRemoteConfigValueUseCase {
14 |
15 | private let remoteConfigRepository: RemoteConfigRepository
16 |
17 | public init(remoteConfigRepository: RemoteConfigRepository) {
18 | self.remoteConfigRepository = remoteConfigRepository
19 | }
20 |
21 | public func execute(_ key: RemoteConfigCoding) async throws -> Bool {
22 | try await remoteConfigRepository.read(key)
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/ios/DomainLayer/SharedDomain/Sources/SharedDomain/Rocket/Models/RocketLaunch.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Created by Petr Chmelar on 01.06.2022
3 | // Copyright © 2022 Matee. All rights reserved.
4 | //
5 |
6 | public struct RocketLaunch: Equatable, Hashable {
7 | public let id: String
8 | public let site: String
9 |
10 | public init(
11 | id: String,
12 | site: String
13 | ) {
14 | self.id = id
15 | self.site = site
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/ios/DomainLayer/SharedDomain/Sources/SharedDomain/Rocket/Repositories/RocketLaunchRepository.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Created by Petr Chmelar on 01.06.2022
3 | // Copyright © 2022 Matee. All rights reserved.
4 | //
5 |
6 | import Spyable
7 |
8 | @Spyable
9 | public protocol RocketLaunchRepository {
10 | func list() -> AsyncThrowingStream<[RocketLaunch], Error>
11 | }
12 |
--------------------------------------------------------------------------------
/ios/DomainLayer/SharedDomain/Sources/SharedDomain/Rocket/UseCases/GetRocketLaunchesUseCase.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Created by Petr Chmelar on 01.06.2022
3 | // Copyright © 2022 Matee. All rights reserved.
4 | //
5 |
6 | import Spyable
7 |
8 | @Spyable
9 | public protocol GetRocketLaunchesUseCase {
10 | func execute() -> AsyncThrowingStream<[RocketLaunch], Error>
11 | }
12 |
13 | public struct GetRocketLaunchesUseCaseImpl: GetRocketLaunchesUseCase {
14 |
15 | private let rocketLaunchRepository: RocketLaunchRepository
16 |
17 | public init(rocketLaunchRepository: RocketLaunchRepository) {
18 | self.rocketLaunchRepository = rocketLaunchRepository
19 | }
20 |
21 | public func execute() -> AsyncThrowingStream<[RocketLaunch], Error> {
22 | rocketLaunchRepository.list()
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/ios/DomainLayer/SharedDomain/Sources/SharedDomain/SourceType.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Created by Petr Chmelar on 08.03.2021
3 | // Copyright © 2021 Matee. All rights reserved.
4 | //
5 |
6 | public enum SourceType: Equatable {
7 | case local
8 | case remote
9 | }
10 |
--------------------------------------------------------------------------------
/ios/DomainLayer/SharedDomain/Sources/SharedDomain/StoreKit/Repositories/StoreKitRepository.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Created by David Kadlček on 11.01.2024
3 | // Copyright © 2024 Matee. All rights reserved.
4 | //
5 |
6 | import Foundation
7 |
8 | public protocol StoreKitRepository {
9 | func requestFeedback() throws
10 | }
11 |
--------------------------------------------------------------------------------
/ios/DomainLayer/SharedDomain/Sources/SharedDomain/User/Repositories/UserRepository.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Created by Petr Chmelar on 05.03.2021
3 | // Copyright © 2021 Matee. All rights reserved.
4 | //
5 |
6 | import Spyable
7 | import Utilities
8 |
9 | @Spyable
10 | public protocol UserRepository {
11 | func read(
12 | _ sourceType: SourceType,
13 | id: String
14 | ) async throws -> User
15 |
16 | func read(
17 | _ sourceType: SourceType,
18 | page: Int,
19 | limit: Int,
20 | sortBy: String?
21 | ) async throws -> Pages
22 |
23 | func update(
24 | _ sourceType: SourceType,
25 | user: User
26 | ) async throws -> User
27 | }
28 |
--------------------------------------------------------------------------------
/ios/DomainLayer/SharedDomain/Sources/SharedDomain/User/UseCases/GetUserUseCase.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Created by Petr Chmelar on 22.02.2021.
3 | // Copyright © 2021 Matee. All rights reserved.
4 | //
5 |
6 | import Spyable
7 |
8 | @Spyable
9 | public protocol GetUserUseCase {
10 | func execute(_ sourceType: SourceType, id: String) async throws -> User
11 | }
12 |
13 | public struct GetUserUseCaseImpl: GetUserUseCase {
14 |
15 | private let userRepository: UserRepository
16 |
17 | public init(userRepository: UserRepository) {
18 | self.userRepository = userRepository
19 | }
20 |
21 | public func execute(_ sourceType: SourceType, id: String) async throws -> User {
22 | try await userRepository.read(sourceType, id: id)
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/ios/DomainLayer/SharedDomain/Sources/SharedDomain/User/UseCases/GetUsersUseCase.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Created by Petr Chmelar on 22.02.2021.
3 | // Copyright © 2021 Matee. All rights reserved.
4 | //
5 |
6 | import Spyable
7 | import Utilities
8 |
9 | @Spyable
10 | public protocol GetUsersUseCase {
11 | func execute(
12 | _ sourceType: SourceType,
13 | page: Int,
14 | limit: Int
15 | ) async throws -> Pages
16 | }
17 |
18 | public struct GetUsersUseCaseImpl: GetUsersUseCase {
19 |
20 | private let userRepository: UserRepository
21 |
22 | public init(userRepository: UserRepository) {
23 | self.userRepository = userRepository
24 | }
25 |
26 | public func execute(
27 | _ sourceType: SourceType,
28 | page: Int,
29 | limit: Int
30 | ) async throws -> Pages {
31 | try await userRepository.read(sourceType, page: page, limit: limit, sortBy: "id")
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/ios/DomainLayer/SharedDomain/Sources/SharedDomain/User/UseCases/UpdateUserUseCase.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Created by Petr Chmelar on 22.02.2021.
3 | // Copyright © 2021 Matee. All rights reserved.
4 | //
5 |
6 | import Spyable
7 |
8 | @Spyable
9 | public protocol UpdateUserUseCase {
10 | func execute(_ sourceType: SourceType, user: User) async throws
11 | }
12 |
13 | public struct UpdateUserUseCaseImpl: UpdateUserUseCase {
14 |
15 | private let userRepository: UserRepository
16 |
17 | public init(userRepository: UserRepository) {
18 | self.userRepository = userRepository
19 | }
20 |
21 | public func execute(_ sourceType: SourceType, user: User) async throws {
22 | _ = try await userRepository.update(sourceType, user: user)
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/ios/DomainLayer/SharedDomain/Sources/SharedDomain/Validation/Errors/ValidationError.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Created by Petr Chmelar on 20.05.2022
3 | // Copyright © 2022 Matee. All rights reserved.
4 | //
5 |
6 | public enum ValidationError: Error, Equatable {
7 | case email(Email)
8 | case password(Password)
9 |
10 | public enum Email {
11 | case isEmpty
12 | }
13 |
14 | public enum Password {
15 | case isEmpty
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/ios/DomainLayer/SharedDomain/Sources/SharedDomain/Validation/UseCases/ValidateEmailUseCase.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Created by Petr Chmelar on 25.05.2022
3 | // Copyright © 2022 Matee. All rights reserved.
4 | //
5 |
6 | import Spyable
7 |
8 | @Spyable
9 | public protocol ValidateEmailUseCase {
10 | func execute(_ email: String) throws
11 | }
12 |
13 | public struct ValidateEmailUseCaseImpl: ValidateEmailUseCase {
14 |
15 | public init() {}
16 |
17 | public func execute(_ email: String) throws {
18 | if email.isEmpty {
19 | throw ValidationError.email(.isEmpty)
20 | }
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/ios/DomainLayer/SharedDomain/Sources/SharedDomain/Validation/UseCases/ValidatePasswordUseCase.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Created by Petr Chmelar on 25.05.2022
3 | // Copyright © 2022 Matee. All rights reserved.
4 | //
5 |
6 | import Spyable
7 |
8 | @Spyable
9 | public protocol ValidatePasswordUseCase {
10 | func execute(_ password: String) throws
11 | }
12 |
13 | public struct ValidatePasswordUseCaseImpl: ValidatePasswordUseCase {
14 |
15 | public init() {}
16 |
17 | public func execute(_ password: String) throws {
18 | if password.isEmpty {
19 | throw ValidationError.password(.isEmpty)
20 | }
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/ios/DomainLayer/SharedDomain/Sources/SharedDomainMocks/KMPUtilities/TestError.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Created by David Kadlček on 05.01.2023
3 | // Copyright © 2023 Matee. All rights reserved.
4 | //
5 |
6 | import Foundation
7 | import DevstackKmpShared
8 |
9 | public final class TestError: ErrorResult {}
10 |
--------------------------------------------------------------------------------
/ios/DomainLayer/SharedDomain/Sources/SharedDomainMocks/Mocks/DevstackKmpSharedMocks.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Created by David Kadlček on 14.04.2023
3 | // Copyright © 2023 Matee. All rights reserved.
4 | //
5 |
6 | import Foundation
7 | import DevstackKmpShared
8 | import SharedDomain
9 |
10 | public final class GetBooksUseCaseMock: UseCaseFlowNoParamsMock<[Book]>, GetBooksUseCase {}
11 | public final class RefreshBooksUseCaseMock: UseCaseResultMock, RefreshBooksUseCase {}
12 |
--------------------------------------------------------------------------------
/ios/DomainLayer/SharedDomain/Sources/SharedDomainMocks/Stubs/Auth/AuthToken+Stubs.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Created by Petr Chmelar on 22.11.2021
3 | // Copyright © 2021 Matee. All rights reserved.
4 | //
5 |
6 | // swiftlint:disable line_length
7 |
8 | import SharedDomain
9 |
10 | public extension AuthToken {
11 | static let stub = AuthToken(
12 | userId: "5c1a3d7b4a74580016faadf8",
13 | token: "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiI1YzFhM2Q3YjRhNzQ1ODAwMTZmYWFkZjgiLCJpYXQiOjE1NDUyMjM1OTcsImV4cCI6MTU0NTgyODM5N30.Gr5GmFDDm2u_hFvltMlwwwmT_JIc_134HNC6seRe90U"
14 | )
15 | }
16 |
--------------------------------------------------------------------------------
/ios/DomainLayer/SharedDomain/Sources/SharedDomainMocks/Stubs/Auth/LoginData+Stubs.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Created by Petr Chmelar on 19.03.2021
3 | // Copyright © 2021 Matee. All rights reserved.
4 | //
5 |
6 | import SharedDomain
7 |
8 | public extension LoginData {
9 | static let stubValid = LoginData(email: "email@email.com", password: "password")
10 | static let stubEmptyEmail = LoginData(email: "", password: "password")
11 | static let stubEmptyPassword = LoginData(email: "email@email.com", password: "")
12 | }
13 |
--------------------------------------------------------------------------------
/ios/DomainLayer/SharedDomain/Sources/SharedDomainMocks/Stubs/Auth/RegistrationData+Stubs.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Created by Petr Chmelar on 19.03.2021
3 | // Copyright © 2021 Matee. All rights reserved.
4 | //
5 |
6 | import SharedDomain
7 |
8 | public extension RegistrationData {
9 | static let stubValid = RegistrationData(
10 | email: "email@email.com",
11 | password: "password",
12 | firstName: "Anonymous",
13 | lastName: ""
14 | )
15 |
16 | static let stubEmptyEmail = RegistrationData(
17 | email: "",
18 | password: "password",
19 | firstName: "Anonymous",
20 | lastName: ""
21 | )
22 |
23 | static let stubEmptyPassword = RegistrationData(
24 | email: "email@email.com",
25 | password: "",
26 | firstName: "Anonymous",
27 | lastName: ""
28 | )
29 | }
30 |
--------------------------------------------------------------------------------
/ios/DomainLayer/SharedDomain/Sources/SharedDomainMocks/Stubs/Book/Book+Stubs.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Created by Petr Chmelar on 08.10.2023
3 | // Copyright © 2023 Matee. All rights reserved.
4 | //
5 |
6 | import DevstackKmpShared
7 |
8 | public extension Book {
9 | static let stub = Book(
10 | id: "1",
11 | name: "Kniha",
12 | author: "David",
13 | pages: 25
14 | )
15 | }
16 |
17 | public extension Array where Element == Book {
18 | static let stub = [
19 | Book(
20 | id: "1",
21 | name: "Kniha",
22 | author: "David",
23 | pages: 25
24 | ),
25 | Book(
26 | id: "2",
27 | name: "Dharma Bums",
28 | author: "Jack Kerouac",
29 | pages: 200
30 | )
31 | ]
32 | }
33 |
--------------------------------------------------------------------------------
/ios/DomainLayer/SharedDomain/Sources/SharedDomainMocks/Stubs/PushNotification/PushNotification+Stubs.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Created by Petr Chmelar on 22.11.2021
3 | // Copyright © 2021 Matee. All rights reserved.
4 | //
5 |
6 | import SharedDomain
7 |
8 | public extension PushNotification {
9 | static let stub = PushNotification(
10 | title: "User updated",
11 | body: "Click for more details",
12 | type: .userDetail,
13 | entityId: "5c1a3d7b4a74580016faadf8"
14 | )
15 | }
16 |
--------------------------------------------------------------------------------
/ios/DomainLayer/SharedDomain/Sources/SharedDomainMocks/Stubs/Rocket/RocketLaunch+Stubs.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Created by Petr Chmelar on 01.06.2022
3 | // Copyright © 2022 Matee. All rights reserved.
4 | //
5 |
6 | import SharedDomain
7 |
8 | public extension RocketLaunch {
9 | static let stub = RocketLaunch(
10 | id: "1",
11 | site: "Cape Canaveral"
12 | )
13 | }
14 |
15 | public extension Array where Element == RocketLaunch {
16 | static let stub = [
17 | RocketLaunch(
18 | id: "1",
19 | site: "Cape Canaveral"
20 | ),
21 | RocketLaunch(
22 | id: "2",
23 | site: "Baikonur"
24 | )
25 | ]
26 | }
27 |
--------------------------------------------------------------------------------
/ios/DomainLayer/SharedDomain/Tests/SharedDomainTests/Analytics/TrackAnalyticsEventUseCaseTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Created by Petr Chmelar on 28.09.2021
3 | // Copyright © 2021 Matee. All rights reserved.
4 | //
5 |
6 | @testable import SharedDomain
7 | import XCTest
8 |
9 | final class TrackAnalyticsEventUseCaseTests: XCTestCase {
10 |
11 | // MARK: Dependencies
12 |
13 | private let analyticsRepository = AnalyticsRepositorySpy()
14 |
15 | // MARK: Tests
16 |
17 | func testExecute() {
18 | let useCase = TrackAnalyticsEventUseCaseImpl(analyticsRepository: analyticsRepository)
19 |
20 | useCase.execute(LoginEvent.screenAppear.analyticsEvent)
21 |
22 | XCTAssert(analyticsRepository.createReceivedInvocations == [LoginEvent.screenAppear.analyticsEvent])
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/ios/DomainLayer/SharedDomain/Tests/SharedDomainTests/Auth/GetProfileIdUseCaseTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Created by Petr Chmelar on 30.03.2021
3 | // Copyright © 2021 Matee. All rights reserved.
4 | //
5 |
6 | @testable import SharedDomain
7 | import XCTest
8 |
9 | final class GetProfileIdUseCaseTests: XCTestCase {
10 |
11 | // MARK: Dependencies
12 |
13 | private let authRepository = AuthRepositorySpy()
14 |
15 | // MARK: Tests
16 |
17 | func testExecute() throws {
18 | let useCase = GetProfileIdUseCaseImpl(authRepository: authRepository)
19 | authRepository.readProfileIdReturnValue = AuthToken.stub.userId
20 |
21 | let profileId = try useCase.execute()
22 |
23 | XCTAssertEqual(profileId, AuthToken.stub.userId)
24 | XCTAssertEqual(authRepository.readProfileIdCallsCount, 1)
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/ios/DomainLayer/SharedDomain/Tests/SharedDomainTests/Auth/IsUserLoggedUseCaseTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Created by Petr Chmelar on 24.05.2022
3 | // Copyright © 2022 Matee. All rights reserved.
4 | //
5 |
6 | @testable import SharedDomain
7 | import XCTest
8 |
9 | final class IsUserLoggedUseCaseTests: XCTestCase {
10 |
11 | // MARK: Dependencies
12 |
13 | private let getProfileIdUseCase = GetProfileIdUseCaseSpy()
14 |
15 | // MARK: Tests
16 |
17 | func testExecute() {
18 | let useCase = IsUserLoggedUseCaseImpl(getProfileIdUseCase: getProfileIdUseCase)
19 | getProfileIdUseCase.executeReturnValue = AuthToken.stub.userId
20 |
21 | let isLogged = useCase.execute()
22 |
23 | XCTAssertEqual(isLogged, true)
24 | XCTAssertEqual(getProfileIdUseCase.executeCallsCount, 1)
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/ios/DomainLayer/SharedDomain/Tests/SharedDomainTests/Auth/LogoutUseCaseTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Created by Petr Chmelar on 30.03.2021
3 | // Copyright © 2021 Matee. All rights reserved.
4 | //
5 |
6 | @testable import SharedDomain
7 | import XCTest
8 |
9 | final class LogoutUseCaseTests: XCTestCase {
10 |
11 | // MARK: Dependencies
12 |
13 | private let authRepository = AuthRepositorySpy()
14 |
15 | // MARK: Tests
16 |
17 | func testExecute() throws {
18 | let useCase = LogoutUseCaseImpl(authRepository: authRepository)
19 |
20 | try useCase.execute()
21 |
22 | XCTAssertEqual(authRepository.logoutCallsCount, 1)
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/ios/DomainLayer/SharedDomain/Tests/SharedDomainTests/PushNotification/RegisterForPushNotificationsUseCaseTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Created by Petr Chmelar on 30.03.2021
3 | // Copyright © 2021 Matee. All rights reserved.
4 | //
5 |
6 | @testable import SharedDomain
7 | import XCTest
8 |
9 | final class RegisterForPushNotificationsUseCaseTests: XCTestCase {
10 |
11 | // MARK: Dependencies
12 |
13 | private let pushNotificationsRepository = PushNotificationsRepositorySpy()
14 |
15 | // MARK: Tests
16 |
17 | func testExecute() {
18 | let useCase = RegisterForPushNotificationsUseCaseImpl(pushNotificationsRepository: pushNotificationsRepository)
19 |
20 | useCase.execute(options: [.alert, .badge, .sound], completionHandler: { _, _ in })
21 |
22 | XCTAssertEqual(pushNotificationsRepository.registerOptionsCompletionHandlerCallsCount, 1)
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/ios/DomainLayer/SharedDomain/Tests/SharedDomainTests/RemoteConfig/GetRemoteConfigValueUseCaseTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Created by Petr Chmelar on 30.03.2021
3 | // Copyright © 2021 Matee. All rights reserved.
4 | //
5 |
6 | @testable import SharedDomain
7 | import XCTest
8 |
9 | final class GetRemoteConfigValueUseCaseTests: XCTestCase {
10 |
11 | // MARK: Dependencies
12 |
13 | private let remoteConfigRepository = RemoteConfigRepositorySpy()
14 |
15 | // MARK: Tests
16 |
17 | func testExecute() async throws {
18 | let useCase = GetRemoteConfigValueUseCaseImpl(remoteConfigRepository: remoteConfigRepository)
19 | remoteConfigRepository.readReturnValue = true
20 |
21 | let value = try await useCase.execute(.profileLabelIsVisible)
22 |
23 | XCTAssertEqual(value, true)
24 | XCTAssert(remoteConfigRepository.readReceivedInvocations == [.profileLabelIsVisible])
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/ios/DomainLayer/SharedDomain/Tests/SharedDomainTests/User/GetUserUseCaseTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Created by Petr Chmelar on 26.03.2021
3 | // Copyright © 2021 Matee. All rights reserved.
4 | //
5 |
6 | @testable import SharedDomain
7 | import XCTest
8 |
9 | final class GetUserUseCaseTests: XCTestCase {
10 |
11 | // MARK: Dependencies
12 |
13 | private let userRepository = UserRepositorySpy()
14 |
15 | // MARK: Tests
16 |
17 | func testExecute() async throws {
18 | let useCase = GetUserUseCaseImpl(userRepository: userRepository)
19 | userRepository.readIdReturnValue = User.stub
20 |
21 | let user = try await useCase.execute(.local, id: User.stub.id)
22 |
23 | XCTAssertEqual(user, User.stub)
24 | XCTAssert(userRepository.readIdReceivedInvocations == [(.local, User.stub.id)])
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/ios/DomainLayer/SharedDomain/Tests/SharedDomainTests/User/GetUsersUseCaseTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Created by Petr Chmelar on 26.03.2021
3 | // Copyright © 2021 Matee. All rights reserved.
4 | //
5 |
6 | @testable import SharedDomain
7 | import XCTest
8 | import Utilities
9 |
10 | final class GetUsersUseCaseTests: XCTestCase {
11 |
12 | // MARK: Dependencies
13 |
14 | private let userRepository = UserRepositorySpy()
15 |
16 | // MARK: Tests
17 |
18 | func testExecute() async throws {
19 | let useCase = GetUsersUseCaseImpl(userRepository: userRepository)
20 | userRepository.readPageLimitSortByReturnValue = Pages.stub
21 |
22 | let users = try await useCase.execute(.local, page: 0, limit: 100)
23 |
24 | XCTAssertEqual(users, Pages.stub)
25 | XCTAssert(userRepository.readPageLimitSortByReceivedInvocations == [(.local, 0, 100, "id")])
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/ios/DomainLayer/SharedDomain/Tests/SharedDomainTests/User/UpdateUserUseCaseTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Created by Petr Chmelar on 30.03.2021
3 | // Copyright © 2021 Matee. All rights reserved.
4 | //
5 |
6 | @testable import SharedDomain
7 | import SharedDomainMocks
8 | import XCTest
9 |
10 | final class UpdateUserUseCaseTests: XCTestCase {
11 |
12 | // MARK: Dependencies
13 |
14 | private let userRepository = UserRepositorySpy()
15 |
16 | // MARK: Tests
17 |
18 | func testExecute() async throws {
19 | let updatedUser = User(copy: User.stub, bio: "Updated user")
20 | let useCase = UpdateUserUseCaseImpl(userRepository: userRepository)
21 | userRepository.updateUserReturnValue = updatedUser
22 |
23 | try await useCase.execute(.local, user: updatedUser)
24 |
25 | XCTAssert(userRepository.updateUserReceivedInvocations == [(.local, updatedUser)])
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/ios/DomainLayer/Utilities/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | /.build
3 | /Packages
4 | /*.xcodeproj
5 | xcuserdata/
6 | DerivedData/
7 | .swiftpm/config/registries.json
8 | .swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata
9 | .netrc
10 |
--------------------------------------------------------------------------------
/ios/DomainLayer/Utilities/Sources/Utilities/Environment.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Created by Petr Chmelar on 08.03.2021
3 | // Copyright © 2021 Matee. All rights reserved.
4 | //
5 |
6 | import Foundation
7 |
8 | public enum EnvironmentType {
9 | case alpha
10 | case beta
11 | case production
12 | }
13 |
14 | public enum EnvironmentFlavor {
15 | case debug
16 | case release
17 | }
18 |
19 | public struct Environment {
20 | public static var type: EnvironmentType = .alpha
21 | public static var flavor: EnvironmentFlavor = .debug
22 | public static var locale: Locale = Locale.current
23 | }
24 |
--------------------------------------------------------------------------------
/ios/DomainLayer/Utilities/Sources/Utilities/Extensions/Decodable+Extensions.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Created by Petr Chmelar on 12.03.2021
3 | // Copyright © 2021 Matee. All rights reserved.
4 | //
5 |
6 | import Foundation
7 |
8 | public extension Decodable {
9 | static func stub(in bundle: Bundle) -> Data {
10 | stub(for: bundle.url(forResource: String(describing: self), withExtension: "json"))
11 | }
12 |
13 | static func stubList(in bundle: Bundle) -> Data {
14 | stub(for: bundle.url(forResource: "\(String(describing: self))List", withExtension: "json"))
15 | }
16 |
17 | private static func stub(for url: URL?) -> Data {
18 | guard let url, let data = try? Data(contentsOf: url) else { return Data() }
19 | return data
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/ios/DomainLayer/Utilities/Sources/Utilities/Extensions/Encodable+Extensions.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Created by Petr Chmelar on 28/08/2018.
3 | // Copyright © 2018 Matee. All rights reserved.
4 | //
5 |
6 | import Foundation
7 |
8 | public extension Encodable {
9 | func encode() throws -> [String: Any] {
10 | let data = try JSONEncoder().encode(self)
11 | guard let json = try JSONSerialization.jsonObject(with: data) as? [String: Any] else {
12 | throw EncodingError.invalidValue(data, .init(codingPath: [], debugDescription: "Object can't be encoded"))
13 | }
14 | return json
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/ios/DomainLayer/Utilities/Sources/Utilities/Extensions/Logger+Extensions.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Created by Petr Chmelar on 06/02/2019.
3 | // Copyright © 2019 Matee. All rights reserved.
4 | //
5 |
6 | import OSLog
7 |
8 | public extension Logger {
9 | static let app = Logger(subsystem: Bundle.main.bundleIdentifier ?? "-", category: "App")
10 | static let lifecycle = Logger(subsystem: Bundle.main.bundleIdentifier ?? "-", category: "Lifecycle")
11 | }
12 |
--------------------------------------------------------------------------------
/ios/DomainLayer/Utilities/Sources/Utilities/Extensions/String+Extensions.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Created by Petr Chmelar on 23/07/2018.
3 | // Copyright © 2018 Matee. All rights reserved.
4 | //
5 |
6 | import Foundation
7 |
8 | extension String {
9 | /// Conversion from String to Date using a given formatter.
10 | func toDate(formatter: DateFormatter = Formatter.Date.default) -> Date? {
11 | formatter.date(from: self)
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/ios/PresentationLayer/MapToolkit/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | /.build
3 | /Packages
4 | /*.xcodeproj
5 | xcuserdata/
6 | DerivedData/
7 | .swiftpm/config/registries.json
8 | .swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata
9 | .netrc
10 |
--------------------------------------------------------------------------------
/ios/PresentationLayer/MapToolkit/Sources/MapToolkit/Views/GoogleMapsView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Created by Petr Chmelar on 03.08.2023
3 | // Copyright © 2023 Matee. All rights reserved.
4 | //
5 |
6 | import GoogleMaps
7 | import SwiftUI
8 | import UIToolkit
9 | import Utilities
10 |
11 | public struct GoogleMapsView: UIViewRepresentable {
12 |
13 | public init() {
14 | // Provide GoogleMaps API key
15 | GMSServices.provideAPIKey(NetworkingConstants.googleMapsAPIKey)
16 | }
17 |
18 | public func makeUIView(context: Context) -> GMSMapView {
19 | let mapView = GMSMapView(frame: .zero)
20 | return mapView
21 | }
22 |
23 | public func updateUIView(_ uiView: GMSMapView, context: Context) {}
24 | }
25 |
--------------------------------------------------------------------------------
/ios/PresentationLayer/Onboarding/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | /.build
3 | /Packages
4 | /*.xcodeproj
5 | xcuserdata/
6 | DerivedData/
7 | .swiftpm/config/registries.json
8 | .swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata
9 | .netrc
10 |
--------------------------------------------------------------------------------
/ios/PresentationLayer/Onboarding/Sources/Onboarding/Errors/AuthError.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Created by Petr Chmelar on 20.05.2022
3 | // Copyright © 2022 Matee. All rights reserved.
4 | //
5 |
6 | import Foundation
7 | import SharedDomain
8 | import UIToolkit
9 |
10 | extension AuthError: LocalizedError {
11 | public var errorDescription: String? {
12 | switch self {
13 | case .registration(let reason):
14 | switch reason {
15 | case .userAlreadyExists: L10n.register_view_email_already_exists
16 | case .failed: L10n.signing_up_failed
17 | }
18 | }
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/ios/PresentationLayer/Onboarding/Sources/Onboarding/Errors/ValidationError.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Created by Petr Chmelar on 20.05.2022
3 | // Copyright © 2022 Matee. All rights reserved.
4 | //
5 |
6 | import Foundation
7 | import DevstackKmpShared
8 | import SharedDomain
9 | import UIToolkit
10 |
11 | extension ValidationError: LocalizedError {
12 | public var errorDescription: String? {
13 | switch self {
14 | case .email(let reason):
15 | switch reason {
16 | case .isEmpty: return MR.strings().invalid_email.desc().localized()
17 | }
18 | case .password(let reason):
19 | switch reason {
20 | case .isEmpty: return MR.strings().invalid_password.desc().localized()
21 | }
22 | }
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/ios/PresentationLayer/Profile/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | /.build
3 | /Packages
4 | /*.xcodeproj
5 | xcuserdata/
6 | DerivedData/
7 | .swiftpm/config/registries.json
8 | .swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata
9 | .netrc
10 |
--------------------------------------------------------------------------------
/ios/PresentationLayer/Recipes/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | /.build
3 | /Packages
4 | /*.xcodeproj
5 | xcuserdata/
6 | DerivedData/
7 | .swiftpm/config/registries.json
8 | .swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata
9 | .netrc
10 |
--------------------------------------------------------------------------------
/ios/PresentationLayer/Recipes/Sources/Recipes/Maps/MapsView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Created by Petr Chmelar on 03.08.2023
3 | // Copyright © 2023 Matee. All rights reserved.
4 | //
5 |
6 | import MapToolkit
7 | import SwiftUI
8 | import UIToolkit
9 |
10 | struct MapsView: View {
11 |
12 | @ObservedObject private var viewModel: MapsViewModel
13 |
14 | init(viewModel: MapsViewModel) {
15 | self.viewModel = viewModel
16 | }
17 |
18 | var body: some View {
19 | return VStack {
20 | GoogleMapsView()
21 | }
22 | .lifecycle(viewModel)
23 | }
24 | }
25 |
26 | #if DEBUG
27 | #Preview {
28 | let vm = MapsViewModel(flowController: nil)
29 | return MapsView(viewModel: vm)
30 | }
31 | #endif
32 |
--------------------------------------------------------------------------------
/ios/PresentationLayer/Recipes/Sources/Recipes/Maps/MapsViewModel.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Created by Petr Chmelar on 03.08.2023
3 | // Copyright © 2023 Matee. All rights reserved.
4 | //
5 |
6 | import SwiftUI
7 | import UIToolkit
8 |
9 | final class MapsViewModel: BaseViewModel, ViewModel, ObservableObject {
10 |
11 | // MARK: Dependencies
12 | private weak var flowController: FlowController?
13 |
14 | init(flowController: FlowController?) {
15 | self.flowController = flowController
16 | super.init()
17 | }
18 |
19 | // MARK: Lifecycle
20 |
21 | override func onAppear() {
22 | super.onAppear()
23 | }
24 |
25 | // MARK: State
26 |
27 | @Published private(set) var state: State = State()
28 |
29 | struct State {
30 | }
31 |
32 | // MARK: Intent
33 | enum Intent {
34 | }
35 |
36 | func onIntent(_ intent: Intent) {}
37 |
38 | // MARK: Private
39 | }
40 |
--------------------------------------------------------------------------------
/ios/PresentationLayer/Recipes/Sources/Recipes/Skeleton/SkeletonView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Created by Petr Chmelar on 07.06.2022
3 | // Copyright © 2022 Matee. All rights reserved.
4 | //
5 |
6 | import SwiftUI
7 | import UIToolkit
8 |
9 | struct SkeletonView: View {
10 |
11 | @ObservedObject private var viewModel: SkeletonViewModel
12 |
13 | init(viewModel: SkeletonViewModel) {
14 | self.viewModel = viewModel
15 | }
16 |
17 | var body: some View {
18 | return VStack {
19 | HeadlineText(viewModel.state.title ?? .placeholder(length: 10))
20 | .skeleton(viewModel.state.title == nil)
21 | }
22 | .lifecycle(viewModel)
23 | .navigationTitle(L10n.skeleton_view_toolbar_title)
24 | }
25 | }
26 |
27 | #if DEBUG
28 | #Preview {
29 | let vm = SkeletonViewModel(flowController: nil)
30 | return SkeletonView(viewModel: vm)
31 | }
32 | #endif
33 |
--------------------------------------------------------------------------------
/ios/PresentationLayer/Recipes/Sources/Recipes/TipKit/Views/ActionTip.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Created by David Kadlček on 24.07.2024
3 | // Copyright © 2024 Matee. All rights reserved.
4 | //
5 |
6 | import Foundation
7 | import TipKit
8 | import UIToolkit
9 |
10 | @available(iOS 17, *)
11 | struct ActionTip: Tip {
12 |
13 | enum Actions {
14 | case pop
15 | case close
16 |
17 | var id: String {
18 | switch self {
19 | case .pop:
20 | "pop-screen"
21 | case .close:
22 | "close-popup"
23 | }
24 | }
25 | }
26 |
27 | var title: Text {
28 | Text(L10n.recipe_tipkit_action_tip_title)
29 | }
30 |
31 | var actions: [Action] {
32 | Action(id: Actions.pop.id, title: "Pop screen")
33 | Action(id: Actions.close.id, title: "Close popup")
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/ios/PresentationLayer/Recipes/Sources/Recipes/TipKit/Views/InlineTip.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Created by David Kadlček on 24.07.2024
3 | // Copyright © 2024 Matee. All rights reserved.
4 | //
5 |
6 | import Foundation
7 | import TipKit
8 | import UIToolkit
9 |
10 | @available(iOS 17, *)
11 | struct InlineTip: Tip {
12 |
13 | var title: Text {
14 | Text(L10n.recipe_tipkit_inline_tip_title)
15 | }
16 |
17 | var message: Text? {
18 | Text(L10n.recipe_tipkit_inline_tip_message)
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/ios/PresentationLayer/Recipes/Sources/Recipes/TipKit/Views/PopoverTip.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Created by David Kadlček on 24.07.2024
3 | // Copyright © 2024 Matee. All rights reserved.
4 | //
5 |
6 | import Foundation
7 | import TipKit
8 | import UIToolkit
9 |
10 | @available(iOS 17, *)
11 | struct PopoverTip: Tip {
12 |
13 | var title: Text {
14 | Text(L10n.recipe_tipkit_popover_tip_title)
15 | }
16 |
17 | var message: Text? {
18 | Text(L10n.recipe_tipkit_popover_tip_message)
19 | }
20 |
21 | var image: Image? {
22 | Asset.Images.brandLogo.image
23 | }
24 |
25 | var options: [any TipOption] {
26 | [Tips.MaxDisplayCount(1)]
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/ios/PresentationLayer/Recipes/Sources/Recipes/TipKit/Views/RuleTip.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Created by David Kadlček on 24.07.2024
3 | // Copyright © 2024 Matee. All rights reserved.
4 | //
5 |
6 | import Foundation
7 | import TipKit
8 | import UIToolkit
9 |
10 | @available(iOS 17, *)
11 | struct RuleTip: Tip {
12 |
13 | /// Event Rule
14 | static let remainToShow = Event(id: "number-value")
15 |
16 | var title: Text {
17 | Text(L10n.recipe_tipkit_rule_tip_title)
18 | }
19 |
20 | var message: Text? {
21 | Text(L10n.recipe_tipkit_rule_tip_message)
22 | }
23 |
24 | var rules: [Rule] {
25 | [
26 | #Rule(Self.remainToShow) { $0.donations.count > 2 }
27 | ]
28 | }
29 |
30 | var options: [any TipOption] = [
31 | IgnoresDisplayFrequency(true)
32 | ]
33 | }
34 |
--------------------------------------------------------------------------------
/ios/PresentationLayer/UIToolkit/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | /.build
3 | /Packages
4 | /*.xcodeproj
5 | xcuserdata/
6 | DerivedData/
7 | .swiftpm/config/registries.json
8 | .swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata
9 | .netrc
10 |
--------------------------------------------------------------------------------
/ios/PresentationLayer/UIToolkit/Sources/UIToolkit/BaseViews/SwiftUI/Button/SecondaryButtonStyle.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Created by Petr Chmelar on 28.02.2022
3 | // Copyright © 2022 Matee. All rights reserved.
4 | //
5 |
6 | import SwiftUI
7 |
8 | public struct SecondaryButtonStyle: ButtonStyle {
9 |
10 | public init() {}
11 |
12 | public func makeBody(configuration: Configuration) -> some View {
13 | configuration.label
14 | .font(AppTheme.Fonts.secondaryButton)
15 | .foregroundColor(AppTheme.Colors.secondaryButtonTitle)
16 | .padding()
17 | }
18 | }
19 |
20 | #if DEBUG
21 | #Preview {
22 | Button("Lorem Ipsum") {}
23 | .buttonStyle(SecondaryButtonStyle())
24 | }
25 | #endif
26 |
--------------------------------------------------------------------------------
/ios/PresentationLayer/UIToolkit/Sources/UIToolkit/BaseViews/SwiftUI/ProgressView/PrimaryProgressView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Created by Petr Chmelar on 23.05.2022
3 | // Copyright © 2022 Matee. All rights reserved.
4 | //
5 |
6 | import SwiftUI
7 |
8 | public struct PrimaryProgressView: View {
9 |
10 | public init() {}
11 |
12 | public var body: some View {
13 | ProgressView()
14 | .progressViewStyle(CircularProgressViewStyle(tint: AppTheme.Colors.progressView))
15 | .scaleEffect(2)
16 | }
17 | }
18 |
19 | #if DEBUG
20 | #Preview {
21 | PrimaryProgressView()
22 | }
23 | #endif
24 |
--------------------------------------------------------------------------------
/ios/PresentationLayer/UIToolkit/Sources/UIToolkit/BaseViews/SwiftUI/Text/HeadlineText.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Created by Petr Chmelar on 27.02.2022
3 | // Copyright © 2022 Matee. All rights reserved.
4 | //
5 |
6 | import SwiftUI
7 |
8 | public struct HeadlineText: View {
9 |
10 | private let content: String
11 |
12 | public init(_ content: String) {
13 | self.content = content
14 | }
15 |
16 | public var body: some View {
17 | Text(content)
18 | .font(AppTheme.Fonts.headlineText)
19 | .foregroundColor(AppTheme.Colors.headlineText)
20 | }
21 | }
22 |
23 | #if DEBUG
24 | #Preview {
25 | HeadlineText("Lorem Ipsum")
26 | }
27 | #endif
28 |
--------------------------------------------------------------------------------
/ios/PresentationLayer/UIToolkit/Sources/UIToolkit/BaseViews/UIKit/UIViewController/BaseNavigationController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Created by Petr Chmelar on 06.10.2021
3 | // Copyright © 2021 Matee. All rights reserved.
4 | //
5 |
6 | import UIKit
7 |
8 | public final class BaseNavigationController: UINavigationController {
9 |
10 | private var statusBarStyle: UIStatusBarStyle = .default
11 |
12 | override public var preferredStatusBarStyle: UIStatusBarStyle {
13 | return statusBarStyle
14 | }
15 |
16 | override public var childForStatusBarStyle: UIViewController? {
17 | return visibleViewController
18 | }
19 |
20 | public convenience init(statusBarStyle: UIStatusBarStyle) {
21 | self.init(nibName: nil, bundle: nil)
22 | self.statusBarStyle = statusBarStyle
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/ios/PresentationLayer/UIToolkit/Sources/UIToolkit/Environment/LoadingKey.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Created by David Kadlček on 06.03.2023
3 | // Copyright © 2023 Matee. All rights reserved.
4 | //
5 |
6 | import Foundation
7 | import SwiftUI
8 |
9 | struct LoadingKey: EnvironmentKey {
10 | static let defaultValue: Bool = false
11 | }
12 |
--------------------------------------------------------------------------------
/ios/PresentationLayer/UIToolkit/Sources/UIToolkit/Extensions/BundleToken.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Created by Petr Chmelar on 30.05.2022
3 | // Copyright © 2022 Matee. All rights reserved.
4 | //
5 |
6 | import Foundation
7 |
8 | private final class BundleToken {}
9 |
--------------------------------------------------------------------------------
/ios/PresentationLayer/UIToolkit/Sources/UIToolkit/Extensions/CLLocationCoordinate2D+Extensions.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Created by Petr Chmelar on 10/02/2020.
3 | // Copyright © 2020 Matee. All rights reserved.
4 | //
5 |
6 | import CoreLocation
7 |
8 | public extension CLLocationCoordinate2D {
9 |
10 | /// Conversion from CLLocationCoordinate2D to String.
11 | func toString(withPlaces places: Int = 4) -> String {
12 | return "\(latitude.rounded(toPlaces: places)); \(longitude.rounded(toPlaces: places))"
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/ios/PresentationLayer/UIToolkit/Sources/UIToolkit/Extensions/Double+Extensions.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Created by Petr Chmelar on 10/02/2020.
3 | // Copyright © 2020 Matee. All rights reserved.
4 | //
5 |
6 | import Foundation
7 |
8 | public extension Double {
9 |
10 | /// Rounds the double to decimal places value
11 | func rounded(toPlaces places: Int) -> Double {
12 | let divisor = pow(10.0, Double(places))
13 | return (self * divisor).rounded() / divisor
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/ios/PresentationLayer/UIToolkit/Sources/UIToolkit/Extensions/EnvironmentValues+Extensions.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Created by David Kadlček on 06.03.2023
3 | // Copyright © 2023 Matee. All rights reserved.
4 | //
5 |
6 | import Foundation
7 | import SwiftUI
8 |
9 | public extension EnvironmentValues {
10 | var isLoading: Bool {
11 | get { self[LoadingKey.self] }
12 | set { self[LoadingKey.self] = newValue }
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/ios/PresentationLayer/UIToolkit/Sources/UIToolkit/Extensions/Int+Extensions.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Created by Viktor Kaderabek on 10/09/2018.
3 | // Copyright © 2018 Matee. All rights reserved.
4 | //
5 |
6 | import Foundation
7 | import Utilities
8 |
9 | public extension Int {
10 |
11 | /// Conversion from Int to String using a given formatter.
12 | func toString(formatter: NumberFormatter = Formatter.Number.default) -> String {
13 | formatter.string(from: NSNumber(value: self)) ?? ""
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/ios/PresentationLayer/UIToolkit/Sources/UIToolkit/Extensions/NSObject+Extensions.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Created by Viktor Kaderabek on 25/07/2018.
3 | // Copyright © 2018 Matee. All rights reserved.
4 | //
5 |
6 | import Foundation
7 |
8 | public extension NSObject {
9 |
10 | /// Class name literal
11 | class var nameOfClass: String {
12 | guard let className = NSStringFromClass(self).components(separatedBy: ".").last else { return "N/A" }
13 | return className
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/ios/PresentationLayer/UIToolkit/Sources/UIToolkit/Extensions/NavigationLink+Extensions.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Created by Petr Chmelar on 29.05.2022
3 | // Copyright © 2022 Matee. All rights reserved.
4 | //
5 |
6 | import SwiftUI
7 |
8 | // Taken from: https://stackoverflow.com/a/66891173
9 |
10 | public extension NavigationLink where Label == EmptyView, Destination == EmptyView {
11 |
12 | /// Useful in cases where a `NavigationLink` is needed but there should not be a destination. e.g. for programmatic navigation.
13 | static var empty: NavigationLink {
14 | self.init(destination: EmptyView(), label: { EmptyView() })
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/ios/PresentationLayer/UIToolkit/Sources/UIToolkit/Extensions/UIViewController+Extensions.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Created by Petr Chmelar on 28.01.2021.
3 | // Copyright © 2021 Matee. All rights reserved.
4 | //
5 |
6 | import UIKit
7 |
8 | public extension UIViewController {
9 |
10 | func add(_ child: UIViewController) {
11 | addChild(child)
12 | view.addSubview(child.view)
13 | child.didMove(toParent: self)
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/ios/PresentationLayer/UIToolkit/Sources/UIToolkit/Resources/Colors.xcassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/ios/PresentationLayer/UIToolkit/Sources/UIToolkit/Resources/Colors.xcassets/Error.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "colors" : [
3 | {
4 | "color" : {
5 | "color-space" : "srgb",
6 | "components" : {
7 | "alpha" : "1.000",
8 | "blue" : "0x00",
9 | "green" : "0x00",
10 | "red" : "0xD9"
11 | }
12 | },
13 | "idiom" : "universal"
14 | },
15 | {
16 | "appearances" : [
17 | {
18 | "appearance" : "luminosity",
19 | "value" : "dark"
20 | }
21 | ],
22 | "color" : {
23 | "color-space" : "srgb",
24 | "components" : {
25 | "alpha" : "1.000",
26 | "blue" : "0x3C",
27 | "green" : "0x3B",
28 | "red" : "0xE7"
29 | }
30 | },
31 | "idiom" : "universal"
32 | }
33 | ],
34 | "info" : {
35 | "author" : "xcode",
36 | "version" : 1
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/ios/PresentationLayer/UIToolkit/Sources/UIToolkit/Resources/Colors.xcassets/Info.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "colors" : [
3 | {
4 | "color" : {
5 | "color-space" : "srgb",
6 | "components" : {
7 | "alpha" : "1.000",
8 | "blue" : "0xAD",
9 | "green" : "0x6B",
10 | "red" : "0x44"
11 | }
12 | },
13 | "idiom" : "universal"
14 | },
15 | {
16 | "appearances" : [
17 | {
18 | "appearance" : "luminosity",
19 | "value" : "dark"
20 | }
21 | ],
22 | "color" : {
23 | "color-space" : "srgb",
24 | "components" : {
25 | "alpha" : "1.000",
26 | "blue" : "0xF0",
27 | "green" : "0x84",
28 | "red" : "0x34"
29 | }
30 | },
31 | "idiom" : "universal"
32 | }
33 | ],
34 | "info" : {
35 | "author" : "xcode",
36 | "version" : 1
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/ios/PresentationLayer/UIToolkit/Sources/UIToolkit/Resources/Colors.xcassets/MateeBlue.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "version" : 1,
4 | "author" : "xcode"
5 | },
6 | "colors" : [
7 | {
8 | "idiom" : "universal",
9 | "color" : {
10 | "color-space" : "srgb",
11 | "components" : {
12 | "red" : "0x23",
13 | "alpha" : "1.000",
14 | "blue" : "0x98",
15 | "green" : "0x3F"
16 | }
17 | }
18 | },
19 | {
20 | "idiom" : "universal",
21 | "appearances" : [
22 | {
23 | "appearance" : "luminosity",
24 | "value" : "dark"
25 | }
26 | ],
27 | "color" : {
28 | "color-space" : "srgb",
29 | "components" : {
30 | "red" : "0x0C",
31 | "alpha" : "1.000",
32 | "blue" : "0x78",
33 | "green" : "0x26"
34 | }
35 | }
36 | }
37 | ]
38 | }
--------------------------------------------------------------------------------
/ios/PresentationLayer/UIToolkit/Sources/UIToolkit/Resources/Colors.xcassets/MateeYellow.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "version" : 1,
4 | "author" : "xcode"
5 | },
6 | "colors" : [
7 | {
8 | "idiom" : "universal",
9 | "color" : {
10 | "color-space" : "srgb",
11 | "components" : {
12 | "red" : "0xE1",
13 | "alpha" : "1.000",
14 | "blue" : "0x2B",
15 | "green" : "0xBE"
16 | }
17 | }
18 | },
19 | {
20 | "idiom" : "universal",
21 | "appearances" : [
22 | {
23 | "appearance" : "luminosity",
24 | "value" : "dark"
25 | }
26 | ],
27 | "color" : {
28 | "color-space" : "srgb",
29 | "components" : {
30 | "red" : "0xCC",
31 | "alpha" : "1.000",
32 | "blue" : "0x14",
33 | "green" : "0xA8"
34 | }
35 | }
36 | }
37 | ]
38 | }
--------------------------------------------------------------------------------
/ios/PresentationLayer/UIToolkit/Sources/UIToolkit/Resources/Colors.xcassets/Success.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "colors" : [
3 | {
4 | "color" : {
5 | "color-space" : "srgb",
6 | "components" : {
7 | "alpha" : "1.000",
8 | "blue" : "0x40",
9 | "green" : "0x81",
10 | "red" : "0x40"
11 | }
12 | },
13 | "idiom" : "universal"
14 | },
15 | {
16 | "appearances" : [
17 | {
18 | "appearance" : "luminosity",
19 | "value" : "dark"
20 | }
21 | ],
22 | "color" : {
23 | "color-space" : "srgb",
24 | "components" : {
25 | "alpha" : "1.000",
26 | "blue" : "0x4A",
27 | "green" : "0x93",
28 | "red" : "0x4A"
29 | }
30 | },
31 | "idiom" : "universal"
32 | }
33 | ],
34 | "info" : {
35 | "author" : "xcode",
36 | "version" : 1
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/ios/PresentationLayer/UIToolkit/Sources/UIToolkit/Resources/Images.xcassets/BrandLogo.imageset/logo-dark@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MateeDevs/devstack-native-app/26a21470027d26623d46c97b6e2e299c1604fb02/ios/PresentationLayer/UIToolkit/Sources/UIToolkit/Resources/Images.xcassets/BrandLogo.imageset/logo-dark@1x.png
--------------------------------------------------------------------------------
/ios/PresentationLayer/UIToolkit/Sources/UIToolkit/Resources/Images.xcassets/BrandLogo.imageset/logo-dark@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MateeDevs/devstack-native-app/26a21470027d26623d46c97b6e2e299c1604fb02/ios/PresentationLayer/UIToolkit/Sources/UIToolkit/Resources/Images.xcassets/BrandLogo.imageset/logo-dark@2x.png
--------------------------------------------------------------------------------
/ios/PresentationLayer/UIToolkit/Sources/UIToolkit/Resources/Images.xcassets/BrandLogo.imageset/logo-dark@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MateeDevs/devstack-native-app/26a21470027d26623d46c97b6e2e299c1604fb02/ios/PresentationLayer/UIToolkit/Sources/UIToolkit/Resources/Images.xcassets/BrandLogo.imageset/logo-dark@3x.png
--------------------------------------------------------------------------------
/ios/PresentationLayer/UIToolkit/Sources/UIToolkit/Resources/Images.xcassets/BrandLogo.imageset/logo-light@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MateeDevs/devstack-native-app/26a21470027d26623d46c97b6e2e299c1604fb02/ios/PresentationLayer/UIToolkit/Sources/UIToolkit/Resources/Images.xcassets/BrandLogo.imageset/logo-light@1x.png
--------------------------------------------------------------------------------
/ios/PresentationLayer/UIToolkit/Sources/UIToolkit/Resources/Images.xcassets/BrandLogo.imageset/logo-light@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MateeDevs/devstack-native-app/26a21470027d26623d46c97b6e2e299c1604fb02/ios/PresentationLayer/UIToolkit/Sources/UIToolkit/Resources/Images.xcassets/BrandLogo.imageset/logo-light@2x.png
--------------------------------------------------------------------------------
/ios/PresentationLayer/UIToolkit/Sources/UIToolkit/Resources/Images.xcassets/BrandLogo.imageset/logo-light@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MateeDevs/devstack-native-app/26a21470027d26623d46c97b6e2e299c1604fb02/ios/PresentationLayer/UIToolkit/Sources/UIToolkit/Resources/Images.xcassets/BrandLogo.imageset/logo-light@3x.png
--------------------------------------------------------------------------------
/ios/PresentationLayer/UIToolkit/Sources/UIToolkit/Resources/Images.xcassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/ios/PresentationLayer/UIToolkit/Sources/UIToolkit/Resources/Images.xcassets/Icons/CheckboxOff.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "filename" : "checkbox_off.png",
6 | "scale" : "1x"
7 | },
8 | {
9 | "idiom" : "universal",
10 | "filename" : "checkbox_off@2x.png",
11 | "scale" : "2x"
12 | },
13 | {
14 | "idiom" : "universal",
15 | "filename" : "checkbox_off@3x.png",
16 | "scale" : "3x"
17 | }
18 | ],
19 | "info" : {
20 | "version" : 1,
21 | "author" : "xcode"
22 | }
23 | }
--------------------------------------------------------------------------------
/ios/PresentationLayer/UIToolkit/Sources/UIToolkit/Resources/Images.xcassets/Icons/CheckboxOff.imageset/checkbox_off.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MateeDevs/devstack-native-app/26a21470027d26623d46c97b6e2e299c1604fb02/ios/PresentationLayer/UIToolkit/Sources/UIToolkit/Resources/Images.xcassets/Icons/CheckboxOff.imageset/checkbox_off.png
--------------------------------------------------------------------------------
/ios/PresentationLayer/UIToolkit/Sources/UIToolkit/Resources/Images.xcassets/Icons/CheckboxOff.imageset/checkbox_off@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MateeDevs/devstack-native-app/26a21470027d26623d46c97b6e2e299c1604fb02/ios/PresentationLayer/UIToolkit/Sources/UIToolkit/Resources/Images.xcassets/Icons/CheckboxOff.imageset/checkbox_off@2x.png
--------------------------------------------------------------------------------
/ios/PresentationLayer/UIToolkit/Sources/UIToolkit/Resources/Images.xcassets/Icons/CheckboxOff.imageset/checkbox_off@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MateeDevs/devstack-native-app/26a21470027d26623d46c97b6e2e299c1604fb02/ios/PresentationLayer/UIToolkit/Sources/UIToolkit/Resources/Images.xcassets/Icons/CheckboxOff.imageset/checkbox_off@3x.png
--------------------------------------------------------------------------------
/ios/PresentationLayer/UIToolkit/Sources/UIToolkit/Resources/Images.xcassets/Icons/CheckboxOn.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "filename" : "checkboox_on.png",
6 | "scale" : "1x"
7 | },
8 | {
9 | "idiom" : "universal",
10 | "filename" : "checkboox_on@2x.png",
11 | "scale" : "2x"
12 | },
13 | {
14 | "idiom" : "universal",
15 | "filename" : "checkboox_on@3x.png",
16 | "scale" : "3x"
17 | }
18 | ],
19 | "info" : {
20 | "version" : 1,
21 | "author" : "xcode"
22 | }
23 | }
--------------------------------------------------------------------------------
/ios/PresentationLayer/UIToolkit/Sources/UIToolkit/Resources/Images.xcassets/Icons/CheckboxOn.imageset/checkboox_on.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MateeDevs/devstack-native-app/26a21470027d26623d46c97b6e2e299c1604fb02/ios/PresentationLayer/UIToolkit/Sources/UIToolkit/Resources/Images.xcassets/Icons/CheckboxOn.imageset/checkboox_on.png
--------------------------------------------------------------------------------
/ios/PresentationLayer/UIToolkit/Sources/UIToolkit/Resources/Images.xcassets/Icons/CheckboxOn.imageset/checkboox_on@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MateeDevs/devstack-native-app/26a21470027d26623d46c97b6e2e299c1604fb02/ios/PresentationLayer/UIToolkit/Sources/UIToolkit/Resources/Images.xcassets/Icons/CheckboxOn.imageset/checkboox_on@2x.png
--------------------------------------------------------------------------------
/ios/PresentationLayer/UIToolkit/Sources/UIToolkit/Resources/Images.xcassets/Icons/CheckboxOn.imageset/checkboox_on@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MateeDevs/devstack-native-app/26a21470027d26623d46c97b6e2e299c1604fb02/ios/PresentationLayer/UIToolkit/Sources/UIToolkit/Resources/Images.xcassets/Icons/CheckboxOn.imageset/checkboox_on@3x.png
--------------------------------------------------------------------------------
/ios/PresentationLayer/UIToolkit/Sources/UIToolkit/Resources/Images.xcassets/Icons/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/ios/PresentationLayer/UIToolkit/Sources/UIToolkit/Resources/Images.xcassets/TabBar/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/ios/PresentationLayer/UIToolkit/Sources/UIToolkit/Resources/Images.xcassets/TabBar/ProfileTabBar.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "filename" : "ic_profile@1x.png",
6 | "scale" : "1x"
7 | },
8 | {
9 | "idiom" : "universal",
10 | "filename" : "ic_profile@2x.png",
11 | "scale" : "2x"
12 | },
13 | {
14 | "idiom" : "universal",
15 | "filename" : "ic_profile@3x.png",
16 | "scale" : "3x"
17 | }
18 | ],
19 | "info" : {
20 | "version" : 1,
21 | "author" : "xcode"
22 | }
23 | }
--------------------------------------------------------------------------------
/ios/PresentationLayer/UIToolkit/Sources/UIToolkit/Resources/Images.xcassets/TabBar/ProfileTabBar.imageset/ic_profile@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MateeDevs/devstack-native-app/26a21470027d26623d46c97b6e2e299c1604fb02/ios/PresentationLayer/UIToolkit/Sources/UIToolkit/Resources/Images.xcassets/TabBar/ProfileTabBar.imageset/ic_profile@1x.png
--------------------------------------------------------------------------------
/ios/PresentationLayer/UIToolkit/Sources/UIToolkit/Resources/Images.xcassets/TabBar/ProfileTabBar.imageset/ic_profile@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MateeDevs/devstack-native-app/26a21470027d26623d46c97b6e2e299c1604fb02/ios/PresentationLayer/UIToolkit/Sources/UIToolkit/Resources/Images.xcassets/TabBar/ProfileTabBar.imageset/ic_profile@2x.png
--------------------------------------------------------------------------------
/ios/PresentationLayer/UIToolkit/Sources/UIToolkit/Resources/Images.xcassets/TabBar/ProfileTabBar.imageset/ic_profile@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MateeDevs/devstack-native-app/26a21470027d26623d46c97b6e2e299c1604fb02/ios/PresentationLayer/UIToolkit/Sources/UIToolkit/Resources/Images.xcassets/TabBar/ProfileTabBar.imageset/ic_profile@3x.png
--------------------------------------------------------------------------------
/ios/PresentationLayer/UIToolkit/Sources/UIToolkit/Resources/Images.xcassets/TabBar/RecipesTabBar.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "filename" : "ic_dashboard@1x.png",
6 | "scale" : "1x"
7 | },
8 | {
9 | "idiom" : "universal",
10 | "filename" : "ic_dashboard@2x.png",
11 | "scale" : "2x"
12 | },
13 | {
14 | "idiom" : "universal",
15 | "filename" : "ic_dashboard@3x.png",
16 | "scale" : "3x"
17 | }
18 | ],
19 | "info" : {
20 | "version" : 1,
21 | "author" : "xcode"
22 | }
23 | }
--------------------------------------------------------------------------------
/ios/PresentationLayer/UIToolkit/Sources/UIToolkit/Resources/Images.xcassets/TabBar/RecipesTabBar.imageset/ic_dashboard@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MateeDevs/devstack-native-app/26a21470027d26623d46c97b6e2e299c1604fb02/ios/PresentationLayer/UIToolkit/Sources/UIToolkit/Resources/Images.xcassets/TabBar/RecipesTabBar.imageset/ic_dashboard@1x.png
--------------------------------------------------------------------------------
/ios/PresentationLayer/UIToolkit/Sources/UIToolkit/Resources/Images.xcassets/TabBar/RecipesTabBar.imageset/ic_dashboard@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MateeDevs/devstack-native-app/26a21470027d26623d46c97b6e2e299c1604fb02/ios/PresentationLayer/UIToolkit/Sources/UIToolkit/Resources/Images.xcassets/TabBar/RecipesTabBar.imageset/ic_dashboard@2x.png
--------------------------------------------------------------------------------
/ios/PresentationLayer/UIToolkit/Sources/UIToolkit/Resources/Images.xcassets/TabBar/RecipesTabBar.imageset/ic_dashboard@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MateeDevs/devstack-native-app/26a21470027d26623d46c97b6e2e299c1604fb02/ios/PresentationLayer/UIToolkit/Sources/UIToolkit/Resources/Images.xcassets/TabBar/RecipesTabBar.imageset/ic_dashboard@3x.png
--------------------------------------------------------------------------------
/ios/PresentationLayer/UIToolkit/Sources/UIToolkit/Resources/Images.xcassets/TabBar/UsersTabBar.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "filename" : "ic_dashboard@1x.png",
6 | "scale" : "1x"
7 | },
8 | {
9 | "idiom" : "universal",
10 | "filename" : "ic_dashboard@2x.png",
11 | "scale" : "2x"
12 | },
13 | {
14 | "idiom" : "universal",
15 | "filename" : "ic_dashboard@3x.png",
16 | "scale" : "3x"
17 | }
18 | ],
19 | "info" : {
20 | "version" : 1,
21 | "author" : "xcode"
22 | }
23 | }
--------------------------------------------------------------------------------
/ios/PresentationLayer/UIToolkit/Sources/UIToolkit/Resources/Images.xcassets/TabBar/UsersTabBar.imageset/ic_dashboard@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MateeDevs/devstack-native-app/26a21470027d26623d46c97b6e2e299c1604fb02/ios/PresentationLayer/UIToolkit/Sources/UIToolkit/Resources/Images.xcassets/TabBar/UsersTabBar.imageset/ic_dashboard@1x.png
--------------------------------------------------------------------------------
/ios/PresentationLayer/UIToolkit/Sources/UIToolkit/Resources/Images.xcassets/TabBar/UsersTabBar.imageset/ic_dashboard@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MateeDevs/devstack-native-app/26a21470027d26623d46c97b6e2e299c1604fb02/ios/PresentationLayer/UIToolkit/Sources/UIToolkit/Resources/Images.xcassets/TabBar/UsersTabBar.imageset/ic_dashboard@2x.png
--------------------------------------------------------------------------------
/ios/PresentationLayer/UIToolkit/Sources/UIToolkit/Resources/Images.xcassets/TabBar/UsersTabBar.imageset/ic_dashboard@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MateeDevs/devstack-native-app/26a21470027d26623d46c97b6e2e299c1604fb02/ios/PresentationLayer/UIToolkit/Sources/UIToolkit/Resources/Images.xcassets/TabBar/UsersTabBar.imageset/ic_dashboard@3x.png
--------------------------------------------------------------------------------
/ios/PresentationLayer/UIToolkit/Sources/UIToolkit/Utilities/Alerts/AlertAction.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Created by Petr Chmelar on 28/06/2020.
3 | // Copyright © 2020 Matee. All rights reserved.
4 | //
5 |
6 | import Foundation
7 |
8 | public enum AlertAction: Equatable {
9 | case showWhisper(_ whisper: WhisperData)
10 | case hideWhisper
11 | case showAlert(_ alert: AlertData)
12 |
13 | public static func == (lhs: AlertAction, rhs: AlertAction) -> Bool {
14 | switch (lhs, rhs) {
15 | case let (.showWhisper(lhsWhisper), .showWhisper(rhsWhisper)): lhsWhisper == rhsWhisper
16 | case (.hideWhisper, .hideWhisper): true
17 | case let (.showAlert(lhsAlert), .showAlert(rhsAlert)): lhsAlert == rhsAlert
18 | default: false
19 | }
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/ios/PresentationLayer/UIToolkit/Sources/UIToolkit/Utilities/Alerts/AlertData+UIKit.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Created by Petr Chmelar on 04.03.2022
3 | // Copyright © 2022 Matee. All rights reserved.
4 | //
5 |
6 | import UIKit
7 |
8 | public extension UIAlertAction {
9 | convenience init(_ action: AlertData.Action) {
10 | self.init(
11 | title: action.title,
12 | style: .init(action.style),
13 | handler: { _ in action.handler() }
14 | )
15 | }
16 | }
17 |
18 | public extension UIAlertAction.Style {
19 | init(_ style: AlertData.Action.Style) {
20 | switch style {
21 | case .default: self = .default
22 | case .cancel: self = .cancel
23 | case .destruction: self = .destructive
24 | }
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/ios/PresentationLayer/UIToolkit/Sources/UIToolkit/Utilities/FlowControllerMock.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Created by Petr Chmelar on 30.05.2022
3 | // Copyright © 2022 Matee. All rights reserved.
4 | //
5 |
6 | public class FlowControllerMock: FlowController {
7 |
8 | public var handleFlowCount = 0
9 | public var handleFlowValue: T?
10 |
11 | override public func handleFlow(_ flow: Flow) {
12 | handleFlowCount += 1
13 | handleFlowValue = flow as? T
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/ios/PresentationLayer/UIToolkit/Sources/UIToolkit/Utilities/MokoFix.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Created by Julia Jakubcova on 23/09/2024
3 | // Copyright © 2024 Matee. All rights reserved.
4 | //
5 |
6 | import DevstackKmpShared
7 |
8 | #warning("TODO: Remove this workaround when issue [https://github.com/icerockdev/moko-resources/issues/714] is resolved")
9 | public func fixMokoResourcesForTests() {
10 | if ProcessInfo.processInfo.processName == "xctest" {
11 | MokoResourcesPreviewWorkaroundKt.nsBundle = Bundle.init(for: KotlinBase.self)
12 | }
13 | }
14 |
15 | #warning("TODO: Remove this workaround when issue [https://github.com/icerockdev/moko-resources/issues/714] is resolved")
16 | public func fixMokoResourcesForPreviews() {
17 | if ProcessInfo.processInfo.processName == "XCPreviewAgent" {
18 | MokoResourcesPreviewWorkaroundKt.nsBundle = Bundle.init(for: KotlinBase.self)
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/ios/PresentationLayer/UIToolkit/Sources/UIToolkit/Utilities/PreferenceKEy/SizePreferenceKey.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Created by Tomáš Batěk on 10.07.2023
3 | // Copyright © 2023 Matee. All rights reserved.
4 | //
5 |
6 | import SwiftUI
7 |
8 | struct SizePreferenceKey: PreferenceKey {
9 | typealias Value = CGSize
10 |
11 | static var defaultValue: Value = .zero
12 |
13 | static func reduce(value _: inout Value, nextValue: () -> Value) {
14 | _ = nextValue()
15 | }
16 | }
17 |
18 |
--------------------------------------------------------------------------------
/ios/PresentationLayer/UIToolkit/Sources/UIToolkit/Utilities/PreferenceKEy/ViewOffsetKey.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Created by Tomáš Batěk on 10.07.2023
3 | // Copyright © 2023 Matee. All rights reserved.
4 | //
5 |
6 | import SwiftUI
7 |
8 | struct ViewOffsetKey: PreferenceKey {
9 |
10 | typealias Value = CGFloat
11 |
12 | static var defaultValue = CGFloat.zero
13 |
14 | static func reduce(value: inout Value, nextValue: () -> Value) {
15 | value += nextValue()
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/ios/PresentationLayer/UIToolkit/Sources/UIToolkit/Utilities/ViewModel.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Created by Petr Chmelar on 21.02.2022
3 | // Copyright © 2022 Matee. All rights reserved.
4 | //
5 |
6 | @MainActor
7 | public protocol ViewModel {
8 | // Lifecycle
9 | func onAppear()
10 | func onDisappear()
11 |
12 | // State
13 | associatedtype State
14 | var state: State { get }
15 |
16 | // Intent
17 | associatedtype Intent
18 | func onIntent(_ intent: Intent)
19 | }
20 |
--------------------------------------------------------------------------------
/ios/PresentationLayer/UIToolkit/Sources/UIToolkit/swiftgen.yml:
--------------------------------------------------------------------------------
1 | output_dir: ${DERIVED_SOURCES_DIR}
2 |
3 | strings:
4 | inputs:
5 | - ${DERIVED_SOURCES_DIR}/../TwinePlugin/Base.lproj/Localizable.strings
6 | outputs:
7 | - templatePath: swiftgen-strings.stencil
8 | output: Localizable.swift
9 | params:
10 | publicAccess: true
11 |
12 | xcassets:
13 | inputs:
14 | - Resources/Colors.xcassets
15 | - Resources/Images.xcassets
16 | outputs:
17 | - templatePath: swiftgen-xcassets.stencil
18 | output: Assets.swift
19 | params:
20 | publicAccess: true
21 |
--------------------------------------------------------------------------------
/ios/PresentationLayer/Users/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | /.build
3 | /Packages
4 | /*.xcodeproj
5 | xcuserdata/
6 | DerivedData/
7 | .swiftpm/config/registries.json
8 | .swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata
9 | .netrc
10 |
--------------------------------------------------------------------------------
/ios/Widget/Assets.xcassets/AccentColor.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "colors" : [
3 | {
4 | "idiom" : "universal"
5 | }
6 | ],
7 | "info" : {
8 | "author" : "xcode",
9 | "version" : 1
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/ios/Widget/Assets.xcassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/ios/Widget/Assets.xcassets/WidgetBackground.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "colors" : [
3 | {
4 | "idiom" : "universal"
5 | }
6 | ],
7 | "info" : {
8 | "author" : "xcode",
9 | "version" : 1
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/ios/Widget/Entitlements/Alpha.entitlements:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | com.apple.security.application-groups
6 |
7 | group.cz.matee.devstack.alpha
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/ios/Widget/Entitlements/Beta.entitlements:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | com.apple.security.application-groups
6 |
7 | group.cz.matee.devstack.beta
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/ios/Widget/Entitlements/Prod.entitlements:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | com.apple.security.application-groups
6 |
7 | group.cz.matee.devstack
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/ios/Widget/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | NSExtension
6 |
7 | NSExtensionPointIdentifier
8 | com.apple.widgetkit-extension
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/ios/Widget/WidgetView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Created by Petr Chmelar on 28.01.2022
3 | // Copyright © 2022 Matee. All rights reserved.
4 | //
5 |
6 | import SwiftUI
7 | import WidgetKit
8 |
9 | struct WidgetView: View {
10 |
11 | let isLogged: Bool
12 |
13 | var body: some View {
14 | if #available(iOS 17.0, *) {
15 | Text(isLogged ? "✅" : "❌")
16 | .font(.largeTitle)
17 | .containerBackground(for: .widget) { Color.white }
18 | } else {
19 | Text(isLogged ? "✅" : "❌")
20 | .font(.largeTitle)
21 | }
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/ios/scripts/IDETemplateMacros.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | FILEHEADER
6 |
7 | // Created by ___FULLNAME___ on ___DATE___
8 | // Copyright © ___YEAR___ Matee. All rights reserved.
9 | //
10 |
11 |
12 |
--------------------------------------------------------------------------------
/ios/scripts/apollo.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | # This ensures that relative paths are correct no matter where the script is executed
4 | cd "$(dirname "$0")"
5 |
6 | cd ../DataLayer/Providers/GraphQLProvider
7 |
8 | echo "⚙️ GraphQLProvider - Installing Codegen CLI"
9 | swift package --allow-writing-to-package-directory apollo-cli-install
10 |
11 | echo "⚙️ RocketToolkit - Downloading GraphQL schema and generating code from queries"
12 | ./apollo-ios-cli fetch-schema --path "../../Toolkits/RocketToolkit/apollo-codegen-config.json"
13 | ./apollo-ios-cli generate --path "../../Toolkits/RocketToolkit/apollo-codegen-config.json"
14 |
15 | cd ../../..
16 |
--------------------------------------------------------------------------------
/ios/scripts/copy-moko-resources.sh:
--------------------------------------------------------------------------------
1 | #!/bin/zsh -l
2 |
3 | configurationWordCount=$(echo "$CONFIGURATION" | wc -w)
4 | if (( configurationWordCount > 1 ))
5 | then
6 | buildType=$(echo "$CONFIGURATION" | awk '{print $2}')
7 | else
8 | buildType="$CONFIGURATION"
9 | fi
10 |
11 | # This is required step for DevstackKmpShared framework
12 | # https://github.com/icerockdev/moko-resources#creating-xcframework-with-resources
13 | "$SRCROOT/../gradlew" -p "$SRCROOT/../" :shared:copyResourcesDevstackKmpShared${buildType}XCFrameworkToApp \
14 | -Pmoko.resources.BUILT_PRODUCTS_DIR=$BUILT_PRODUCTS_DIR \
15 | -Pmoko.resources.CONTENTS_FOLDER_PATH=$CONTENTS_FOLDER_PATH \
16 | -PXCODE_CONFIGURATION="$CONFIGURATION"
--------------------------------------------------------------------------------
/ios/scripts/generate-error-messages.sh:
--------------------------------------------------------------------------------
1 | #!/bin/zsh -l
2 |
3 | # This ensures that relative paths are correct no matter where the script is executed
4 | cd "$(dirname "$0")"
5 |
6 | cd ../..
7 |
8 | echo "Generating Localizable files for error messages in specified languages"
9 | ./gradlew generateErrorsTwine
10 |
11 | echo "Generating MR resources from .xml files"
12 | ./gradlew :shared:generateMRcommonMain
--------------------------------------------------------------------------------
/ios/scripts/swiftlint-analyze.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | # This ensures that relative paths are correct no matter where the script is executed
4 | cd "$(dirname "$0")"
5 |
6 | echo "⚙️ Building project in order to obtain xcodebuild.log"
7 | xcodebuild -workspace ../DevStack.xcworkspace -scheme ../DevStack_Alpha > ../xcodebuild.log
8 |
9 | echo "⚙️ Running SwiftLint Static Analyzer"
10 | swiftlint analyze --config ../swiftlint.yml --compiler-log-path ../xcodebuild.log
11 |
--------------------------------------------------------------------------------
/ios/scripts/twine.sh:
--------------------------------------------------------------------------------
1 | #!/bin/zsh -l
2 |
3 | # This ensures that relative paths are correct no matter where the script is executed
4 | cd "$(dirname "$0")"
5 |
6 | echo "⚙️ Generating Localizable files for specified languages"
7 | mkdir ${DERIVED_SOURCES_DIR}/Base.lproj
8 | twine generate-localization-file ../../twine/strings.txt ${DERIVED_SOURCES_DIR}/Base.lproj/Localizable.strings --lang en
9 | mkdir ${DERIVED_SOURCES_DIR}/cs.lproj
10 | twine generate-localization-file ../../twine/strings.txt ${DERIVED_SOURCES_DIR}/cs.lproj/Localizable.strings --lang cs
11 | mkdir ${DERIVED_SOURCES_DIR}/sk.lproj
12 | twine generate-localization-file ../../twine/strings.txt ${DERIVED_SOURCES_DIR}/sk.lproj/Localizable.strings --lang sk
13 | mkdir ${DERIVED_SOURCES_DIR}/en.lproj
14 | twine generate-localization-file ../../twine/strings.txt ${DERIVED_SOURCES_DIR}/en.lproj/Localizable.strings --lang en
15 |
--------------------------------------------------------------------------------
/ios/signing/Development_Matee.cer:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MateeDevs/devstack-native-app/26a21470027d26623d46c97b6e2e299c1604fb02/ios/signing/Development_Matee.cer
--------------------------------------------------------------------------------
/ios/signing/Development_Matee.p12:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MateeDevs/devstack-native-app/26a21470027d26623d46c97b6e2e299c1604fb02/ios/signing/Development_Matee.p12
--------------------------------------------------------------------------------
/other/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | Change Log
2 | ==========
3 |
4 | ### Updated
--------------------------------------------------------------------------------
/other/keystore/debug.jks:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MateeDevs/devstack-native-app/26a21470027d26623d46c97b6e2e299c1604fb02/other/keystore/debug.jks
--------------------------------------------------------------------------------
/other/keystore/release.jks:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MateeDevs/devstack-native-app/26a21470027d26623d46c97b6e2e299c1604fb02/other/keystore/release.jks
--------------------------------------------------------------------------------
/other/tools/mainframer_starter.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | mainframer.sh ./gradlew app:assembleFastBuildDebug -Pandroid.enableBuildCache=true
--------------------------------------------------------------------------------
/other/tools/run_bundler.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | echo "Running bundler"
4 | bundle config set path vendor/bundle
5 | bundle install
--------------------------------------------------------------------------------
/settings.gradle.kts:
--------------------------------------------------------------------------------
1 | pluginManagement {
2 | includeBuild("build-logic")
3 | repositories {
4 | gradlePluginPortal()
5 | mavenCentral()
6 | google()
7 | }
8 | }
9 |
10 | dependencyResolutionManagement {
11 | repositories {
12 | gradlePluginPortal()
13 | mavenCentral()
14 | google()
15 | maven("https://plugins.gradle.org/m2/")
16 | }
17 | }
18 |
19 | rootProject.buildFileName = "build.gradle.kts"
20 | rootProject.name = "devstack-native-app"
21 | include(":android:app", ":android:shared", ":shared")
22 | include(":android:login")
23 | include(":android:profile")
24 | include(":android:users")
25 | include(":android:recipes")
26 | include(":android:books")
27 |
--------------------------------------------------------------------------------
/shared/.gitignore:
--------------------------------------------------------------------------------
1 | /swiftpackage
--------------------------------------------------------------------------------
/shared/build.gradle.kts:
--------------------------------------------------------------------------------
1 | @Suppress("DSL_SCOPE_VIOLATION") // Remove after upgrading to gradle 8.1
2 | plugins {
3 | alias(libs.plugins.devstack.kmm.library)
4 | }
5 |
6 | android {
7 | namespace = "kmp.shared"
8 | }
9 |
10 | multiplatformResources {
11 | multiplatformResourcesPackage = "kmp.shared"
12 | disableStaticFrameworkWarning = true
13 | }
14 |
15 | sqldelight {
16 | database("Database") {
17 | packageName = "kmp"
18 | }
19 | }
20 |
21 | kotlin {
22 | sourceSets {
23 | all {
24 | languageSettings.optIn("org.mobilenativefoundation.store.core5.ExperimentalStoreApi")
25 | }
26 | }
27 | }
28 |
29 | ktlint {
30 | filter {
31 | exclude { entry ->
32 | entry.file.toString().contains("generated")
33 | }
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/shared/src/androidMain/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/shared/src/androidMain/kotlin/kmp/shared/base/error/ErrorMessageProviderImpl.kt:
--------------------------------------------------------------------------------
1 | package kmp.shared.base.error
2 |
3 | import android.content.Context
4 | import dev.icerock.moko.resources.desc.StringDesc
5 |
6 | internal class ErrorMessageProviderImpl(private val context: Context) : ErrorMessageProvider() {
7 | override fun StringDesc.toMessageString(): String = toString(context)
8 | }
9 |
--------------------------------------------------------------------------------
/shared/src/androidMain/kotlin/kmp/shared/di/KoinAndroid.kt:
--------------------------------------------------------------------------------
1 | package kmp.shared.di
2 |
3 | import io.ktor.client.engine.android.Android
4 | import kmp.shared.base.error.ErrorMessageProvider
5 | import kmp.shared.base.error.ErrorMessageProviderImpl
6 | import kmp.shared.infrastructure.local.DriverFactory
7 | import kmp.shared.system.Config
8 | import kmp.shared.system.ConfigImpl
9 | import kmp.shared.system.Log
10 | import kmp.shared.system.Logger
11 | import org.koin.dsl.module
12 |
13 | actual val platformModule = module {
14 | single { ConfigImpl() }
15 | single { DriverFactory(get()) }
16 | single { Log }
17 | single { Android.create() }
18 | single { ErrorMessageProviderImpl(get()) }
19 | }
20 |
--------------------------------------------------------------------------------
/shared/src/androidMain/kotlin/kmp/shared/infrastructure/local/DriverFactory.kt:
--------------------------------------------------------------------------------
1 | package kmp.shared.infrastructure.local
2 |
3 | import android.content.Context
4 | import com.squareup.sqldelight.android.AndroidSqliteDriver
5 | import com.squareup.sqldelight.db.SqlDriver
6 | import kmp.Database
7 |
8 | internal actual class DriverFactory(private val context: Context) {
9 | actual fun createDriver(dbName: String): SqlDriver =
10 | AndroidSqliteDriver(Database.Schema, context, dbName)
11 | }
12 |
--------------------------------------------------------------------------------
/shared/src/androidMain/kotlin/kmp/shared/system/ConfigImpl.kt:
--------------------------------------------------------------------------------
1 | package kmp.shared.system
2 |
3 | import kmp.shared.BuildConfig
4 |
5 | actual class ConfigImpl : Config {
6 | override val isRelease: Boolean
7 | get() = BuildConfig.BUILD_TYPE == "release"
8 | }
9 |
--------------------------------------------------------------------------------
/shared/src/androidMain/kotlin/kmp/shared/system/Log.kt:
--------------------------------------------------------------------------------
1 | package kmp.shared.system
2 |
3 | import android.util.Log as AndroidLog
4 |
5 | actual object Log : Logger {
6 |
7 | override fun d(tag: String, message: String) {
8 | AndroidLog.d(tag, message)
9 | }
10 |
11 | override fun w(tag: String, message: String, throwable: Throwable?) {
12 | AndroidLog.w(tag, message, throwable)
13 | }
14 |
15 | override fun e(tag: String, message: String, throwable: Throwable) {
16 | AndroidLog.e(tag, message, throwable)
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/shared/src/commonMain/kotlin/kmp/shared/base/Result.kt:
--------------------------------------------------------------------------------
1 | package kmp.shared.base
2 |
3 | sealed class Result {
4 |
5 | data class Success(val data: T) : Result()
6 |
7 | data class Error(val error: ErrorResult, val data: T? = null) : Result()
8 | }
9 |
10 | open class ErrorResult(open var message: String? = null, open var throwable: Throwable? = null)
11 |
12 | class ErrorResultException(val error: ErrorResult) : Exception(error.message, error.throwable)
13 |
--------------------------------------------------------------------------------
/shared/src/commonMain/kotlin/kmp/shared/base/error/domain/AuthError.kt:
--------------------------------------------------------------------------------
1 | package kmp.shared.base.error.domain
2 |
3 | import kmp.shared.base.ErrorResult
4 |
5 | sealed class AuthError(
6 | message: String? = null,
7 | throwable: Throwable? = null,
8 | ) : ErrorResult(message, throwable) {
9 |
10 | class InvalidLoginCredentials(throwable: Throwable?) : AuthError(null, throwable)
11 | class EmailAlreadyExist(throwable: Throwable?) : AuthError(null, throwable)
12 | class LoginFailed(throwable: Throwable?) : AuthError(null, throwable)
13 | class RegistrationFailed(throwable: Throwable?) : AuthError(null, throwable)
14 | }
15 |
--------------------------------------------------------------------------------
/shared/src/commonMain/kotlin/kmp/shared/base/error/domain/BackendError.kt:
--------------------------------------------------------------------------------
1 | package kmp.shared.base.error.domain
2 |
3 | import kmp.shared.base.ErrorResult
4 |
5 | /**
6 | * Error type used when handling responses from backend
7 | * @param throwable optional [Throwable] parameter used for debugging or crash reporting
8 | */
9 | sealed class BackendError(
10 | throwable: Throwable? = null,
11 | responseMessage: String? = null,
12 | ) : ErrorResult(throwable = throwable, message = responseMessage) {
13 |
14 | class NotAuthorized(
15 | responseMessage: String?,
16 | throwable: Throwable? = null,
17 | ) : BackendError(throwable, responseMessage)
18 | }
19 |
--------------------------------------------------------------------------------
/shared/src/commonMain/kotlin/kmp/shared/base/error/domain/CommonError.kt:
--------------------------------------------------------------------------------
1 | package kmp.shared.base.error.domain
2 |
3 | import kmp.shared.base.ErrorResult
4 |
5 | /**
6 | * Error type used anywhere in the project. Contains subclasses for common exceptions that can happen anywhere
7 | * @param throwable optional [Throwable] parameter used for debugging or crash reporting
8 | */
9 | sealed class CommonError(throwable: Throwable? = null) : ErrorResult(throwable = throwable) {
10 | class NoNetworkConnection(t: Throwable?) : CommonError(t)
11 | object NoUserLoggedIn : CommonError()
12 | object Unknown : CommonError()
13 | class UnhandledExceptionError(origin: Throwable) : CommonError(origin)
14 | }
15 |
--------------------------------------------------------------------------------
/shared/src/commonMain/kotlin/kmp/shared/base/usecase/UseCaseFlow.kt:
--------------------------------------------------------------------------------
1 | package kmp.shared.base.usecase
2 |
3 | import kotlinx.coroutines.flow.Flow
4 |
5 | interface UseCaseFlow {
6 | suspend operator fun invoke(params: Params): Flow
7 | }
8 |
9 | interface UseCaseFlowNoParams {
10 | suspend operator fun invoke(): Flow
11 | }
12 |
--------------------------------------------------------------------------------
/shared/src/commonMain/kotlin/kmp/shared/base/util/extension/Result.kt:
--------------------------------------------------------------------------------
1 | package kmp.shared.base.util.extension
2 |
3 | import kmp.shared.base.Result
4 |
5 | /** Transform Result data object */
6 | inline fun Result.map(transform: (T) -> R) =
7 | when (this) {
8 | is Result.Success -> Result.Success(transform(data))
9 | is Result.Error -> Result.Error(error, data?.let(transform))
10 | }
11 |
12 | fun Result.toEmptyResult() = map { }
13 |
14 | fun Result.getOrNull(): T? =
15 | (this as? Result.Success)?.data
16 |
--------------------------------------------------------------------------------
/shared/src/commonMain/kotlin/kmp/shared/data/source/AuthSource.kt:
--------------------------------------------------------------------------------
1 | package kmp.shared.data.source
2 |
3 | import kmp.shared.base.Result
4 | import kmp.shared.infrastructure.model.LoginDto
5 | import kmp.shared.infrastructure.model.RegistrationDto
6 | import kotlinx.serialization.Serializable
7 |
8 | internal interface AuthSource {
9 | suspend fun login(request: LoginRequest): Result
10 | suspend fun register(request: RegistrationRequest): Result
11 | suspend fun deleteUserData(): Result
12 | }
13 |
14 | @Serializable
15 | internal data class LoginRequest(val email: String, val pass: String)
16 |
17 | @Serializable
18 | internal data class RegistrationRequest(
19 | val email: String,
20 | val firstName: String,
21 | val lastName: String,
22 | val pass: String,
23 | )
24 |
--------------------------------------------------------------------------------
/shared/src/commonMain/kotlin/kmp/shared/data/source/BookLocalSource.kt:
--------------------------------------------------------------------------------
1 | package kmp.shared.data.source
2 |
3 | import kmp.shared.infrastructure.local.BookEntity
4 | import kotlinx.coroutines.flow.Flow
5 |
6 | internal interface BookLocalSource {
7 | fun getAll(): Flow>
8 | suspend fun updateOrInsert(items: List)
9 | }
10 |
--------------------------------------------------------------------------------
/shared/src/commonMain/kotlin/kmp/shared/domain/model/Book.kt:
--------------------------------------------------------------------------------
1 | package kmp.shared.domain.model
2 |
3 | data class Book(
4 | val id: String,
5 | val name: String,
6 | val author: String,
7 | val pages: Long,
8 | )
9 |
--------------------------------------------------------------------------------
/shared/src/commonMain/kotlin/kmp/shared/domain/model/User.kt:
--------------------------------------------------------------------------------
1 | package kmp.shared.domain.model
2 |
3 | data class User(
4 | val id: String,
5 | val email: String,
6 | val bio: String,
7 | val firstName: String,
8 | val lastName: String,
9 | val phone: String?,
10 | )
11 |
--------------------------------------------------------------------------------
/shared/src/commonMain/kotlin/kmp/shared/domain/model/UserPagingResult.kt:
--------------------------------------------------------------------------------
1 | package kmp.shared.domain.model
2 |
3 | data class UserPagingResult(
4 | val users: List,
5 | val totalCount: Int,
6 | val limit: Int,
7 | val offset: Int,
8 | )
9 |
10 | data class UserPagingData(
11 | val id: String,
12 | val email: String,
13 | val firstName: String?,
14 | val lastName: String?,
15 | )
16 |
--------------------------------------------------------------------------------
/shared/src/commonMain/kotlin/kmp/shared/domain/repository/AuthRepository.kt:
--------------------------------------------------------------------------------
1 | package kmp.shared.domain.repository
2 |
3 | import kmp.shared.base.Result
4 |
5 | internal interface AuthRepository {
6 | suspend fun login(email: String, pass: String): Result
7 | suspend fun register(
8 | email: String,
9 | firstName: String,
10 | lastName: String,
11 | pass: String,
12 | ): Result
13 |
14 | suspend fun deleteUserData(): Result
15 | }
16 |
--------------------------------------------------------------------------------
/shared/src/commonMain/kotlin/kmp/shared/domain/repository/BookRepository.kt:
--------------------------------------------------------------------------------
1 | package kmp.shared.domain.repository
2 |
3 | import kmp.shared.base.Result
4 | import kmp.shared.domain.model.Book
5 | import kotlinx.coroutines.flow.Flow
6 |
7 | internal interface BookRepository {
8 | fun getAllBooks(): Flow>
9 |
10 | suspend fun reloadAllBooks(): Result
11 | }
12 |
--------------------------------------------------------------------------------
/shared/src/commonMain/kotlin/kmp/shared/domain/usecase/DeleteAuthDataUseCaseImpl.kt:
--------------------------------------------------------------------------------
1 | package kmp.shared.domain.usecase
2 |
3 | import kmp.shared.base.usecase.UseCaseResultNoParams
4 | import kmp.shared.domain.repository.AuthRepository
5 |
6 | interface DeleteAuthDataUseCase : UseCaseResultNoParams
7 |
8 | internal class DeleteAuthDataUseCaseImpl internal constructor(
9 | private val authRepository: AuthRepository,
10 | ) : DeleteAuthDataUseCase {
11 |
12 | override suspend fun invoke() = authRepository.deleteUserData()
13 | }
14 |
--------------------------------------------------------------------------------
/shared/src/commonMain/kotlin/kmp/shared/domain/usecase/LoginUseCaseImpl.kt:
--------------------------------------------------------------------------------
1 | package kmp.shared.domain.usecase
2 |
3 | import kmp.shared.base.Result
4 | import kmp.shared.base.usecase.UseCaseResult
5 | import kmp.shared.domain.repository.AuthRepository
6 |
7 | interface LoginUseCase : UseCaseResult {
8 | data class Params(val email: String, val password: String)
9 | }
10 |
11 | internal class LoginUseCaseImpl internal constructor(
12 | private val authRepository: AuthRepository,
13 | ) : LoginUseCase {
14 |
15 | override suspend fun invoke(params: LoginUseCase.Params): Result =
16 | authRepository.login(params.email, params.password)
17 | }
18 |
--------------------------------------------------------------------------------
/shared/src/commonMain/kotlin/kmp/shared/domain/usecase/RegisterUseCaseImpl.kt:
--------------------------------------------------------------------------------
1 | package kmp.shared.domain.usecase
2 |
3 | import kmp.shared.base.Result
4 | import kmp.shared.base.usecase.UseCaseResult
5 | import kmp.shared.domain.repository.AuthRepository
6 |
7 | interface RegisterUseCase : UseCaseResult {
8 | data class Params(
9 | val email: String,
10 | val firstName: String,
11 | val lastName: String,
12 | val password: String,
13 | )
14 | }
15 |
16 | internal class RegisterUseCaseImpl internal constructor(
17 | private val authRepository: AuthRepository,
18 | ) : RegisterUseCase {
19 |
20 | override suspend fun invoke(params: RegisterUseCase.Params): Result =
21 | authRepository.register(params.email, params.firstName, params.lastName, params.password)
22 | }
23 |
--------------------------------------------------------------------------------
/shared/src/commonMain/kotlin/kmp/shared/domain/usecase/book/GetBooksUseCaseImpl.kt:
--------------------------------------------------------------------------------
1 | package kmp.shared.domain.usecase.book
2 |
3 | import kmp.shared.base.usecase.UseCaseFlowNoParams
4 | import kmp.shared.domain.model.Book
5 | import kmp.shared.domain.repository.BookRepository
6 |
7 | interface GetBooksUseCase : UseCaseFlowNoParams>
8 |
9 | internal class GetBooksUseCaseImpl constructor(
10 | private val repository: BookRepository,
11 | ) : GetBooksUseCase {
12 |
13 | override suspend fun invoke() = repository.getAllBooks()
14 | }
15 |
--------------------------------------------------------------------------------
/shared/src/commonMain/kotlin/kmp/shared/domain/usecase/book/RefreshBooksUseCaseImpl.kt:
--------------------------------------------------------------------------------
1 | package kmp.shared.domain.usecase.book
2 |
3 | import kmp.shared.base.usecase.UseCaseResult
4 | import kmp.shared.domain.repository.BookRepository
5 | import kotlinx.coroutines.Dispatchers
6 | import kotlinx.coroutines.delay
7 | import kotlinx.coroutines.withContext
8 |
9 | interface RefreshBooksUseCase : UseCaseResult
10 |
11 | internal class RefreshBooksUseCaseImpl constructor(
12 | private val repository: BookRepository,
13 | ) : RefreshBooksUseCase {
14 |
15 | override suspend fun invoke(params: Int) = withContext(Dispatchers.Default) {
16 | delay(2_000)
17 | // var factorial = 1
18 | // for (i in 1..params) {
19 | // factorial *= i
20 | // }
21 | repository.reloadAllBooks()
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/shared/src/commonMain/kotlin/kmp/shared/domain/usecase/user/GetLocalUsersUseCaseImpl.kt:
--------------------------------------------------------------------------------
1 | package kmp.shared.domain.usecase.user
2 |
3 | import kmp.shared.base.usecase.UseCaseFlow
4 | import kmp.shared.domain.model.UserPagingResult
5 | import kmp.shared.domain.repository.UserPagingParameters
6 | import kmp.shared.domain.repository.UserRepository
7 | import kotlinx.coroutines.flow.Flow
8 |
9 | interface GetLocalUsersUseCase : UseCaseFlow
10 |
11 | internal class GetLocalUsersUseCaseImpl internal constructor(
12 | private val userRepository: UserRepository,
13 | ) : GetLocalUsersUseCase {
14 |
15 | override suspend fun invoke(params: UserPagingParameters): Flow =
16 | userRepository.getUserPagingLocal(
17 | UserPagingParameters(params.offset, params.limit),
18 | )
19 | }
20 |
--------------------------------------------------------------------------------
/shared/src/commonMain/kotlin/kmp/shared/domain/usecase/user/GetLoggedInUserUseCaseImpl.kt:
--------------------------------------------------------------------------------
1 | package kmp.shared.domain.usecase.user
2 |
3 | import kmp.shared.base.Result
4 | import kmp.shared.base.usecase.UseCaseFlowResultNoParams
5 | import kmp.shared.domain.model.User
6 | import kmp.shared.domain.repository.UserRepository
7 | import kotlinx.coroutines.flow.Flow
8 |
9 | interface GetLoggedInUserUseCase : UseCaseFlowResultNoParams
10 |
11 | internal class GetLoggedInUserUseCaseImpl internal constructor(
12 | private val userRepository: UserRepository,
13 | ) : GetLoggedInUserUseCase {
14 | override suspend fun invoke(): Flow> = userRepository.getUser()
15 | }
16 |
--------------------------------------------------------------------------------
/shared/src/commonMain/kotlin/kmp/shared/domain/usecase/user/GetRemoteUsersUseCaseImpl.kt:
--------------------------------------------------------------------------------
1 | package kmp.shared.domain.usecase.user
2 |
3 | import kmp.shared.base.Result
4 | import kmp.shared.base.usecase.UseCaseResult
5 | import kmp.shared.domain.model.UserPagingResult
6 | import kmp.shared.domain.repository.UserPagingParameters
7 | import kmp.shared.domain.repository.UserRepository
8 |
9 | interface GetRemoteUsersUseCase : UseCaseResult
10 |
11 | internal class GetRemoteUsersUseCaseImpl internal constructor(
12 | private val userRepository: UserRepository,
13 | ) : GetRemoteUsersUseCase {
14 |
15 | override suspend fun invoke(params: UserPagingParameters): Result =
16 | userRepository.getUserPagingRemote(
17 | UserPagingParameters(params.offset, params.limit),
18 | )
19 | }
20 |
--------------------------------------------------------------------------------
/shared/src/commonMain/kotlin/kmp/shared/domain/usecase/user/GetUserUseCaseImpl.kt:
--------------------------------------------------------------------------------
1 | package kmp.shared.domain.usecase.user
2 |
3 | import kmp.shared.base.Result
4 | import kmp.shared.base.usecase.UseCaseFlowResult
5 | import kmp.shared.domain.model.User
6 | import kmp.shared.domain.repository.UserRepository
7 | import kotlinx.coroutines.flow.Flow
8 |
9 | interface GetUserUseCase : UseCaseFlowResult {
10 | class Params(val userId: String)
11 | }
12 |
13 | internal class GetUserUseCaseImpl internal constructor(
14 | private val userRepository: UserRepository,
15 | ) : GetUserUseCase {
16 | override suspend fun invoke(params: GetUserUseCase.Params): Flow> =
17 | userRepository.getUser(params.userId)
18 | }
19 |
--------------------------------------------------------------------------------
/shared/src/commonMain/kotlin/kmp/shared/domain/usecase/user/IsUserLoggedInUseCaseImpl.kt:
--------------------------------------------------------------------------------
1 | package kmp.shared.domain.usecase.user
2 |
3 | import kmp.shared.base.Result
4 | import kmp.shared.base.usecase.UseCaseResultNoParams
5 | import kmp.shared.domain.repository.UserRepository
6 |
7 | interface IsUserLoggedInUseCase : UseCaseResultNoParams
8 | internal class IsUserLoggedInUseCaseImpl internal constructor(
9 | private val userRepository: UserRepository,
10 | ) : IsUserLoggedInUseCase {
11 | override suspend fun invoke(): Result =
12 | Result.Success(userRepository.isUserLoggedIn)
13 | }
14 |
--------------------------------------------------------------------------------
/shared/src/commonMain/kotlin/kmp/shared/domain/usecase/user/RefreshUsersUseCaseImpl.kt:
--------------------------------------------------------------------------------
1 | package kmp.shared.domain.usecase.user
2 |
3 | import kmp.shared.base.Result
4 | import kmp.shared.base.usecase.UseCaseResult
5 | import kmp.shared.data.source.UserPagingRequest
6 | import kmp.shared.domain.repository.UserRepository
7 |
8 | interface RefreshUsersUseCase : UseCaseResult {
9 | data class Params(val offset: Int, val limit: Int)
10 | }
11 |
12 | internal class RefreshUsersUseCaseImpl internal constructor(
13 | private val repository: UserRepository,
14 | ) : RefreshUsersUseCase {
15 |
16 | override suspend fun invoke(params: RefreshUsersUseCase.Params): Result {
17 | return repository.refreshUsers(UserPagingRequest(params.offset, params.limit))
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/shared/src/commonMain/kotlin/kmp/shared/domain/usecase/user/ReplaceUserCacheWithUseCaseImpl.kt:
--------------------------------------------------------------------------------
1 | package kmp.shared.domain.usecase.user
2 |
3 | import kmp.shared.base.Result
4 | import kmp.shared.base.usecase.UseCaseResult
5 | import kmp.shared.domain.model.UserPagingData
6 | import kmp.shared.domain.repository.UserRepository
7 |
8 | interface ReplaceUserCacheWithUseCase : UseCaseResult, Unit>
9 | internal class ReplaceUserCacheWithUseCaseImpl internal constructor(
10 | private val userRepository: UserRepository,
11 | ) : ReplaceUserCacheWithUseCase {
12 | override suspend fun invoke(params: List): Result =
13 | Result.Success(userRepository.replaceLocalCacheWith(params))
14 | }
15 |
--------------------------------------------------------------------------------
/shared/src/commonMain/kotlin/kmp/shared/domain/usecase/user/UpdateLocalUserCacheUseCaseImpl.kt:
--------------------------------------------------------------------------------
1 | package kmp.shared.domain.usecase.user
2 |
3 | import kmp.shared.base.Result
4 | import kmp.shared.base.usecase.UseCaseResult
5 | import kmp.shared.domain.model.UserPagingData
6 | import kmp.shared.domain.repository.UserRepository
7 |
8 | interface UpdateLocalUserCacheUseCase : UseCaseResult, Unit>
9 | internal class UpdateLocalUserCacheUseCaseImpl internal constructor(
10 | private val userRepository: UserRepository,
11 | ) : UpdateLocalUserCacheUseCase {
12 | override suspend fun invoke(params: List): Result =
13 | Result.Success(userRepository.updateUserPagingCache(params))
14 | }
15 |
--------------------------------------------------------------------------------
/shared/src/commonMain/kotlin/kmp/shared/domain/usecase/user/UpdateUserUseCaseImpl.kt:
--------------------------------------------------------------------------------
1 | package kmp.shared.domain.usecase.user
2 |
3 | import kmp.shared.base.Result
4 | import kmp.shared.base.usecase.UseCaseResult
5 | import kmp.shared.domain.model.User
6 | import kmp.shared.domain.repository.UserRepository
7 |
8 | interface UpdateUserUseCase : UseCaseResult
9 | internal class UpdateUserUseCaseImpl(
10 | private val userRepository: UserRepository,
11 | ) : UpdateUserUseCase {
12 |
13 | override suspend fun invoke(params: User): Result = userRepository.updateUser(params)
14 | }
15 |
--------------------------------------------------------------------------------
/shared/src/commonMain/kotlin/kmp/shared/domain/usecase/user/UserCacheChangeFlowUseCaseImpl.kt:
--------------------------------------------------------------------------------
1 | package kmp.shared.domain.usecase.user
2 |
3 | import kmp.shared.base.Result
4 | import kmp.shared.base.usecase.UseCaseResultNoParams
5 | import kmp.shared.domain.repository.UserRepository
6 | import kotlinx.coroutines.flow.Flow
7 |
8 | interface UserCacheChangeFlowUseCase : UseCaseResultNoParams>
9 |
10 | internal class UserCacheChangeFlowUseCaseImpl internal constructor(
11 | private val userRepository: UserRepository,
12 | ) : UserCacheChangeFlowUseCase {
13 | override suspend fun invoke(): Result> =
14 | Result.Success(userRepository.localCacheChanges())
15 | }
16 |
--------------------------------------------------------------------------------
/shared/src/commonMain/kotlin/kmp/shared/extension/Book.kt:
--------------------------------------------------------------------------------
1 | package kmp.shared.extension
2 |
3 | import kmp.shared.domain.model.Book
4 | import kmp.shared.infrastructure.local.BookEntity
5 |
6 | internal val BookEntity.asDomain
7 | get() = Book(id, name, author ?: "", pageCount ?: 0)
8 |
--------------------------------------------------------------------------------
/shared/src/commonMain/kotlin/kmp/shared/extension/User.kt:
--------------------------------------------------------------------------------
1 | package kmp.shared.extension
2 |
3 | import kmp.shared.domain.model.User
4 | import kmp.shared.infrastructure.local.UserEntity
5 | import kmp.shared.infrastructure.model.UserDto
6 |
7 | // Infrastructure -> Domain
8 |
9 | internal val UserEntity.asDomain
10 | get() = User(id, email, bio ?: "", firstName ?: "", lastName ?: "", phone)
11 |
12 | internal val UserDto.asDomain
13 | get() = User(id, email, bio, firstName, lastName, phone)
14 |
15 | // Domain -> Infrastructure
16 |
17 | internal val User.asEntity
18 | get() = UserEntity(id, email, firstName, lastName, phone, bio)
19 |
20 | // Helpers
21 |
22 | val User.fullName
23 | get() = "$firstName $lastName"
24 |
--------------------------------------------------------------------------------
/shared/src/commonMain/kotlin/kmp/shared/extension/UserPaging.kt:
--------------------------------------------------------------------------------
1 | package kmp.shared.extension
2 |
3 | import kmp.shared.domain.model.UserPagingData
4 | import kmp.shared.domain.model.UserPagingResult
5 | import kmp.shared.infrastructure.local.UserCache
6 | import kmp.shared.infrastructure.model.UserPagingDataDto
7 | import kmp.shared.infrastructure.model.UserPagingDto
8 |
9 | // Infrastructure -> Domain
10 |
11 | internal val UserPagingDto.asDomain
12 | get() = UserPagingResult(
13 | `data`.map(UserPagingDataDto::asDomain),
14 | totalCount = totalCount,
15 | limit = limit,
16 | offset = page * limit,
17 | )
18 |
19 | internal val UserPagingDataDto.asDomain
20 | get() = UserPagingData(id, email, firstName, lastName)
21 |
22 | // Domain -> Infrastructure
23 |
24 | internal val UserPagingData.asUserCache
25 | get() = UserCache(id, email, firstName, lastName)
26 |
--------------------------------------------------------------------------------
/shared/src/commonMain/kotlin/kmp/shared/infrastructure/local/Database.kt:
--------------------------------------------------------------------------------
1 | package kmp.shared.infrastructure.local
2 |
3 | import com.squareup.sqldelight.db.SqlDriver
4 | import kmp.Database
5 |
6 | internal expect class DriverFactory {
7 | fun createDriver(dbName: String): SqlDriver
8 | }
9 |
10 | internal fun createDatabase(driverFactory: DriverFactory): Database =
11 | Database(driverFactory.createDriver("kmp.db"))
12 |
--------------------------------------------------------------------------------
/shared/src/commonMain/kotlin/kmp/shared/infrastructure/model/LoginDto.kt:
--------------------------------------------------------------------------------
1 | package kmp.shared.infrastructure.model
2 |
3 | import kotlinx.serialization.Serializable
4 |
5 | @Serializable
6 | internal data class LoginDto(
7 | val email: String,
8 | val token: String,
9 | val userId: String,
10 | )
11 |
--------------------------------------------------------------------------------
/shared/src/commonMain/kotlin/kmp/shared/infrastructure/model/RegistrationDto.kt:
--------------------------------------------------------------------------------
1 | package kmp.shared.infrastructure.model
2 |
3 | import kotlinx.serialization.Serializable
4 |
5 | @Serializable
6 | internal data class RegistrationDto(
7 | val email: String,
8 | val firstName: String,
9 | val id: String,
10 | val lastName: String,
11 | )
12 |
--------------------------------------------------------------------------------
/shared/src/commonMain/kotlin/kmp/shared/infrastructure/model/UserDto.kt:
--------------------------------------------------------------------------------
1 | package kmp.shared.infrastructure.model
2 |
3 | import kotlinx.serialization.Serializable
4 |
5 | @Serializable
6 | internal data class UserDto(
7 | val id: String,
8 | val email: String,
9 | val firstName: String = "",
10 | val lastName: String = "",
11 | val bio: String = "",
12 | val phone: String? = null,
13 | )
14 |
--------------------------------------------------------------------------------
/shared/src/commonMain/kotlin/kmp/shared/infrastructure/model/UserPagingDto.kt:
--------------------------------------------------------------------------------
1 | package kmp.shared.infrastructure.model
2 |
3 | import kotlinx.serialization.Serializable
4 |
5 | @Serializable
6 | internal data class UserPagingDto(
7 | val `data`: List,
8 | val totalCount: Int,
9 | val lastPage: Int,
10 | val limit: Int,
11 | val page: Int,
12 | )
13 |
14 | @Serializable
15 | internal data class UserPagingDataDto(
16 | val email: String,
17 | val firstName: String,
18 | val id: String,
19 | val lastName: String,
20 | )
21 |
--------------------------------------------------------------------------------
/shared/src/commonMain/kotlin/kmp/shared/infrastructure/source/BookLocalSourceImpl.kt:
--------------------------------------------------------------------------------
1 | package kmp.shared.infrastructure.source
2 |
3 | import com.squareup.sqldelight.runtime.coroutines.asFlow
4 | import com.squareup.sqldelight.runtime.coroutines.mapToList
5 | import kmp.shared.data.source.BookLocalSource
6 | import kmp.shared.infrastructure.local.BookEntity
7 | import kmp.shared.infrastructure.local.BookQueries
8 | import kotlinx.coroutines.flow.Flow
9 |
10 | internal class BookLocalSourceImpl(
11 | private val queries: BookQueries,
12 | ) : BookLocalSource {
13 | override fun getAll(): Flow> {
14 | return queries.getAllBooks().asFlow().mapToList()
15 | }
16 |
17 | override suspend fun updateOrInsert(items: List) {
18 | queries.deleteAllBooks()
19 | items.forEach(queries::insertOrReplace)
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/shared/src/commonMain/kotlin/kmp/shared/system/Config.kt:
--------------------------------------------------------------------------------
1 | package kmp.shared.system
2 |
3 | interface Config {
4 | val isRelease: Boolean
5 | }
6 |
7 | expect class ConfigImpl : Config
8 |
--------------------------------------------------------------------------------
/shared/src/commonMain/kotlin/kmp/shared/system/Logger.kt:
--------------------------------------------------------------------------------
1 | package kmp.shared.system
2 |
3 | interface Logger {
4 | fun d(tag: String, message: String)
5 | fun w(tag: String, message: String, throwable: Throwable?)
6 | fun e(tag: String, message: String, throwable: Throwable)
7 | }
8 |
9 | expect object Log : Logger
10 |
--------------------------------------------------------------------------------
/shared/src/commonMain/sqldelight/kmp/shared/infrastructure/local/Book.sq:
--------------------------------------------------------------------------------
1 | CREATE TABLE BookEntity (
2 | id TEXT PRIMARY KEY,
3 | name TEXT NOT NULL UNIQUE,
4 | author TEXT,
5 | pageCount INTEGER
6 | );
7 |
8 | getBook:
9 | SELECT * FROM BookEntity WHERE id = ?;
10 |
11 | getAllBooks:
12 | SELECT * FROM BookEntity;
13 |
14 | insertOrReplace:
15 | REPLACE INTO BookEntity VALUES ?;
16 |
17 | delete:
18 | DELETE FROM BookEntity WHERE id = ?;
19 |
20 | deleteAllBooks:
21 | DELETE FROM BookEntity;
22 |
23 |
--------------------------------------------------------------------------------
/shared/src/commonMain/sqldelight/kmp/shared/infrastructure/local/User.sq:
--------------------------------------------------------------------------------
1 | CREATE TABLE UserEntity (
2 | id TEXT PRIMARY KEY,
3 | email TEXT NOT NULL UNIQUE,
4 | firstName TEXT,
5 | lastName TEXT,
6 | phone TEXT,
7 | bio TEXT
8 | );
9 |
10 | getUser:
11 | SELECT * FROM UserEntity WHERE id = ?;
12 |
13 | getAllUsers:
14 | SELECT * FROM UserEntity;
15 |
16 | insertOrReplace:
17 | REPLACE INTO UserEntity VALUES ?;
18 |
19 | deleteUser:
20 | DELETE FROM UserEntity WHERE id = ?;
21 |
22 | deleteAllUsers:
23 | DELETE FROM UserEntity;
24 |
25 |
--------------------------------------------------------------------------------
/shared/src/commonMain/sqldelight/kmp/shared/infrastructure/local/UserCache.sq:
--------------------------------------------------------------------------------
1 | CREATE TABLE UserCache (
2 | id TEXT PRIMARY KEY,
3 | email TEXT NOT NULL UNIQUE,
4 | firstName TEXT,
5 | lastName TEXT
6 | );
7 |
8 | getUsersPaginated:
9 | SELECT * FROM UserCache
10 | ORDER BY id
11 | LIMIT :limit OFFSET :offset;
12 |
13 | getCache:
14 | SELECT * FROM UserCache
15 | ORDER BY id;
16 |
17 | getUserCount:
18 | SELECT COUNT(*) FROM UserCache;
19 |
20 | insertOrReplace:
21 | REPLACE INTO UserCache VALUES ?;
22 |
23 | delete:
24 | DELETE FROM UserCache WHERE id = ?;
25 |
26 | deleteCache:
27 | DELETE FROM UserCache;
28 |
29 |
--------------------------------------------------------------------------------
/shared/src/iosMain/kotlin/kmp/shared/base/error/ErrorMessageProviderImpl.kt:
--------------------------------------------------------------------------------
1 | package kmp.shared.base.error
2 |
3 | import dev.icerock.moko.resources.desc.StringDesc
4 | import kmp.shared.base.ErrorResult
5 | import kotlin.experimental.ExperimentalObjCName
6 |
7 | internal class ErrorMessageProviderImpl : ErrorMessageProvider() {
8 | override fun StringDesc.toMessageString(): String = localized()
9 | }
10 |
11 | @OptIn(ExperimentalObjCName::class)
12 | fun ErrorResult.localizedMessage(@ObjCName(swiftName = "_") defMessage: String? = null): String {
13 | return ErrorMessageProviderImpl().getMessage(this, defMessage)
14 | }
15 |
--------------------------------------------------------------------------------
/shared/src/iosMain/kotlin/kmp/shared/infrastructure/local/DriverFactory.kt:
--------------------------------------------------------------------------------
1 | package kmp.shared.infrastructure.local
2 |
3 | import com.squareup.sqldelight.db.SqlDriver
4 | import com.squareup.sqldelight.drivers.native.NativeSqliteDriver
5 | import kmp.Database
6 |
7 | internal actual class DriverFactory {
8 | actual fun createDriver(dbName: String): SqlDriver =
9 | NativeSqliteDriver(Database.Schema, dbName)
10 | }
11 |
--------------------------------------------------------------------------------
/shared/src/iosMain/kotlin/kmp/shared/system/ConfigImpl.kt:
--------------------------------------------------------------------------------
1 | package kmp.shared.system
2 |
3 | actual class ConfigImpl : Config {
4 | override val isRelease: Boolean
5 | get() = true
6 | }
7 |
--------------------------------------------------------------------------------
/shared/src/iosMain/kotlin/kmp/shared/system/Log.kt:
--------------------------------------------------------------------------------
1 | package kmp.shared.system
2 |
3 | import platform.Foundation.NSLog
4 |
5 | actual object Log : Logger {
6 |
7 | override fun d(tag: String, message: String) {
8 | NSLog("$tag: $message")
9 | }
10 |
11 | override fun w(tag: String, message: String, throwable: Throwable?) {
12 | NSLog("$tag: $message - $throwable")
13 | }
14 |
15 | override fun e(tag: String, message: String, throwable: Throwable) {
16 | NSLog("$tag: $message - $throwable")
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/shared/src/iosMain/kotlin/kmp/shared/utils/FlowTestHelper.kt:
--------------------------------------------------------------------------------
1 | package kmp.shared.utils
2 |
3 | import kotlinx.coroutines.flow.flow
4 |
5 | /**
6 | * This object is needed because of Tests on iOS platform.
7 | */
8 | object FlowTestHelper {
9 | fun arrayToFlow(array: List) = flow {
10 | array.forEach {
11 | emit(it)
12 | }
13 | }
14 | }
15 |
--------------------------------------------------------------------------------