From 7432472af95137f46e9cb5045d11019056d79a1f Mon Sep 17 00:00:00 2001 From: rodvar Date: Thu, 14 Nov 2024 11:37:55 +1100 Subject: [PATCH] Feature/repositories architecture (#41) * Shared observable repository data models setup - define interface and abstractions - implementation of Single object repository allowing for persistance (not implemented yet) - examples of usages replacing current greeting in basic UI - implementation for androidNode, and both clients - showcasing a mechanism to link a specific field of the model we want to observe in the presenter for the view (as the view requests it) * - field Koin injection for views (important for nested views) * - adjustments for iOS --- .../bisq/mobile/client/MainActivity.kt | 6 +- .../network/bisq/mobile/Test.android.kt | 2 +- .../bisq/mobile/android/node/Greeting.kt | 8 +- .../bisq/mobile/android/node/MainActivity.kt | 6 +- .../android/node/di/AndroidNodeModule.kt | 14 +- .../domain/data/repository/Repositories.kt | 8 + .../node/presentation/MainNodePresenter.kt | 13 +- bisqapps/gradle/libs.versions.toml | 6 +- bisqapps/iosClient/Podfile.lock | 2 +- bisqapps/iosClient/Pods/Manifest.lock | 2 +- .../Pods/Pods.xcodeproj/project.pbxproj | 203 ++++++++---------- .../Pods-iosClient.debug.xcconfig | 1 + .../Pods-iosClient.release.xcconfig | 1 + .../iosClient/iosClient/ContentView.swift | 12 +- .../LifecycleAwareComposeViewController.swift | 2 +- bisqapps/iosClient/iosClient/iosClient.swift | 7 +- bisqapps/shared/domain/build.gradle.kts | 1 + .../bisq/mobile/domain/Test.android.kt | 1 + .../network/bisq/mobile/domain/Greeting.kt | 17 -- .../mobile/domain/data/model/BaseModel.kt | 9 + .../bisq/mobile/domain/data/model/Greeting.kt | 20 ++ .../data/persistance/PersistanceSource.kt | 13 ++ .../data/repository/GreetingRepository.kt | 13 -- .../domain/data/repository/Repositories.kt | 7 + .../domain/data/repository/Repository.kt | 43 ++++ .../data/repository/SingleObjectRepository.kt | 64 ++++++ .../bisq/mobile/domain/di/DomainModule.kt | 6 +- .../kotlin/network/bisq/mobile/domain/Test.kt | 1 + .../di/{HelperDI.kt => DomainDIHelper.kt} | 2 +- .../network/bisq/mobile/domain/Test.ios.kt | 1 + .../bisq/mobile/presentation/MainPresenter.kt | 26 ++- .../presentation/di/PresentationModule.kt | 4 +- .../bisq/mobile/presentation/ui/App.kt | 4 +- .../network/bisq/mobile/MainViewController.kt | 7 - .../mobile/presentation/MainViewController.kt | 6 + .../presentation/di/PresentationDIHelper.kt | 10 + 36 files changed, 355 insertions(+), 193 deletions(-) create mode 100644 bisqapps/androidNode/src/androidMain/kotlin/network/bisq/mobile/android/node/domain/data/repository/Repositories.kt delete mode 100644 bisqapps/shared/domain/src/commonMain/kotlin/network/bisq/mobile/domain/Greeting.kt create mode 100644 bisqapps/shared/domain/src/commonMain/kotlin/network/bisq/mobile/domain/data/model/BaseModel.kt create mode 100644 bisqapps/shared/domain/src/commonMain/kotlin/network/bisq/mobile/domain/data/model/Greeting.kt create mode 100644 bisqapps/shared/domain/src/commonMain/kotlin/network/bisq/mobile/domain/data/persistance/PersistanceSource.kt delete mode 100644 bisqapps/shared/domain/src/commonMain/kotlin/network/bisq/mobile/domain/data/repository/GreetingRepository.kt create mode 100644 bisqapps/shared/domain/src/commonMain/kotlin/network/bisq/mobile/domain/data/repository/Repositories.kt create mode 100644 bisqapps/shared/domain/src/commonMain/kotlin/network/bisq/mobile/domain/data/repository/Repository.kt create mode 100644 bisqapps/shared/domain/src/commonMain/kotlin/network/bisq/mobile/domain/data/repository/SingleObjectRepository.kt rename bisqapps/shared/domain/src/iosMain/kotlin/network/bisq/mobile/domain/di/{HelperDI.kt => DomainDIHelper.kt} (75%) delete mode 100644 bisqapps/shared/presentation/src/iosMain/kotlin/network/bisq/mobile/MainViewController.kt create mode 100644 bisqapps/shared/presentation/src/iosMain/kotlin/network/bisq/mobile/presentation/MainViewController.kt create mode 100644 bisqapps/shared/presentation/src/iosMain/kotlin/network/bisq/mobile/presentation/di/PresentationDIHelper.kt diff --git a/bisqapps/androidClient/src/androidMain/kotlin/network/bisq/mobile/client/MainActivity.kt b/bisqapps/androidClient/src/androidMain/kotlin/network/bisq/mobile/client/MainActivity.kt index d69271c4..11bc18b7 100644 --- a/bisqapps/androidClient/src/androidMain/kotlin/network/bisq/mobile/client/MainActivity.kt +++ b/bisqapps/androidClient/src/androidMain/kotlin/network/bisq/mobile/client/MainActivity.kt @@ -5,7 +5,7 @@ import androidx.activity.ComponentActivity import androidx.activity.compose.setContent import androidx.compose.runtime.Composable import androidx.compose.ui.tooling.preview.Preview -import network.bisq.mobile.domain.data.repository.GreetingRepository +import network.bisq.mobile.domain.data.repository.SingleObjectRepository import network.bisq.mobile.presentation.MainPresenter import network.bisq.mobile.presentation.ui.App import org.koin.android.ext.android.inject @@ -17,7 +17,7 @@ class MainActivity : ComponentActivity() { presenter.attachView(this) setContent { - App(presenter) + App() } } @@ -51,5 +51,5 @@ class MainActivity : ComponentActivity() { @Preview @Composable fun AppAndroidPreview() { - App(MainPresenter(GreetingRepository())) + App() } \ No newline at end of file diff --git a/bisqapps/androidClient/src/androidUnitTest/kotlin/network/bisq/mobile/Test.android.kt b/bisqapps/androidClient/src/androidUnitTest/kotlin/network/bisq/mobile/Test.android.kt index 3c6a701e..e19ffd1f 100644 --- a/bisqapps/androidClient/src/androidUnitTest/kotlin/network/bisq/mobile/Test.android.kt +++ b/bisqapps/androidClient/src/androidUnitTest/kotlin/network/bisq/mobile/Test.android.kt @@ -1,6 +1,6 @@ package network.bisq.mobile -import network.bisq.mobile.domain.Greeting +import network.bisq.mobile.domain.data.model.Greeting import org.junit.Assert.assertTrue import org.junit.Test diff --git a/bisqapps/androidNode/src/androidMain/kotlin/network/bisq/mobile/android/node/Greeting.kt b/bisqapps/androidNode/src/androidMain/kotlin/network/bisq/mobile/android/node/Greeting.kt index 67ae497a..a49fa35f 100644 --- a/bisqapps/androidNode/src/androidMain/kotlin/network/bisq/mobile/android/node/Greeting.kt +++ b/bisqapps/androidNode/src/androidMain/kotlin/network/bisq/mobile/android/node/Greeting.kt @@ -1,12 +1,10 @@ package network.bisq.mobile.android.node -import network.bisq.mobile.domain.Greeting -import network.bisq.mobile.domain.GreetingFactory +import network.bisq.mobile.domain.data.model.Greeting +import network.bisq.mobile.domain.data.model.GreetingFactory class AndroidNodeGreeting : Greeting() { - override fun greet(): String { - return "Hello Node, ${platform.name}!" - } + override val greetText = "Hello Node, ${platform.name}!" } class AndroidNodeGreetingFactory : GreetingFactory { override fun createGreeting() = AndroidNodeGreeting() diff --git a/bisqapps/androidNode/src/androidMain/kotlin/network/bisq/mobile/android/node/MainActivity.kt b/bisqapps/androidNode/src/androidMain/kotlin/network/bisq/mobile/android/node/MainActivity.kt index 1a3b6559..70828de0 100644 --- a/bisqapps/androidNode/src/androidMain/kotlin/network/bisq/mobile/android/node/MainActivity.kt +++ b/bisqapps/androidNode/src/androidMain/kotlin/network/bisq/mobile/android/node/MainActivity.kt @@ -5,7 +5,7 @@ import androidx.activity.ComponentActivity import androidx.activity.compose.setContent import androidx.compose.runtime.Composable import androidx.compose.ui.tooling.preview.Preview -import network.bisq.mobile.domain.data.repository.GreetingRepository +import network.bisq.mobile.domain.data.repository.SingleObjectRepository import network.bisq.mobile.presentation.MainPresenter import network.bisq.mobile.presentation.ui.App import org.koin.android.ext.android.inject @@ -18,7 +18,7 @@ class MainActivity : ComponentActivity() { presenter.attachView(this) setContent { - App(presenter) + App() } } @@ -52,5 +52,5 @@ class MainActivity : ComponentActivity() { @Preview @Composable fun AppAndroidPreview() { - App(MainPresenter(GreetingRepository(AndroidNodeGreetingFactory()))) + App() } \ No newline at end of file diff --git a/bisqapps/androidNode/src/androidMain/kotlin/network/bisq/mobile/android/node/di/AndroidNodeModule.kt b/bisqapps/androidNode/src/androidMain/kotlin/network/bisq/mobile/android/node/di/AndroidNodeModule.kt index f8821880..dbb47815 100644 --- a/bisqapps/androidNode/src/androidMain/kotlin/network/bisq/mobile/android/node/di/AndroidNodeModule.kt +++ b/bisqapps/androidNode/src/androidMain/kotlin/network/bisq/mobile/android/node/di/AndroidNodeModule.kt @@ -1,12 +1,18 @@ package network.bisq.mobile.android.node.di -import network.bisq.mobile.android.node.AndroidNodeGreetingFactory +import network.bisq.mobile.android.node.AndroidNodeGreeting +import network.bisq.mobile.android.node.domain.data.repository.NodeGreetingRepository import network.bisq.mobile.android.node.presentation.MainNodePresenter -import network.bisq.mobile.domain.GreetingFactory +import network.bisq.mobile.domain.data.repository.SingleObjectRepository import network.bisq.mobile.presentation.MainPresenter +import network.bisq.mobile.presentation.ui.AppPresenter +import org.koin.dsl.bind import org.koin.dsl.module val androidNodeModule = module { - single { AndroidNodeGreetingFactory() } - single { MainNodePresenter(get()) } + // this one is for example properties, will be eliminated soon + single { NodeGreetingRepository() } + // this line showcases both, the posibility to change behaviour of the app by changing one definiton + // and binding the same obj to 2 different abstractions + single { MainNodePresenter(get()) } bind AppPresenter::class } \ No newline at end of file diff --git a/bisqapps/androidNode/src/androidMain/kotlin/network/bisq/mobile/android/node/domain/data/repository/Repositories.kt b/bisqapps/androidNode/src/androidMain/kotlin/network/bisq/mobile/android/node/domain/data/repository/Repositories.kt new file mode 100644 index 00000000..841187e5 --- /dev/null +++ b/bisqapps/androidNode/src/androidMain/kotlin/network/bisq/mobile/android/node/domain/data/repository/Repositories.kt @@ -0,0 +1,8 @@ +package network.bisq.mobile.android.node.domain.data.repository + +import network.bisq.mobile.android.node.AndroidNodeGreeting +import network.bisq.mobile.domain.data.repository.GreetingRepository + +// this way of definingsupports both platforms +// add your repositories here and then in your DI module call this classes for instanciation +class NodeGreetingRepository: GreetingRepository() \ No newline at end of file diff --git a/bisqapps/androidNode/src/androidMain/kotlin/network/bisq/mobile/android/node/presentation/MainNodePresenter.kt b/bisqapps/androidNode/src/androidMain/kotlin/network/bisq/mobile/android/node/presentation/MainNodePresenter.kt index 55373f84..394c8a55 100644 --- a/bisqapps/androidNode/src/androidMain/kotlin/network/bisq/mobile/android/node/presentation/MainNodePresenter.kt +++ b/bisqapps/androidNode/src/androidMain/kotlin/network/bisq/mobile/android/node/presentation/MainNodePresenter.kt @@ -2,7 +2,6 @@ package network.bisq.mobile.android.node.presentation import android.app.Activity import android.os.Build -import network.bisq.mobile.domain.data.repository.GreetingRepository import network.bisq.mobile.presentation.MainPresenter import bisq.common.facades.FacadeProvider import bisq.common.facades.android.AndroidGuavaFacade @@ -37,8 +36,13 @@ import bisq.user.identity.NymIdGenerator import bisq.user.identity.UserIdentity import bisq.user.profile.UserProfile import kotlinx.coroutines.* +import network.bisq.mobile.android.node.AndroidNodeGreeting +import network.bisq.mobile.android.node.domain.data.repository.NodeGreetingRepository import network.bisq.mobile.android.node.service.AndroidApplicationService import network.bisq.mobile.android.node.service.AndroidMemoryReportService +import network.bisq.mobile.domain.data.model.Greeting +import network.bisq.mobile.domain.data.repository.GreetingRepository +import network.bisq.mobile.domain.data.repository.SingleObjectRepository import java.util.Optional import java.util.concurrent.CompletableFuture import java.util.concurrent.TimeUnit @@ -47,7 +51,8 @@ import kotlin.jvm.optionals.getOrElse import kotlin.math.max import kotlin.random.Random -class MainNodePresenter(greetingRepository: GreetingRepository): MainPresenter(greetingRepository) { +@Suppress("UNCHECKED_CAST") +class MainNodePresenter(greetingRepository: NodeGreetingRepository): MainPresenter(greetingRepository as GreetingRepository) { companion object { private const val AVATAR_VERSION = 0 } @@ -77,6 +82,10 @@ class MainNodePresenter(greetingRepository: GreetingRepository): MainPresenter(g Security.removeProvider("BC") Security.addProvider(BouncyCastleProvider()) log("Static Bisq core setup ready") + + CoroutineScope(Dispatchers.IO).launch { + greetingRepository.create(AndroidNodeGreeting()) + } } override fun onViewAttached() { super.onViewAttached() diff --git a/bisqapps/gradle/libs.versions.toml b/bisqapps/gradle/libs.versions.toml index 62b7cab9..295d58d8 100644 --- a/bisqapps/gradle/libs.versions.toml +++ b/bisqapps/gradle/libs.versions.toml @@ -50,8 +50,6 @@ i2p-v2 = { strictly = '2.4.0' } jackson-lib = { strictly = '2.17.2' } koin = "4.0.0" -koin-compose = "4.0.0" -koin-android = "4.0.0" lombok-lib = { strictly = '1.18.34' } typesafe-config-lib = { strictly = '1.4.3' } @@ -145,8 +143,8 @@ jackson-databind = { module = 'com.fasterxml.jackson.core:jackson-databind', ver # koin koin-core = { module = "io.insert-koin:koin-core", version.ref = "koin" } -koin-compose = { module = "io.insert-koin:koin-compose", version.ref = "koin-compose" } -koin-android = { module = "io.insert-koin:koin-android", version.ref = "koin-android" } +koin-compose = { module = "io.insert-koin:koin-compose", version.ref = "koin" } +koin-android = { module = "io.insert-koin:koin-android", version.ref = "koin" } [bundles] glassfish-jersey = ['glassfish-jersey-jdk-http', 'glassfish-jersey-json-jackson', 'glassfish-jersey-inject-hk2', 'glassfish-jaxb-runtime'] diff --git a/bisqapps/iosClient/Podfile.lock b/bisqapps/iosClient/Podfile.lock index 1a06817f..295e6bb7 100644 --- a/bisqapps/iosClient/Podfile.lock +++ b/bisqapps/iosClient/Podfile.lock @@ -18,4 +18,4 @@ SPEC CHECKSUMS: PODFILE CHECKSUM: e4e5bb187d8247572973af90a35b57b84c96b4c6 -COCOAPODS: 1.15.2 +COCOAPODS: 1.16.2 diff --git a/bisqapps/iosClient/Pods/Manifest.lock b/bisqapps/iosClient/Pods/Manifest.lock index 1a06817f..295e6bb7 100644 --- a/bisqapps/iosClient/Pods/Manifest.lock +++ b/bisqapps/iosClient/Pods/Manifest.lock @@ -18,4 +18,4 @@ SPEC CHECKSUMS: PODFILE CHECKSUM: e4e5bb187d8247572973af90a35b57b84c96b4c6 -COCOAPODS: 1.15.2 +COCOAPODS: 1.16.2 diff --git a/bisqapps/iosClient/Pods/Pods.xcodeproj/project.pbxproj b/bisqapps/iosClient/Pods/Pods.xcodeproj/project.pbxproj index a3f03f32..69267914 100644 --- a/bisqapps/iosClient/Pods/Pods.xcodeproj/project.pbxproj +++ b/bisqapps/iosClient/Pods/Pods.xcodeproj/project.pbxproj @@ -399,36 +399,55 @@ /* End PBXTargetDependency section */ /* Begin XCBuildConfiguration section */ - 154EEB0F238C8DA20BBA3F86805D666C /* Debug */ = { + 018F2F2185808E1CFC64820D5D133A46 /* Release */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 8EA548F0CBB66C825F5F196469AE443C /* presentation.debug.xcconfig */; + baseConfigurationReference = 12A9F0027D088186CD39E0238C94B9C8 /* Pods-iosClient.release.xcconfig */; buildSettings = { - ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; - ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = NO; CLANG_ENABLE_OBJC_WEAK = NO; + "CODE_SIGN_IDENTITY[sdk=appletvos*]" = ""; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; + "CODE_SIGN_IDENTITY[sdk=watchos*]" = ""; CURRENT_PROJECT_VERSION = 1; - GENERATE_INFOPLIST_FILE = YES; + DEFINES_MODULE = YES; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + ENABLE_MODULE_VERIFIER = NO; + ENABLE_USER_SCRIPT_SANDBOXING = NO; + INFOPLIST_FILE = "Target Support Files/Pods-iosClient/Pods-iosClient-Info.plist"; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; IPHONEOS_DEPLOYMENT_TARGET = 16.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", + "@loader_path/Frameworks", ); - MARKETING_VERSION = 1.0; + MACH_O_TYPE = staticlib; + MODULEMAP_FILE = "Target Support Files/Pods-iosClient/Pods-iosClient.modulemap"; + OTHER_LDFLAGS = ""; + OTHER_LIBTOOLFLAGS = ""; + PODS_ROOT = "$(SRCROOT)"; + PRODUCT_BUNDLE_IDENTIFIER = "org.cocoapods.${PRODUCT_NAME:rfc1034identifier}"; + PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; SDKROOT = iphoneos; - SWIFT_EMIT_LOC_STRINGS = YES; + SKIP_INSTALL = YES; TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; }; - name = Debug; + name = Release; }; - 58E95B6C3EF6B2F82B44C25510BC16F7 /* Release */ = { + 30E0B9EFD9A5C45D0D351231E81B30B3 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; - ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; - CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_ENABLE_OBJC_WEAK = YES; @@ -458,8 +477,7 @@ DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; - ENABLE_USER_SCRIPT_SANDBOXING = YES; - GCC_C_LANGUAGE_STANDARD = gnu17; + GCC_C_LANGUAGE_STANDARD = gnu11; GCC_NO_COMMON_BLOCKS = YES; GCC_PREPROCESSOR_DEFINITIONS = ( "POD_CONFIGURATION_RELEASE=1", @@ -472,62 +490,55 @@ GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; IPHONEOS_DEPLOYMENT_TARGET = 16.0; - LOCALIZATION_PREFERS_STRING_CATALOGS = YES; MTL_ENABLE_DEBUG_INFO = NO; MTL_FAST_MATH = YES; PRODUCT_NAME = "$(TARGET_NAME)"; STRIP_INSTALLED_PRODUCT = NO; SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; SWIFT_VERSION = 5.0; SYMROOT = "${SRCROOT}/../build"; }; name = Release; }; - 6ED65CB851B4DEAA7C4DCF7BFE69789D /* Release */ = { + 437EA4C44DE0B4C7DCDF7777DED031FA /* Debug */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 12A9F0027D088186CD39E0238C94B9C8 /* Pods-iosClient.release.xcconfig */; + baseConfigurationReference = 8EA548F0CBB66C825F5F196469AE443C /* presentation.debug.xcconfig */; buildSettings = { - ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = NO; + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; CLANG_ENABLE_OBJC_WEAK = NO; - "CODE_SIGN_IDENTITY[sdk=appletvos*]" = ""; - "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; - "CODE_SIGN_IDENTITY[sdk=watchos*]" = ""; - CURRENT_PROJECT_VERSION = 1; - DEFINES_MODULE = YES; - DYLIB_COMPATIBILITY_VERSION = 1; - DYLIB_CURRENT_VERSION = 1; - DYLIB_INSTALL_NAME_BASE = "@rpath"; - ENABLE_MODULE_VERIFIER = YES; - GENERATE_INFOPLIST_FILE = YES; - INFOPLIST_FILE = "Target Support Files/Pods-iosClient/Pods-iosClient-Info.plist"; - INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + ENABLE_USER_SCRIPT_SANDBOXING = NO; + IPHONEOS_DEPLOYMENT_TARGET = 16.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + SDKROOT = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + 48F2FDB6889B575B7DB16DE30260C86D /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 204E5861AA89F6485AE0E7BE7CC61EB9 /* presentation.release.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + CLANG_ENABLE_OBJC_WEAK = NO; + ENABLE_USER_SCRIPT_SANDBOXING = NO; IPHONEOS_DEPLOYMENT_TARGET = 16.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", - "@loader_path/Frameworks", ); - MACH_O_TYPE = staticlib; - MARKETING_VERSION = 1.0; - MODULEMAP_FILE = "Target Support Files/Pods-iosClient/Pods-iosClient.modulemap"; - MODULE_VERIFIER_SUPPORTED_LANGUAGES = "objective-c objective-c++"; - MODULE_VERIFIER_SUPPORTED_LANGUAGE_STANDARDS = "gnu17 gnu++20"; - OTHER_LDFLAGS = ""; - OTHER_LIBTOOLFLAGS = ""; - PODS_ROOT = "$(SRCROOT)"; - PRODUCT_BUNDLE_IDENTIFIER = "org.cocoapods.${PRODUCT_NAME:rfc1034identifier}"; - PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; SDKROOT = iphoneos; - SKIP_INSTALL = YES; - SWIFT_EMIT_LOC_STRINGS = YES; TARGETED_DEVICE_FAMILY = "1,2"; VALIDATE_PRODUCT = YES; - VERSIONING_SYSTEM = "apple-generic"; - VERSION_INFO_PREFIX = ""; }; name = Release; }; - 83501AA1465F66968DB1E04BAFB2FD6B /* Debug */ = { + CB606ED3E79A498A37998C1B0EED7638 /* Debug */ = { isa = XCBuildConfiguration; baseConfigurationReference = A733908725930A882438524B1F97DAE3 /* Pods-iosClient.debug.xcconfig */; buildSettings = { @@ -541,8 +552,8 @@ DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; - ENABLE_MODULE_VERIFIER = YES; - GENERATE_INFOPLIST_FILE = YES; + ENABLE_MODULE_VERIFIER = NO; + ENABLE_USER_SCRIPT_SANDBOXING = NO; INFOPLIST_FILE = "Target Support Files/Pods-iosClient/Pods-iosClient-Info.plist"; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; IPHONEOS_DEPLOYMENT_TARGET = 16.0; @@ -552,10 +563,7 @@ "@loader_path/Frameworks", ); MACH_O_TYPE = staticlib; - MARKETING_VERSION = 1.0; MODULEMAP_FILE = "Target Support Files/Pods-iosClient/Pods-iosClient.modulemap"; - MODULE_VERIFIER_SUPPORTED_LANGUAGES = "objective-c objective-c++"; - MODULE_VERIFIER_SUPPORTED_LANGUAGE_STANDARDS = "gnu17 gnu++20"; OTHER_LDFLAGS = ""; OTHER_LIBTOOLFLAGS = ""; PODS_ROOT = "$(SRCROOT)"; @@ -563,87 +571,40 @@ PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; SDKROOT = iphoneos; SKIP_INSTALL = YES; - SWIFT_EMIT_LOC_STRINGS = YES; TARGETED_DEVICE_FAMILY = "1,2"; VERSIONING_SYSTEM = "apple-generic"; VERSION_INFO_PREFIX = ""; }; name = Debug; }; - D1DA436F10502234939B19E1A43796C6 /* Debug */ = { - isa = XCBuildConfiguration; - baseConfigurationReference = 804065BB4F007C057F539031BA89C4D2 /* domain.debug.xcconfig */; - buildSettings = { - ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; - ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; - CLANG_ENABLE_OBJC_WEAK = NO; - CURRENT_PROJECT_VERSION = 1; - GENERATE_INFOPLIST_FILE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 16.0; - LD_RUNPATH_SEARCH_PATHS = ( - "$(inherited)", - "@executable_path/Frameworks", - ); - MARKETING_VERSION = 1.0; - SDKROOT = iphoneos; - SWIFT_EMIT_LOC_STRINGS = YES; - TARGETED_DEVICE_FAMILY = "1,2"; - }; - name = Debug; - }; - EAEBBFC9873E38DD022C68BDF8CBE2BC /* Release */ = { - isa = XCBuildConfiguration; - baseConfigurationReference = 204E5861AA89F6485AE0E7BE7CC61EB9 /* presentation.release.xcconfig */; - buildSettings = { - ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; - ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; - CLANG_ENABLE_OBJC_WEAK = NO; - CURRENT_PROJECT_VERSION = 1; - GENERATE_INFOPLIST_FILE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 16.0; - LD_RUNPATH_SEARCH_PATHS = ( - "$(inherited)", - "@executable_path/Frameworks", - ); - MARKETING_VERSION = 1.0; - SDKROOT = iphoneos; - SWIFT_EMIT_LOC_STRINGS = YES; - TARGETED_DEVICE_FAMILY = "1,2"; - VALIDATE_PRODUCT = YES; - }; - name = Release; - }; - F914239A6EFBBE362B34774AE3FEA672 /* Release */ = { + D347EF77A9147218FFD617F7E00D9870 /* Release */ = { isa = XCBuildConfiguration; baseConfigurationReference = DA1AD272BC5CBE1D4ED4240B2689D453 /* domain.release.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; CLANG_ENABLE_OBJC_WEAK = NO; - CURRENT_PROJECT_VERSION = 1; - GENERATE_INFOPLIST_FILE = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; IPHONEOS_DEPLOYMENT_TARGET = 16.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 1.0; SDKROOT = iphoneos; - SWIFT_EMIT_LOC_STRINGS = YES; TARGETED_DEVICE_FAMILY = "1,2"; VALIDATE_PRODUCT = YES; }; name = Release; }; - FB72CE38903842F8E47596367768D50E /* Debug */ = { + F4FF6A0D1970CA9705974E3CB2134802 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; - ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; - CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_ENABLE_OBJC_WEAK = YES; @@ -673,8 +634,7 @@ DEBUG_INFORMATION_FORMAT = dwarf; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_TESTABILITY = YES; - ENABLE_USER_SCRIPT_SANDBOXING = YES; - GCC_C_LANGUAGE_STANDARD = gnu17; + GCC_C_LANGUAGE_STANDARD = gnu11; GCC_DYNAMIC_NO_PIC = NO; GCC_NO_COMMON_BLOCKS = YES; GCC_OPTIMIZATION_LEVEL = 0; @@ -690,7 +650,6 @@ GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; IPHONEOS_DEPLOYMENT_TARGET = 16.0; - LOCALIZATION_PREFERS_STRING_CATALOGS = YES; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; ONLY_ACTIVE_ARCH = YES; @@ -703,14 +662,32 @@ }; name = Debug; }; + F6743C549671A6AE48C5AF30AA7C423A /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 804065BB4F007C057F539031BA89C4D2 /* domain.debug.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + CLANG_ENABLE_OBJC_WEAK = NO; + ENABLE_USER_SCRIPT_SANDBOXING = NO; + IPHONEOS_DEPLOYMENT_TARGET = 16.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + SDKROOT = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ 4821239608C13582E20E6DA73FD5F1F9 /* Build configuration list for PBXProject "Pods" */ = { isa = XCConfigurationList; buildConfigurations = ( - FB72CE38903842F8E47596367768D50E /* Debug */, - 58E95B6C3EF6B2F82B44C25510BC16F7 /* Release */, + F4FF6A0D1970CA9705974E3CB2134802 /* Debug */, + 30E0B9EFD9A5C45D0D351231E81B30B3 /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; @@ -718,8 +695,8 @@ 84A7558F1F961C4A23CF638108708641 /* Build configuration list for PBXAggregateTarget "presentation" */ = { isa = XCConfigurationList; buildConfigurations = ( - 154EEB0F238C8DA20BBA3F86805D666C /* Debug */, - EAEBBFC9873E38DD022C68BDF8CBE2BC /* Release */, + 437EA4C44DE0B4C7DCDF7777DED031FA /* Debug */, + 48F2FDB6889B575B7DB16DE30260C86D /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; @@ -727,8 +704,8 @@ B978635EC7F36DF902EF0C770E5CBB10 /* Build configuration list for PBXAggregateTarget "domain" */ = { isa = XCConfigurationList; buildConfigurations = ( - D1DA436F10502234939B19E1A43796C6 /* Debug */, - F914239A6EFBBE362B34774AE3FEA672 /* Release */, + F6743C549671A6AE48C5AF30AA7C423A /* Debug */, + D347EF77A9147218FFD617F7E00D9870 /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; @@ -736,8 +713,8 @@ BD19120D9E7ED18653B7259546989F15 /* Build configuration list for PBXNativeTarget "Pods-iosClient" */ = { isa = XCConfigurationList; buildConfigurations = ( - 83501AA1465F66968DB1E04BAFB2FD6B /* Debug */, - 6ED65CB851B4DEAA7C4DCF7BFE69789D /* Release */, + CB606ED3E79A498A37998C1B0EED7638 /* Debug */, + 018F2F2185808E1CFC64820D5D133A46 /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; diff --git a/bisqapps/iosClient/Pods/Target Support Files/Pods-iosClient/Pods-iosClient.debug.xcconfig b/bisqapps/iosClient/Pods/Target Support Files/Pods-iosClient/Pods-iosClient.debug.xcconfig index b5042e50..f7d607d8 100644 --- a/bisqapps/iosClient/Pods/Target Support Files/Pods-iosClient/Pods-iosClient.debug.xcconfig +++ b/bisqapps/iosClient/Pods/Target Support Files/Pods-iosClient/Pods-iosClient.debug.xcconfig @@ -4,6 +4,7 @@ FRAMEWORK_SEARCH_PATHS = $(inherited) "${PODS_ROOT}/../../shared/domain/build/co GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 LD_RUNPATH_SEARCH_PATHS = $(inherited) '@executable_path/Frameworks' '@loader_path/Frameworks' OTHER_LDFLAGS = $(inherited) -l"c++" -framework "domain" -framework "presentation" +OTHER_MODULE_VERIFIER_FLAGS = $(inherited) "-F${PODS_CONFIGURATION_BUILD_DIR}/domain" "-F${PODS_CONFIGURATION_BUILD_DIR}/presentation" PODS_BUILD_DIR = ${BUILD_DIR} PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) PODS_PODFILE_DIR_PATH = ${SRCROOT}/. diff --git a/bisqapps/iosClient/Pods/Target Support Files/Pods-iosClient/Pods-iosClient.release.xcconfig b/bisqapps/iosClient/Pods/Target Support Files/Pods-iosClient/Pods-iosClient.release.xcconfig index b5042e50..f7d607d8 100644 --- a/bisqapps/iosClient/Pods/Target Support Files/Pods-iosClient/Pods-iosClient.release.xcconfig +++ b/bisqapps/iosClient/Pods/Target Support Files/Pods-iosClient/Pods-iosClient.release.xcconfig @@ -4,6 +4,7 @@ FRAMEWORK_SEARCH_PATHS = $(inherited) "${PODS_ROOT}/../../shared/domain/build/co GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 LD_RUNPATH_SEARCH_PATHS = $(inherited) '@executable_path/Frameworks' '@loader_path/Frameworks' OTHER_LDFLAGS = $(inherited) -l"c++" -framework "domain" -framework "presentation" +OTHER_MODULE_VERIFIER_FLAGS = $(inherited) "-F${PODS_CONFIGURATION_BUILD_DIR}/domain" "-F${PODS_CONFIGURATION_BUILD_DIR}/presentation" PODS_BUILD_DIR = ${BUILD_DIR} PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) PODS_PODFILE_DIR_PATH = ${SRCROOT}/. diff --git a/bisqapps/iosClient/iosClient/ContentView.swift b/bisqapps/iosClient/iosClient/ContentView.swift index fac81255..7df3ff70 100644 --- a/bisqapps/iosClient/iosClient/ContentView.swift +++ b/bisqapps/iosClient/iosClient/ContentView.swift @@ -5,7 +5,17 @@ import presentation import domain struct ComposeView: UIViewControllerRepresentable { - private let presenter = MainPresenter(greetingRepository: DomainGreetingRepository()) // Initialize the presenter for iOS + + // TODO DI injection is not fully resolved for iOS. we need to fix this with Koin or worst case ask the + // view for the presenter its using + // it can also work because this is just the main presenter that binds to lifecycle, that we allow + // this hardcoded dependnecy here. + private let presenter: MainPresenter + + init() { + let repository = DomainGreetingRepository() + self.presenter = MainPresenter(greetingRepository: repository) + } func makeUIViewController(context: Context) -> UIViewController { return LifecycleAwareComposeViewController(presenter: presenter) diff --git a/bisqapps/iosClient/iosClient/LifecycleAwareComposeViewController.swift b/bisqapps/iosClient/iosClient/LifecycleAwareComposeViewController.swift index ccb940e5..c800a17a 100644 --- a/bisqapps/iosClient/iosClient/LifecycleAwareComposeViewController.swift +++ b/bisqapps/iosClient/iosClient/LifecycleAwareComposeViewController.swift @@ -40,7 +40,7 @@ class LifecycleAwareComposeViewController: UIViewController { super.loadView() // Instantiate the Kotlin-based MainViewController - let mainViewController = MainViewControllerKt.MainViewController(presenter: presenter) + let mainViewController = MainViewControllerKt.MainViewController() // Add MainViewController as a child addChild(mainViewController) diff --git a/bisqapps/iosClient/iosClient/iosClient.swift b/bisqapps/iosClient/iosClient/iosClient.swift index b4e21cc2..58f18c1d 100644 --- a/bisqapps/iosClient/iosClient/iosClient.swift +++ b/bisqapps/iosClient/iosClient/iosClient.swift @@ -1,10 +1,11 @@ import SwiftUI -import domain +import presentation @main struct iosClient: App { init() { - HelperDIKt.doInitKoin() + // TODO might need to get away the helper approach in favour of adding koin pods in + PresentationDIHelperKt.doInitKoin() } var body: some Scene { @@ -12,4 +13,4 @@ struct iosClient: App { ContentView() } } -} \ No newline at end of file +} diff --git a/bisqapps/shared/domain/build.gradle.kts b/bisqapps/shared/domain/build.gradle.kts index 614bc6df..68a37263 100644 --- a/bisqapps/shared/domain/build.gradle.kts +++ b/bisqapps/shared/domain/build.gradle.kts @@ -34,6 +34,7 @@ kotlin { commonMain.dependencies { //put your multiplatform dependencies here implementation(libs.koin.core) + implementation(libs.kotlinx.coroutines) } commonTest.dependencies { implementation(libs.kotlin.test) diff --git a/bisqapps/shared/domain/src/androidUnitTest/kotlin/network/bisq/mobile/domain/Test.android.kt b/bisqapps/shared/domain/src/androidUnitTest/kotlin/network/bisq/mobile/domain/Test.android.kt index 7634df68..7e624783 100644 --- a/bisqapps/shared/domain/src/androidUnitTest/kotlin/network/bisq/mobile/domain/Test.android.kt +++ b/bisqapps/shared/domain/src/androidUnitTest/kotlin/network/bisq/mobile/domain/Test.android.kt @@ -1,5 +1,6 @@ package network.bisq.mobile.domain +import network.bisq.mobile.domain.data.model.Greeting import kotlin.test.Test import kotlin.test.assertTrue diff --git a/bisqapps/shared/domain/src/commonMain/kotlin/network/bisq/mobile/domain/Greeting.kt b/bisqapps/shared/domain/src/commonMain/kotlin/network/bisq/mobile/domain/Greeting.kt deleted file mode 100644 index 0283a00b..00000000 --- a/bisqapps/shared/domain/src/commonMain/kotlin/network/bisq/mobile/domain/Greeting.kt +++ /dev/null @@ -1,17 +0,0 @@ -package network.bisq.mobile.domain - -open class Greeting { - protected val platform = getPlatform() - - open fun greet(): String { - return "Hello, ${platform.name}!" - } -} - -interface GreetingFactory { - fun createGreeting(): Greeting -} - -class DefaultGreetingFactory : GreetingFactory { - override fun createGreeting() = Greeting() -} \ No newline at end of file diff --git a/bisqapps/shared/domain/src/commonMain/kotlin/network/bisq/mobile/domain/data/model/BaseModel.kt b/bisqapps/shared/domain/src/commonMain/kotlin/network/bisq/mobile/domain/data/model/BaseModel.kt new file mode 100644 index 00000000..c9f497da --- /dev/null +++ b/bisqapps/shared/domain/src/commonMain/kotlin/network/bisq/mobile/domain/data/model/BaseModel.kt @@ -0,0 +1,9 @@ +package network.bisq.mobile.domain.data.model + +/** + * BisqApps Base Domain Model definition + */ +abstract class BaseModel { + // Add here any common properties of models (id?, timestamps?) +// abstract val id: String +} \ No newline at end of file diff --git a/bisqapps/shared/domain/src/commonMain/kotlin/network/bisq/mobile/domain/data/model/Greeting.kt b/bisqapps/shared/domain/src/commonMain/kotlin/network/bisq/mobile/domain/data/model/Greeting.kt new file mode 100644 index 00000000..cde04698 --- /dev/null +++ b/bisqapps/shared/domain/src/commonMain/kotlin/network/bisq/mobile/domain/data/model/Greeting.kt @@ -0,0 +1,20 @@ +package network.bisq.mobile.domain.data.model + +import network.bisq.mobile.domain.getPlatform + +open class Greeting: BaseModel() { + protected val platform = getPlatform() + protected open val greetText = "Hello, ${platform.name}!" + + fun greet(): String { + return greetText + } +} + +interface GreetingFactory { + fun createGreeting(): Greeting +} + +class DefaultGreetingFactory : GreetingFactory { + override fun createGreeting() = Greeting() +} \ No newline at end of file diff --git a/bisqapps/shared/domain/src/commonMain/kotlin/network/bisq/mobile/domain/data/persistance/PersistanceSource.kt b/bisqapps/shared/domain/src/commonMain/kotlin/network/bisq/mobile/domain/data/persistance/PersistanceSource.kt new file mode 100644 index 00000000..cfd7d03c --- /dev/null +++ b/bisqapps/shared/domain/src/commonMain/kotlin/network/bisq/mobile/domain/data/persistance/PersistanceSource.kt @@ -0,0 +1,13 @@ +package network.bisq.mobile.domain.data.persistance + +import network.bisq.mobile.domain.data.model.BaseModel + +interface PersistenceSource { + suspend fun save(item: T) + suspend fun saveAll(items: List) + suspend fun get(): T? + suspend fun getAll(): List + suspend fun delete(item: T) + suspend fun deleteAll() + suspend fun clear() +} \ No newline at end of file diff --git a/bisqapps/shared/domain/src/commonMain/kotlin/network/bisq/mobile/domain/data/repository/GreetingRepository.kt b/bisqapps/shared/domain/src/commonMain/kotlin/network/bisq/mobile/domain/data/repository/GreetingRepository.kt deleted file mode 100644 index c5fe436f..00000000 --- a/bisqapps/shared/domain/src/commonMain/kotlin/network/bisq/mobile/domain/data/repository/GreetingRepository.kt +++ /dev/null @@ -1,13 +0,0 @@ -package network.bisq.mobile.domain.data.repository - -import network.bisq.mobile.domain.DefaultGreetingFactory -import network.bisq.mobile.domain.GreetingFactory - -// TODO generalize repository behaviour, what it can and can't do, what it depends on (data sources?) -// for now as a quick example just uses an in mem factory -class GreetingRepository(private val greetingFactory: GreetingFactory = DefaultGreetingFactory()) { - // Secondary constructor for Swift compatibility - constructor() : this(DefaultGreetingFactory()) - - fun getValue() = greetingFactory.createGreeting().greet() -} \ No newline at end of file diff --git a/bisqapps/shared/domain/src/commonMain/kotlin/network/bisq/mobile/domain/data/repository/Repositories.kt b/bisqapps/shared/domain/src/commonMain/kotlin/network/bisq/mobile/domain/data/repository/Repositories.kt new file mode 100644 index 00000000..fa593a7d --- /dev/null +++ b/bisqapps/shared/domain/src/commonMain/kotlin/network/bisq/mobile/domain/data/repository/Repositories.kt @@ -0,0 +1,7 @@ +package network.bisq.mobile.domain.data.repository + +import network.bisq.mobile.domain.data.model.Greeting + +// this way of definingsupports both platforms +// add your repositories here and then in your DI module call this classes for instanciation +open class GreetingRepository: SingleObjectRepository() diff --git a/bisqapps/shared/domain/src/commonMain/kotlin/network/bisq/mobile/domain/data/repository/Repository.kt b/bisqapps/shared/domain/src/commonMain/kotlin/network/bisq/mobile/domain/data/repository/Repository.kt new file mode 100644 index 00000000..015a1584 --- /dev/null +++ b/bisqapps/shared/domain/src/commonMain/kotlin/network/bisq/mobile/domain/data/repository/Repository.kt @@ -0,0 +1,43 @@ +package network.bisq.mobile.domain.data.repository + +import kotlinx.coroutines.flow.StateFlow +import network.bisq.mobile.domain.data.model.BaseModel + +/** + * Behaviour definition for a BisqApps Domain Repository + * A repository should have a in memory observable cached object and all of the CRUD operations forcing the use of coroutines + */ +interface Repository { + val data: StateFlow + + /** + * Creates the data in the repository. If data already exists, an exception is thrown. + * @throws IllegalStateException if the data is already created + */ + suspend fun create(data: @UnsafeVariance T) + + /** + * Fetches the data from the repository whatever this is + */ + suspend fun fetch(): T? + + /** + * Updates the data of this repository. If existent, it will be replaced + */ + suspend fun update(data: @UnsafeVariance T) + + /** + * Deletes the data from the respository + */ + suspend fun delete(data: @UnsafeVariance T) + + /** + * Cancel any ongoing operation. + */ + suspend fun clear() + + /** + * @return true if data is set, false otherwise + */ + fun exists() = this.data.value != null +} \ No newline at end of file diff --git a/bisqapps/shared/domain/src/commonMain/kotlin/network/bisq/mobile/domain/data/repository/SingleObjectRepository.kt b/bisqapps/shared/domain/src/commonMain/kotlin/network/bisq/mobile/domain/data/repository/SingleObjectRepository.kt new file mode 100644 index 00000000..689393e7 --- /dev/null +++ b/bisqapps/shared/domain/src/commonMain/kotlin/network/bisq/mobile/domain/data/repository/SingleObjectRepository.kt @@ -0,0 +1,64 @@ +package network.bisq.mobile.domain.data.repository + +import kotlinx.coroutines.* +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow +import network.bisq.mobile.domain.data.model.BaseModel +import network.bisq.mobile.domain.data.model.Greeting +import network.bisq.mobile.domain.data.persistance.PersistenceSource +import kotlin.jvm.JvmStatic + +/** + * Repository implementation for a single object. Allows for persistance if the persistance source if provided, otherwise is mem-only. + * + * TODO: create a map-based multi object repository when needed (might need to leverage some kind of id generation on the base model) + */ +abstract class SingleObjectRepository( + private val persistenceSource: PersistenceSource? = null +) : Repository { + + private val _data = MutableStateFlow(null) + override val data: StateFlow = _data + + private val job = Job() + private val scope = CoroutineScope(job + Dispatchers.IO) + + init { + // Load from persistence on initialization if available + persistenceSource?.let { + scope.launch { + _data.value = it.get() + } + } + } + + override suspend fun create(data: @UnsafeVariance T) { + _data.value = data + persistenceSource?.save(data) + } + + override suspend fun update(data: @UnsafeVariance T) { + _data.value = data + persistenceSource?.save(data) + } + + override suspend fun delete(data: @UnsafeVariance T) { + _data.value = null + persistenceSource?.delete(data) + } + + override suspend fun fetch(): T? { + return _data.value ?: persistenceSource?.get().also { _data.value = it } + } + + override suspend fun clear() { + try { + scope.cancel() + } catch (e: Exception) { + // TODO log error + } finally { + _data.value = null + persistenceSource?.clear() + } + } +} \ No newline at end of file diff --git a/bisqapps/shared/domain/src/commonMain/kotlin/network/bisq/mobile/domain/di/DomainModule.kt b/bisqapps/shared/domain/src/commonMain/kotlin/network/bisq/mobile/domain/di/DomainModule.kt index 036d2751..583f629d 100644 --- a/bisqapps/shared/domain/src/commonMain/kotlin/network/bisq/mobile/domain/di/DomainModule.kt +++ b/bisqapps/shared/domain/src/commonMain/kotlin/network/bisq/mobile/domain/di/DomainModule.kt @@ -1,11 +1,9 @@ package network.bisq.mobile.domain.di -import network.bisq.mobile.domain.DefaultGreetingFactory -import network.bisq.mobile.domain.GreetingFactory +import network.bisq.mobile.domain.data.model.Greeting import network.bisq.mobile.domain.data.repository.GreetingRepository import org.koin.dsl.module val domainModule = module { - single { DefaultGreetingFactory() } - single { GreetingRepository(get()) } + single> { GreetingRepository() } } diff --git a/bisqapps/shared/domain/src/commonTest/kotlin/network/bisq/mobile/domain/Test.kt b/bisqapps/shared/domain/src/commonTest/kotlin/network/bisq/mobile/domain/Test.kt index 5b7b3433..7b83f0dc 100644 --- a/bisqapps/shared/domain/src/commonTest/kotlin/network/bisq/mobile/domain/Test.kt +++ b/bisqapps/shared/domain/src/commonTest/kotlin/network/bisq/mobile/domain/Test.kt @@ -1,5 +1,6 @@ package network.bisq.mobile.domain +import network.bisq.mobile.domain.data.model.Greeting import kotlin.test.Test import kotlin.test.assertTrue diff --git a/bisqapps/shared/domain/src/iosMain/kotlin/network/bisq/mobile/domain/di/HelperDI.kt b/bisqapps/shared/domain/src/iosMain/kotlin/network/bisq/mobile/domain/di/DomainDIHelper.kt similarity index 75% rename from bisqapps/shared/domain/src/iosMain/kotlin/network/bisq/mobile/domain/di/HelperDI.kt rename to bisqapps/shared/domain/src/iosMain/kotlin/network/bisq/mobile/domain/di/DomainDIHelper.kt index e4c84790..810cbb5b 100644 --- a/bisqapps/shared/domain/src/iosMain/kotlin/network/bisq/mobile/domain/di/HelperDI.kt +++ b/bisqapps/shared/domain/src/iosMain/kotlin/network/bisq/mobile/domain/di/DomainDIHelper.kt @@ -4,6 +4,6 @@ import org.koin.core.context.startKoin fun initKoin() { startKoin { - modules(domainModule) + modules(listOf(domainModule)) } } \ No newline at end of file diff --git a/bisqapps/shared/domain/src/iosTest/kotlin/network/bisq/mobile/domain/Test.ios.kt b/bisqapps/shared/domain/src/iosTest/kotlin/network/bisq/mobile/domain/Test.ios.kt index 3224e858..14e4178b 100644 --- a/bisqapps/shared/domain/src/iosTest/kotlin/network/bisq/mobile/domain/Test.ios.kt +++ b/bisqapps/shared/domain/src/iosTest/kotlin/network/bisq/mobile/domain/Test.ios.kt @@ -1,5 +1,6 @@ package network.bisq.mobile.domain +import network.bisq.mobile.domain.data.model.Greeting import kotlin.test.Test import kotlin.test.assertTrue diff --git a/bisqapps/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/MainPresenter.kt b/bisqapps/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/MainPresenter.kt index 62c28386..b87dd2b5 100644 --- a/bisqapps/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/MainPresenter.kt +++ b/bisqapps/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/MainPresenter.kt @@ -1,27 +1,40 @@ package network.bisq.mobile.presentation import co.touchlab.kermit.Logger -import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.IO +import kotlinx.coroutines.flow.* +import kotlinx.coroutines.launch import network.bisq.mobile.android.node.BuildNodeConfig import network.bisq.mobile.client.shared.BuildConfig +import network.bisq.mobile.domain.data.model.Greeting import network.bisq.mobile.domain.data.repository.GreetingRepository import network.bisq.mobile.presentation.ui.AppPresenter /** * Main Presenter as an example of implementation for now. */ -open class MainPresenter(private val greetingRepository: GreetingRepository) : BasePresenter(), AppPresenter { +open class MainPresenter(private val greetingRepository: GreetingRepository) : BasePresenter(), AppPresenter { private val log = Logger.withTag("MainPresenter") // Observable state private val _isContentVisible = MutableStateFlow(false) override val isContentVisible: StateFlow = _isContentVisible - private val _greetingText = MutableStateFlow("Welcome!") + // The following bounds the specific field I want to grab from the model using the stateflow to automatically observe updates + private val _greetingText: StateFlow = greetingRepository.data + .map { it?.greet() ?: "" } // Transform Greeting to String, we don't want the null + .stateIn( + scope = CoroutineScope(Dispatchers.Main), // Use an appropriate CoroutineScope for lifecycle control + started = SharingStarted.Lazily, + initialValue = "Welcome!" + ) override val greetingText: StateFlow = _greetingText - fun refresh() { - _greetingText.value = greetingRepository.getValue() + init { + CoroutineScope(Dispatchers.IO).launch { + greetingRepository.create(Greeting()) + } } // Toggle action @@ -35,6 +48,5 @@ open class MainPresenter(private val greetingRepository: GreetingRepository) : B log.i { "iOS Client Version: ${BuildConfig.IOS_APP_VERSION}" } log.i { "Android Client Version: ${BuildConfig.IOS_APP_VERSION}" } log.i { "Android Node Version: ${BuildNodeConfig.APP_VERSION}" } - refresh() } } \ No newline at end of file diff --git a/bisqapps/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/di/PresentationModule.kt b/bisqapps/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/di/PresentationModule.kt index 36f2c665..cd05633d 100644 --- a/bisqapps/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/di/PresentationModule.kt +++ b/bisqapps/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/di/PresentationModule.kt @@ -1,8 +1,10 @@ package network.bisq.mobile.presentation.di import network.bisq.mobile.presentation.MainPresenter +import network.bisq.mobile.presentation.ui.AppPresenter +import org.koin.dsl.bind import org.koin.dsl.module val presentationModule = module { - single { MainPresenter(get()) } + single { MainPresenter(get()) } bind AppPresenter::class } \ No newline at end of file diff --git a/bisqapps/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/App.kt b/bisqapps/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/App.kt index 42f08ee0..40e2505f 100644 --- a/bisqapps/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/App.kt +++ b/bisqapps/shared/presentation/src/commonMain/kotlin/network/bisq/mobile/presentation/ui/App.kt @@ -16,6 +16,7 @@ import org.jetbrains.compose.ui.tooling.preview.Preview import bisqapps.shared.presentation.generated.resources.Res import bisqapps.shared.presentation.generated.resources.compose_multiplatform import kotlinx.coroutines.flow.StateFlow +import org.koin.compose.koinInject interface AppPresenter { // Observables for state @@ -31,7 +32,8 @@ interface AppPresenter { */ @Composable @Preview -fun App(presenter: AppPresenter) { +fun App() { + val presenter: AppPresenter = koinInject() MaterialTheme { // Collecting state from presenter val showContent by presenter.isContentVisible.collectAsState() diff --git a/bisqapps/shared/presentation/src/iosMain/kotlin/network/bisq/mobile/MainViewController.kt b/bisqapps/shared/presentation/src/iosMain/kotlin/network/bisq/mobile/MainViewController.kt deleted file mode 100644 index 8bacd182..00000000 --- a/bisqapps/shared/presentation/src/iosMain/kotlin/network/bisq/mobile/MainViewController.kt +++ /dev/null @@ -1,7 +0,0 @@ -package network.bisq.mobile - -import androidx.compose.ui.window.ComposeUIViewController -import network.bisq.mobile.presentation.ui.App -import network.bisq.mobile.presentation.ui.AppPresenter - -fun MainViewController(presenter: AppPresenter) = ComposeUIViewController { App(presenter) } \ No newline at end of file diff --git a/bisqapps/shared/presentation/src/iosMain/kotlin/network/bisq/mobile/presentation/MainViewController.kt b/bisqapps/shared/presentation/src/iosMain/kotlin/network/bisq/mobile/presentation/MainViewController.kt new file mode 100644 index 00000000..106967ca --- /dev/null +++ b/bisqapps/shared/presentation/src/iosMain/kotlin/network/bisq/mobile/presentation/MainViewController.kt @@ -0,0 +1,6 @@ +package network.bisq.mobile.presentation + +import androidx.compose.ui.window.ComposeUIViewController +import network.bisq.mobile.presentation.ui.App + +fun MainViewController() = ComposeUIViewController { App() } \ No newline at end of file diff --git a/bisqapps/shared/presentation/src/iosMain/kotlin/network/bisq/mobile/presentation/di/PresentationDIHelper.kt b/bisqapps/shared/presentation/src/iosMain/kotlin/network/bisq/mobile/presentation/di/PresentationDIHelper.kt new file mode 100644 index 00000000..26b1857f --- /dev/null +++ b/bisqapps/shared/presentation/src/iosMain/kotlin/network/bisq/mobile/presentation/di/PresentationDIHelper.kt @@ -0,0 +1,10 @@ +package network.bisq.mobile.presentation.di + +import network.bisq.mobile.domain.di.domainModule +import org.koin.core.context.startKoin + +fun initKoin() { + startKoin { + modules(listOf(domainModule, presentationModule)) + } +} \ No newline at end of file