├── .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 | --------------------------------------------------------------------------------