From 04e99d5b3cfe6671fda8faa5f3ca69f3568e1f3b Mon Sep 17 00:00:00 2001 From: Karan Sharma <55722391+ksharma-xyz@users.noreply.github.com> Date: Wed, 13 Nov 2024 22:17:07 +1100 Subject: [PATCH 01/67] Add iOS App module --- iosApp/Configuration/Config.xcconfig | 3 + iosApp/iosApp.xcodeproj/project.pbxproj | 393 ++++++++++++++++++ .../xcshareddata/IDEWorkspaceChecks.plist | 8 + .../AccentColor.colorset/Contents.json | 11 + .../AppIcon.appiconset/Contents.json | 14 + .../AppIcon.appiconset/app-icon-1024.png | Bin 0 -> 67285 bytes iosApp/iosApp/Assets.xcassets/Contents.json | 6 + iosApp/iosApp/ContentView.swift | 18 + iosApp/iosApp/Info.plist | 50 +++ .../Preview Assets.xcassets/Contents.json | 6 + iosApp/iosApp/iOSApp.swift | 15 + 11 files changed, 524 insertions(+) create mode 100644 iosApp/Configuration/Config.xcconfig create mode 100644 iosApp/iosApp.xcodeproj/project.pbxproj create mode 100644 iosApp/iosApp.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist create mode 100644 iosApp/iosApp/Assets.xcassets/AccentColor.colorset/Contents.json create mode 100644 iosApp/iosApp/Assets.xcassets/AppIcon.appiconset/Contents.json create mode 100644 iosApp/iosApp/Assets.xcassets/AppIcon.appiconset/app-icon-1024.png create mode 100644 iosApp/iosApp/Assets.xcassets/Contents.json create mode 100644 iosApp/iosApp/ContentView.swift create mode 100644 iosApp/iosApp/Info.plist create mode 100644 iosApp/iosApp/Preview Content/Preview Assets.xcassets/Contents.json create mode 100644 iosApp/iosApp/iOSApp.swift diff --git a/iosApp/Configuration/Config.xcconfig b/iosApp/Configuration/Config.xcconfig new file mode 100644 index 00000000..c713f578 --- /dev/null +++ b/iosApp/Configuration/Config.xcconfig @@ -0,0 +1,3 @@ +TEAM_ID= +BUNDLE_ID=xyz.ksharma.krail +APP_NAME=Krail App diff --git a/iosApp/iosApp.xcodeproj/project.pbxproj b/iosApp/iosApp.xcodeproj/project.pbxproj new file mode 100644 index 00000000..19b67fb2 --- /dev/null +++ b/iosApp/iosApp.xcodeproj/project.pbxproj @@ -0,0 +1,393 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 56; + objects = { + +/* Begin PBXBuildFile section */ + 058557BB273AAA24004C7B11 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 058557BA273AAA24004C7B11 /* Assets.xcassets */; }; + 058557D9273AAEEB004C7B11 /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 058557D8273AAEEB004C7B11 /* Preview Assets.xcassets */; }; + 2152FB042600AC8F00CF470E /* iOSApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2152FB032600AC8F00CF470E /* iOSApp.swift */; }; + 7555FF83242A565900829871 /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7555FF82242A565900829871 /* ContentView.swift */; }; +/* End PBXBuildFile section */ + +/* Begin PBXFileReference section */ + 058557BA273AAA24004C7B11 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; + 058557D8273AAEEB004C7B11 /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = ""; }; + 2152FB032600AC8F00CF470E /* iOSApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = iOSApp.swift; sourceTree = ""; }; + 7555FF7B242A565900829871 /* KMP App.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "KMP App.app"; sourceTree = BUILT_PRODUCTS_DIR; }; + 7555FF82242A565900829871 /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = ""; }; + 7555FF8C242A565B00829871 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + AB3632DC29227652001CCB65 /* Config.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = Config.xcconfig; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + B92378962B6B1156000C7307 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 058557D7273AAEEB004C7B11 /* Preview Content */ = { + isa = PBXGroup; + children = ( + 058557D8273AAEEB004C7B11 /* Preview Assets.xcassets */, + ); + path = "Preview Content"; + sourceTree = ""; + }; + 42799AB246E5F90AF97AA0EF /* Frameworks */ = { + isa = PBXGroup; + children = ( + ); + name = Frameworks; + sourceTree = ""; + }; + 7555FF72242A565900829871 = { + isa = PBXGroup; + children = ( + AB1DB47929225F7C00F7AF9C /* Configuration */, + 7555FF7D242A565900829871 /* iosApp */, + 7555FF7C242A565900829871 /* Products */, + 42799AB246E5F90AF97AA0EF /* Frameworks */, + ); + sourceTree = ""; + }; + 7555FF7C242A565900829871 /* Products */ = { + isa = PBXGroup; + children = ( + 7555FF7B242A565900829871 /* KMP App.app */, + ); + name = Products; + sourceTree = ""; + }; + 7555FF7D242A565900829871 /* iosApp */ = { + isa = PBXGroup; + children = ( + 058557BA273AAA24004C7B11 /* Assets.xcassets */, + 7555FF82242A565900829871 /* ContentView.swift */, + 7555FF8C242A565B00829871 /* Info.plist */, + 2152FB032600AC8F00CF470E /* iOSApp.swift */, + 058557D7273AAEEB004C7B11 /* Preview Content */, + ); + path = iosApp; + sourceTree = ""; + }; + AB1DB47929225F7C00F7AF9C /* Configuration */ = { + isa = PBXGroup; + children = ( + AB3632DC29227652001CCB65 /* Config.xcconfig */, + ); + path = Configuration; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 7555FF7A242A565900829871 /* iosApp */ = { + isa = PBXNativeTarget; + buildConfigurationList = 7555FFA5242A565B00829871 /* Build configuration list for PBXNativeTarget "iosApp" */; + buildPhases = ( + F36B1CEB2AD83DDC00CB74D5 /* Compile Kotlin Framework */, + 7555FF77242A565900829871 /* Sources */, + B92378962B6B1156000C7307 /* Frameworks */, + 7555FF79242A565900829871 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = iosApp; + packageProductDependencies = ( + ); + productName = iosApp; + productReference = 7555FF7B242A565900829871 /* KMP App.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 7555FF73242A565900829871 /* Project object */ = { + isa = PBXProject; + attributes = { + BuildIndependentTargetsInParallel = YES; + LastSwiftUpdateCheck = 1130; + LastUpgradeCheck = 1540; + ORGANIZATIONNAME = orgName; + TargetAttributes = { + 7555FF7A242A565900829871 = { + CreatedOnToolsVersion = 11.3.1; + }; + }; + }; + buildConfigurationList = 7555FF76242A565900829871 /* Build configuration list for PBXProject "iosApp" */; + compatibilityVersion = "Xcode 14.0"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 7555FF72242A565900829871; + packageReferences = ( + ); + productRefGroup = 7555FF7C242A565900829871 /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 7555FF7A242A565900829871 /* iosApp */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 7555FF79242A565900829871 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 058557D9273AAEEB004C7B11 /* Preview Assets.xcassets in Resources */, + 058557BB273AAA24004C7B11 /* Assets.xcassets in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXShellScriptBuildPhase section */ + F36B1CEB2AD83DDC00CB74D5 /* Compile Kotlin Framework */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + ); + name = "Compile Kotlin Framework"; + outputFileListPaths = ( + ); + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "if [ \"YES\" = \"$OVERRIDE_KOTLIN_BUILD_IDE_SUPPORTED\" ]; then\n echo \"Skipping Gradle build task invocation due to OVERRIDE_KOTLIN_BUILD_IDE_SUPPORTED environment variable set to \\\"YES\\\"\"\n exit 0\nfi\ncd \"$SRCROOT/..\"\n./gradlew :composeApp:embedAndSignAppleFrameworkForXcode\n"; + }; +/* End PBXShellScriptBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 7555FF77242A565900829871 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 2152FB042600AC8F00CF470E /* iOSApp.swift in Sources */, + 7555FF83242A565900829871 /* ContentView.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin XCBuildConfiguration section */ + 7555FFA3242A565B00829871 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = AB3632DC29227652001CCB65 /* Config.xcconfig */; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 15.3; + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + }; + name = Debug; + }; + 7555FFA4242A565B00829871 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = AB3632DC29227652001CCB65 /* Config.xcconfig */; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 15.3; + MTL_ENABLE_DEBUG_INFO = NO; + MTL_FAST_MATH = YES; + SDKROOT = iphoneos; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; + 7555FFA6242A565B00829871 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CODE_SIGN_IDENTITY = "Apple Development"; + CODE_SIGN_STYLE = Automatic; + DEVELOPMENT_ASSET_PATHS = "\"iosApp/Preview Content\""; + DEVELOPMENT_TEAM = "${TEAM_ID}"; + ENABLE_PREVIEWS = YES; + FRAMEWORK_SEARCH_PATHS = ( + "$(inherited)", + "$(SRCROOT)/../shared/build/xcode-frameworks/$(CONFIGURATION)/$(SDK_NAME)\n$(SRCROOT)/../composeApp/build/xcode-frameworks/$(CONFIGURATION)/$(SDK_NAME)", + ); + INFOPLIST_FILE = iosApp/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 15.3; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = "${BUNDLE_ID}${TEAM_ID}"; + PRODUCT_NAME = "${APP_NAME}"; + PROVISIONING_PROFILE_SPECIFIER = ""; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + 7555FFA7242A565B00829871 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CODE_SIGN_IDENTITY = "Apple Development"; + CODE_SIGN_STYLE = Automatic; + DEVELOPMENT_ASSET_PATHS = "\"iosApp/Preview Content\""; + DEVELOPMENT_TEAM = "${TEAM_ID}"; + ENABLE_PREVIEWS = YES; + FRAMEWORK_SEARCH_PATHS = ( + "$(inherited)", + "$(SRCROOT)/../shared/build/xcode-frameworks/$(CONFIGURATION)/$(SDK_NAME)\n$(SRCROOT)/../composeApp/build/xcode-frameworks/$(CONFIGURATION)/$(SDK_NAME)", + ); + INFOPLIST_FILE = iosApp/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 15.3; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = "${BUNDLE_ID}${TEAM_ID}"; + PRODUCT_NAME = "${APP_NAME}"; + PROVISIONING_PROFILE_SPECIFIER = ""; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 7555FF76242A565900829871 /* Build configuration list for PBXProject "iosApp" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 7555FFA3242A565B00829871 /* Debug */, + 7555FFA4242A565B00829871 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 7555FFA5242A565B00829871 /* Build configuration list for PBXNativeTarget "iosApp" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 7555FFA6242A565B00829871 /* Debug */, + 7555FFA7242A565B00829871 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 7555FF73242A565900829871 /* Project object */; +} diff --git a/iosApp/iosApp.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/iosApp/iosApp.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 00000000..18d98100 --- /dev/null +++ b/iosApp/iosApp.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/iosApp/iosApp/Assets.xcassets/AccentColor.colorset/Contents.json b/iosApp/iosApp/Assets.xcassets/AccentColor.colorset/Contents.json new file mode 100644 index 00000000..ee7e3ca0 --- /dev/null +++ b/iosApp/iosApp/Assets.xcassets/AccentColor.colorset/Contents.json @@ -0,0 +1,11 @@ +{ + "colors" : [ + { + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} \ No newline at end of file diff --git a/iosApp/iosApp/Assets.xcassets/AppIcon.appiconset/Contents.json b/iosApp/iosApp/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 00000000..8edf56e7 --- /dev/null +++ b/iosApp/iosApp/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,14 @@ +{ + "images" : [ + { + "filename" : "app-icon-1024.png", + "idiom" : "universal", + "platform" : "ios", + "size" : "1024x1024" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/iosApp/iosApp/Assets.xcassets/AppIcon.appiconset/app-icon-1024.png b/iosApp/iosApp/Assets.xcassets/AppIcon.appiconset/app-icon-1024.png new file mode 100644 index 0000000000000000000000000000000000000000..53fc536fb9ac5c1dbb27c7e1da13db3760070a11 GIT binary patch literal 67285 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 BXNCX( literal 0 HcmV?d00001 diff --git a/iosApp/iosApp/Assets.xcassets/Contents.json b/iosApp/iosApp/Assets.xcassets/Contents.json new file mode 100644 index 00000000..4aa7c535 --- /dev/null +++ b/iosApp/iosApp/Assets.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} \ No newline at end of file diff --git a/iosApp/iosApp/ContentView.swift b/iosApp/iosApp/ContentView.swift new file mode 100644 index 00000000..9c87bd2e --- /dev/null +++ b/iosApp/iosApp/ContentView.swift @@ -0,0 +1,18 @@ +import UIKit +import SwiftUI +import ComposeApp + +struct ComposeView: UIViewControllerRepresentable { + func makeUIViewController(context: Context) -> UIViewController { + MainViewControllerKt.MainViewController() + } + + func updateUIViewController(_ uiViewController: UIViewController, context: Context) {} +} + +struct ContentView: View { + var body: some View { + ComposeView() + .ignoresSafeArea() + } +} diff --git a/iosApp/iosApp/Info.plist b/iosApp/iosApp/Info.plist new file mode 100644 index 00000000..412e3781 --- /dev/null +++ b/iosApp/iosApp/Info.plist @@ -0,0 +1,50 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + $(PRODUCT_BUNDLE_PACKAGE_TYPE) + CFBundleShortVersionString + 1.0 + CFBundleVersion + 1 + LSRequiresIPhoneOS + + CADisableMinimumFrameDurationOnPhone + + UIApplicationSceneManifest + + UIApplicationSupportsMultipleScenes + + + UILaunchScreen + + UIRequiredDeviceCapabilities + + armv7 + + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UISupportedInterfaceOrientations~ipad + + UIInterfaceOrientationPortrait + UIInterfaceOrientationPortraitUpsideDown + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + + diff --git a/iosApp/iosApp/Preview Content/Preview Assets.xcassets/Contents.json b/iosApp/iosApp/Preview Content/Preview Assets.xcassets/Contents.json new file mode 100644 index 00000000..4aa7c535 --- /dev/null +++ b/iosApp/iosApp/Preview Content/Preview Assets.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} \ No newline at end of file diff --git a/iosApp/iosApp/iOSApp.swift b/iosApp/iosApp/iOSApp.swift new file mode 100644 index 00000000..e466410c --- /dev/null +++ b/iosApp/iosApp/iOSApp.swift @@ -0,0 +1,15 @@ +import SwiftUI +import ComposeApp + +@main +struct iOSApp: App { + init() { + KoinKt.doInitKoin() + } + + var body: some Scene { + WindowGroup { + ContentView() + } + } +} From 19ce3e4a53db567742d8b6090b2434f9564ac71e Mon Sep 17 00:00:00 2001 From: Karan Sharma <55722391+ksharma-xyz@users.noreply.github.com> Date: Wed, 13 Nov 2024 22:27:14 +1100 Subject: [PATCH 02/67] Add ktor and compose multiplatform lib --- gradle/libs.versions.toml | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index e5280aae..10396710 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -25,6 +25,8 @@ retrofit = "2.11.0" retrofit2KotlinxSerializationConverter = "1.0.0" timber = "5.0.1" wire = "5.1.0" +compose-multiplatform = "1.7.0" +ktor = "3.0.0" #SDK minSdk = "26" @@ -67,6 +69,12 @@ okhttp-logging-interceptor = { module = "com.squareup.okhttp3:logging-intercepto okhttp = { module = "com.squareup.okhttp3:okhttp" } okhttp-bom = { module = "com.squareup.okhttp3:okhttp-bom", version.ref = "okhttpBom" } retrofit = { module = "com.squareup.retrofit2:retrofit", version.ref = "retrofit" } +ktor-client-content-negotiation = { module = "io.ktor:ktor-client-content-negotiation", version.ref = "ktor" } +ktor-client-core = { module = "io.ktor:ktor-client-core", version.ref = "ktor" } +ktor-client-darwin = { module = "io.ktor:ktor-client-darwin", version.ref = "ktor" } +ktor-client-okhttp = { module = "io.ktor:ktor-client-okhttp", version.ref = "ktor" } +ktor-serialization-kotlinx-json = { module = "io.ktor:ktor-serialization-kotlinx-json", version.ref = "ktor" } + #Test test-composeUiTestManifest = { group = "androidx.compose.ui", name = "ui-test-manifest" } @@ -109,3 +117,5 @@ krail-android-hilt = { id = "krail.android.hilt", version = "unspecified" } krail-android-library = { id = "krail.android.library", version = "unspecified" } krail-android-library-compose = { id = "krail.android.library.compose", version = "unspecified" } krail-jvm-library = { id = "krail.jvm.library", version = "unspecified" } +composeMultiplatform = { id = "org.jetbrains.compose", version.ref = "compose-multiplatform" } +kotlinMultiplatform = { id = "org.jetbrains.kotlin.multiplatform", version.ref = "kotlin" } From 7fe459c9a5512854092f88222e756d1c4ef7a53c Mon Sep 17 00:00:00 2001 From: Karan Sharma <55722391+ksharma-xyz@users.noreply.github.com> Date: Wed, 13 Nov 2024 22:43:23 +1100 Subject: [PATCH 03/67] Move build logic to gradle dir --- app/build.gradle.kts | 1 - build-logic/settings.gradle.kts | 14 ---- build.gradle.kts | 78 +------------------ .../build-logic}/convention/build.gradle.kts | 0 .../krail/AndroidHiltConventionPlugin.kt | 2 + .../AndroidLibraryComposeConventionPlugin.kt | 2 + .../krail/AndroidLibraryConventionPlugin.kt | 2 + .../krail/ApplicationConventionPlugin.kt | 2 + .../krail/JvmLibraryConventionPlugin.kt | 2 + gradle/build-logic/settings.gradle.kts | 21 +++++ gradle/libs.versions.toml | 1 - settings.gradle.kts | 22 ++++-- 12 files changed, 49 insertions(+), 98 deletions(-) delete mode 100644 build-logic/settings.gradle.kts rename {build-logic => gradle/build-logic}/convention/build.gradle.kts (100%) rename {build-logic => gradle/build-logic}/convention/src/main/kotlin/xyz/ksharma/krail/AndroidHiltConventionPlugin.kt (96%) rename {build-logic => gradle/build-logic}/convention/src/main/kotlin/xyz/ksharma/krail/AndroidLibraryComposeConventionPlugin.kt (98%) rename {build-logic => gradle/build-logic}/convention/src/main/kotlin/xyz/ksharma/krail/AndroidLibraryConventionPlugin.kt (98%) rename {build-logic => gradle/build-logic}/convention/src/main/kotlin/xyz/ksharma/krail/ApplicationConventionPlugin.kt (99%) rename {build-logic => gradle/build-logic}/convention/src/main/kotlin/xyz/ksharma/krail/JvmLibraryConventionPlugin.kt (98%) create mode 100644 gradle/build-logic/settings.gradle.kts diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 9c81af2b..df4e1b63 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -79,5 +79,4 @@ dependencies { // Test androidTestImplementation(libs.test.androidxTestExtJunit) testImplementation(libs.test.composeUiTestJunit4) - testImplementation(libs.test.paparazzi) } diff --git a/build-logic/settings.gradle.kts b/build-logic/settings.gradle.kts deleted file mode 100644 index 62257853..00000000 --- a/build-logic/settings.gradle.kts +++ /dev/null @@ -1,14 +0,0 @@ -dependencyResolutionManagement { - repositories { - google() - mavenCentral() - } - versionCatalogs { - create("libs") { - from(files("../gradle/libs.versions.toml")) - } - } -} - -rootProject.name = "build-logic" -include(":convention") \ No newline at end of file diff --git a/build.gradle.kts b/build.gradle.kts index 2b049a2e..98f1293b 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -13,80 +13,6 @@ plugins { alias(libs.plugins.google.services) apply false alias(libs.plugins.firebase.crashlyticsPlugin) apply false alias(libs.plugins.firebase.performancePlugin) apply false -} - -subprojects { - plugins.withId("app.cash.paparazzi") { - // Defer until afterEvaluate so that testImplementation is created by Android plugin. - afterEvaluate { - dependencies.constraints { - add("testImplementation", "com.google.guava:guava") { - attributes { - attribute( - TargetJvmEnvironment.TARGET_JVM_ENVIRONMENT_ATTRIBUTE, - objects.named( - TargetJvmEnvironment::class.java, - TargetJvmEnvironment.STANDARD_JVM - ) - ) - } - because( - "LayoutLib and sdk-common depend on Guava's -jre published variant." + - "See https://github.com/cashapp/paparazzi/issues/906." - ) - } - } - } - } -} -true // Needed to make the Suppress annotation work for the plugins block - -val excludeFilesDetekt = listOf( - "**/.gradle/**", - "**/.idea/**", - "**/build/**", - ".github/**", - "gradle/**", -) - -tasks { - withType { - config.setFrom(files("$rootDir/config/detekt/detekt.yaml")) - baseline.set(file("$rootDir/config/detekt/detekt-baseline.xml")) - - autoCorrect = true - buildUponDefaultConfig = true - parallel = true - debug = true - - source = files(subprojects.flatMap { subproject -> - listOf( - File(subproject.projectDir, "/src/main/kotlin"), - File(subproject.projectDir, "/src/main/java"), - File(subproject.projectDir, "/src/debug/kotlin"), - File(subproject.projectDir, "/src/debug/java"), - ) - }).asFileTree - - excludes.addAll(excludeFilesDetekt) - - reports { - html.required = true - html.outputLocation.set(file("build/reports/detekt.html")) - txt.required = true - md.required = true - xml.required = false - sarif.required = false - } - } - project.detekt { - basePath = rootProject.projectDir.absolutePath - toolVersion = libs.versions.detekt.get() - autoCorrect = true - } -} - -dependencies { - detektPlugins(libs.detektFormat) - detektPlugins(libs.detektCompose) + alias(libs.plugins.composeMultiplatform) apply false + alias(libs.plugins.kotlinMultiplatform) apply false } diff --git a/build-logic/convention/build.gradle.kts b/gradle/build-logic/convention/build.gradle.kts similarity index 100% rename from build-logic/convention/build.gradle.kts rename to gradle/build-logic/convention/build.gradle.kts diff --git a/build-logic/convention/src/main/kotlin/xyz/ksharma/krail/AndroidHiltConventionPlugin.kt b/gradle/build-logic/convention/src/main/kotlin/xyz/ksharma/krail/AndroidHiltConventionPlugin.kt similarity index 96% rename from build-logic/convention/src/main/kotlin/xyz/ksharma/krail/AndroidHiltConventionPlugin.kt rename to gradle/build-logic/convention/src/main/kotlin/xyz/ksharma/krail/AndroidHiltConventionPlugin.kt index 460f4982..a3be7820 100644 --- a/build-logic/convention/src/main/kotlin/xyz/ksharma/krail/AndroidHiltConventionPlugin.kt +++ b/gradle/build-logic/convention/src/main/kotlin/xyz/ksharma/krail/AndroidHiltConventionPlugin.kt @@ -1,3 +1,5 @@ +package xyz.ksharma.krail + import org.gradle.api.Plugin import org.gradle.api.Project import org.gradle.api.artifacts.VersionCatalogsExtension diff --git a/build-logic/convention/src/main/kotlin/xyz/ksharma/krail/AndroidLibraryComposeConventionPlugin.kt b/gradle/build-logic/convention/src/main/kotlin/xyz/ksharma/krail/AndroidLibraryComposeConventionPlugin.kt similarity index 98% rename from build-logic/convention/src/main/kotlin/xyz/ksharma/krail/AndroidLibraryComposeConventionPlugin.kt rename to gradle/build-logic/convention/src/main/kotlin/xyz/ksharma/krail/AndroidLibraryComposeConventionPlugin.kt index 38fecea1..f89d65e3 100644 --- a/build-logic/convention/src/main/kotlin/xyz/ksharma/krail/AndroidLibraryComposeConventionPlugin.kt +++ b/gradle/build-logic/convention/src/main/kotlin/xyz/ksharma/krail/AndroidLibraryComposeConventionPlugin.kt @@ -1,3 +1,5 @@ +package xyz.ksharma.krail + import com.android.build.gradle.LibraryExtension import org.gradle.api.JavaVersion import org.gradle.api.Plugin diff --git a/build-logic/convention/src/main/kotlin/xyz/ksharma/krail/AndroidLibraryConventionPlugin.kt b/gradle/build-logic/convention/src/main/kotlin/xyz/ksharma/krail/AndroidLibraryConventionPlugin.kt similarity index 98% rename from build-logic/convention/src/main/kotlin/xyz/ksharma/krail/AndroidLibraryConventionPlugin.kt rename to gradle/build-logic/convention/src/main/kotlin/xyz/ksharma/krail/AndroidLibraryConventionPlugin.kt index 61072bb4..c8c898d0 100644 --- a/build-logic/convention/src/main/kotlin/xyz/ksharma/krail/AndroidLibraryConventionPlugin.kt +++ b/gradle/build-logic/convention/src/main/kotlin/xyz/ksharma/krail/AndroidLibraryConventionPlugin.kt @@ -1,3 +1,5 @@ +package xyz.ksharma.krail + import com.android.build.gradle.LibraryExtension import org.gradle.api.JavaVersion import org.gradle.api.Plugin diff --git a/build-logic/convention/src/main/kotlin/xyz/ksharma/krail/ApplicationConventionPlugin.kt b/gradle/build-logic/convention/src/main/kotlin/xyz/ksharma/krail/ApplicationConventionPlugin.kt similarity index 99% rename from build-logic/convention/src/main/kotlin/xyz/ksharma/krail/ApplicationConventionPlugin.kt rename to gradle/build-logic/convention/src/main/kotlin/xyz/ksharma/krail/ApplicationConventionPlugin.kt index 91c488af..e79126c0 100644 --- a/build-logic/convention/src/main/kotlin/xyz/ksharma/krail/ApplicationConventionPlugin.kt +++ b/gradle/build-logic/convention/src/main/kotlin/xyz/ksharma/krail/ApplicationConventionPlugin.kt @@ -1,3 +1,5 @@ +package xyz.ksharma.krail + import com.android.build.api.dsl.ApplicationExtension import org.gradle.api.JavaVersion import org.gradle.api.Plugin diff --git a/build-logic/convention/src/main/kotlin/xyz/ksharma/krail/JvmLibraryConventionPlugin.kt b/gradle/build-logic/convention/src/main/kotlin/xyz/ksharma/krail/JvmLibraryConventionPlugin.kt similarity index 98% rename from build-logic/convention/src/main/kotlin/xyz/ksharma/krail/JvmLibraryConventionPlugin.kt rename to gradle/build-logic/convention/src/main/kotlin/xyz/ksharma/krail/JvmLibraryConventionPlugin.kt index 863735c9..1f135eae 100644 --- a/build-logic/convention/src/main/kotlin/xyz/ksharma/krail/JvmLibraryConventionPlugin.kt +++ b/gradle/build-logic/convention/src/main/kotlin/xyz/ksharma/krail/JvmLibraryConventionPlugin.kt @@ -1,3 +1,5 @@ +package xyz.ksharma.krail + import org.gradle.api.JavaVersion import org.gradle.api.Plugin import org.gradle.api.Project diff --git a/gradle/build-logic/settings.gradle.kts b/gradle/build-logic/settings.gradle.kts new file mode 100644 index 00000000..c9fe22f2 --- /dev/null +++ b/gradle/build-logic/settings.gradle.kts @@ -0,0 +1,21 @@ +dependencyResolutionManagement { + repositories { + google { + content { + includeGroupByRegex(".*google.*") + includeGroupByRegex(".*android.*") + } + } + mavenCentral() + gradlePluginPortal() + } + + versionCatalogs { + create("libs") { + from(files("../libs.versions.toml")) + } + } +} + +rootProject.name = "build-logic" +include(":convention") diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 10396710..171c3729 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -87,7 +87,6 @@ test-turbine = { group = "app.cash.turbine", name = "turbine", version = "1.2.0" test-mockitoKotlin = { group = "org.mockito.kotlin", name = "mockito-kotlin", version = "5.4.0" } test-kotlinxCoroutineTest = { group = "org.jetbrains.kotlinx", name = "kotlinx-coroutines-test", version = "1.9.0" } test-androidxCoreKtx = { group = "androidx.test", name = "core-ktx", version.ref = "androidx-test" } -test-paparazzi = { group = "app.cash.paparazzi", name = "paparazzi-gradle-plugin", version.ref = "paparazzi" } #BuildLogic kotlin-gradlePlugin = { group = "org.jetbrains.kotlin", name = "kotlin-gradle-plugin", version.ref = "kotlin" } diff --git a/settings.gradle.kts b/settings.gradle.kts index eb44a49d..e0d4a213 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -1,24 +1,34 @@ enableFeaturePreview("TYPESAFE_PROJECT_ACCESSORS") pluginManagement { - - includeBuild("build-logic") - repositories { - google() + google { + mavenContent { + includeGroupAndSubgroups("androidx") + includeGroupAndSubgroups("com.android") + includeGroupAndSubgroups("com.google") + } + } mavenCentral() gradlePluginPortal() } } + dependencyResolutionManagement { - repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS) repositories { - google() + google { + mavenContent { + includeGroupAndSubgroups("androidx") + includeGroupAndSubgroups("com.android") + includeGroupAndSubgroups("com.google") + } + } mavenCentral() } } rootProject.name = "Krail" +include(":composeApp") include(":app") include(":core:coroutines-ext") include(":core:date-time") From 10aa3333e616bb004b623b231d01502e3dde2028 Mon Sep 17 00:00:00 2001 From: Karan Sharma <55722391+ksharma-xyz@users.noreply.github.com> Date: Thu, 14 Nov 2024 20:47:21 +1100 Subject: [PATCH 04/67] Remove detekt, firebase --- .../workflows/firebase-app-distribution.yml | 54 ------------------- app/build.gradle.kts | 9 ---- build.gradle.kts | 4 -- .../build-logic/convention/build.gradle.kts | 2 + .../krail/ApplicationConventionPlugin.kt | 2 - gradle/libs.versions.toml | 20 +------ 6 files changed, 4 insertions(+), 87 deletions(-) delete mode 100644 .github/workflows/firebase-app-distribution.yml diff --git a/.github/workflows/firebase-app-distribution.yml b/.github/workflows/firebase-app-distribution.yml deleted file mode 100644 index 9ffcf9af..00000000 --- a/.github/workflows/firebase-app-distribution.yml +++ /dev/null @@ -1,54 +0,0 @@ -name: Firebase App Distribution - -on: - pull_request: - branches: - - debug-alpha - -jobs: - distribute: - runs-on: ubuntu-latest - - steps: - - uses: actions/checkout@v4 - name: Setup environment variables - env: - NSW_TRANSPORT_API_KEY: ${{ secrets.NSW_TRANSPORT_API_KEY }} - run: | - echo "::set-env name=NSW_TRANSPORT_API_KEY::${{ secrets.NSW_TRANSPORT_API_KEY }}" - - - name: set up JDK - uses: actions/setup-java@v4 - with: - distribution: zulu - java-version: 21 - - - name: Cache Gradle and wrapper - uses: actions/cache@v4 - with: - path: | - ~/.gradle/caches - ~/.gradle/wrapper - key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*') }} - restore-keys: | - ${{ runner.os }}-gradle- - - - name: Firebase (Debug) - Google Services.json file - env: - DATA: ${{ secrets.FIREBASE_GOOGLE_SERVICES_JSON_DEBUG }} - run: echo $DATA | base64 -di > app/src/debug/google-services.json - - - name: Make Gradle executable - run: chmod +x ./gradlew - - - name: Build APK - run: ./gradlew assembleDebug --no-daemon - - - name: Upload to Firebase App Distribution - env: - FIREBASE_TOKEN: ${{ secrets.FIREBASE_SERVICE_ACCOUNT_KEY }} - run: | - firebase appdistribution:distribute app/build/outputs/apk/debug/app-debug.apk \ - --app ${{ secrets.FIREBASE_APP_ID_DEBUG }} \ - --groups "Friends" \ - --release-notes "Automated release" diff --git a/app/build.gradle.kts b/app/build.gradle.kts index df4e1b63..b82c7c30 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -38,9 +38,6 @@ android { debugSymbolLevel = "FULL" } proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro") - firebaseCrashlytics { - nativeSymbolUploadEnabled = true - } } } @@ -70,12 +67,6 @@ dependencies { implementation(libs.timber) implementation(libs.hilt.navigation.compose) - // Firebase - implementation(platform(libs.firebase.bom)) - implementation(libs.firebase.analytics) - implementation(libs.firebase.crashlytics) - implementation(libs.firebase.perf) - // Test androidTestImplementation(libs.test.androidxTestExtJunit) testImplementation(libs.test.composeUiTestJunit4) diff --git a/build.gradle.kts b/build.gradle.kts index 98f1293b..b6aa3f9e 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,4 +1,3 @@ -import io.gitlab.arturbosch.detekt.Detekt // Top-level build file where you can add configuration options common to all sub-projects/modules. @Suppress("DSL_SCOPE_VIOLATION") // TODO: Remove once KTIJ-19369 is fixed @@ -8,11 +7,8 @@ plugins { alias(libs.plugins.hilt) apply false alias(libs.plugins.ksp) apply false alias(libs.plugins.kotlin.serialization) apply false - alias(libs.plugins.detekt) alias(libs.plugins.compose.compiler) apply false alias(libs.plugins.google.services) apply false - alias(libs.plugins.firebase.crashlyticsPlugin) apply false - alias(libs.plugins.firebase.performancePlugin) apply false alias(libs.plugins.composeMultiplatform) apply false alias(libs.plugins.kotlinMultiplatform) apply false } diff --git a/gradle/build-logic/convention/build.gradle.kts b/gradle/build-logic/convention/build.gradle.kts index 0b55affa..a1ef50c4 100644 --- a/gradle/build-logic/convention/build.gradle.kts +++ b/gradle/build-logic/convention/build.gradle.kts @@ -23,6 +23,8 @@ tasks.withType().configureEach { dependencies { compileOnly(libs.kotlin.gradlePlugin) compileOnly(libs.android.gradlePlugin) + compileOnly(libs.compose.gradlePlugin) + compileOnly(libs.composeCompiler.gradlePlugin) } gradlePlugin { diff --git a/gradle/build-logic/convention/src/main/kotlin/xyz/ksharma/krail/ApplicationConventionPlugin.kt b/gradle/build-logic/convention/src/main/kotlin/xyz/ksharma/krail/ApplicationConventionPlugin.kt index e79126c0..0189dcef 100644 --- a/gradle/build-logic/convention/src/main/kotlin/xyz/ksharma/krail/ApplicationConventionPlugin.kt +++ b/gradle/build-logic/convention/src/main/kotlin/xyz/ksharma/krail/ApplicationConventionPlugin.kt @@ -29,8 +29,6 @@ class ApplicationConventionPlugin : Plugin { apply("org.jetbrains.kotlin.android") apply("org.jetbrains.kotlin.plugin.compose") apply("com.google.gms.google-services") - apply("com.google.firebase.crashlytics") - apply("com.google.firebase.firebase-perf") } extensions.configure { compileSdk = compileSdkVersion diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 171c3729..689d386e 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -10,9 +10,6 @@ android-lifecycle = "2.8.7" activity-compose = "1.9.3" compose-bom = "2024.10.01" compose-navigation = "2.8.3" -detekt = "1.23.7" -detektCompose = "0.4.17" -firebaseBom = "33.5.1" hilt = "2.52" hiltNavigationCompose = "1.2.0" kotlinxCollectionsImmutable = "0.3.8" @@ -24,7 +21,6 @@ paparazzi = "1.3.5" retrofit = "2.11.0" retrofit2KotlinxSerializationConverter = "1.0.0" timber = "5.0.1" -wire = "5.1.0" compose-multiplatform = "1.7.0" ktor = "3.0.0" @@ -36,7 +32,6 @@ targetSdk = "35" [libraries] core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "core-ktx" } activity-compose = { group = "androidx.activity", name = "activity-compose", version.ref = "activity-compose" } -firebase-perf = { module = "com.google.firebase:firebase-perf" } kotlinx-collections-immutable = { module = "org.jetbrains.kotlinx:kotlinx-collections-immutable", version.ref = "kotlinxCollectionsImmutable" } kotlinx-datetime = { module = "org.jetbrains.kotlinx:kotlinx-datetime", version.ref = "kotlinxDatetime" } lifecycle-runtime-ktx = { group = "androidx.lifecycle", name = "lifecycle-runtime-ktx", version.ref = "android-lifecycle" } @@ -53,11 +48,6 @@ compose-ui-tooling-preview = { group = "androidx.compose.ui", name = "ui-tooling compose-navigation = { group = "androidx.navigation", name = "navigation-compose", version.ref = "compose-navigation" } compose-material3 = { group = "androidx.compose.material3", name = "material3", version = "1.3.1" } -#Firebase -firebase-bom = { group = "com.google.firebase", name = "firebase-bom", version.ref = "firebaseBom" } -firebase-analytics = { module = "com.google.firebase:firebase-analytics" } -firebase-crashlytics = { module = "com.google.firebase:firebase-crashlytics" } - #Hilt hilt-android = { group = "com.google.dagger", name = "hilt-android", version.ref = "hilt" } hilt-compiler = { group = "com.google.dagger", name = "hilt-compiler", version.ref = "hilt" } @@ -75,7 +65,6 @@ ktor-client-darwin = { module = "io.ktor:ktor-client-darwin", version.ref = "kto ktor-client-okhttp = { module = "io.ktor:ktor-client-okhttp", version.ref = "ktor" } ktor-serialization-kotlinx-json = { module = "io.ktor:ktor-serialization-kotlinx-json", version.ref = "ktor" } - #Test test-composeUiTestManifest = { group = "androidx.compose.ui", name = "ui-test-manifest" } test-composeUiTestJunit4 = { group = "androidx.compose.ui", name = "ui-test-junit4" } @@ -92,9 +81,8 @@ test-androidxCoreKtx = { group = "androidx.test", name = "core-ktx", version.ref kotlin-gradlePlugin = { group = "org.jetbrains.kotlin", name = "kotlin-gradle-plugin", version.ref = "kotlin" } android-gradlePlugin = { group = "com.android.tools.build", name = "gradle", version.ref = "agp" } -#CodeStyle -detektFormat = { group = "io.gitlab.arturbosch.detekt", name = "detekt-formatting", version.ref = "detekt" } -detektCompose = { group = "io.nlopez.compose.rules", name = "detekt", version.ref = "detektCompose" } +#CodeStyle Use spotless plugin for multiplatform support + [plugins] androidApplication = { id = "com.android.application", version.ref = "agp" } @@ -103,12 +91,8 @@ hilt = { id = "com.google.dagger.hilt.android", version.ref = "hilt" } kotlin-serialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlin" } cash-paparazzi = { id = "app.cash.paparazzi", version.ref = "paparazzi" } ksp = { id = "com.google.devtools.ksp", version.ref = "ksp" } -detekt = { id = "io.gitlab.arturbosch.detekt", version.ref = "detekt" } compose-compiler = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" } -wire = { id = "com.squareup.wire", version.ref = "wire" } google-services = { id = "com.google.gms.google-services", version = "4.4.2" } -firebase-crashlyticsPlugin = { id = "com.google.firebase.crashlytics", version = "3.0.2" } -firebase-performancePlugin = { id = "com.google.firebase.firebase-perf", version = "1.4.2" } #Convention Plugins krail-android-application = { id = "krail.android.application", version = "unspecified" } From 66ea60aeb8ddb04f265b146f317b379e368e4063 Mon Sep 17 00:00:00 2001 From: Karan Sharma <55722391+ksharma-xyz@users.noreply.github.com> Date: Thu, 14 Nov 2024 21:13:54 +1100 Subject: [PATCH 05/67] Update settings.gradle.kts --- gradle/build-logic/settings.gradle.kts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/gradle/build-logic/settings.gradle.kts b/gradle/build-logic/settings.gradle.kts index c9fe22f2..5f522649 100644 --- a/gradle/build-logic/settings.gradle.kts +++ b/gradle/build-logic/settings.gradle.kts @@ -2,8 +2,9 @@ dependencyResolutionManagement { repositories { google { content { - includeGroupByRegex(".*google.*") - includeGroupByRegex(".*android.*") + includeGroupAndSubgroups("androidx") + includeGroupAndSubgroups("com.android") + includeGroupAndSubgroups("com.google") } } mavenCentral() From ad2618432186fd7ca5573f56415ca314805399fc Mon Sep 17 00:00:00 2001 From: Karan Sharma <55722391+ksharma-xyz@users.noreply.github.com> Date: Thu, 14 Nov 2024 22:07:49 +1100 Subject: [PATCH 06/67] Add androidMain --- app/src/.DS_Store | Bin 6148 -> 0 bytes app/src/debug/.gitkeep | 2 - .../res/drawable/ic_launcher_foreground.xml | 9 --- .../xyz/ksharma/krail/KrailApplication.kt | 16 ----- .../res/drawable/ic_launcher_background.xml | 11 ---- .../java/xyz/ksharma/start/ExampleUnitTest.kt | 17 ----- composeApp/build.gradle.kts | 62 ++++++++++++++++++ .../src/androidMain}/AndroidManifest.xml | 0 .../kotlin/xyz/ksharma/krail/KrailApp.kt | 0 .../xyz/ksharma/krail/KrailApplication.kt | 10 +++ .../kotlin/xyz/ksharma/krail/MainActivity.kt | 2 - .../ksharma/krail/navigation/KrailNavHost.kt | 0 .../xyz/ksharma/krail/splash/SplashScreen.kt | 0 .../ksharma/krail/splash/SplashViewModel.kt | 2 - .../src/androidMain}/proguard-rules.pro | 0 .../res/color/gradient_background.xml | 0 .../res/drawable/ic_launcher_background.xml | 0 .../res/drawable/ic_launcher_foreground.xml | 0 .../res/mipmap-anydpi-v26/ic_launcher.xml | 0 .../mipmap-anydpi-v26/ic_launcher_round.xml | 0 .../androidMain}/res/values-night/colors.xml | 0 .../src/androidMain}/res/values/colors.xml | 0 .../src/androidMain}/res/values/strings.xml | 0 .../src/androidMain}/res/values/themes.xml | 0 .../src/androidMain}/res/xml/backup_rules.xml | 0 .../res/xml/data_extraction_rules.xml | 0 26 files changed, 72 insertions(+), 59 deletions(-) delete mode 100644 app/src/.DS_Store delete mode 100644 app/src/debug/.gitkeep delete mode 100644 app/src/debug/res/drawable/ic_launcher_foreground.xml delete mode 100644 app/src/main/kotlin/xyz/ksharma/krail/KrailApplication.kt delete mode 100644 app/src/main/res/drawable/ic_launcher_background.xml delete mode 100644 app/src/test/java/xyz/ksharma/start/ExampleUnitTest.kt create mode 100644 composeApp/build.gradle.kts rename {app/src/main => composeApp/src/androidMain}/AndroidManifest.xml (100%) rename {app/src/main => composeApp/src/androidMain}/kotlin/xyz/ksharma/krail/KrailApp.kt (100%) create mode 100644 composeApp/src/androidMain/kotlin/xyz/ksharma/krail/KrailApplication.kt rename {app/src/main => composeApp/src/androidMain}/kotlin/xyz/ksharma/krail/MainActivity.kt (88%) rename {app/src/main => composeApp/src/androidMain}/kotlin/xyz/ksharma/krail/navigation/KrailNavHost.kt (100%) rename {app/src/main => composeApp/src/androidMain}/kotlin/xyz/ksharma/krail/splash/SplashScreen.kt (100%) rename {app/src/main => composeApp/src/androidMain}/kotlin/xyz/ksharma/krail/splash/SplashViewModel.kt (95%) rename {app => composeApp/src/androidMain}/proguard-rules.pro (100%) rename {app/src/main => composeApp/src/androidMain}/res/color/gradient_background.xml (100%) rename {app/src/debug => composeApp/src/androidMain}/res/drawable/ic_launcher_background.xml (100%) rename {app/src/main => composeApp/src/androidMain}/res/drawable/ic_launcher_foreground.xml (100%) rename {app/src/main => composeApp/src/androidMain}/res/mipmap-anydpi-v26/ic_launcher.xml (100%) rename {app/src/main => composeApp/src/androidMain}/res/mipmap-anydpi-v26/ic_launcher_round.xml (100%) rename {app/src/main => composeApp/src/androidMain}/res/values-night/colors.xml (100%) rename {app/src/main => composeApp/src/androidMain}/res/values/colors.xml (100%) rename {app/src/main => composeApp/src/androidMain}/res/values/strings.xml (100%) rename {app/src/main => composeApp/src/androidMain}/res/values/themes.xml (100%) rename {app/src/main => composeApp/src/androidMain}/res/xml/backup_rules.xml (100%) rename {app/src/main => composeApp/src/androidMain}/res/xml/data_extraction_rules.xml (100%) diff --git a/app/src/.DS_Store b/app/src/.DS_Store deleted file mode 100644 index ede8a9ca48cc7e672e3028e377360b4aa4ec3b56..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6148 zcmeHKF-`+P47A~jNHi&FknROY{9qM@A~hd?00mMY1xbG$&*6z=#x{t{K}&(gl0CO; z&z^3I^V-aOv46NSo157TPP8|MsWDHV*h%dWooB2z+xzuqyT&>WoG_*vE5gC{#6_`}75yNY8@T4Q(s(wgJx_P`A=hU0M zCKRv1!}};VZ;6UhKnjc%IL+mp{r?*N%ltnkX(a`uz`s(!yXDPtflsR5I{7&E+8%xj q|1{J_IYP8zV6 - - diff --git a/app/src/main/kotlin/xyz/ksharma/krail/KrailApplication.kt b/app/src/main/kotlin/xyz/ksharma/krail/KrailApplication.kt deleted file mode 100644 index 814848fa..00000000 --- a/app/src/main/kotlin/xyz/ksharma/krail/KrailApplication.kt +++ /dev/null @@ -1,16 +0,0 @@ -package xyz.ksharma.krail - -import android.app.Application -import dagger.hilt.android.HiltAndroidApp -import timber.log.Timber - -@HiltAndroidApp -class KrailApplication : Application() { - - override fun onCreate() { - super.onCreate() - if (BuildConfig.DEBUG) { - Timber.plant(Timber.DebugTree()) - } - } -} diff --git a/app/src/main/res/drawable/ic_launcher_background.xml b/app/src/main/res/drawable/ic_launcher_background.xml deleted file mode 100644 index 532196b3..00000000 --- a/app/src/main/res/drawable/ic_launcher_background.xml +++ /dev/null @@ -1,11 +0,0 @@ - - - - diff --git a/app/src/test/java/xyz/ksharma/start/ExampleUnitTest.kt b/app/src/test/java/xyz/ksharma/start/ExampleUnitTest.kt deleted file mode 100644 index d7eaf556..00000000 --- a/app/src/test/java/xyz/ksharma/start/ExampleUnitTest.kt +++ /dev/null @@ -1,17 +0,0 @@ -package xyz.ksharma.krail - -import org.junit.Test - -import org.junit.Assert.* - -/** - * Example local unit test, which will execute on the development machine (host). - * - * See [testing documentation](http://d.android.com/tools/testing). - */ -class ExampleUnitTest { - @Test - fun addition_isCorrect() { - assertEquals(4, 2 + 2) - } -} diff --git a/composeApp/build.gradle.kts b/composeApp/build.gradle.kts new file mode 100644 index 00000000..8ea6cfba --- /dev/null +++ b/composeApp/build.gradle.kts @@ -0,0 +1,62 @@ + +plugins { + alias(libs.plugins.kotlinMultiplatform) + alias(libs.plugins.krail.android.application) + alias(libs.plugins.composeMultiplatform) + alias(libs.plugins.composeCompiler) + alias(libs.plugins.kotlin.serialization) +} + +kotlin { + androidTarget { + @OptIn(ExperimentalKotlinGradlePluginApi::class) + compilerOptions { + jvmTarget.set(JvmTarget.JVM_17) + } + } + + listOf( + iosX64(), + iosArm64(), + iosSimulatorArm64() + ).forEach { iosTarget -> + iosTarget.binaries.framework { + baseName = "ComposeApp" + isStatic = true + } + } + + sourceSets { + androidMain.dependencies { + implementation(libs.androidx.compose.ui.tooling.preview) + implementation(libs.androidx.activity.compose) + implementation(libs.ktor.client.okhttp) + } + iosMain.dependencies { + implementation(libs.ktor.client.darwin) + } + commonMain.dependencies { + implementation(compose.runtime) + implementation(compose.foundation) + implementation(compose.material3) + implementation(compose.ui) + implementation(compose.components.resources) + implementation(compose.components.uiToolingPreview) + + implementation(libs.ktor.client.core) + implementation(libs.ktor.client.content.negotiation) + implementation(libs.ktor.serialization.kotlinx.json) + + implementation(libs.coil.compose) + implementation(libs.coil.network.ktor) + implementation(libs.koin.core) + implementation(libs.koin.compose.viewmodel) + implementation(libs.navigation.compose) + } + } +} + + +dependencies { + debugImplementation(libs.androidx.compose.ui.tooling) +} diff --git a/app/src/main/AndroidManifest.xml b/composeApp/src/androidMain/AndroidManifest.xml similarity index 100% rename from app/src/main/AndroidManifest.xml rename to composeApp/src/androidMain/AndroidManifest.xml diff --git a/app/src/main/kotlin/xyz/ksharma/krail/KrailApp.kt b/composeApp/src/androidMain/kotlin/xyz/ksharma/krail/KrailApp.kt similarity index 100% rename from app/src/main/kotlin/xyz/ksharma/krail/KrailApp.kt rename to composeApp/src/androidMain/kotlin/xyz/ksharma/krail/KrailApp.kt diff --git a/composeApp/src/androidMain/kotlin/xyz/ksharma/krail/KrailApplication.kt b/composeApp/src/androidMain/kotlin/xyz/ksharma/krail/KrailApplication.kt new file mode 100644 index 00000000..48d1ce94 --- /dev/null +++ b/composeApp/src/androidMain/kotlin/xyz/ksharma/krail/KrailApplication.kt @@ -0,0 +1,10 @@ +package xyz.ksharma.krail + +import android.app.Application + +class KrailApplication : Application() { + + override fun onCreate() { + super.onCreate() + } +} diff --git a/app/src/main/kotlin/xyz/ksharma/krail/MainActivity.kt b/composeApp/src/androidMain/kotlin/xyz/ksharma/krail/MainActivity.kt similarity index 88% rename from app/src/main/kotlin/xyz/ksharma/krail/MainActivity.kt rename to composeApp/src/androidMain/kotlin/xyz/ksharma/krail/MainActivity.kt index 12ff4c52..331e1bd0 100644 --- a/app/src/main/kotlin/xyz/ksharma/krail/MainActivity.kt +++ b/composeApp/src/androidMain/kotlin/xyz/ksharma/krail/MainActivity.kt @@ -4,10 +4,8 @@ import android.os.Bundle import androidx.activity.ComponentActivity import androidx.activity.compose.setContent import androidx.activity.enableEdgeToEdge -import dagger.hilt.android.AndroidEntryPoint import xyz.ksharma.krail.design.system.theme.KrailTheme -@AndroidEntryPoint class MainActivity : ComponentActivity() { override fun onCreate(savedInstanceState: Bundle?) { diff --git a/app/src/main/kotlin/xyz/ksharma/krail/navigation/KrailNavHost.kt b/composeApp/src/androidMain/kotlin/xyz/ksharma/krail/navigation/KrailNavHost.kt similarity index 100% rename from app/src/main/kotlin/xyz/ksharma/krail/navigation/KrailNavHost.kt rename to composeApp/src/androidMain/kotlin/xyz/ksharma/krail/navigation/KrailNavHost.kt diff --git a/app/src/main/kotlin/xyz/ksharma/krail/splash/SplashScreen.kt b/composeApp/src/androidMain/kotlin/xyz/ksharma/krail/splash/SplashScreen.kt similarity index 100% rename from app/src/main/kotlin/xyz/ksharma/krail/splash/SplashScreen.kt rename to composeApp/src/androidMain/kotlin/xyz/ksharma/krail/splash/SplashScreen.kt diff --git a/app/src/main/kotlin/xyz/ksharma/krail/splash/SplashViewModel.kt b/composeApp/src/androidMain/kotlin/xyz/ksharma/krail/splash/SplashViewModel.kt similarity index 95% rename from app/src/main/kotlin/xyz/ksharma/krail/splash/SplashViewModel.kt rename to composeApp/src/androidMain/kotlin/xyz/ksharma/krail/splash/SplashViewModel.kt index 05748b94..5c9c8e08 100644 --- a/app/src/main/kotlin/xyz/ksharma/krail/splash/SplashViewModel.kt +++ b/composeApp/src/androidMain/kotlin/xyz/ksharma/krail/splash/SplashViewModel.kt @@ -2,7 +2,6 @@ package xyz.ksharma.krail.splash import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope -import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.SharingStarted @@ -15,7 +14,6 @@ import xyz.ksharma.krail.sandook.di.SandookFactory import xyz.ksharma.krail.trip.planner.ui.state.TransportMode import javax.inject.Inject -@HiltViewModel class SplashViewModel @Inject constructor( sandookFactory: SandookFactory, ) : ViewModel() { diff --git a/app/proguard-rules.pro b/composeApp/src/androidMain/proguard-rules.pro similarity index 100% rename from app/proguard-rules.pro rename to composeApp/src/androidMain/proguard-rules.pro diff --git a/app/src/main/res/color/gradient_background.xml b/composeApp/src/androidMain/res/color/gradient_background.xml similarity index 100% rename from app/src/main/res/color/gradient_background.xml rename to composeApp/src/androidMain/res/color/gradient_background.xml diff --git a/app/src/debug/res/drawable/ic_launcher_background.xml b/composeApp/src/androidMain/res/drawable/ic_launcher_background.xml similarity index 100% rename from app/src/debug/res/drawable/ic_launcher_background.xml rename to composeApp/src/androidMain/res/drawable/ic_launcher_background.xml diff --git a/app/src/main/res/drawable/ic_launcher_foreground.xml b/composeApp/src/androidMain/res/drawable/ic_launcher_foreground.xml similarity index 100% rename from app/src/main/res/drawable/ic_launcher_foreground.xml rename to composeApp/src/androidMain/res/drawable/ic_launcher_foreground.xml diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/composeApp/src/androidMain/res/mipmap-anydpi-v26/ic_launcher.xml similarity index 100% rename from app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml rename to composeApp/src/androidMain/res/mipmap-anydpi-v26/ic_launcher.xml diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml b/composeApp/src/androidMain/res/mipmap-anydpi-v26/ic_launcher_round.xml similarity index 100% rename from app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml rename to composeApp/src/androidMain/res/mipmap-anydpi-v26/ic_launcher_round.xml diff --git a/app/src/main/res/values-night/colors.xml b/composeApp/src/androidMain/res/values-night/colors.xml similarity index 100% rename from app/src/main/res/values-night/colors.xml rename to composeApp/src/androidMain/res/values-night/colors.xml diff --git a/app/src/main/res/values/colors.xml b/composeApp/src/androidMain/res/values/colors.xml similarity index 100% rename from app/src/main/res/values/colors.xml rename to composeApp/src/androidMain/res/values/colors.xml diff --git a/app/src/main/res/values/strings.xml b/composeApp/src/androidMain/res/values/strings.xml similarity index 100% rename from app/src/main/res/values/strings.xml rename to composeApp/src/androidMain/res/values/strings.xml diff --git a/app/src/main/res/values/themes.xml b/composeApp/src/androidMain/res/values/themes.xml similarity index 100% rename from app/src/main/res/values/themes.xml rename to composeApp/src/androidMain/res/values/themes.xml diff --git a/app/src/main/res/xml/backup_rules.xml b/composeApp/src/androidMain/res/xml/backup_rules.xml similarity index 100% rename from app/src/main/res/xml/backup_rules.xml rename to composeApp/src/androidMain/res/xml/backup_rules.xml diff --git a/app/src/main/res/xml/data_extraction_rules.xml b/composeApp/src/androidMain/res/xml/data_extraction_rules.xml similarity index 100% rename from app/src/main/res/xml/data_extraction_rules.xml rename to composeApp/src/androidMain/res/xml/data_extraction_rules.xml From f9482fb7a63cf1441b5246a1eb2206d3a981e8ca Mon Sep 17 00:00:00 2001 From: Karan Sharma <55722391+ksharma-xyz@users.noreply.github.com> Date: Thu, 14 Nov 2024 22:15:04 +1100 Subject: [PATCH 07/67] Add commonmain --- composeApp/src/commonMain/composeResources/values/strings.xml | 4 ++++ .../kotlin/xyz/ksharma/krail/KrailApp.kt | 2 +- .../kotlin/xyz/ksharma/krail/navigation/KrailNavHost.kt | 0 .../kotlin/xyz/ksharma/krail/splash/SplashScreen.kt | 0 .../kotlin/xyz/ksharma/krail/splash/SplashViewModel.kt | 0 5 files changed, 5 insertions(+), 1 deletion(-) create mode 100644 composeApp/src/commonMain/composeResources/values/strings.xml rename composeApp/src/{androidMain => commonMain}/kotlin/xyz/ksharma/krail/KrailApp.kt (85%) rename composeApp/src/{androidMain => commonMain}/kotlin/xyz/ksharma/krail/navigation/KrailNavHost.kt (100%) rename composeApp/src/{androidMain => commonMain}/kotlin/xyz/ksharma/krail/splash/SplashScreen.kt (100%) rename composeApp/src/{androidMain => commonMain}/kotlin/xyz/ksharma/krail/splash/SplashViewModel.kt (100%) diff --git a/composeApp/src/commonMain/composeResources/values/strings.xml b/composeApp/src/commonMain/composeResources/values/strings.xml new file mode 100644 index 00000000..d171a450 --- /dev/null +++ b/composeApp/src/commonMain/composeResources/values/strings.xml @@ -0,0 +1,4 @@ + + + Krail App + diff --git a/composeApp/src/androidMain/kotlin/xyz/ksharma/krail/KrailApp.kt b/composeApp/src/commonMain/kotlin/xyz/ksharma/krail/KrailApp.kt similarity index 85% rename from composeApp/src/androidMain/kotlin/xyz/ksharma/krail/KrailApp.kt rename to composeApp/src/commonMain/kotlin/xyz/ksharma/krail/KrailApp.kt index 059819d5..bf694f0e 100644 --- a/composeApp/src/androidMain/kotlin/xyz/ksharma/krail/KrailApp.kt +++ b/composeApp/src/commonMain/kotlin/xyz/ksharma/krail/KrailApp.kt @@ -4,6 +4,6 @@ import androidx.compose.runtime.Composable import xyz.ksharma.krail.navigation.KrailNavHost @Composable -internal fun KrailApp() { +fun KrailApp() { KrailNavHost() } diff --git a/composeApp/src/androidMain/kotlin/xyz/ksharma/krail/navigation/KrailNavHost.kt b/composeApp/src/commonMain/kotlin/xyz/ksharma/krail/navigation/KrailNavHost.kt similarity index 100% rename from composeApp/src/androidMain/kotlin/xyz/ksharma/krail/navigation/KrailNavHost.kt rename to composeApp/src/commonMain/kotlin/xyz/ksharma/krail/navigation/KrailNavHost.kt diff --git a/composeApp/src/androidMain/kotlin/xyz/ksharma/krail/splash/SplashScreen.kt b/composeApp/src/commonMain/kotlin/xyz/ksharma/krail/splash/SplashScreen.kt similarity index 100% rename from composeApp/src/androidMain/kotlin/xyz/ksharma/krail/splash/SplashScreen.kt rename to composeApp/src/commonMain/kotlin/xyz/ksharma/krail/splash/SplashScreen.kt diff --git a/composeApp/src/androidMain/kotlin/xyz/ksharma/krail/splash/SplashViewModel.kt b/composeApp/src/commonMain/kotlin/xyz/ksharma/krail/splash/SplashViewModel.kt similarity index 100% rename from composeApp/src/androidMain/kotlin/xyz/ksharma/krail/splash/SplashViewModel.kt rename to composeApp/src/commonMain/kotlin/xyz/ksharma/krail/splash/SplashViewModel.kt From 234a34b210860aff8ed69e9ef15bd674bdf9cefd Mon Sep 17 00:00:00 2001 From: Karan Sharma <55722391+ksharma-xyz@users.noreply.github.com> Date: Thu, 14 Nov 2024 22:18:08 +1100 Subject: [PATCH 08/67] Add iosApp --- composeApp/build.gradle.kts | 2 +- .../iosMain/kotlin/xyz/ksharma/krail/MainViewController.kt | 5 +++++ settings.gradle.kts | 1 - 3 files changed, 6 insertions(+), 2 deletions(-) create mode 100644 composeApp/src/iosMain/kotlin/xyz/ksharma/krail/MainViewController.kt diff --git a/composeApp/build.gradle.kts b/composeApp/build.gradle.kts index 8ea6cfba..345221e7 100644 --- a/composeApp/build.gradle.kts +++ b/composeApp/build.gradle.kts @@ -3,7 +3,7 @@ plugins { alias(libs.plugins.kotlinMultiplatform) alias(libs.plugins.krail.android.application) alias(libs.plugins.composeMultiplatform) - alias(libs.plugins.composeCompiler) + alias(libs.plugins.compose.compiler) alias(libs.plugins.kotlin.serialization) } diff --git a/composeApp/src/iosMain/kotlin/xyz/ksharma/krail/MainViewController.kt b/composeApp/src/iosMain/kotlin/xyz/ksharma/krail/MainViewController.kt new file mode 100644 index 00000000..5192bd54 --- /dev/null +++ b/composeApp/src/iosMain/kotlin/xyz/ksharma/krail/MainViewController.kt @@ -0,0 +1,5 @@ +package xyz.ksharma.krail + +import androidx.compose.ui.window.ComposeUIViewController + +fun MainViewController() = ComposeUIViewController { App() } diff --git a/settings.gradle.kts b/settings.gradle.kts index e0d4a213..c546d08e 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -29,7 +29,6 @@ dependencyResolutionManagement { rootProject.name = "Krail" include(":composeApp") -include(":app") include(":core:coroutines-ext") include(":core:date-time") include(":core:di") From 07dfda618b351a09aa2d1ccc7539bf60c38000c7 Mon Sep 17 00:00:00 2001 From: Karan Sharma <55722391+ksharma-xyz@users.noreply.github.com> Date: Thu, 14 Nov 2024 22:53:33 +1100 Subject: [PATCH 09/67] trying to build by adding dependencies --- composeApp/build.gradle.kts | 24 ++++++++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/composeApp/build.gradle.kts b/composeApp/build.gradle.kts index 345221e7..5b6e6860 100644 --- a/composeApp/build.gradle.kts +++ b/composeApp/build.gradle.kts @@ -1,7 +1,7 @@ plugins { alias(libs.plugins.kotlinMultiplatform) - alias(libs.plugins.krail.android.application) + id("krail.android.application") alias(libs.plugins.composeMultiplatform) alias(libs.plugins.compose.compiler) alias(libs.plugins.kotlin.serialization) @@ -21,7 +21,7 @@ kotlin { iosSimulatorArm64() ).forEach { iosTarget -> iosTarget.binaries.framework { - baseName = "ComposeApp" + baseName = "KrailApp" isStatic = true } } @@ -31,6 +31,26 @@ kotlin { implementation(libs.androidx.compose.ui.tooling.preview) implementation(libs.androidx.activity.compose) implementation(libs.ktor.client.okhttp) + + // Projects + implementation(projects.core.designSystem) + implementation(projects.core.network) + implementation(projects.core.utils) + implementation(projects.feature.tripPlanner.network.api) + implementation(projects.feature.tripPlanner.network.real) + implementation(projects.feature.tripPlanner.state) + implementation(projects.feature.tripPlanner.ui) + implementation(projects.sandook.api) + implementation(projects.sandook.real) + + implementation(libs.activity.compose) + implementation(libs.compose.foundation) + implementation(libs.compose.navigation) + implementation(libs.core.ktx) + implementation(libs.kotlinx.serialization.json) + implementation(libs.lifecycle.runtime.ktx) + implementation(libs.timber) + implementation(libs.hilt.navigation.compose) } iosMain.dependencies { implementation(libs.ktor.client.darwin) From a56eff6f4adff74bbecdc6e853678c547f053151 Mon Sep 17 00:00:00 2001 From: Karan Sharma <55722391+ksharma-xyz@users.noreply.github.com> Date: Fri, 15 Nov 2024 18:13:48 +1100 Subject: [PATCH 10/67] Add configureAndroid --- composeApp/build.gradle.kts | 1 - .../build-logic/convention/build.gradle.kts | 8 ++- .../krail/AndroidHiltConventionPlugin.kt | 25 ---------- .../xyz/ksharma/krail/gradle/Android.kt | 50 +++++++++++++++++++ .../AndroidApplicationConventionPlugin.kt | 17 +++++++ .../gradle/AndroidLibraryConventionPlugin.kt | 16 ++++++ .../kotlin/xyz/ksharma/krail/gradle/Libs.kt | 9 ++++ gradle/build-logic/gradle.properties | 3 ++ gradle/libs.versions.toml | 2 + settings.gradle.kts | 6 ++- 10 files changed, 105 insertions(+), 32 deletions(-) delete mode 100644 gradle/build-logic/convention/src/main/kotlin/xyz/ksharma/krail/AndroidHiltConventionPlugin.kt create mode 100644 gradle/build-logic/convention/src/main/kotlin/xyz/ksharma/krail/gradle/Android.kt create mode 100644 gradle/build-logic/convention/src/main/kotlin/xyz/ksharma/krail/gradle/AndroidApplicationConventionPlugin.kt create mode 100644 gradle/build-logic/convention/src/main/kotlin/xyz/ksharma/krail/gradle/AndroidLibraryConventionPlugin.kt create mode 100644 gradle/build-logic/convention/src/main/kotlin/xyz/ksharma/krail/gradle/Libs.kt create mode 100644 gradle/build-logic/gradle.properties diff --git a/composeApp/build.gradle.kts b/composeApp/build.gradle.kts index 5b6e6860..db4c00e5 100644 --- a/composeApp/build.gradle.kts +++ b/composeApp/build.gradle.kts @@ -1,7 +1,6 @@ plugins { alias(libs.plugins.kotlinMultiplatform) - id("krail.android.application") alias(libs.plugins.composeMultiplatform) alias(libs.plugins.compose.compiler) alias(libs.plugins.kotlin.serialization) diff --git a/gradle/build-logic/convention/build.gradle.kts b/gradle/build-logic/convention/build.gradle.kts index a1ef50c4..2ba442ad 100644 --- a/gradle/build-logic/convention/build.gradle.kts +++ b/gradle/build-logic/convention/build.gradle.kts @@ -5,7 +5,7 @@ plugins { `kotlin-dsl` } -group = "xyz.ksharma.buildlogic" +group = "xyz.ksharma.krail.gradle" val javaVersion = libs.versions.java.get().toInt() @@ -28,15 +28,12 @@ dependencies { } gradlePlugin { +/* plugins { register("application") { id = "krail.android.application" implementationClass = "ApplicationConventionPlugin" } - register("androidHilt") { - id = "krail.android.hilt" - implementationClass = "AndroidHiltConventionPlugin" - } register("androidLibrary") { id = "krail.android.library" implementationClass = "AndroidLibraryConventionPlugin" @@ -50,4 +47,5 @@ gradlePlugin { implementationClass = "JvmLibraryConventionPlugin" } } +*/ } diff --git a/gradle/build-logic/convention/src/main/kotlin/xyz/ksharma/krail/AndroidHiltConventionPlugin.kt b/gradle/build-logic/convention/src/main/kotlin/xyz/ksharma/krail/AndroidHiltConventionPlugin.kt deleted file mode 100644 index a3be7820..00000000 --- a/gradle/build-logic/convention/src/main/kotlin/xyz/ksharma/krail/AndroidHiltConventionPlugin.kt +++ /dev/null @@ -1,25 +0,0 @@ -package xyz.ksharma.krail - -import org.gradle.api.Plugin -import org.gradle.api.Project -import org.gradle.api.artifacts.VersionCatalogsExtension -import org.gradle.kotlin.dsl.dependencies -import org.gradle.kotlin.dsl.getByType - -class AndroidHiltConventionPlugin : Plugin { - override fun apply(target: Project) { - with(target) { - val libs = extensions.getByType().named("libs") - - with(pluginManager) { - apply("com.google.dagger.hilt.android") - apply("com.google.devtools.ksp") - } - - dependencies { - "implementation"(libs.findLibrary("hilt.android").get()) - "ksp"(libs.findLibrary("hilt.compiler").get()) - } - } - } -} diff --git a/gradle/build-logic/convention/src/main/kotlin/xyz/ksharma/krail/gradle/Android.kt b/gradle/build-logic/convention/src/main/kotlin/xyz/ksharma/krail/gradle/Android.kt new file mode 100644 index 00000000..bcc7f52d --- /dev/null +++ b/gradle/build-logic/convention/src/main/kotlin/xyz/ksharma/krail/gradle/Android.kt @@ -0,0 +1,50 @@ +package xyz.ksharma.krail.gradle + +import com.android.build.api.dsl.CommonExtension +import com.android.build.gradle.BaseExtension +import org.gradle.api.JavaVersion +import org.gradle.api.Project +import org.gradle.kotlin.dsl.configure +import org.gradle.kotlin.dsl.withType +import org.jetbrains.kotlin.gradle.dsl.JvmTarget +import org.jetbrains.kotlin.gradle.tasks.KotlinCompile + +fun Project.configureAndroid( +) { + extensions.configure { + compileSdkVersion(AndroidVersion.COMPILE_SDK) + + defaultConfig { + minSdk = AndroidVersion.MIN_SDK + targetSdk = AndroidVersion.TARGET_SDK + } + tasks.withType().configureEach { + compilerOptions { + jvmTarget.set(JvmTarget.JVM_17) + } + } + + if (this is CommonExtension<*, *, *, *, *, *>) { + buildFeatures { + compose = true + buildConfig = true + } + packaging { + resources { + excludes += "/META-INF/{AL2.0,LGPL2.1}" + } + } + } + + compileOptions { + sourceCompatibility = JavaVersion.VERSION_17 + targetCompatibility = JavaVersion.VERSION_17 + } + } +} + +object AndroidVersion { + const val COMPILE_SDK = 35 + const val MIN_SDK = 26 // Oreo 8.0 + const val TARGET_SDK = 35 +} diff --git a/gradle/build-logic/convention/src/main/kotlin/xyz/ksharma/krail/gradle/AndroidApplicationConventionPlugin.kt b/gradle/build-logic/convention/src/main/kotlin/xyz/ksharma/krail/gradle/AndroidApplicationConventionPlugin.kt new file mode 100644 index 00000000..e89f6c0c --- /dev/null +++ b/gradle/build-logic/convention/src/main/kotlin/xyz/ksharma/krail/gradle/AndroidApplicationConventionPlugin.kt @@ -0,0 +1,17 @@ +package xyz.ksharma.krail.gradle + +import org.gradle.api.Plugin +import org.gradle.api.Project + +class AndroidApplicationConventionPlugin : Plugin { + override fun apply(target: Project) { + with(target) { + with(pluginManager) { + apply("com.android.application") + apply("org.gradle.android.cache-fix") + } + + configureAndroid() + } + } +} diff --git a/gradle/build-logic/convention/src/main/kotlin/xyz/ksharma/krail/gradle/AndroidLibraryConventionPlugin.kt b/gradle/build-logic/convention/src/main/kotlin/xyz/ksharma/krail/gradle/AndroidLibraryConventionPlugin.kt new file mode 100644 index 00000000..008c51bd --- /dev/null +++ b/gradle/build-logic/convention/src/main/kotlin/xyz/ksharma/krail/gradle/AndroidLibraryConventionPlugin.kt @@ -0,0 +1,16 @@ +package xyz.ksharma.krail.gradle + +import org.gradle.api.Plugin +import org.gradle.api.Project + +class AndroidLibraryConventionPlugin : Plugin { + override fun apply(target: Project) { + with(target) { + with(pluginManager) { + apply("com.android.library") + apply("org.gradle.android.cache-fix") + } + configureAndroid() + } + } +} diff --git a/gradle/build-logic/convention/src/main/kotlin/xyz/ksharma/krail/gradle/Libs.kt b/gradle/build-logic/convention/src/main/kotlin/xyz/ksharma/krail/gradle/Libs.kt new file mode 100644 index 00000000..750c52aa --- /dev/null +++ b/gradle/build-logic/convention/src/main/kotlin/xyz/ksharma/krail/gradle/Libs.kt @@ -0,0 +1,9 @@ +package xyz.ksharma.krail.gradle + +import org.gradle.api.Project +import org.gradle.api.artifacts.VersionCatalog +import org.gradle.api.artifacts.VersionCatalogsExtension +import org.gradle.kotlin.dsl.getByType + +val Project.libs + get(): VersionCatalog = extensions.getByType().named("libs") diff --git a/gradle/build-logic/gradle.properties b/gradle/build-logic/gradle.properties new file mode 100644 index 00000000..c6cd2a7e --- /dev/null +++ b/gradle/build-logic/gradle.properties @@ -0,0 +1,3 @@ +org.gradle.parallel=true +org.gradle.caching=true +org.gradle.configureondemand=true diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 689d386e..1fbf3e0f 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -80,6 +80,8 @@ test-androidxCoreKtx = { group = "androidx.test", name = "core-ktx", version.ref #BuildLogic kotlin-gradlePlugin = { group = "org.jetbrains.kotlin", name = "kotlin-gradle-plugin", version.ref = "kotlin" } android-gradlePlugin = { group = "com.android.tools.build", name = "gradle", version.ref = "agp" } +compose-gradlePlugin = { module = "org.jetbrains.compose:compose-gradle-plugin", version.ref = "compose-multiplatform" } +composeCompiler-gradlePlugin = { module = "org.jetbrains.kotlin:compose-compiler-gradle-plugin", version.ref = "kotlin" } #CodeStyle Use spotless plugin for multiplatform support diff --git a/settings.gradle.kts b/settings.gradle.kts index c546d08e..59ee15fb 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -1,6 +1,8 @@ enableFeaturePreview("TYPESAFE_PROJECT_ACCESSORS") pluginManagement { + includeBuild("gradle/build-logic") + repositories { google { mavenContent { @@ -28,7 +30,8 @@ dependencyResolutionManagement { } rootProject.name = "Krail" -include(":composeApp") +//include(":composeApp") +/* include(":core:coroutines-ext") include(":core:date-time") include(":core:di") @@ -40,3 +43,4 @@ include(":feature:trip-planner:state") include(":feature:trip-planner:ui") include(":sandook:api") include(":sandook:real") +*/ From a7bc3063234cbfc0e445aea9ed84e2a1079c302a Mon Sep 17 00:00:00 2001 From: Karan Sharma <55722391+ksharma-xyz@users.noreply.github.com> Date: Fri, 15 Nov 2024 19:15:27 +1100 Subject: [PATCH 11/67] Add ComposeMultiplatformConventionPlugin --- .../xyz/ksharma/krail/gradle/Android.kt | 7 ++--- .../AndroidApplicationConventionPlugin.kt | 1 + .../ComposeMultiplatformConventionPlugin.kt | 27 +++++++++++++++++++ .../kotlin/xyz/ksharma/krail/gradle/Java.kt | 15 +++++++++++ 4 files changed, 45 insertions(+), 5 deletions(-) create mode 100644 gradle/build-logic/convention/src/main/kotlin/xyz/ksharma/krail/gradle/ComposeMultiplatformConventionPlugin.kt create mode 100644 gradle/build-logic/convention/src/main/kotlin/xyz/ksharma/krail/gradle/Java.kt diff --git a/gradle/build-logic/convention/src/main/kotlin/xyz/ksharma/krail/gradle/Android.kt b/gradle/build-logic/convention/src/main/kotlin/xyz/ksharma/krail/gradle/Android.kt index bcc7f52d..5179c28a 100644 --- a/gradle/build-logic/convention/src/main/kotlin/xyz/ksharma/krail/gradle/Android.kt +++ b/gradle/build-logic/convention/src/main/kotlin/xyz/ksharma/krail/gradle/Android.kt @@ -18,11 +18,8 @@ fun Project.configureAndroid( minSdk = AndroidVersion.MIN_SDK targetSdk = AndroidVersion.TARGET_SDK } - tasks.withType().configureEach { - compilerOptions { - jvmTarget.set(JvmTarget.JVM_17) - } - } + + configureJava() if (this is CommonExtension<*, *, *, *, *, *>) { buildFeatures { diff --git a/gradle/build-logic/convention/src/main/kotlin/xyz/ksharma/krail/gradle/AndroidApplicationConventionPlugin.kt b/gradle/build-logic/convention/src/main/kotlin/xyz/ksharma/krail/gradle/AndroidApplicationConventionPlugin.kt index e89f6c0c..c909d744 100644 --- a/gradle/build-logic/convention/src/main/kotlin/xyz/ksharma/krail/gradle/AndroidApplicationConventionPlugin.kt +++ b/gradle/build-logic/convention/src/main/kotlin/xyz/ksharma/krail/gradle/AndroidApplicationConventionPlugin.kt @@ -9,6 +9,7 @@ class AndroidApplicationConventionPlugin : Plugin { with(pluginManager) { apply("com.android.application") apply("org.gradle.android.cache-fix") + apply("org.jetbrains.kotlin.android") // Support Kotlin in Android projects } configureAndroid() diff --git a/gradle/build-logic/convention/src/main/kotlin/xyz/ksharma/krail/gradle/ComposeMultiplatformConventionPlugin.kt b/gradle/build-logic/convention/src/main/kotlin/xyz/ksharma/krail/gradle/ComposeMultiplatformConventionPlugin.kt new file mode 100644 index 00000000..2eeaef8e --- /dev/null +++ b/gradle/build-logic/convention/src/main/kotlin/xyz/ksharma/krail/gradle/ComposeMultiplatformConventionPlugin.kt @@ -0,0 +1,27 @@ +package xyz.ksharma.krail.gradle + +import org.gradle.api.Plugin +import org.gradle.api.Project +import org.gradle.kotlin.dsl.configure +import org.jetbrains.kotlin.gradle.dsl.KotlinMultiplatformExtension + +class ComposeMultiplatformConventionPlugin : Plugin { + override fun apply(target: Project) = with(target) { + + with(pluginManager) { + apply("org.jetbrains.kotlin.multiplatform") // Kotlin Multiplatform plugin - JVM, Android, iOS, JS + apply("org.jetbrains.compose") // Compose Multiplatform plugin - Android Desktop, Web + apply("org.jetbrains.kotlin.plugin.compose") + } + + extensions.configure { + applyDefaultHierarchyTemplate() + + + iosArm64() + iosSimulatorArm64() + + configureJava() + } + } +} diff --git a/gradle/build-logic/convention/src/main/kotlin/xyz/ksharma/krail/gradle/Java.kt b/gradle/build-logic/convention/src/main/kotlin/xyz/ksharma/krail/gradle/Java.kt new file mode 100644 index 00000000..5fb27c7b --- /dev/null +++ b/gradle/build-logic/convention/src/main/kotlin/xyz/ksharma/krail/gradle/Java.kt @@ -0,0 +1,15 @@ +package xyz.ksharma.krail.gradle + +import org.gradle.api.JavaVersion +import org.gradle.api.Project +import org.gradle.api.plugins.JavaPluginExtension +import org.gradle.jvm.toolchain.JavaLanguageVersion +import org.gradle.kotlin.dsl.configure + +internal fun Project.configureJava() { + extensions.configure { + toolchain { + languageVersion.set(JavaLanguageVersion.of(JavaVersion.VERSION_17.majorVersion)) + } + } +} From f047ab8f02b68e9597621aa3fee7a5d723e46cde Mon Sep 17 00:00:00 2001 From: Karan Sharma <55722391+ksharma-xyz@users.noreply.github.com> Date: Fri, 15 Nov 2024 20:21:55 +1100 Subject: [PATCH 12/67] Register plugin --- .github/workflows/ci.yml | 4 +- {app => android-app}/build.gradle.kts | 16 +- config/detekt/detekt-baseline.xml | 5 - config/detekt/detekt.yaml | 408 ------------------ .../ui/navigation/TripPlannerDestinations.kt | 2 +- .../ui/timetable/TimeTableViewModel.kt | 2 +- .../build-logic/convention/build.gradle.kts | 54 ++- .../AndroidLibraryComposeConventionPlugin.kt | 64 --- .../krail/AndroidLibraryConventionPlugin.kt | 67 --- .../krail/ApplicationConventionPlugin.kt | 81 ---- .../krail/JvmLibraryConventionPlugin.kt | 46 -- .../AndroidApplicationConventionPlugin.kt | 2 - .../gradle/AndroidLibraryConventionPlugin.kt | 1 - .../ComposeMultiplatformConventionPlugin.kt | 18 +- .../gradle/KotlinAndroidConventionPlugin.kt | 16 + .../KotlinMultiplatformConventionPlugin.kt | 24 ++ gradle/libs.versions.toml | 10 +- settings.gradle.kts | 1 + 18 files changed, 107 insertions(+), 714 deletions(-) rename {app => android-app}/build.gradle.kts (84%) delete mode 100644 config/detekt/detekt-baseline.xml delete mode 100644 config/detekt/detekt.yaml delete mode 100644 gradle/build-logic/convention/src/main/kotlin/xyz/ksharma/krail/AndroidLibraryComposeConventionPlugin.kt delete mode 100644 gradle/build-logic/convention/src/main/kotlin/xyz/ksharma/krail/AndroidLibraryConventionPlugin.kt delete mode 100644 gradle/build-logic/convention/src/main/kotlin/xyz/ksharma/krail/ApplicationConventionPlugin.kt delete mode 100644 gradle/build-logic/convention/src/main/kotlin/xyz/ksharma/krail/JvmLibraryConventionPlugin.kt create mode 100644 gradle/build-logic/convention/src/main/kotlin/xyz/ksharma/krail/gradle/KotlinAndroidConventionPlugin.kt create mode 100644 gradle/build-logic/convention/src/main/kotlin/xyz/ksharma/krail/gradle/KotlinMultiplatformConventionPlugin.kt diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 9e8f882d..1f0c243e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -40,12 +40,12 @@ jobs: - name: Firebase (Release) - Google Services.json file env: DATA: ${{ secrets.FIREBASE_GOOGLE_SERVICES_JSON_RELEASE }} - run: echo $DATA | base64 -di > app/google-services.json + run: echo $DATA | base64 -di > android-app/google-services.json - name: Firebase (Debug) - Google Services.json file env: DATA: ${{ secrets.FIREBASE_GOOGLE_SERVICES_JSON_DEBUG }} - run: echo $DATA | base64 -di > app/src/debug/google-services.json + run: echo $DATA | base64 -di > android-app/src/debug/google-services.json - name: Detekt Checks run: ./gradlew detekt diff --git a/app/build.gradle.kts b/android-app/build.gradle.kts similarity index 84% rename from app/build.gradle.kts rename to android-app/build.gradle.kts index b82c7c30..f6cf1f02 100644 --- a/app/build.gradle.kts +++ b/android-app/build.gradle.kts @@ -1,6 +1,7 @@ plugins { alias(libs.plugins.krail.android.application) - alias(libs.plugins.krail.android.hilt) + alias(libs.plugins.krail.kotlin.android) + alias(libs.plugins.krail.compose.multiplatform) alias(libs.plugins.kotlin.serialization) } @@ -40,15 +41,11 @@ android { proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro") } } - - hilt { - enableAggregatingTask = true - } } dependencies { - // Projects + /* // Projects implementation(projects.core.designSystem) implementation(projects.core.network) implementation(projects.feature.tripPlanner.network.api) @@ -56,7 +53,7 @@ dependencies { implementation(projects.feature.tripPlanner.state) implementation(projects.feature.tripPlanner.ui) implementation(projects.sandook.api) - implementation(projects.sandook.real) + implementation(projects.sandook.real)*/ implementation(libs.activity.compose) implementation(libs.compose.foundation) @@ -64,10 +61,5 @@ dependencies { implementation(libs.core.ktx) implementation(libs.kotlinx.serialization.json) implementation(libs.lifecycle.runtime.ktx) - implementation(libs.timber) implementation(libs.hilt.navigation.compose) - - // Test - androidTestImplementation(libs.test.androidxTestExtJunit) - testImplementation(libs.test.composeUiTestJunit4) } diff --git a/config/detekt/detekt-baseline.xml b/config/detekt/detekt-baseline.xml deleted file mode 100644 index 64a56649..00000000 --- a/config/detekt/detekt-baseline.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/config/detekt/detekt.yaml b/config/detekt/detekt.yaml deleted file mode 100644 index a562a979..00000000 --- a/config/detekt/detekt.yaml +++ /dev/null @@ -1,408 +0,0 @@ -Compose: - ComposableAnnotationNaming: - active: true - ComposableNaming: - active: true - # -- You can optionally disable the checks in this rule for regex matches against the composable name (e.g. molecule presenters) - # allowedComposableFunctionNames: .*Presenter,.*MoleculePresenter - ComposableParamOrder: - active: true - # -- You can optionally have a list of types to be treated as lambdas (e.g. typedefs or fun interfaces not picked up automatically) - # treatAsLambda: MyLambdaType - CompositionLocalAllowlist: - active: true - # -- You can optionally define a list of CompositionLocals that are allowed here - allowedCompositionLocals: LocalTextColor,LocalContentAlpha, LocalTextStyle,LocalContentColor,LocalOnContentColor,LocalKrailTypography,LocalKrailColors,LocalThemeColor,LocalThemeContentColor - CompositionLocalNaming: - active: true - ContentEmitterReturningValues: - active: true - # -- You can optionally add your own composables here - # contentEmitters: MyComposable,MyOtherComposable - ContentTrailingLambda: - active: true - # -- You can optionally have a list of types to be treated as composable lambdas (e.g. typedefs or fun interfaces not picked up automatically) - # treatAsComposableLambda: MyComposableLambdaType - DefaultsVisibility: - active: true - LambdaParameterInRestartableEffect: - active: false - # -- You can optionally have a list of types to be treated as lambdas (e.g. typedefs or fun interfaces not picked up automatically) - # treatAsLambda: MyLambdaType - Material2: - active: true # Opt-in, disabled by default. Turn on if you want to disallow Material 2 usages. - # -- You can optionally allow parts of it, if you are in the middle of a migration. - # allowedFromM2: icons.Icons,TopAppBar - ModifierClickableOrder: - active: true - # -- You can optionally add your own Modifier types - # customModifiers: BananaModifier,PotatoModifier - ModifierComposable: - active: true - # -- You can optionally add your own Modifier types - # customModifiers: BananaModifier,PotatoModifier - ModifierComposed: - active: true - # -- You can optionally add your own Modifier types - # customModifiers: BananaModifier,PotatoModifier - ModifierMissing: - active: true - # -- You can optionally control the visibility of which composables to check for here - # -- Possible values are: `only_public`, `public_and_internal` and `all` (default is `only_public`) - # checkModifiersForVisibility: only_public - # -- You can optionally add your own Modifier types - # customModifiers: BananaModifier,PotatoModifier - ModifierNaming: - active: true - # -- You can optionally add your own Modifier types - # customModifiers: BananaModifier,PotatoModifier - ModifierNotUsedAtRoot: - active: true - # -- You can optionally add your own composables here - # contentEmitters: MyComposable,MyOtherComposable - # -- You can optionally add your own Modifier types - # customModifiers: BananaModifier,PotatoModifier - ModifierReused: - active: true - # -- You can optionally add your own Modifier types - # customModifiers: BananaModifier,PotatoModifier - ModifierWithoutDefault: - active: true - MultipleEmitters: - active: true - # -- You can optionally add your own composables here that will count as content emitters - # contentEmitters: MyComposable,MyOtherComposable - # -- You can add composables here that you don't want to count as content emitters (e.g. custom dialogs or modals) - # contentEmittersDenylist: MyNonEmitterComposable - MutableParams: - active: true - MutableStateAutoboxing: - active: true - MutableStateParam: - active: true - ParameterNaming: - active: true - # -- You can optionally have a list of types to be treated as composable lambdas (e.g. typedefs or fun interfaces not picked up automatically) - # treatAsComposableLambda: MyComposableLambdaType - PreviewAnnotationNaming: - active: true - PreviewPublic: - active: true - RememberMissing: - active: true - RememberContentMissing: - active: true - UnstableCollections: - active: false # Opt-in, disabled by default. Turn on if you want to enforce this (e.g. you have strong skipping disabled) - ViewModelForwarding: - active: true - # -- You can optionally use this rule on things other than types ending in "ViewModel" or "Presenter" (which are the defaults). You can add your own via a regex here: - # allowedStateHolderNames: .*ViewModel,.*Presenter - # -- You can optionally add an allowlist for Composable names that won't be affected by this rule - # allowedForwarding: .*Content,.*FancyStuff - # -- You can optionally add an allowlist for ViewModel/StateHolder names that won't be affected by this rule - # allowedForwardingOfTypes: PotatoViewModel,(Apple|Banana)ViewModel,.*FancyViewModel - ViewModelInjection: - active: true - # -- You can optionally add your own ViewModel factories here - # viewModelFactories: hiltViewModel,potatoViewModel - -comments: - CommentOverPrivateProperty: - active: true - UndocumentedPublicClass: - active: true - excludes: ['**/*.kt'] - includes: ['**/detekt-api/src/main/**/api/*.kt'] - UndocumentedPublicFunction: - active: true - excludes: ['**/*.kt'] - includes: ['**/detekt-api/src/main/**/api/*.kt'] - -complexity: - StringLiteralDuplication: - active: true - excludes: ['**/test/**', '**/*Test.kt', '**/*Spec.kt'] - threshold: 5 - ignoreAnnotation: true - excludeStringsWithLessThan5Characters: true - ignoreStringsRegex: '$^' - ignoreAnnotated: ['Preview', 'PreviewLightDark', 'PreviewComponent'] - ComplexInterface: - active: false - threshold: 10 - includeStaticDeclarations: false - includePrivateDeclarations: false - CyclomaticComplexMethod: - active: true - ignoreSingleWhenExpression: true - LargeClass: - active: true - excludes: ['**/test/**', '**/*.Test.kt', '**/*.Spec.kt'] - MethodOverloading: - active: true - TooManyFunctions: - active: false - ignoreAnnotatedFunctions: ['Composable'] - excludes: ['**/test/**', '**/functionalTest/**'] - LongMethod: - active: true - threshold: 60 - ignoreAnnotated: ['Composable'] - LongParameterList: - active: false - ignoreAnnotated: ['Composable', 'Test', 'GET', 'POST'] - -coroutines: - active: true - GlobalCoroutineUsage: - active: true - RedundantSuspendModifier: - active: true - SleepInsteadOfDelay: - active: true - SuspendFunWithFlowReturnType: - active: true - -exceptions: - InstanceOfCheckForException: - active: true - NotImplementedDeclaration: - active: true - ObjectExtendsThrowable: - active: true - RethrowCaughtException: - active: true - ReturnFromFinally: - active: true - ThrowingExceptionFromFinally: - active: true - ThrowingExceptionInMain: - active: true - ThrowingExceptionsWithoutMessageOrCause: - active: true - ThrowingNewInstanceOfSameException: - active: true - -formatting: - active: true - android: false - autoCorrect: true - ContextReceiverMapping: - active: true - Filename: - active: false - MaximumLineLength: - active: false - ParameterListSpacing: - active: true - TypeParameterListSpacing: - active: true - Indentation: - active: true - indentSize: 4 - TrailingCommaOnCallSite: - active: true - TrailingCommaOnDeclarationSite: - active: true - -naming: - ClassNaming: - ignoreAnnotated: ['org.junit.jupiter.api.Nested'] - FunctionNaming: - active: true - excludes: [] - ignoreAnnotated: ['Test', 'ParameterizedTest', 'RepeatedTest', 'TestFactory', 'Composable'] - TopLevelPropertyNaming: - constantPattern: '[a-z][_A-Za-z0-9]*|[A-Z][_A-Z0-9]*' - InvalidPackageDeclaration: - active: true - excludes: ['**/build-logic/**/*.kt', '**/*.kts'] - NoNameShadowing: - active: true - NonBooleanPropertyPrefixedWithIs: - active: true - VariableMaxLength: - active: true - VariableMinLength: - active: true - -performance: - SpreadOperator: - excludes: ['**/test/**', '**/functionalTest/**'] - -potential-bugs: - AvoidReferentialEquality: - active: true - DontDowncastCollectionTypes: - active: true - ElseCaseInsteadOfExhaustiveWhen: - active: true - ExitOutsideMain: - active: false - HasPlatformType: - active: true - IgnoredReturnValue: - active: true - ImplicitUnitReturnType: - active: true - MapGetWithNotNullAssertionOperator: - active: true - UnconditionalJumpStatementInLoop: - active: true - UnreachableCatchBlock: - active: true - UnsafeCast: - active: true - excludes: ['**/test/**', '**/*.Test.kt', '**/*.Spec.kt'] - UselessPostfixExpression: - active: true - -style: - BracesOnIfStatements: - active: true - singleLine: 'consistent' - multiLine: 'consistent' - CanBeNonNullable: - active: true - CascadingCallWrapping: - active: false - ClassOrdering: - active: true - CollapsibleIfStatements: - active: true - DestructuringDeclarationWithTooManyEntries: - active: true - EqualsOnSignatureLine: - active: true - ExplicitCollectionElementAccessMethod: - active: true - ExplicitItLambdaParameter: - active: true - ForbiddenComment: - active: true - comments: - - value: 'FIXME:' - reason: 'Forbidden FIXME todo marker in comment, please fix the problem.' - - value: 'STOPSHIP:' - reason: 'Forbidden STOPSHIP todo marker in comment, please address the problem before shipping the code.' - - value: 'TODO:' - reason: 'Forbidden TODO todo marker in comment, please do the changes.' - - value: '@author' - reason: 'Authors are not recorded in KDoc.' - - value: '@requiresTypeResolution' - reason: 'Use @RequiresTypeResolution annotation on the class instead.' - excludes: ['**/detekt-rules-style/**/ForbiddenComment.kt'] - ForbiddenImport: - active: true - imports: - - value: 'org.assertj.core.api.Assertions' - reason: 'Import Assertions.assertThat instead.' - - value: 'org.junit.jupiter.api.Assertions*' - reason: 'Use AssertJ assertions instead.' - - value: 'org.junit.jupiter.api.assertAll' - reason: 'Use AssertJ assertSoftly instead.' - - value: 'org.junit.jupiter.api.assertThrows' - reason: 'Use AssertJ assertThatCode { }.isInstanceOf() or assertThatExceptionOfType().isThrownBy { } instead.' - - value: 'org.junit.jupiter.api.assertDoesNotThrow' - reason: 'Use AssertJ assertThatCode { }.doesNotThrowAnyException() instead.' - # These don't have AssertJ alternatives, so just allow them: - #- value: 'org.junit.jupiter.api.fail' - #- value: 'org.junit.jupiter.api.assertTimeout' - #- value: 'org.junit.jupiter.api.assertTimeoutPreemptively' - - value: 'java.util.stream.*' - reason: "Use Kotlin's sequences instead." - ForbiddenMethodCall: - active: true - methods: - - 'kotlin.io.print' - - 'kotlin.io.println' - - 'java.net.URL.openStream' - - 'java.lang.Class.getResourceAsStream' - - 'java.lang.ClassLoader.getResourceAsStream' - - 'org.jetbrains.kotlin.diagnostics.DiagnosticUtils.getLineAndColumnInPsiFile' - ForbiddenVoid: - active: true - MagicNumber: - active: false - excludes: ['**/test/**', '**/*Test.kt', '**/*Spec.kt'] - ignorePropertyDeclaration: true - ignoreAnnotation: true - ignoreEnums: true - ignoreNumbers: - - '-1' - - '0' - - '1' - - '2' - - '100' - - '1000' - MandatoryBracesLoops: - active: true - MaxLineLength: - active: true - excludes: ['**/test/**', '**/*Test.kt', '**/*Spec.kt'] - excludeCommentStatements: true - autoCorrect: true - NestedClassesVisibility: - active: true - ObjectLiteralToLambda: - active: true - PreferToOverPairSyntax: - active: true - RedundantExplicitType: - active: true - RedundantHigherOrderMapUsage: - active: true - RedundantVisibilityModifierRule: - active: true - ReturnCount: - active: true - excludeGuardClauses: true - SpacingBetweenPackageAndImports: - active: true - TrimMultilineRawString: - active: true - UnderscoresInNumericLiterals: - active: true - UnnecessaryAnnotationUseSiteTarget: - active: true - UnnecessaryBackticks: - active: true - UnnecessaryFilter: - active: true - UnnecessaryLet: - active: true - UnnecessaryInnerClass: - active: true - ignoreAnnotated: ['Nested'] - UntilInsteadOfRangeTo: - active: true - UnusedImports: - active: false # formatting already have this rule enabled - UnusedPrivateMember: - active: false - allowedNames: '(_|ignored|expected)' - UseAnyOrNoneInsteadOfFind: - active: true - UseCheckOrError: - active: true - UseEmptyCounterpart: - active: true - UseIfEmptyOrIfBlank: - active: true - UseIsNullOrEmpty: - active: true - UseLet: - active: true - UseOrEmpty: - active: true - UseRequire: - active: true - UseRequireNotNull: - active: true - VarCouldBeVal: - active: true - ignoreAnnotated: ['Parameter'] - WildcardImport: - active: true - excludeImports: [] diff --git a/feature/trip-planner/ui/src/main/kotlin/xyz/ksharma/krail/trip/planner/ui/navigation/TripPlannerDestinations.kt b/feature/trip-planner/ui/src/main/kotlin/xyz/ksharma/krail/trip/planner/ui/navigation/TripPlannerDestinations.kt index 5de59eea..6e7b888c 100644 --- a/feature/trip-planner/ui/src/main/kotlin/xyz/ksharma/krail/trip/planner/ui/navigation/TripPlannerDestinations.kt +++ b/feature/trip-planner/ui/src/main/kotlin/xyz/ksharma/krail/trip/planner/ui/navigation/TripPlannerDestinations.kt @@ -15,7 +15,7 @@ import xyz.ksharma.krail.trip.planner.ui.usualride.usualRideDestination * It contains all the screens in the feature Trip Planner. */ fun NavGraphBuilder.tripPlannerDestinations( - // TODO - do not wanna add NavController here, but moving all callbacks to app module is not scaleable. + // TODO - do not wanna add NavController here, but moving all callbacks to android-app module is not scaleable. navController: NavHostController, ) { navigation(startDestination = SavedTripsRoute) { diff --git a/feature/trip-planner/ui/src/main/kotlin/xyz/ksharma/krail/trip/planner/ui/timetable/TimeTableViewModel.kt b/feature/trip-planner/ui/src/main/kotlin/xyz/ksharma/krail/trip/planner/ui/timetable/TimeTableViewModel.kt index ace2c8bb..29faab37 100644 --- a/feature/trip-planner/ui/src/main/kotlin/xyz/ksharma/krail/trip/planner/ui/timetable/TimeTableViewModel.kt +++ b/feature/trip-planner/ui/src/main/kotlin/xyz/ksharma/krail/trip/planner/ui/timetable/TimeTableViewModel.kt @@ -51,7 +51,7 @@ class TimeTableViewModel @Inject constructor( private val _isLoading: MutableStateFlow = MutableStateFlow(false) val isLoading: StateFlow = _isLoading - // Will start fetching the trip as soon as the screen is visible, which means if app goes + // Will start fetching the trip as soon as the screen is visible, which means if android-app goes // to background and come back up again, the API call will be made. // Probably good to have data up to date. .onStart { diff --git a/gradle/build-logic/convention/build.gradle.kts b/gradle/build-logic/convention/build.gradle.kts index 2ba442ad..36c0f058 100644 --- a/gradle/build-logic/convention/build.gradle.kts +++ b/gradle/build-logic/convention/build.gradle.kts @@ -20,6 +20,10 @@ tasks.withType().configureEach { } } +kotlin { + jvmToolchain(javaVersion) +} + dependencies { compileOnly(libs.kotlin.gradlePlugin) compileOnly(libs.android.gradlePlugin) @@ -28,6 +32,50 @@ dependencies { } gradlePlugin { + plugins { + /** + * Supports building UIs for Android, Desktop, and Web using Jetpack Compose. + */ + register("composeMultiplatform") { + id = "krail.compose.multiplatform" + implementationClass = "xyz.ksharma.krail.gradle.ComposeMultiplatformConventionPlugin" + } + + /** + * Configures the project for Android application development. + */ + register("androidApplication") { + id = "krail.android.application" + implementationClass = "xyz.ksharma.krail.gradle.AndroidApplicationConventionPlugin" + } + + /** + * Configures the project for developing Android libraries. + */ + register("androidLibrary") { + id = "krail.android.library" + implementationClass = "xyz.ksharma.krail.gradle.AndroidLibraryConventionPlugin" + } + + /** + * Adds support for Kotlin in Android projects. + */ + register("kotlinAndroid") { + id = "krail.kotlin.android" + implementationClass = "xyz.ksharma.krail.gradle.KotlinAndroidConventionPlugin" + } + + /** + * Configures the project for Kotlin Multiplatform development, supporting JVM, Android, iOS, + * and JS targets. + */ + register("kotlinMultiplatform") { + id = "krail.kotlin.multiplatform" + implementationClass = "xyz.ksharma.krail.gradle.KotlinMultiplatformConventionPlugin" + } + } +} + /* plugins { register("application") { @@ -38,14 +86,10 @@ gradlePlugin { id = "krail.android.library" implementationClass = "AndroidLibraryConventionPlugin" } - register("androidLibraryCompose") { - id = "krail.android.library.compose" - implementationClass = "AndroidLibraryComposeConventionPlugin" - } + register("jvmLibrary") { id = "krail.jvm.library" implementationClass = "JvmLibraryConventionPlugin" } } */ -} diff --git a/gradle/build-logic/convention/src/main/kotlin/xyz/ksharma/krail/AndroidLibraryComposeConventionPlugin.kt b/gradle/build-logic/convention/src/main/kotlin/xyz/ksharma/krail/AndroidLibraryComposeConventionPlugin.kt deleted file mode 100644 index f89d65e3..00000000 --- a/gradle/build-logic/convention/src/main/kotlin/xyz/ksharma/krail/AndroidLibraryComposeConventionPlugin.kt +++ /dev/null @@ -1,64 +0,0 @@ -package xyz.ksharma.krail - -import com.android.build.gradle.LibraryExtension -import org.gradle.api.JavaVersion -import org.gradle.api.Plugin -import org.gradle.api.Project -import org.gradle.api.artifacts.VersionCatalogsExtension -import org.gradle.kotlin.dsl.configure -import org.gradle.kotlin.dsl.getByType -import org.gradle.kotlin.dsl.withType -import org.jetbrains.kotlin.gradle.dsl.JvmTarget -import org.jetbrains.kotlin.gradle.tasks.KotlinCompile - -@Suppress("UNUSED") -class AndroidLibraryComposeConventionPlugin : Plugin { - override fun apply(target: Project) { - with(target) { - val libs = extensions.getByType().named("libs") - val javaVersion = libs.findVersion("java").get().toString().toInt() - val minSdkVersion = libs.findVersion("minSdk").get().toString().toInt() - val compileSdkVersion = libs.findVersion("compileSdk").get().toString().toInt() - - with(pluginManager) { - apply("com.android.library") - apply("org.jetbrains.kotlin.android") - apply("org.jetbrains.kotlin.plugin.compose") - } - - extensions.configure { - compileSdk = compileSdkVersion - - defaultConfig { - minSdk = minSdkVersion - } - - buildTypes { - release { - isMinifyEnabled = false - } - } - - compileOptions { - sourceCompatibility = JavaVersion.values()[javaVersion - 1] - targetCompatibility = JavaVersion.values()[javaVersion - 1] - } - - tasks.withType().configureEach { - compilerOptions { - jvmTarget.set(JvmTarget.JVM_17) - - freeCompilerArgs.add("-opt-in=kotlin.RequiresOptIn") - freeCompilerArgs.add("-opt-in=kotlinx.coroutines.FlowPreview") - freeCompilerArgs.add("-opt-in=kotlinx.coroutines.ExperimentalCoroutinesApi") - } - } - - buildFeatures { - compose = true - buildConfig = true - } - } - } - } -} diff --git a/gradle/build-logic/convention/src/main/kotlin/xyz/ksharma/krail/AndroidLibraryConventionPlugin.kt b/gradle/build-logic/convention/src/main/kotlin/xyz/ksharma/krail/AndroidLibraryConventionPlugin.kt deleted file mode 100644 index c8c898d0..00000000 --- a/gradle/build-logic/convention/src/main/kotlin/xyz/ksharma/krail/AndroidLibraryConventionPlugin.kt +++ /dev/null @@ -1,67 +0,0 @@ -package xyz.ksharma.krail - -import com.android.build.gradle.LibraryExtension -import org.gradle.api.JavaVersion -import org.gradle.api.Plugin -import org.gradle.api.Project -import org.gradle.api.artifacts.VersionCatalogsExtension -import org.gradle.kotlin.dsl.configure -import org.gradle.kotlin.dsl.dependencies -import org.gradle.kotlin.dsl.getByType -import org.gradle.kotlin.dsl.withType -import org.jetbrains.kotlin.gradle.dsl.JvmTarget -import org.jetbrains.kotlin.gradle.tasks.KotlinCompile - -@Suppress("UNUSED") -class AndroidLibraryConventionPlugin : Plugin { - override fun apply(target: Project) { - with(target) { - val libs = extensions.getByType().named("libs") - val javaVersion = libs.findVersion("java").get().toString().toInt() - val minSdkVersion = libs.findVersion("minSdk").get().toString().toInt() - val compileSdkVersion = libs.findVersion("compileSdk").get().toString().toInt() - - with(pluginManager) { - apply("com.android.library") - apply("org.jetbrains.kotlin.android") - } - - extensions.configure { - compileSdk = compileSdkVersion - - defaultConfig { - minSdk = minSdkVersion - } - - buildTypes { - release { - isMinifyEnabled = false - } - } - - compileOptions { - sourceCompatibility = JavaVersion.values()[javaVersion - 1] - targetCompatibility = JavaVersion.values()[javaVersion - 1] - } - - tasks.withType().configureEach { - compilerOptions { - jvmTarget.set(JvmTarget.JVM_17) - - freeCompilerArgs.add("-opt-in=kotlin.RequiresOptIn") - freeCompilerArgs.add("-opt-in=kotlinx.coroutines.FlowPreview") - freeCompilerArgs.add("-opt-in=kotlinx.coroutines.ExperimentalCoroutinesApi") - } - } - - buildFeatures { - buildConfig = true - } - } - - dependencies { - "implementation"(libs.findLibrary("timber").get()) - } - } - } -} diff --git a/gradle/build-logic/convention/src/main/kotlin/xyz/ksharma/krail/ApplicationConventionPlugin.kt b/gradle/build-logic/convention/src/main/kotlin/xyz/ksharma/krail/ApplicationConventionPlugin.kt deleted file mode 100644 index 0189dcef..00000000 --- a/gradle/build-logic/convention/src/main/kotlin/xyz/ksharma/krail/ApplicationConventionPlugin.kt +++ /dev/null @@ -1,81 +0,0 @@ -package xyz.ksharma.krail - -import com.android.build.api.dsl.ApplicationExtension -import org.gradle.api.JavaVersion -import org.gradle.api.Plugin -import org.gradle.api.Project -import org.gradle.api.artifacts.VersionCatalogsExtension -import org.gradle.kotlin.dsl.apply -import org.gradle.kotlin.dsl.configure -import org.gradle.kotlin.dsl.dependencies -import org.gradle.kotlin.dsl.getByType -import org.gradle.kotlin.dsl.withType -import org.jetbrains.kotlin.gradle.dsl.JvmTarget -import org.jetbrains.kotlin.gradle.tasks.KotlinCompile - -@Suppress("UNUSED") -class ApplicationConventionPlugin : Plugin { - override fun apply(target: Project) { - with(target) { - val libs = extensions.getByType().named("libs") - val javaVersion = libs.findVersion("java").get().toString().toInt() - val minSdkVersion = libs.findVersion("minSdk").get().toString().toInt() - val targetSdkVersion = libs.findVersion("targetSdk").get().toString().toInt() - val compileSdkVersion = libs.findVersion("compileSdk").get().toString().toInt() - val kotlinVersion = libs.findVersion("kotlin").get().toString() - - with(pluginManager) { - apply("com.android.application") - apply("org.jetbrains.kotlin.android") - apply("org.jetbrains.kotlin.plugin.compose") - apply("com.google.gms.google-services") - } - extensions.configure { - compileSdk = compileSdkVersion - - defaultConfig { - minSdk = minSdkVersion - targetSdk = targetSdkVersion - vectorDrawables { - useSupportLibrary = true - } - } - - compileOptions { - sourceCompatibility = JavaVersion.values()[javaVersion - 1] - targetCompatibility = JavaVersion.values()[javaVersion - 1] - } - - tasks.withType().configureEach { - compilerOptions { - jvmTarget.set(JvmTarget.JVM_17) - - freeCompilerArgs.add("-opt-in=kotlin.RequiresOptIn") - freeCompilerArgs.add("-opt-in=kotlinx.coroutines.FlowPreview") - freeCompilerArgs.add("-opt-in=kotlinx.coroutines.ExperimentalCoroutinesApi") - } - } - - buildFeatures { - compose = true - buildConfig = true - } - - composeOptions { - // Kotlin and Compose compiler version is same after K2 is released. - kotlinCompilerExtensionVersion = kotlinVersion - } - - packaging { - resources { - excludes += "/META-INF/{AL2.0,LGPL2.1}" - } - } - - dependencies { - "implementation"(libs.findLibrary("timber").get()) - } - } - } - } -} diff --git a/gradle/build-logic/convention/src/main/kotlin/xyz/ksharma/krail/JvmLibraryConventionPlugin.kt b/gradle/build-logic/convention/src/main/kotlin/xyz/ksharma/krail/JvmLibraryConventionPlugin.kt deleted file mode 100644 index 1f135eae..00000000 --- a/gradle/build-logic/convention/src/main/kotlin/xyz/ksharma/krail/JvmLibraryConventionPlugin.kt +++ /dev/null @@ -1,46 +0,0 @@ -package xyz.ksharma.krail - -import org.gradle.api.JavaVersion -import org.gradle.api.Plugin -import org.gradle.api.Project -import org.gradle.api.artifacts.VersionCatalogsExtension -import org.gradle.api.plugins.JavaPluginExtension -import org.gradle.kotlin.dsl.configure -import org.gradle.kotlin.dsl.getByType -import org.gradle.kotlin.dsl.provideDelegate -import org.gradle.kotlin.dsl.withType -import org.jetbrains.kotlin.gradle.dsl.JvmTarget -import org.jetbrains.kotlin.gradle.tasks.KotlinCompilationTask -import org.jetbrains.kotlin.gradle.tasks.KotlinCompile - -class JvmLibraryConventionPlugin : Plugin { - override fun apply(target: Project) { - with(target) { - val libs = extensions.getByType().named("libs") - val javaVersion = libs.findVersion("java").get().toString().toInt() - - with(pluginManager) { - apply("org.jetbrains.kotlin.jvm") - } - - extensions.configure { - sourceCompatibility = JavaVersion.values()[javaVersion - 1] - targetCompatibility = JavaVersion.values()[javaVersion - 1] - } - - tasks.withType().configureEach { - - compilerOptions { - val warningsAsErrors: String? by project - allWarningsAsErrors.set(warningsAsErrors.toBoolean()) - - jvmTarget.set(JvmTarget.JVM_17) - - freeCompilerArgs.add("-opt-in=kotlin.RequiresOptIn") - freeCompilerArgs.add("-opt-in=kotlinx.coroutines.FlowPreview") - freeCompilerArgs.add("-opt-in=kotlinx.coroutines.ExperimentalCoroutinesApi") - } - } - } - } -} diff --git a/gradle/build-logic/convention/src/main/kotlin/xyz/ksharma/krail/gradle/AndroidApplicationConventionPlugin.kt b/gradle/build-logic/convention/src/main/kotlin/xyz/ksharma/krail/gradle/AndroidApplicationConventionPlugin.kt index c909d744..5bc31781 100644 --- a/gradle/build-logic/convention/src/main/kotlin/xyz/ksharma/krail/gradle/AndroidApplicationConventionPlugin.kt +++ b/gradle/build-logic/convention/src/main/kotlin/xyz/ksharma/krail/gradle/AndroidApplicationConventionPlugin.kt @@ -8,8 +8,6 @@ class AndroidApplicationConventionPlugin : Plugin { with(target) { with(pluginManager) { apply("com.android.application") - apply("org.gradle.android.cache-fix") - apply("org.jetbrains.kotlin.android") // Support Kotlin in Android projects } configureAndroid() diff --git a/gradle/build-logic/convention/src/main/kotlin/xyz/ksharma/krail/gradle/AndroidLibraryConventionPlugin.kt b/gradle/build-logic/convention/src/main/kotlin/xyz/ksharma/krail/gradle/AndroidLibraryConventionPlugin.kt index 008c51bd..82a804d3 100644 --- a/gradle/build-logic/convention/src/main/kotlin/xyz/ksharma/krail/gradle/AndroidLibraryConventionPlugin.kt +++ b/gradle/build-logic/convention/src/main/kotlin/xyz/ksharma/krail/gradle/AndroidLibraryConventionPlugin.kt @@ -8,7 +8,6 @@ class AndroidLibraryConventionPlugin : Plugin { with(target) { with(pluginManager) { apply("com.android.library") - apply("org.gradle.android.cache-fix") } configureAndroid() } diff --git a/gradle/build-logic/convention/src/main/kotlin/xyz/ksharma/krail/gradle/ComposeMultiplatformConventionPlugin.kt b/gradle/build-logic/convention/src/main/kotlin/xyz/ksharma/krail/gradle/ComposeMultiplatformConventionPlugin.kt index 2eeaef8e..09683492 100644 --- a/gradle/build-logic/convention/src/main/kotlin/xyz/ksharma/krail/gradle/ComposeMultiplatformConventionPlugin.kt +++ b/gradle/build-logic/convention/src/main/kotlin/xyz/ksharma/krail/gradle/ComposeMultiplatformConventionPlugin.kt @@ -2,26 +2,16 @@ package xyz.ksharma.krail.gradle import org.gradle.api.Plugin import org.gradle.api.Project -import org.gradle.kotlin.dsl.configure -import org.jetbrains.kotlin.gradle.dsl.KotlinMultiplatformExtension class ComposeMultiplatformConventionPlugin : Plugin { override fun apply(target: Project) = with(target) { with(pluginManager) { - apply("org.jetbrains.kotlin.multiplatform") // Kotlin Multiplatform plugin - JVM, Android, iOS, JS - apply("org.jetbrains.compose") // Compose Multiplatform plugin - Android Desktop, Web - apply("org.jetbrains.kotlin.plugin.compose") - } - - extensions.configure { - applyDefaultHierarchyTemplate() + // Compose Multiplatform plugin - Supports building UIs for Android, Desktop, and Web + apply("org.jetbrains.compose") - - iosArm64() - iosSimulatorArm64() - - configureJava() + // Enables Compose-specific features in Kotlin projects + apply("org.jetbrains.kotlin.plugin.compose") } } } diff --git a/gradle/build-logic/convention/src/main/kotlin/xyz/ksharma/krail/gradle/KotlinAndroidConventionPlugin.kt b/gradle/build-logic/convention/src/main/kotlin/xyz/ksharma/krail/gradle/KotlinAndroidConventionPlugin.kt new file mode 100644 index 00000000..6ee1d27f --- /dev/null +++ b/gradle/build-logic/convention/src/main/kotlin/xyz/ksharma/krail/gradle/KotlinAndroidConventionPlugin.kt @@ -0,0 +1,16 @@ +package xyz.ksharma.krail.gradle + +import org.gradle.api.Plugin +import org.gradle.api.Project +import org.gradle.kotlin.dsl.configure +import org.jetbrains.kotlin.gradle.dsl.KotlinMultiplatformExtension + +class KotlinAndroidConventionPlugin : Plugin { + override fun apply(target: Project) = with(target) { + + with(pluginManager) { + apply("org.jetbrains.kotlin.android") // Support Kotlin in Android projects + } + configureJava() + } +} diff --git a/gradle/build-logic/convention/src/main/kotlin/xyz/ksharma/krail/gradle/KotlinMultiplatformConventionPlugin.kt b/gradle/build-logic/convention/src/main/kotlin/xyz/ksharma/krail/gradle/KotlinMultiplatformConventionPlugin.kt new file mode 100644 index 00000000..ad747bbd --- /dev/null +++ b/gradle/build-logic/convention/src/main/kotlin/xyz/ksharma/krail/gradle/KotlinMultiplatformConventionPlugin.kt @@ -0,0 +1,24 @@ +package xyz.ksharma.krail.gradle + +import org.gradle.api.Plugin +import org.gradle.api.Project +import org.gradle.kotlin.dsl.configure +import org.jetbrains.kotlin.gradle.dsl.KotlinMultiplatformExtension + +class KotlinMultiplatformConventionPlugin : Plugin { + override fun apply(target: Project) = with(target) { + + with(pluginManager) { + apply("org.jetbrains.kotlin.multiplatform") // Kotlin Multiplatform plugin - JVM, Android, iOS, JS + } + + extensions.configure { + applyDefaultHierarchyTemplate() + + iosArm64() + iosSimulatorArm64() + + configureJava() + } + } +} diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 1fbf3e0f..d565621a 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -95,12 +95,12 @@ cash-paparazzi = { id = "app.cash.paparazzi", version.ref = "paparazzi" } ksp = { id = "com.google.devtools.ksp", version.ref = "ksp" } compose-compiler = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" } google-services = { id = "com.google.gms.google-services", version = "4.4.2" } +composeMultiplatform = { id = "org.jetbrains.compose", version.ref = "compose-multiplatform" } +kotlinMultiplatform = { id = "org.jetbrains.kotlin.multiplatform", version.ref = "kotlin" } #Convention Plugins krail-android-application = { id = "krail.android.application", version = "unspecified" } -krail-android-hilt = { id = "krail.android.hilt", version = "unspecified" } +krail-compose-multiplatform = { id = "krail.compose.multiplatform", version = "unspecified" } krail-android-library = { id = "krail.android.library", version = "unspecified" } -krail-android-library-compose = { id = "krail.android.library.compose", version = "unspecified" } -krail-jvm-library = { id = "krail.jvm.library", version = "unspecified" } -composeMultiplatform = { id = "org.jetbrains.compose", version.ref = "compose-multiplatform" } -kotlinMultiplatform = { id = "org.jetbrains.kotlin.multiplatform", version.ref = "kotlin" } +krail-kotlin-android = { id = "krail.kotlin.android", version = "unspecified" } +krail-kotlin-multiplatform = { id = "krail.kotlin.multiplatform", version = "unspecified" } diff --git a/settings.gradle.kts b/settings.gradle.kts index 59ee15fb..5c3a61c9 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -30,6 +30,7 @@ dependencyResolutionManagement { } rootProject.name = "Krail" +include(":android-app") //include(":composeApp") /* include(":core:coroutines-ext") From d42e6c1ddb6959b4c325320ff92c210e7f79601b Mon Sep 17 00:00:00 2001 From: Karan Sharma <55722391+ksharma-xyz@users.noreply.github.com> Date: Fri, 15 Nov 2024 21:42:45 +1100 Subject: [PATCH 13/67] Android App Builds Yay! --- .../src/main}/AndroidManifest.xml | 0 .../xyz/ksharma/krail/KrailApplication.kt | 0 .../kotlin/xyz/ksharma/krail/MainActivity.kt | 32 +++++++++++++++++++ .../main}/res/color/gradient_background.xml | 0 .../res/drawable/ic_launcher_background.xml | 0 .../res/drawable/ic_launcher_foreground.xml | 0 .../res/mipmap-anydpi-v26/ic_launcher.xml | 0 .../mipmap-anydpi-v26/ic_launcher_round.xml | 0 .../src/main}/res/values-night/colors.xml | 0 .../src/main}/res/values/colors.xml | 0 .../src/main}/res/values/strings.xml | 0 android-app/src/main/res/values/themes.xml | 7 ++++ .../src/main}/res/xml/backup_rules.xml | 0 .../main}/res/xml/data_extraction_rules.xml | 0 .../kotlin/xyz/ksharma/krail/MainActivity.kt | 20 ------------ .../src/androidMain/res/values/themes.xml | 6 ---- core/design-system/build.gradle.kts | 1 - .../build-logic/convention/build.gradle.kts | 18 ----------- 18 files changed, 39 insertions(+), 45 deletions(-) rename {composeApp/src/androidMain => android-app/src/main}/AndroidManifest.xml (100%) rename {composeApp/src/androidMain => android-app/src/main}/kotlin/xyz/ksharma/krail/KrailApplication.kt (100%) create mode 100644 android-app/src/main/kotlin/xyz/ksharma/krail/MainActivity.kt rename {composeApp/src/androidMain => android-app/src/main}/res/color/gradient_background.xml (100%) rename {composeApp/src/androidMain => android-app/src/main}/res/drawable/ic_launcher_background.xml (100%) rename {composeApp/src/androidMain => android-app/src/main}/res/drawable/ic_launcher_foreground.xml (100%) rename {composeApp/src/androidMain => android-app/src/main}/res/mipmap-anydpi-v26/ic_launcher.xml (100%) rename {composeApp/src/androidMain => android-app/src/main}/res/mipmap-anydpi-v26/ic_launcher_round.xml (100%) rename {composeApp/src/androidMain => android-app/src/main}/res/values-night/colors.xml (100%) rename {composeApp/src/androidMain => android-app/src/main}/res/values/colors.xml (100%) rename {composeApp/src/androidMain => android-app/src/main}/res/values/strings.xml (100%) create mode 100644 android-app/src/main/res/values/themes.xml rename {composeApp/src/androidMain => android-app/src/main}/res/xml/backup_rules.xml (100%) rename {composeApp/src/androidMain => android-app/src/main}/res/xml/data_extraction_rules.xml (100%) delete mode 100644 composeApp/src/androidMain/kotlin/xyz/ksharma/krail/MainActivity.kt delete mode 100644 composeApp/src/androidMain/res/values/themes.xml diff --git a/composeApp/src/androidMain/AndroidManifest.xml b/android-app/src/main/AndroidManifest.xml similarity index 100% rename from composeApp/src/androidMain/AndroidManifest.xml rename to android-app/src/main/AndroidManifest.xml diff --git a/composeApp/src/androidMain/kotlin/xyz/ksharma/krail/KrailApplication.kt b/android-app/src/main/kotlin/xyz/ksharma/krail/KrailApplication.kt similarity index 100% rename from composeApp/src/androidMain/kotlin/xyz/ksharma/krail/KrailApplication.kt rename to android-app/src/main/kotlin/xyz/ksharma/krail/KrailApplication.kt diff --git a/android-app/src/main/kotlin/xyz/ksharma/krail/MainActivity.kt b/android-app/src/main/kotlin/xyz/ksharma/krail/MainActivity.kt new file mode 100644 index 00000000..e946ee11 --- /dev/null +++ b/android-app/src/main/kotlin/xyz/ksharma/krail/MainActivity.kt @@ -0,0 +1,32 @@ +package xyz.ksharma.krail + +import android.os.Bundle +import androidx.activity.ComponentActivity +import androidx.activity.compose.setContent +import androidx.activity.enableEdgeToEdge +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color + +//import xyz.ksharma.krail.design.system.theme.KrailTheme + +class MainActivity : ComponentActivity() { + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + enableEdgeToEdge() + setContent { + Column(modifier = Modifier + .fillMaxSize() + .background(color = Color.Red)) { + } + /* + KrailTheme { + KrailApp() + } + */ + } + } +} diff --git a/composeApp/src/androidMain/res/color/gradient_background.xml b/android-app/src/main/res/color/gradient_background.xml similarity index 100% rename from composeApp/src/androidMain/res/color/gradient_background.xml rename to android-app/src/main/res/color/gradient_background.xml diff --git a/composeApp/src/androidMain/res/drawable/ic_launcher_background.xml b/android-app/src/main/res/drawable/ic_launcher_background.xml similarity index 100% rename from composeApp/src/androidMain/res/drawable/ic_launcher_background.xml rename to android-app/src/main/res/drawable/ic_launcher_background.xml diff --git a/composeApp/src/androidMain/res/drawable/ic_launcher_foreground.xml b/android-app/src/main/res/drawable/ic_launcher_foreground.xml similarity index 100% rename from composeApp/src/androidMain/res/drawable/ic_launcher_foreground.xml rename to android-app/src/main/res/drawable/ic_launcher_foreground.xml diff --git a/composeApp/src/androidMain/res/mipmap-anydpi-v26/ic_launcher.xml b/android-app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml similarity index 100% rename from composeApp/src/androidMain/res/mipmap-anydpi-v26/ic_launcher.xml rename to android-app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml diff --git a/composeApp/src/androidMain/res/mipmap-anydpi-v26/ic_launcher_round.xml b/android-app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml similarity index 100% rename from composeApp/src/androidMain/res/mipmap-anydpi-v26/ic_launcher_round.xml rename to android-app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml diff --git a/composeApp/src/androidMain/res/values-night/colors.xml b/android-app/src/main/res/values-night/colors.xml similarity index 100% rename from composeApp/src/androidMain/res/values-night/colors.xml rename to android-app/src/main/res/values-night/colors.xml diff --git a/composeApp/src/androidMain/res/values/colors.xml b/android-app/src/main/res/values/colors.xml similarity index 100% rename from composeApp/src/androidMain/res/values/colors.xml rename to android-app/src/main/res/values/colors.xml diff --git a/composeApp/src/androidMain/res/values/strings.xml b/android-app/src/main/res/values/strings.xml similarity index 100% rename from composeApp/src/androidMain/res/values/strings.xml rename to android-app/src/main/res/values/strings.xml diff --git a/android-app/src/main/res/values/themes.xml b/android-app/src/main/res/values/themes.xml new file mode 100644 index 00000000..931e639d --- /dev/null +++ b/android-app/src/main/res/values/themes.xml @@ -0,0 +1,7 @@ + + + + diff --git a/composeApp/src/androidMain/res/xml/backup_rules.xml b/android-app/src/main/res/xml/backup_rules.xml similarity index 100% rename from composeApp/src/androidMain/res/xml/backup_rules.xml rename to android-app/src/main/res/xml/backup_rules.xml diff --git a/composeApp/src/androidMain/res/xml/data_extraction_rules.xml b/android-app/src/main/res/xml/data_extraction_rules.xml similarity index 100% rename from composeApp/src/androidMain/res/xml/data_extraction_rules.xml rename to android-app/src/main/res/xml/data_extraction_rules.xml diff --git a/composeApp/src/androidMain/kotlin/xyz/ksharma/krail/MainActivity.kt b/composeApp/src/androidMain/kotlin/xyz/ksharma/krail/MainActivity.kt deleted file mode 100644 index 331e1bd0..00000000 --- a/composeApp/src/androidMain/kotlin/xyz/ksharma/krail/MainActivity.kt +++ /dev/null @@ -1,20 +0,0 @@ -package xyz.ksharma.krail - -import android.os.Bundle -import androidx.activity.ComponentActivity -import androidx.activity.compose.setContent -import androidx.activity.enableEdgeToEdge -import xyz.ksharma.krail.design.system.theme.KrailTheme - -class MainActivity : ComponentActivity() { - - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - enableEdgeToEdge() - setContent { - KrailTheme { - KrailApp() - } - } - } -} diff --git a/composeApp/src/androidMain/res/values/themes.xml b/composeApp/src/androidMain/res/values/themes.xml deleted file mode 100644 index 5c71db05..00000000 --- a/composeApp/src/androidMain/res/values/themes.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - diff --git a/core/design-system/build.gradle.kts b/core/design-system/build.gradle.kts index 3181d038..d8436ac6 100644 --- a/core/design-system/build.gradle.kts +++ b/core/design-system/build.gradle.kts @@ -1,6 +1,5 @@ plugins { alias(libs.plugins.krail.android.library.compose) - alias(libs.plugins.cash.paparazzi) } android { diff --git a/gradle/build-logic/convention/build.gradle.kts b/gradle/build-logic/convention/build.gradle.kts index 36c0f058..24daebf4 100644 --- a/gradle/build-logic/convention/build.gradle.kts +++ b/gradle/build-logic/convention/build.gradle.kts @@ -75,21 +75,3 @@ gradlePlugin { } } } - -/* - plugins { - register("application") { - id = "krail.android.application" - implementationClass = "ApplicationConventionPlugin" - } - register("androidLibrary") { - id = "krail.android.library" - implementationClass = "AndroidLibraryConventionPlugin" - } - - register("jvmLibrary") { - id = "krail.jvm.library" - implementationClass = "JvmLibraryConventionPlugin" - } - } -*/ From b9c9447d19795e956967cbc2d840605c3c14cd2e Mon Sep 17 00:00:00 2001 From: Karan Sharma <55722391+ksharma-xyz@users.noreply.github.com> Date: Fri, 15 Nov 2024 22:06:24 +1100 Subject: [PATCH 14/67] Use design-system in :android-app --- android-app/build.gradle.kts | 16 +++++++------- .../kotlin/xyz/ksharma/krail/MainActivity.kt | 21 ++++++++++--------- core/design-system/build.gradle.kts | 8 +++++-- .../xyz/ksharma/krail/gradle/Android.kt | 5 +++-- .../KotlinMultiplatformConventionPlugin.kt | 4 ++++ gradle/libs.versions.toml | 5 +++-- settings.gradle.kts | 2 +- 7 files changed, 36 insertions(+), 25 deletions(-) diff --git a/android-app/build.gradle.kts b/android-app/build.gradle.kts index f6cf1f02..a8c9e42a 100644 --- a/android-app/build.gradle.kts +++ b/android-app/build.gradle.kts @@ -45,15 +45,15 @@ android { dependencies { - /* // Projects + // Projects implementation(projects.core.designSystem) - implementation(projects.core.network) - implementation(projects.feature.tripPlanner.network.api) - implementation(projects.feature.tripPlanner.network.real) - implementation(projects.feature.tripPlanner.state) - implementation(projects.feature.tripPlanner.ui) - implementation(projects.sandook.api) - implementation(projects.sandook.real)*/ + /* implementation(projects.core.network) + implementation(projects.feature.tripPlanner.network.api) + implementation(projects.feature.tripPlanner.network.real) + implementation(projects.feature.tripPlanner.state) + implementation(projects.feature.tripPlanner.ui) + implementation(projects.sandook.api) + implementation(projects.sandook.real)*/ implementation(libs.activity.compose) implementation(libs.compose.foundation) diff --git a/android-app/src/main/kotlin/xyz/ksharma/krail/MainActivity.kt b/android-app/src/main/kotlin/xyz/ksharma/krail/MainActivity.kt index e946ee11..3ee46d13 100644 --- a/android-app/src/main/kotlin/xyz/ksharma/krail/MainActivity.kt +++ b/android-app/src/main/kotlin/xyz/ksharma/krail/MainActivity.kt @@ -9,8 +9,8 @@ import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color - -//import xyz.ksharma.krail.design.system.theme.KrailTheme +import xyz.ksharma.krail.design.system.components.Text +import xyz.ksharma.krail.design.system.theme.KrailTheme class MainActivity : ComponentActivity() { @@ -18,15 +18,16 @@ class MainActivity : ComponentActivity() { super.onCreate(savedInstanceState) enableEdgeToEdge() setContent { - Column(modifier = Modifier - .fillMaxSize() - .background(color = Color.Red)) { + KrailTheme { + Column( + modifier = Modifier + .fillMaxSize() + .background(color = KrailTheme.colors.surface) + ) { + Text("Hello World") +// KrailApp() + } } - /* - KrailTheme { - KrailApp() - } - */ } } } diff --git a/core/design-system/build.gradle.kts b/core/design-system/build.gradle.kts index d8436ac6..60a8d40d 100644 --- a/core/design-system/build.gradle.kts +++ b/core/design-system/build.gradle.kts @@ -1,5 +1,7 @@ plugins { - alias(libs.plugins.krail.android.library.compose) + alias(libs.plugins.krail.android.library) + alias(libs.plugins.krail.compose.multiplatform) + alias(libs.plugins.krail.kotlin.multiplatform) } android { @@ -12,8 +14,10 @@ dependencies { implementation(libs.compose.ui) implementation(libs.compose.ui.graphics) api(libs.compose.ui.tooling.preview) - implementation(libs.compose.material3) // adding for reading code inspiration. + implementation(libs.compose.material3) + implementation(libs.core.ktx) // adding for reading code inspiration. + // Testing androidTestImplementation(platform(libs.compose.bom)) debugApi(libs.compose.ui.tooling) debugApi(libs.test.composeUiTestManifest) diff --git a/gradle/build-logic/convention/src/main/kotlin/xyz/ksharma/krail/gradle/Android.kt b/gradle/build-logic/convention/src/main/kotlin/xyz/ksharma/krail/gradle/Android.kt index 5179c28a..ef7fcdf0 100644 --- a/gradle/build-logic/convention/src/main/kotlin/xyz/ksharma/krail/gradle/Android.kt +++ b/gradle/build-logic/convention/src/main/kotlin/xyz/ksharma/krail/gradle/Android.kt @@ -41,7 +41,8 @@ fun Project.configureAndroid( } object AndroidVersion { - const val COMPILE_SDK = 35 + // https://developer.android.com/build/releases/gradle-plugin#api-level-support + const val COMPILE_SDK = 34 const val MIN_SDK = 26 // Oreo 8.0 - const val TARGET_SDK = 35 + const val TARGET_SDK = 34 } diff --git a/gradle/build-logic/convention/src/main/kotlin/xyz/ksharma/krail/gradle/KotlinMultiplatformConventionPlugin.kt b/gradle/build-logic/convention/src/main/kotlin/xyz/ksharma/krail/gradle/KotlinMultiplatformConventionPlugin.kt index ad747bbd..3552daf5 100644 --- a/gradle/build-logic/convention/src/main/kotlin/xyz/ksharma/krail/gradle/KotlinMultiplatformConventionPlugin.kt +++ b/gradle/build-logic/convention/src/main/kotlin/xyz/ksharma/krail/gradle/KotlinMultiplatformConventionPlugin.kt @@ -15,6 +15,10 @@ class KotlinMultiplatformConventionPlugin : Plugin { extensions.configure { applyDefaultHierarchyTemplate() + if (pluginManager.hasPlugin("com.android.library")) { + androidTarget() + } + iosArm64() iosSimulatorArm64() diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index d565621a..22a35491 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,8 +1,9 @@ [versions] java = "17" -agp = "8.7.2" # Android Gradle Plugin +# AGP - Android API level mapping https://developer.android.com/build/releases/gradle-plugin#api-level-support +agp = "8.5.2" # Android Gradle Plugin kotlin = "2.0.21" -core-ktx = "1.15.0" +core-ktx = "1.13.0" junit = "4.13.2" androidx-test = "1.6.1" androidx-test-ext-junit = "1.2.1" diff --git a/settings.gradle.kts b/settings.gradle.kts index 5c3a61c9..fb22e3b7 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -31,12 +31,12 @@ dependencyResolutionManagement { rootProject.name = "Krail" include(":android-app") +include(":core:design-system") //include(":composeApp") /* include(":core:coroutines-ext") include(":core:date-time") include(":core:di") -include(":core:design-system") include(":core:network") include(":feature:trip-planner:network:api") include(":feature:trip-planner:network:real") From 56583727dd6246dbb5cfc2ec1466c73dc75dc399 Mon Sep 17 00:00:00 2001 From: Karan Sharma <55722391+ksharma-xyz@users.noreply.github.com> Date: Fri, 15 Nov 2024 22:55:55 +1100 Subject: [PATCH 15/67] iOS / Common --- .../src/main}/proguard-rules.pro | 0 composeApp/build.gradle.kts | 64 +++--- .../ksharma/krail/common/Platform-android.kt | 9 + .../kotlin/xyz/ksharma/krail/KrailApp.kt | 9 - .../xyz/ksharma/krail/common/KrailApp.kt | 17 ++ .../xyz/ksharma/krail/common/Platform.kt | 7 + .../ksharma/krail/navigation/KrailNavHost.kt | 99 --------- .../xyz/ksharma/krail/splash/SplashScreen.kt | 201 ------------------ .../ksharma/krail/splash/SplashViewModel.kt | 39 ---- .../xyz/ksharma/krail/MainViewController.kt | 5 - .../krail/common/MainViewController.kt | 5 + .../xyz/ksharma/krail/common/Platform.ios.kt | 10 + gradle/libs.versions.toml | 5 + iosApp/iosApp/ContentView.swift | 2 +- iosApp/iosApp/iOSApp.swift | 2 - settings.gradle.kts | 2 +- 16 files changed, 80 insertions(+), 396 deletions(-) rename {composeApp/src/androidMain => android-app/src/main}/proguard-rules.pro (100%) create mode 100644 composeApp/src/androidMain/kotlin/xyz/ksharma/krail/common/Platform-android.kt delete mode 100644 composeApp/src/commonMain/kotlin/xyz/ksharma/krail/KrailApp.kt create mode 100644 composeApp/src/commonMain/kotlin/xyz/ksharma/krail/common/KrailApp.kt create mode 100644 composeApp/src/commonMain/kotlin/xyz/ksharma/krail/common/Platform.kt delete mode 100644 composeApp/src/commonMain/kotlin/xyz/ksharma/krail/navigation/KrailNavHost.kt delete mode 100644 composeApp/src/commonMain/kotlin/xyz/ksharma/krail/splash/SplashScreen.kt delete mode 100644 composeApp/src/commonMain/kotlin/xyz/ksharma/krail/splash/SplashViewModel.kt delete mode 100644 composeApp/src/iosMain/kotlin/xyz/ksharma/krail/MainViewController.kt create mode 100644 composeApp/src/iosMain/kotlin/xyz/ksharma/krail/common/MainViewController.kt create mode 100644 composeApp/src/iosMain/kotlin/xyz/ksharma/krail/common/Platform.ios.kt diff --git a/composeApp/src/androidMain/proguard-rules.pro b/android-app/src/main/proguard-rules.pro similarity index 100% rename from composeApp/src/androidMain/proguard-rules.pro rename to android-app/src/main/proguard-rules.pro diff --git a/composeApp/build.gradle.kts b/composeApp/build.gradle.kts index db4c00e5..f844a72d 100644 --- a/composeApp/build.gradle.kts +++ b/composeApp/build.gradle.kts @@ -1,21 +1,19 @@ +android { + namespace = "xyz.ksharma.krail.common" +} plugins { - alias(libs.plugins.kotlinMultiplatform) - alias(libs.plugins.composeMultiplatform) + alias(libs.plugins.krail.kotlin.multiplatform) + alias(libs.plugins.krail.compose.multiplatform) + alias(libs.plugins.krail.android.library) alias(libs.plugins.compose.compiler) alias(libs.plugins.kotlin.serialization) } kotlin { - androidTarget { - @OptIn(ExperimentalKotlinGradlePluginApi::class) - compilerOptions { - jvmTarget.set(JvmTarget.JVM_17) - } - } + androidTarget() listOf( - iosX64(), iosArm64(), iosSimulatorArm64() ).forEach { iosTarget -> @@ -27,55 +25,43 @@ kotlin { sourceSets { androidMain.dependencies { - implementation(libs.androidx.compose.ui.tooling.preview) - implementation(libs.androidx.activity.compose) - implementation(libs.ktor.client.okhttp) + implementation(compose.preview) + implementation(libs.activity.compose) // Projects implementation(projects.core.designSystem) - implementation(projects.core.network) - implementation(projects.core.utils) - implementation(projects.feature.tripPlanner.network.api) - implementation(projects.feature.tripPlanner.network.real) - implementation(projects.feature.tripPlanner.state) - implementation(projects.feature.tripPlanner.ui) - implementation(projects.sandook.api) - implementation(projects.sandook.real) - - implementation(libs.activity.compose) - implementation(libs.compose.foundation) + /* + implementation(projects.core.network) + implementation(projects.core.utils) + implementation(projects.feature.tripPlanner.network.api) + implementation(projects.feature.tripPlanner.network.real) + implementation(projects.feature.tripPlanner.state) + implementation(projects.feature.tripPlanner.ui) + implementation(projects.sandook.api) + implementation(projects.sandook.real) + */ + implementation(compose.foundation) implementation(libs.compose.navigation) implementation(libs.core.ktx) implementation(libs.kotlinx.serialization.json) implementation(libs.lifecycle.runtime.ktx) - implementation(libs.timber) - implementation(libs.hilt.navigation.compose) - } - iosMain.dependencies { - implementation(libs.ktor.client.darwin) } + commonMain.dependencies { implementation(compose.runtime) implementation(compose.foundation) - implementation(compose.material3) + implementation(compose.material) implementation(compose.ui) implementation(compose.components.resources) implementation(compose.components.uiToolingPreview) + implementation(libs.androidx.lifecycle.viewmodel) + implementation(libs.androidx.lifecycle.runtime.compose) - implementation(libs.ktor.client.core) - implementation(libs.ktor.client.content.negotiation) - implementation(libs.ktor.serialization.kotlinx.json) - - implementation(libs.coil.compose) - implementation(libs.coil.network.ktor) - implementation(libs.koin.core) - implementation(libs.koin.compose.viewmodel) - implementation(libs.navigation.compose) } } } dependencies { - debugImplementation(libs.androidx.compose.ui.tooling) +// debugImplementation(libs.androidx.compose.ui.tooling) } diff --git a/composeApp/src/androidMain/kotlin/xyz/ksharma/krail/common/Platform-android.kt b/composeApp/src/androidMain/kotlin/xyz/ksharma/krail/common/Platform-android.kt new file mode 100644 index 00000000..e8d01c6e --- /dev/null +++ b/composeApp/src/androidMain/kotlin/xyz/ksharma/krail/common/Platform-android.kt @@ -0,0 +1,9 @@ +package xyz.ksharma.krail.common + +import android.os.Build + +class AndroidPlatform : Platform { + override val name: String = "Android ${Build.VERSION.SDK_INT}" +} + +actual fun getPlatform(): Platform = AndroidPlatform() diff --git a/composeApp/src/commonMain/kotlin/xyz/ksharma/krail/KrailApp.kt b/composeApp/src/commonMain/kotlin/xyz/ksharma/krail/KrailApp.kt deleted file mode 100644 index bf694f0e..00000000 --- a/composeApp/src/commonMain/kotlin/xyz/ksharma/krail/KrailApp.kt +++ /dev/null @@ -1,9 +0,0 @@ -package xyz.ksharma.krail - -import androidx.compose.runtime.Composable -import xyz.ksharma.krail.navigation.KrailNavHost - -@Composable -fun KrailApp() { - KrailNavHost() -} diff --git a/composeApp/src/commonMain/kotlin/xyz/ksharma/krail/common/KrailApp.kt b/composeApp/src/commonMain/kotlin/xyz/ksharma/krail/common/KrailApp.kt new file mode 100644 index 00000000..e8229bc6 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/xyz/ksharma/krail/common/KrailApp.kt @@ -0,0 +1,17 @@ +package xyz.ksharma.krail.common + +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.material.MaterialTheme +import androidx.compose.material.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color + +@Composable +fun KrailApp() { + Column(modifier = Modifier.fillMaxSize().background(color = Color.Blue)) { + Text("Hello, Krail!", style = MaterialTheme.typography.h1) + } +} diff --git a/composeApp/src/commonMain/kotlin/xyz/ksharma/krail/common/Platform.kt b/composeApp/src/commonMain/kotlin/xyz/ksharma/krail/common/Platform.kt new file mode 100644 index 00000000..61e8824d --- /dev/null +++ b/composeApp/src/commonMain/kotlin/xyz/ksharma/krail/common/Platform.kt @@ -0,0 +1,7 @@ +package xyz.ksharma.krail.common + +interface Platform { + val name: String +} + +expect fun getPlatform(): Platform diff --git a/composeApp/src/commonMain/kotlin/xyz/ksharma/krail/navigation/KrailNavHost.kt b/composeApp/src/commonMain/kotlin/xyz/ksharma/krail/navigation/KrailNavHost.kt deleted file mode 100644 index bc1e9d92..00000000 --- a/composeApp/src/commonMain/kotlin/xyz/ksharma/krail/navigation/KrailNavHost.kt +++ /dev/null @@ -1,99 +0,0 @@ -package xyz.ksharma.krail.navigation - -import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.runtime.Composable -import androidx.compose.runtime.CompositionLocalProvider -import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.saveable.rememberSaveable -import androidx.compose.runtime.setValue -import androidx.compose.ui.Modifier -import androidx.hilt.navigation.compose.hiltViewModel -import androidx.lifecycle.compose.collectAsStateWithLifecycle -import androidx.navigation.NavOptions -import androidx.navigation.compose.NavHost -import androidx.navigation.compose.composable -import androidx.navigation.compose.rememberNavController -import kotlinx.serialization.Serializable -import xyz.ksharma.krail.design.system.LocalThemeColor -import xyz.ksharma.krail.design.system.LocalThemeContentColor -import xyz.ksharma.krail.design.system.theme.KrailTheme -import xyz.ksharma.krail.design.system.theme.getForegroundColor -import xyz.ksharma.krail.design.system.unspecifiedColor -import xyz.ksharma.krail.splash.SplashScreen -import xyz.ksharma.krail.splash.SplashViewModel -import xyz.ksharma.krail.trip.planner.ui.components.hexToComposeColor -import xyz.ksharma.krail.trip.planner.ui.components.toHex -import xyz.ksharma.krail.trip.planner.ui.navigation.SavedTripsRoute -import xyz.ksharma.krail.trip.planner.ui.navigation.UsualRideRoute -import xyz.ksharma.krail.trip.planner.ui.navigation.tripPlannerDestinations - -/** - * TODO - I don't like [NavHost] defined in app module, I would love to refactor it to :core:navigation module - * but that results in a cyclic dependency. Feature module needs to depend on :core:navigation for navigation logic and - * then core:navigation needs to depend on feature module for the destinations / nested navigation graphs. - * This results in putting all nav logic in the app module, which will have negative impacts on the build time. - * Why is navigation so hard in Compose? - * - * Navigation logic is currently taken from [NowInAndroid](https://github.com/android/nowinandroid] app, - * so fine for now. But I will want to refactor it to something nicer e.g. using Circuit library - * from Slack, but that would also mean refactoring to use MVP instead of MVVM. - */ -@Composable -fun KrailNavHost(modifier: Modifier = Modifier) { - val navController = rememberNavController() - val themeColorHexCode = rememberSaveable { mutableStateOf(unspecifiedColor) } - var productClass: Int? by rememberSaveable { mutableStateOf(null) } - val themeContentColorHexCode = rememberSaveable { mutableStateOf(unspecifiedColor) } - themeContentColorHexCode.value = - getForegroundColor( - backgroundColor = themeColorHexCode.value.hexToComposeColor(), - ).toHex() - - CompositionLocalProvider( - LocalThemeColor provides themeColorHexCode, - LocalThemeContentColor provides themeContentColorHexCode, - ) { - NavHost( - navController = navController, - startDestination = SplashScreen, - modifier = modifier.fillMaxSize(), - ) { - tripPlannerDestinations(navController = navController) - - composable { - val viewModel = hiltViewModel() - val isLoading by viewModel.isLoading.collectAsStateWithLifecycle() - val mode by viewModel.uiState.collectAsStateWithLifecycle() - - productClass = mode?.productClass - themeColorHexCode.value = mode?.colorCode ?: unspecifiedColor - - SplashScreen( - logoColor = if (productClass != null && themeColorHexCode.value != unspecifiedColor) { - themeColorHexCode.value.hexToComposeColor() - } else { - KrailTheme.colors.onSurface - }, - backgroundColor = KrailTheme.colors.surface, - onSplashComplete = { - navController.navigate( - route = if (productClass != null) { - SavedTripsRoute - } else { - UsualRideRoute - }, - navOptions = NavOptions.Builder() - .setLaunchSingleTop(true) - .setPopUpTo(inclusive = true) - .build(), - ) - }, - ) - } - } - } -} - -@Serializable -private data object SplashScreen diff --git a/composeApp/src/commonMain/kotlin/xyz/ksharma/krail/splash/SplashScreen.kt b/composeApp/src/commonMain/kotlin/xyz/ksharma/krail/splash/SplashScreen.kt deleted file mode 100644 index aed7fc57..00000000 --- a/composeApp/src/commonMain/kotlin/xyz/ksharma/krail/splash/SplashScreen.kt +++ /dev/null @@ -1,201 +0,0 @@ -package xyz.ksharma.krail.splash - -import androidx.compose.animation.core.FastOutSlowInEasing -import androidx.compose.animation.core.LinearEasing -import androidx.compose.animation.core.RepeatMode -import androidx.compose.animation.core.StartOffset -import androidx.compose.animation.core.animateFloat -import androidx.compose.animation.core.infiniteRepeatable -import androidx.compose.animation.core.keyframes -import androidx.compose.animation.core.rememberInfiniteTransition -import androidx.compose.foundation.background -import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.padding -import androidx.compose.runtime.Composable -import androidx.compose.runtime.LaunchedEffect -import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableFloatStateOf -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.remember -import androidx.compose.runtime.rememberUpdatedState -import androidx.compose.runtime.setValue -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.Color -import androidx.compose.ui.graphics.graphicsLayer -import androidx.compose.ui.text.font.FontWeight -import androidx.compose.ui.text.style.TextAlign -import androidx.compose.ui.tooling.preview.Preview -import androidx.compose.ui.tooling.preview.PreviewLightDark -import androidx.compose.ui.unit.TextUnit -import androidx.compose.ui.unit.TextUnitType -import androidx.compose.ui.unit.dp -import androidx.compose.ui.unit.sp -import kotlinx.coroutines.delay -import xyz.ksharma.krail.design.system.components.Text -import xyz.ksharma.krail.design.system.theme.KrailTheme - -@Composable -fun SplashScreen( - logoColor: Color?, - backgroundColor: Color?, - onSplashComplete: () -> Unit, - modifier: Modifier = Modifier, -) { - Box( - modifier = modifier - .fillMaxSize() - .background(color = backgroundColor ?: KrailTheme.colors.surface), - contentAlignment = Alignment.Center, - ) { - AnimatedKrailLogo(logoColor = logoColor ?: KrailTheme.colors.onSurface) - - val splashComplete by rememberUpdatedState(onSplashComplete) - LaunchedEffect(key1 = Unit) { - delay(1200) - splashComplete() - } - } -} - -@Composable -private fun AnimatedKrailLogo( - logoColor: Color, - modifier: Modifier = Modifier, -) { - var animationStarted by remember { mutableStateOf(false) } - - LaunchedEffect(key1 = Unit) { - animationStarted = true - } - Column(modifier = modifier, horizontalAlignment = Alignment.CenterHorizontally) { - Row { - AnimatedLetter( - letter = "K", - animationStarted = animationStarted, - fontSize = 80.sp, - delayMillis = 0, - logoColor = logoColor, - modifier = Modifier.alignByBaseline(), - ) - AnimatedLetter( - letter = "R", - animationStarted = animationStarted, - logoColor = logoColor, - delayMillis = 50, - modifier = Modifier.alignByBaseline(), - ) - AnimatedLetter( - letter = "A", - animationStarted = animationStarted, - logoColor = logoColor, - delayMillis = 100, - modifier = Modifier.alignByBaseline(), - ) - AnimatedLetter( - letter = "I", - animationStarted = animationStarted, - logoColor = logoColor, - delayMillis = 150, - modifier = Modifier.alignByBaseline(), - ) - AnimatedLetter( - letter = "L", - animationStarted = animationStarted, - logoColor = logoColor, - delayMillis = 200, - modifier = Modifier.alignByBaseline(), - ) - } - - Text( - text = "Ride the rail, without fail.", - style = KrailTheme.typography.titleMedium.copy( - fontWeight = FontWeight.Normal, - ), - textAlign = TextAlign.Center, - color = logoColor, - modifier = Modifier - .fillMaxWidth() - .padding(horizontal = 16.dp, vertical = 0.dp), - ) - } -} - -@Composable -private fun AnimatedLetter( - letter: String, - animationStarted: Boolean, - logoColor: Color, - modifier: Modifier = Modifier, - fontSize: TextUnit = TextUnit(65F, TextUnitType.Sp), - delayMillis: Int = 100, -) { - val infiniteTransition = rememberInfiniteTransition(label = "animeAnimation") - - // Scale animation with anticipation and squash/stretch - val scale by infiniteTransition.animateFloat( - initialValue = 1f, - targetValue = 1.2f, - animationSpec = infiniteRepeatable( - animation = keyframes { - durationMillis = 1100 - 0.0f at 0 using LinearEasing // Hold initial scale - 0.7f at 200 using FastOutSlowInEasing // Anticipation (quick shrink) - 1.2f at 500 using FastOutSlowInEasing // Squash/stretch (overshoot) - 1.0f at 1000 using FastOutSlowInEasing // Settle back to normal scale - 1.0f at 1100 using LinearEasing // Keep at normal scale - }, - repeatMode = RepeatMode.Reverse, - initialStartOffset = StartOffset(offsetMillis = delayMillis), - ), - label = "animeAnimation", - ) - - val letterScale by remember(scale) { - mutableFloatStateOf(if (animationStarted) scale else 1f) - } - - Text( - text = letter, - color = logoColor, - style = KrailTheme.typography.displayLarge.copy( - fontSize = fontSize, - letterSpacing = 4.sp, - fontWeight = FontWeight.ExtraBold, - ), - modifier = modifier - .graphicsLayer { - scaleX = letterScale - scaleY = letterScale - } - .padding(4.dp), - ) -} - -@PreviewLightDark -@Preview -@Composable -private fun PreviewLogo() { - KrailTheme { - Column(modifier = Modifier.background(color = KrailTheme.colors.surface)) { - AnimatedKrailLogo(logoColor = Color(0xFFF6891F)) - } - } -} - -@Preview -@Composable -private fun PreviewSplashScreen() { - KrailTheme { - SplashScreen( - onSplashComplete = {}, - logoColor = KrailTheme.colors.onSurface, - backgroundColor = Color(0xFF009B77), - ) - } -} diff --git a/composeApp/src/commonMain/kotlin/xyz/ksharma/krail/splash/SplashViewModel.kt b/composeApp/src/commonMain/kotlin/xyz/ksharma/krail/splash/SplashViewModel.kt deleted file mode 100644 index 5c9c8e08..00000000 --- a/composeApp/src/commonMain/kotlin/xyz/ksharma/krail/splash/SplashViewModel.kt +++ /dev/null @@ -1,39 +0,0 @@ -package xyz.ksharma.krail.splash - -import androidx.lifecycle.ViewModel -import androidx.lifecycle.viewModelScope -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.flow.SharingStarted -import kotlinx.coroutines.flow.StateFlow -import kotlinx.coroutines.flow.onStart -import kotlinx.coroutines.flow.stateIn -import kotlinx.coroutines.launch -import xyz.ksharma.krail.sandook.Sandook -import xyz.ksharma.krail.sandook.di.SandookFactory -import xyz.ksharma.krail.trip.planner.ui.state.TransportMode -import javax.inject.Inject - -class SplashViewModel @Inject constructor( - sandookFactory: SandookFactory, -) : ViewModel() { - - private val sandook: Sandook = sandookFactory.create(SandookFactory.SandookKey.THEME) - - private val _uiState: MutableStateFlow = MutableStateFlow(null) - val uiState: MutableStateFlow = _uiState - - private val _isLoading: MutableStateFlow = MutableStateFlow(false) - val isLoading: StateFlow = _isLoading - .onStart { - getThemeTransportMode() - }.stateIn(viewModelScope, SharingStarted.WhileSubscribed(5000), true) - - private fun getThemeTransportMode() { - viewModelScope.launch(Dispatchers.IO) { - val productClass = sandook.getInt("selectedMode") - val mode = TransportMode.toTransportModeType(productClass) - _uiState.value = mode - } - } -} diff --git a/composeApp/src/iosMain/kotlin/xyz/ksharma/krail/MainViewController.kt b/composeApp/src/iosMain/kotlin/xyz/ksharma/krail/MainViewController.kt deleted file mode 100644 index 5192bd54..00000000 --- a/composeApp/src/iosMain/kotlin/xyz/ksharma/krail/MainViewController.kt +++ /dev/null @@ -1,5 +0,0 @@ -package xyz.ksharma.krail - -import androidx.compose.ui.window.ComposeUIViewController - -fun MainViewController() = ComposeUIViewController { App() } diff --git a/composeApp/src/iosMain/kotlin/xyz/ksharma/krail/common/MainViewController.kt b/composeApp/src/iosMain/kotlin/xyz/ksharma/krail/common/MainViewController.kt new file mode 100644 index 00000000..2e00a5f6 --- /dev/null +++ b/composeApp/src/iosMain/kotlin/xyz/ksharma/krail/common/MainViewController.kt @@ -0,0 +1,5 @@ +package xyz.ksharma.krail.common + +import androidx.compose.ui.window.ComposeUIViewController + +fun MainViewController() = ComposeUIViewController { KrailApp() } diff --git a/composeApp/src/iosMain/kotlin/xyz/ksharma/krail/common/Platform.ios.kt b/composeApp/src/iosMain/kotlin/xyz/ksharma/krail/common/Platform.ios.kt new file mode 100644 index 00000000..d790f524 --- /dev/null +++ b/composeApp/src/iosMain/kotlin/xyz/ksharma/krail/common/Platform.ios.kt @@ -0,0 +1,10 @@ +package xyz.ksharma.krail.common + +import platform.UIKit.UIDevice + +class IOSPlatform : Platform { + override val name: String = + UIDevice.currentDevice.systemName() + " " + UIDevice.currentDevice.systemVersion +} + +actual fun getPlatform(): Platform = IOSPlatform() diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 22a35491..dda22521 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -24,6 +24,7 @@ retrofit2KotlinxSerializationConverter = "1.0.0" timber = "5.0.1" compose-multiplatform = "1.7.0" ktor = "3.0.0" +androidx-lifecycle = "2.8.3" #SDK minSdk = "26" @@ -49,6 +50,10 @@ compose-ui-tooling-preview = { group = "androidx.compose.ui", name = "ui-tooling compose-navigation = { group = "androidx.navigation", name = "navigation-compose", version.ref = "compose-navigation" } compose-material3 = { group = "androidx.compose.material3", name = "material3", version = "1.3.1" } +androidx-lifecycle-viewmodel = { group = "org.jetbrains.androidx.lifecycle", name = "lifecycle-viewmodel", version.ref = "androidx-lifecycle" } +androidx-lifecycle-runtime-compose = { group = "org.jetbrains.androidx.lifecycle", name = "lifecycle-runtime-compose", version.ref = "androidx-lifecycle" } + + #Hilt hilt-android = { group = "com.google.dagger", name = "hilt-android", version.ref = "hilt" } hilt-compiler = { group = "com.google.dagger", name = "hilt-compiler", version.ref = "hilt" } diff --git a/iosApp/iosApp/ContentView.swift b/iosApp/iosApp/ContentView.swift index 9c87bd2e..762bcbe2 100644 --- a/iosApp/iosApp/ContentView.swift +++ b/iosApp/iosApp/ContentView.swift @@ -1,6 +1,6 @@ import UIKit import SwiftUI -import ComposeApp +import KrailApp struct ComposeView: UIViewControllerRepresentable { func makeUIViewController(context: Context) -> UIViewController { diff --git a/iosApp/iosApp/iOSApp.swift b/iosApp/iosApp/iOSApp.swift index e466410c..8fa5091d 100644 --- a/iosApp/iosApp/iOSApp.swift +++ b/iosApp/iosApp/iOSApp.swift @@ -1,10 +1,8 @@ import SwiftUI -import ComposeApp @main struct iOSApp: App { init() { - KoinKt.doInitKoin() } var body: some Scene { diff --git a/settings.gradle.kts b/settings.gradle.kts index fb22e3b7..05ae7f7c 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -32,7 +32,7 @@ dependencyResolutionManagement { rootProject.name = "Krail" include(":android-app") include(":core:design-system") -//include(":composeApp") +include(":composeApp") /* include(":core:coroutines-ext") include(":core:date-time") From 7569b83080dce013738f86b75fb66f62a94c825e Mon Sep 17 00:00:00 2001 From: Karan Sharma <55722391+ksharma-xyz@users.noreply.github.com> Date: Sat, 16 Nov 2024 00:14:48 +1100 Subject: [PATCH 16/67] Add design system to :commonMain Wanted to add as a module but it's not working --- android-app/build.gradle.kts | 6 +- .../kotlin/xyz/ksharma/krail/MainActivity.kt | 19 +--- composeApp/build.gradle.kts | 15 ++-- .../xyz/ksharma/krail/common/KrailApp.kt | 16 ++-- .../common/designSystem}/CompositionLocals.kt | 2 +- .../krail/common/designSystem}/DpExt.kt | 2 +- .../designSystem}/components/Divider.kt | 9 +- .../components/RoundIconButton.kt | 30 ++----- .../designSystem}/components/SeparatorIcon.kt | 8 +- .../common/designSystem}/components/Text.kt | 13 ++- .../designSystem}/components/TextField.kt | 23 +++-- .../components/TextFieldButton.kt | 29 ++---- .../designSystem/components/TitleBar.kt | 55 ++++++++++++ .../common/designSystem}/theme/A11yColors.kt | 2 +- .../krail/common/designSystem}/theme/Color.kt | 2 +- .../designSystem}/theme/KrailTypography.kt | 2 +- .../krail/common/designSystem}/theme/Theme.kt | 2 +- .../designSystem}/tokens/ButtonTokens.kt | 2 +- .../designSystem}/tokens/TextFieldTokens.kt | 2 +- core/design-system/README.md | 6 -- core/design-system/build.gradle.kts | 24 ----- .../src/main/kotlin/xyz/.DS_Store | Bin 6148 -> 0 bytes .../src/main/kotlin/xyz/ksharma/.DS_Store | Bin 6148 -> 0 bytes .../main/kotlin/xyz/ksharma/krail/.DS_Store | Bin 6148 -> 0 bytes .../krail/design/system/components/Fab.kt | 85 ------------------ .../design/system/components/TitleBar.kt | 12 +-- .../design/system/preview/PreviewComponent.kt | 14 --- .../src/main/res/drawable/star_filled.xml | 9 -- .../src/main/res/drawable/star_outline.xml | 10 --- .../src/test/kotlin/xyz/.DS_Store | Bin 6148 -> 0 bytes .../src/test/kotlin/xyz/ksharma/.DS_Store | Bin 6148 -> 0 bytes .../test/kotlin/xyz/ksharma/start/.DS_Store | Bin 6148 -> 0 bytes .../kotlin/xyz/ksharma/start/design/.DS_Store | Bin 6148 -> 0 bytes .../planner/ui/alerts/CollapsibleAlert.kt | 4 +- .../planner/ui/components/ErrorMessage.kt | 2 +- .../trip/planner/ui/components/JourneyCard.kt | 4 +- gradle/libs.versions.toml | 18 ---- settings.gradle.kts | 1 - 38 files changed, 130 insertions(+), 298 deletions(-) rename {core/design-system/src/main/kotlin/xyz/ksharma/krail/design/system => composeApp/src/commonMain/kotlin/xyz/ksharma/krail/common/designSystem}/CompositionLocals.kt (93%) rename {core/design-system/src/main/kotlin/xyz/ksharma/krail/design/system => composeApp/src/commonMain/kotlin/xyz/ksharma/krail/common/designSystem}/DpExt.kt (95%) rename {core/design-system/src/main/kotlin/xyz/ksharma/krail/design/system => composeApp/src/commonMain/kotlin/xyz/ksharma/krail/common/designSystem}/components/Divider.kt (89%) rename {core/design-system/src/main/kotlin/xyz/ksharma/krail/design/system => composeApp/src/commonMain/kotlin/xyz/ksharma/krail/common/designSystem}/components/RoundIconButton.kt (65%) rename {core/design-system/src/main/kotlin/xyz/ksharma/krail/design/system => composeApp/src/commonMain/kotlin/xyz/ksharma/krail/common/designSystem}/components/SeparatorIcon.kt (80%) rename {core/design-system/src/main/kotlin/xyz/ksharma/krail/design/system => composeApp/src/commonMain/kotlin/xyz/ksharma/krail/common/designSystem}/components/Text.kt (89%) rename {core/design-system/src/main/kotlin/xyz/ksharma/krail/design/system => composeApp/src/commonMain/kotlin/xyz/ksharma/krail/common/designSystem}/components/TextField.kt (90%) rename {core/design-system/src/main/kotlin/xyz/ksharma/krail/design/system => composeApp/src/commonMain/kotlin/xyz/ksharma/krail/common/designSystem}/components/TextFieldButton.kt (73%) create mode 100644 composeApp/src/commonMain/kotlin/xyz/ksharma/krail/common/designSystem/components/TitleBar.kt rename {core/design-system/src/main/kotlin/xyz/ksharma/krail/design/system => composeApp/src/commonMain/kotlin/xyz/ksharma/krail/common/designSystem}/theme/A11yColors.kt (94%) rename {core/design-system/src/main/kotlin/xyz/ksharma/krail/design/system => composeApp/src/commonMain/kotlin/xyz/ksharma/krail/common/designSystem}/theme/Color.kt (98%) rename {core/design-system/src/main/kotlin/xyz/ksharma/krail/design/system => composeApp/src/commonMain/kotlin/xyz/ksharma/krail/common/designSystem}/theme/KrailTypography.kt (99%) rename {core/design-system/src/main/kotlin/xyz/ksharma/krail/design/system => composeApp/src/commonMain/kotlin/xyz/ksharma/krail/common/designSystem}/theme/Theme.kt (93%) rename {core/design-system/src/main/kotlin/xyz/ksharma/krail/design/system => composeApp/src/commonMain/kotlin/xyz/ksharma/krail/common/designSystem}/tokens/ButtonTokens.kt (65%) rename {core/design-system/src/main/kotlin/xyz/ksharma/krail/design/system => composeApp/src/commonMain/kotlin/xyz/ksharma/krail/common/designSystem}/tokens/TextFieldTokens.kt (81%) delete mode 100644 core/design-system/README.md delete mode 100644 core/design-system/build.gradle.kts delete mode 100644 core/design-system/src/main/kotlin/xyz/.DS_Store delete mode 100644 core/design-system/src/main/kotlin/xyz/ksharma/.DS_Store delete mode 100644 core/design-system/src/main/kotlin/xyz/ksharma/krail/.DS_Store delete mode 100644 core/design-system/src/main/kotlin/xyz/ksharma/krail/design/system/components/Fab.kt delete mode 100644 core/design-system/src/main/kotlin/xyz/ksharma/krail/design/system/preview/PreviewComponent.kt delete mode 100644 core/design-system/src/main/res/drawable/star_filled.xml delete mode 100644 core/design-system/src/main/res/drawable/star_outline.xml delete mode 100644 core/design-system/src/test/kotlin/xyz/.DS_Store delete mode 100644 core/design-system/src/test/kotlin/xyz/ksharma/.DS_Store delete mode 100644 core/design-system/src/test/kotlin/xyz/ksharma/start/.DS_Store delete mode 100644 core/design-system/src/test/kotlin/xyz/ksharma/start/design/.DS_Store diff --git a/android-app/build.gradle.kts b/android-app/build.gradle.kts index a8c9e42a..9e971b55 100644 --- a/android-app/build.gradle.kts +++ b/android-app/build.gradle.kts @@ -46,7 +46,7 @@ android { dependencies { // Projects - implementation(projects.core.designSystem) + implementation(projects.composeApp) /* implementation(projects.core.network) implementation(projects.feature.tripPlanner.network.api) implementation(projects.feature.tripPlanner.network.real) @@ -56,10 +56,8 @@ dependencies { implementation(projects.sandook.real)*/ implementation(libs.activity.compose) - implementation(libs.compose.foundation) - implementation(libs.compose.navigation) + implementation(compose.foundation) implementation(libs.core.ktx) implementation(libs.kotlinx.serialization.json) implementation(libs.lifecycle.runtime.ktx) - implementation(libs.hilt.navigation.compose) } diff --git a/android-app/src/main/kotlin/xyz/ksharma/krail/MainActivity.kt b/android-app/src/main/kotlin/xyz/ksharma/krail/MainActivity.kt index 3ee46d13..9041f704 100644 --- a/android-app/src/main/kotlin/xyz/ksharma/krail/MainActivity.kt +++ b/android-app/src/main/kotlin/xyz/ksharma/krail/MainActivity.kt @@ -4,13 +4,7 @@ import android.os.Bundle import androidx.activity.ComponentActivity import androidx.activity.compose.setContent import androidx.activity.enableEdgeToEdge -import androidx.compose.foundation.background -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.Color -import xyz.ksharma.krail.design.system.components.Text -import xyz.ksharma.krail.design.system.theme.KrailTheme +import xyz.ksharma.krail.common.KrailApp class MainActivity : ComponentActivity() { @@ -18,16 +12,7 @@ class MainActivity : ComponentActivity() { super.onCreate(savedInstanceState) enableEdgeToEdge() setContent { - KrailTheme { - Column( - modifier = Modifier - .fillMaxSize() - .background(color = KrailTheme.colors.surface) - ) { - Text("Hello World") -// KrailApp() - } - } + KrailApp() } } } diff --git a/composeApp/build.gradle.kts b/composeApp/build.gradle.kts index f844a72d..53731e01 100644 --- a/composeApp/build.gradle.kts +++ b/composeApp/build.gradle.kts @@ -1,3 +1,6 @@ +import org.jetbrains.kotlin.gradle.ExperimentalKotlinGradlePluginApi +import org.jetbrains.kotlin.gradle.dsl.JvmTarget + android { namespace = "xyz.ksharma.krail.common" } @@ -11,7 +14,13 @@ plugins { } kotlin { - androidTarget() + applyDefaultHierarchyTemplate() + androidTarget { + @OptIn(ExperimentalKotlinGradlePluginApi::class) + compilerOptions { + jvmTarget.set(JvmTarget.JVM_17) + } + } listOf( iosArm64(), @@ -29,7 +38,6 @@ kotlin { implementation(libs.activity.compose) // Projects - implementation(projects.core.designSystem) /* implementation(projects.core.network) implementation(projects.core.utils) @@ -41,7 +49,6 @@ kotlin { implementation(projects.sandook.real) */ implementation(compose.foundation) - implementation(libs.compose.navigation) implementation(libs.core.ktx) implementation(libs.kotlinx.serialization.json) implementation(libs.lifecycle.runtime.ktx) @@ -61,7 +68,5 @@ kotlin { } } - dependencies { -// debugImplementation(libs.androidx.compose.ui.tooling) } diff --git a/composeApp/src/commonMain/kotlin/xyz/ksharma/krail/common/KrailApp.kt b/composeApp/src/commonMain/kotlin/xyz/ksharma/krail/common/KrailApp.kt index e8229bc6..eca6b202 100644 --- a/composeApp/src/commonMain/kotlin/xyz/ksharma/krail/common/KrailApp.kt +++ b/composeApp/src/commonMain/kotlin/xyz/ksharma/krail/common/KrailApp.kt @@ -3,15 +3,21 @@ package xyz.ksharma.krail.common import androidx.compose.foundation.background import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.material.MaterialTheme -import androidx.compose.material.Text +import androidx.compose.foundation.layout.statusBarsPadding import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.Color +import xyz.ksharma.krail.common.designSystem.components.Text +import xyz.ksharma.krail.common.designSystem.theme.KrailTheme @Composable fun KrailApp() { - Column(modifier = Modifier.fillMaxSize().background(color = Color.Blue)) { - Text("Hello, Krail!", style = MaterialTheme.typography.h1) + KrailTheme { + Column(modifier = Modifier.fillMaxSize().background(color = KrailTheme.colors.surface)) { + Text( + "Hello, Krail!", + style = KrailTheme.typography.titleLarge, + modifier = Modifier.statusBarsPadding() + ) + } } } diff --git a/core/design-system/src/main/kotlin/xyz/ksharma/krail/design/system/CompositionLocals.kt b/composeApp/src/commonMain/kotlin/xyz/ksharma/krail/common/designSystem/CompositionLocals.kt similarity index 93% rename from core/design-system/src/main/kotlin/xyz/ksharma/krail/design/system/CompositionLocals.kt rename to composeApp/src/commonMain/kotlin/xyz/ksharma/krail/common/designSystem/CompositionLocals.kt index ccf29ae5..2472c93c 100644 --- a/core/design-system/src/main/kotlin/xyz/ksharma/krail/design/system/CompositionLocals.kt +++ b/composeApp/src/commonMain/kotlin/xyz/ksharma/krail/common/designSystem/CompositionLocals.kt @@ -1,4 +1,4 @@ -package xyz.ksharma.krail.design.system +package xyz.ksharma.krail.common.designSystem import androidx.compose.runtime.compositionLocalOf import androidx.compose.runtime.mutableStateOf diff --git a/core/design-system/src/main/kotlin/xyz/ksharma/krail/design/system/DpExt.kt b/composeApp/src/commonMain/kotlin/xyz/ksharma/krail/common/designSystem/DpExt.kt similarity index 95% rename from core/design-system/src/main/kotlin/xyz/ksharma/krail/design/system/DpExt.kt rename to composeApp/src/commonMain/kotlin/xyz/ksharma/krail/common/designSystem/DpExt.kt index f699e24e..1b6e6510 100644 --- a/core/design-system/src/main/kotlin/xyz/ksharma/krail/design/system/DpExt.kt +++ b/composeApp/src/commonMain/kotlin/xyz/ksharma/krail/common/designSystem/DpExt.kt @@ -1,4 +1,4 @@ -package xyz.ksharma.krail.design.system +package xyz.ksharma.krail.common.designSystem import androidx.compose.runtime.Composable import androidx.compose.ui.platform.LocalDensity diff --git a/core/design-system/src/main/kotlin/xyz/ksharma/krail/design/system/components/Divider.kt b/composeApp/src/commonMain/kotlin/xyz/ksharma/krail/common/designSystem/components/Divider.kt similarity index 89% rename from core/design-system/src/main/kotlin/xyz/ksharma/krail/design/system/components/Divider.kt rename to composeApp/src/commonMain/kotlin/xyz/ksharma/krail/common/designSystem/components/Divider.kt index b4204686..ea2011eb 100644 --- a/core/design-system/src/main/kotlin/xyz/ksharma/krail/design/system/components/Divider.kt +++ b/composeApp/src/commonMain/kotlin/xyz/ksharma/krail/common/designSystem/components/Divider.kt @@ -1,4 +1,4 @@ -package xyz.ksharma.krail.design.system.components +package xyz.ksharma.krail.common.designSystem.components import androidx.compose.foundation.background import androidx.compose.foundation.layout.Box @@ -12,10 +12,9 @@ import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color -import androidx.compose.ui.tooling.preview.PreviewLightDark import androidx.compose.ui.unit.dp -import xyz.ksharma.krail.design.system.LocalContentColor -import xyz.ksharma.krail.design.system.theme.KrailTheme +import xyz.ksharma.krail.common.designSystem.LocalContentColor +import xyz.ksharma.krail.common.designSystem.theme.KrailTheme @Composable fun Divider( @@ -49,7 +48,6 @@ enum class DividerType { VERTICAL, } -@PreviewLightDark @Composable private fun DividerPreview() { KrailTheme { @@ -65,7 +63,6 @@ private fun DividerPreview() { } } -@PreviewLightDark @Composable private fun DividerVerticalPreview() { KrailTheme { diff --git a/core/design-system/src/main/kotlin/xyz/ksharma/krail/design/system/components/RoundIconButton.kt b/composeApp/src/commonMain/kotlin/xyz/ksharma/krail/common/designSystem/components/RoundIconButton.kt similarity index 65% rename from core/design-system/src/main/kotlin/xyz/ksharma/krail/design/system/components/RoundIconButton.kt rename to composeApp/src/commonMain/kotlin/xyz/ksharma/krail/common/designSystem/components/RoundIconButton.kt index 678fb409..8b5d1c3a 100644 --- a/core/design-system/src/main/kotlin/xyz/ksharma/krail/design/system/components/RoundIconButton.kt +++ b/composeApp/src/commonMain/kotlin/xyz/ksharma/krail/common/designSystem/components/RoundIconButton.kt @@ -1,6 +1,5 @@ -package xyz.ksharma.krail.design.system.components +package xyz.ksharma.krail.common.designSystem.components -import androidx.compose.foundation.Image import androidx.compose.foundation.background import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Box @@ -12,16 +11,11 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.Color -import androidx.compose.ui.graphics.ColorFilter -import androidx.compose.ui.graphics.vector.ImageVector -import androidx.compose.ui.res.vectorResource import androidx.compose.ui.semantics.Role -import xyz.ksharma.krail.design.system.LocalContentColor -import xyz.ksharma.krail.design.system.LocalOnContentColor -import xyz.ksharma.krail.design.system.R -import xyz.ksharma.krail.design.system.preview.PreviewComponent -import xyz.ksharma.krail.design.system.theme.KrailTheme -import xyz.ksharma.krail.design.system.tokens.ButtonTokens.RoundButtonSize +import xyz.ksharma.krail.common.designSystem.LocalContentColor +import xyz.ksharma.krail.common.designSystem.LocalOnContentColor +import xyz.ksharma.krail.common.designSystem.theme.KrailTheme +import xyz.ksharma.krail.common.designSystem.tokens.ButtonTokens.RoundButtonSize /** * A round icon button with customizable content and colors. @@ -62,18 +56,4 @@ fun RoundIconButton( // region Previews -@PreviewComponent -@Composable -private fun RoundIconButtonPreview() { - KrailTheme { - RoundIconButton(onClick = {}) { - Image( - imageVector = ImageVector.vectorResource(R.drawable.star_outline), - contentDescription = null, - colorFilter = ColorFilter.tint(LocalOnContentColor.current), - ) - } - } -} - // endregion diff --git a/core/design-system/src/main/kotlin/xyz/ksharma/krail/design/system/components/SeparatorIcon.kt b/composeApp/src/commonMain/kotlin/xyz/ksharma/krail/common/designSystem/components/SeparatorIcon.kt similarity index 80% rename from core/design-system/src/main/kotlin/xyz/ksharma/krail/design/system/components/SeparatorIcon.kt rename to composeApp/src/commonMain/kotlin/xyz/ksharma/krail/common/designSystem/components/SeparatorIcon.kt index 22eef7d9..0cb62b64 100644 --- a/core/design-system/src/main/kotlin/xyz/ksharma/krail/design/system/components/SeparatorIcon.kt +++ b/composeApp/src/commonMain/kotlin/xyz/ksharma/krail/common/designSystem/components/SeparatorIcon.kt @@ -1,4 +1,4 @@ -package xyz.ksharma.krail.design.system.components +package xyz.ksharma.krail.common.designSystem.components import androidx.compose.foundation.background import androidx.compose.foundation.layout.Box @@ -10,9 +10,8 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.Color import androidx.compose.ui.unit.dp -import xyz.ksharma.krail.design.system.preview.PreviewComponent -import xyz.ksharma.krail.design.system.theme.KrailTheme -import xyz.ksharma.krail.design.system.toAdaptiveSize +import xyz.ksharma.krail.common.designSystem.theme.KrailTheme +import xyz.ksharma.krail.common.designSystem.toAdaptiveSize @Composable fun SeparatorIcon(modifier: Modifier = Modifier, color: Color = KrailTheme.colors.onSurface) { @@ -27,7 +26,6 @@ fun SeparatorIcon(modifier: Modifier = Modifier, color: Color = KrailTheme.color // region Previews -@PreviewComponent @Composable private fun SeparatorIconPreview() { KrailTheme { diff --git a/core/design-system/src/main/kotlin/xyz/ksharma/krail/design/system/components/Text.kt b/composeApp/src/commonMain/kotlin/xyz/ksharma/krail/common/designSystem/components/Text.kt similarity index 89% rename from core/design-system/src/main/kotlin/xyz/ksharma/krail/design/system/components/Text.kt rename to composeApp/src/commonMain/kotlin/xyz/ksharma/krail/common/designSystem/components/Text.kt index e76467d7..56e88959 100644 --- a/core/design-system/src/main/kotlin/xyz/ksharma/krail/design/system/components/Text.kt +++ b/composeApp/src/commonMain/kotlin/xyz/ksharma/krail/common/designSystem/components/Text.kt @@ -1,4 +1,4 @@ -package xyz.ksharma.krail.design.system.components +package xyz.ksharma.krail.common.designSystem.components import androidx.compose.foundation.background import androidx.compose.foundation.layout.Column @@ -13,11 +13,10 @@ import androidx.compose.ui.text.TextStyle import androidx.compose.ui.text.font.FontFamily import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.text.style.TextOverflow -import xyz.ksharma.krail.design.system.LocalContentAlpha -import xyz.ksharma.krail.design.system.LocalTextColor -import xyz.ksharma.krail.design.system.LocalTextStyle -import xyz.ksharma.krail.design.system.preview.PreviewComponent -import xyz.ksharma.krail.design.system.theme.KrailTheme +import xyz.ksharma.krail.common.designSystem.LocalContentAlpha +import xyz.ksharma.krail.common.designSystem.LocalTextColor +import xyz.ksharma.krail.common.designSystem.LocalTextStyle +import xyz.ksharma.krail.common.designSystem.theme.KrailTheme @Composable fun Text( @@ -79,7 +78,6 @@ fun Text( // region Previews -@PreviewComponent @Composable private fun TextPreview() { KrailTheme { @@ -92,7 +90,6 @@ private fun TextPreview() { } } -@PreviewComponent @Composable private fun TextWithColorPreview() { KrailTheme { diff --git a/core/design-system/src/main/kotlin/xyz/ksharma/krail/design/system/components/TextField.kt b/composeApp/src/commonMain/kotlin/xyz/ksharma/krail/common/designSystem/components/TextField.kt similarity index 90% rename from core/design-system/src/main/kotlin/xyz/ksharma/krail/design/system/components/TextField.kt rename to composeApp/src/commonMain/kotlin/xyz/ksharma/krail/common/designSystem/components/TextField.kt index 4b36d631..4fc1eaf6 100644 --- a/core/design-system/src/main/kotlin/xyz/ksharma/krail/design/system/components/TextField.kt +++ b/composeApp/src/commonMain/kotlin/xyz/ksharma/krail/common/designSystem/components/TextField.kt @@ -30,16 +30,16 @@ import androidx.compose.ui.text.input.ImeAction import androidx.compose.ui.text.input.KeyboardCapitalization import androidx.compose.ui.text.input.KeyboardType import androidx.compose.ui.text.intl.LocaleList -import androidx.compose.ui.tooling.preview.PreviewLightDark import androidx.compose.ui.unit.dp -import xyz.ksharma.krail.design.system.LocalContentAlpha -import xyz.ksharma.krail.design.system.LocalTextColor -import xyz.ksharma.krail.design.system.LocalTextStyle -import xyz.ksharma.krail.design.system.theme.KrailTheme -import xyz.ksharma.krail.design.system.tokens.TextFieldTokens -import xyz.ksharma.krail.design.system.tokens.TextFieldTokens.PlaceholderOpacity -import xyz.ksharma.krail.design.system.tokens.TextFieldTokens.TextFieldHeight -import xyz.ksharma.krail.design.system.tokens.TextFieldTokens.TextSelectionBackgroundOpacity +import xyz.ksharma.krail.common.designSystem.components.Text +import xyz.ksharma.krail.common.designSystem.LocalContentAlpha +import xyz.ksharma.krail.common.designSystem.LocalTextColor +import xyz.ksharma.krail.common.designSystem.LocalTextStyle +import xyz.ksharma.krail.common.designSystem.theme.KrailTheme +import xyz.ksharma.krail.common.designSystem.tokens.TextFieldTokens +import xyz.ksharma.krail.common.designSystem.tokens.TextFieldTokens.PlaceholderOpacity +import xyz.ksharma.krail.common.designSystem.tokens.TextFieldTokens.TextFieldHeight +import xyz.ksharma.krail.common.designSystem.tokens.TextFieldTokens.TextSelectionBackgroundOpacity /** * Important documentation links: @@ -147,7 +147,7 @@ private fun TextFieldPlaceholder(placeholder: String? = null) { // region Previews -@PreviewLightDark + @Composable private fun TextFieldEnabledPreview() { KrailTheme { @@ -155,7 +155,6 @@ private fun TextFieldEnabledPreview() { } } -@PreviewLightDark @Composable private fun TextFieldEnabledPlaceholderPreview() { KrailTheme { @@ -163,7 +162,6 @@ private fun TextFieldEnabledPlaceholderPreview() { } } -@PreviewLightDark @Composable private fun TextFieldDisabledPreview() { KrailTheme { @@ -171,7 +169,6 @@ private fun TextFieldDisabledPreview() { } } -@PreviewLightDark @Composable private fun TextFieldDisabledPlaceholderPreview() { KrailTheme { diff --git a/core/design-system/src/main/kotlin/xyz/ksharma/krail/design/system/components/TextFieldButton.kt b/composeApp/src/commonMain/kotlin/xyz/ksharma/krail/common/designSystem/components/TextFieldButton.kt similarity index 73% rename from core/design-system/src/main/kotlin/xyz/ksharma/krail/design/system/components/TextFieldButton.kt rename to composeApp/src/commonMain/kotlin/xyz/ksharma/krail/common/designSystem/components/TextFieldButton.kt index bffec20c..82b4899f 100644 --- a/core/design-system/src/main/kotlin/xyz/ksharma/krail/design/system/components/TextFieldButton.kt +++ b/composeApp/src/commonMain/kotlin/xyz/ksharma/krail/common/designSystem/components/TextFieldButton.kt @@ -1,4 +1,4 @@ -package xyz.ksharma.krail.design.system.components +package xyz.ksharma.krail.common.designSystem.components import androidx.compose.foundation.background import androidx.compose.foundation.clickable @@ -14,13 +14,12 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.semantics.Role import androidx.compose.ui.unit.dp -import xyz.ksharma.krail.design.system.LocalContentAlpha -import xyz.ksharma.krail.design.system.LocalTextColor -import xyz.ksharma.krail.design.system.LocalTextStyle -import xyz.ksharma.krail.design.system.preview.PreviewComponent -import xyz.ksharma.krail.design.system.theme.KrailTheme -import xyz.ksharma.krail.design.system.tokens.TextFieldTokens -import xyz.ksharma.krail.design.system.tokens.TextFieldTokens.TextFieldHeight +import xyz.ksharma.krail.common.designSystem.LocalContentAlpha +import xyz.ksharma.krail.common.designSystem.LocalTextColor +import xyz.ksharma.krail.common.designSystem.LocalTextStyle +import xyz.ksharma.krail.common.designSystem.theme.KrailTheme +import xyz.ksharma.krail.common.designSystem.tokens.TextFieldTokens +import xyz.ksharma.krail.common.designSystem.tokens.TextFieldTokens.TextFieldHeight /** * A button that looks like a text field. @@ -59,17 +58,3 @@ fun TextFieldButton( } } } - -// region Previews - -@PreviewComponent -@Composable -private fun TextFieldButtonPreview() { - KrailTheme { - TextFieldButton(onClick = {}) { - Text(text = "Search") - } - } -} - -// endregion diff --git a/composeApp/src/commonMain/kotlin/xyz/ksharma/krail/common/designSystem/components/TitleBar.kt b/composeApp/src/commonMain/kotlin/xyz/ksharma/krail/common/designSystem/components/TitleBar.kt new file mode 100644 index 00000000..6f21be04 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/xyz/ksharma/krail/common/designSystem/components/TitleBar.kt @@ -0,0 +1,55 @@ +package xyz.ksharma.krail.common.designSystem.components + +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.heightIn +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.statusBarsPadding +import androidx.compose.runtime.Composable +import androidx.compose.runtime.CompositionLocalProvider +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.unit.dp +import xyz.ksharma.krail.common.designSystem.LocalContentColor +import xyz.ksharma.krail.common.designSystem.LocalTextColor +import xyz.ksharma.krail.common.designSystem.LocalTextStyle +import xyz.ksharma.krail.common.designSystem.theme.KrailTheme + +@Composable +fun TitleBar( + title: @Composable () -> Unit, + modifier: Modifier = Modifier, + actions: @Composable (() -> Unit)? = null, +) { + Row( + modifier = modifier + .statusBarsPadding() + .fillMaxWidth() + .heightIn(min = 56.dp) + .padding(horizontal = 16.dp, vertical = 4.dp), + horizontalArrangement = Arrangement.SpaceBetween, + verticalAlignment = Alignment.CenterVertically, + ) { + Row(modifier = Modifier.weight(1f)) { + CompositionLocalProvider( + LocalTextColor provides KrailTheme.colors.onSurface, + LocalTextStyle provides KrailTheme.typography.headlineMedium, + ) { + title() + } + } + actions?.let { + Row( + modifier = Modifier.padding(start = 16.dp), + horizontalArrangement = Arrangement.spacedBy(8.dp), + ) { + CompositionLocalProvider( + LocalContentColor provides KrailTheme.colors.onSurface, + ) { + actions() + } + } + } + } +} diff --git a/core/design-system/src/main/kotlin/xyz/ksharma/krail/design/system/theme/A11yColors.kt b/composeApp/src/commonMain/kotlin/xyz/ksharma/krail/common/designSystem/theme/A11yColors.kt similarity index 94% rename from core/design-system/src/main/kotlin/xyz/ksharma/krail/design/system/theme/A11yColors.kt rename to composeApp/src/commonMain/kotlin/xyz/ksharma/krail/common/designSystem/theme/A11yColors.kt index 3d61e68c..3ae059ad 100644 --- a/core/design-system/src/main/kotlin/xyz/ksharma/krail/design/system/theme/A11yColors.kt +++ b/composeApp/src/commonMain/kotlin/xyz/ksharma/krail/common/designSystem/theme/A11yColors.kt @@ -1,4 +1,4 @@ -package xyz.ksharma.krail.design.system.theme +package xyz.ksharma.krail.common.designSystem.theme import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.luminance diff --git a/core/design-system/src/main/kotlin/xyz/ksharma/krail/design/system/theme/Color.kt b/composeApp/src/commonMain/kotlin/xyz/ksharma/krail/common/designSystem/theme/Color.kt similarity index 98% rename from core/design-system/src/main/kotlin/xyz/ksharma/krail/design/system/theme/Color.kt rename to composeApp/src/commonMain/kotlin/xyz/ksharma/krail/common/designSystem/theme/Color.kt index 6e344775..75b92d1e 100644 --- a/core/design-system/src/main/kotlin/xyz/ksharma/krail/design/system/theme/Color.kt +++ b/composeApp/src/commonMain/kotlin/xyz/ksharma/krail/common/designSystem/theme/Color.kt @@ -1,4 +1,4 @@ -package xyz.ksharma.krail.design.system.theme +package xyz.ksharma.krail.common.designSystem.theme import androidx.compose.runtime.Immutable import androidx.compose.runtime.staticCompositionLocalOf diff --git a/core/design-system/src/main/kotlin/xyz/ksharma/krail/design/system/theme/KrailTypography.kt b/composeApp/src/commonMain/kotlin/xyz/ksharma/krail/common/designSystem/theme/KrailTypography.kt similarity index 99% rename from core/design-system/src/main/kotlin/xyz/ksharma/krail/design/system/theme/KrailTypography.kt rename to composeApp/src/commonMain/kotlin/xyz/ksharma/krail/common/designSystem/theme/KrailTypography.kt index 4b70f5dd..96305649 100644 --- a/core/design-system/src/main/kotlin/xyz/ksharma/krail/design/system/theme/KrailTypography.kt +++ b/composeApp/src/commonMain/kotlin/xyz/ksharma/krail/common/designSystem/theme/KrailTypography.kt @@ -1,4 +1,4 @@ -package xyz.ksharma.krail.design.system.theme +package xyz.ksharma.krail.common.designSystem.theme import androidx.compose.runtime.Immutable import androidx.compose.runtime.staticCompositionLocalOf diff --git a/core/design-system/src/main/kotlin/xyz/ksharma/krail/design/system/theme/Theme.kt b/composeApp/src/commonMain/kotlin/xyz/ksharma/krail/common/designSystem/theme/Theme.kt similarity index 93% rename from core/design-system/src/main/kotlin/xyz/ksharma/krail/design/system/theme/Theme.kt rename to composeApp/src/commonMain/kotlin/xyz/ksharma/krail/common/designSystem/theme/Theme.kt index 1845d6f3..0be93719 100644 --- a/core/design-system/src/main/kotlin/xyz/ksharma/krail/design/system/theme/Theme.kt +++ b/composeApp/src/commonMain/kotlin/xyz/ksharma/krail/common/designSystem/theme/Theme.kt @@ -1,4 +1,4 @@ -package xyz.ksharma.krail.design.system.theme +package xyz.ksharma.krail.common.designSystem.theme import androidx.compose.foundation.isSystemInDarkTheme import androidx.compose.runtime.Composable diff --git a/core/design-system/src/main/kotlin/xyz/ksharma/krail/design/system/tokens/ButtonTokens.kt b/composeApp/src/commonMain/kotlin/xyz/ksharma/krail/common/designSystem/tokens/ButtonTokens.kt similarity index 65% rename from core/design-system/src/main/kotlin/xyz/ksharma/krail/design/system/tokens/ButtonTokens.kt rename to composeApp/src/commonMain/kotlin/xyz/ksharma/krail/common/designSystem/tokens/ButtonTokens.kt index b28eeaf5..149401be 100644 --- a/core/design-system/src/main/kotlin/xyz/ksharma/krail/design/system/tokens/ButtonTokens.kt +++ b/composeApp/src/commonMain/kotlin/xyz/ksharma/krail/common/designSystem/tokens/ButtonTokens.kt @@ -1,4 +1,4 @@ -package xyz.ksharma.krail.design.system.tokens +package xyz.ksharma.krail.common.designSystem.tokens import androidx.compose.ui.unit.dp diff --git a/core/design-system/src/main/kotlin/xyz/ksharma/krail/design/system/tokens/TextFieldTokens.kt b/composeApp/src/commonMain/kotlin/xyz/ksharma/krail/common/designSystem/tokens/TextFieldTokens.kt similarity index 81% rename from core/design-system/src/main/kotlin/xyz/ksharma/krail/design/system/tokens/TextFieldTokens.kt rename to composeApp/src/commonMain/kotlin/xyz/ksharma/krail/common/designSystem/tokens/TextFieldTokens.kt index 35f1e697..a30f6d93 100644 --- a/core/design-system/src/main/kotlin/xyz/ksharma/krail/design/system/tokens/TextFieldTokens.kt +++ b/composeApp/src/commonMain/kotlin/xyz/ksharma/krail/common/designSystem/tokens/TextFieldTokens.kt @@ -1,4 +1,4 @@ -package xyz.ksharma.krail.design.system.tokens +package xyz.ksharma.krail.common.designSystem.tokens import androidx.compose.ui.unit.dp diff --git a/core/design-system/README.md b/core/design-system/README.md deleted file mode 100644 index 9b32f5fa..00000000 --- a/core/design-system/README.md +++ /dev/null @@ -1,6 +0,0 @@ -## Important links for documentation - -Compose Foundation https://developer.android.com/jetpack/androidx/releases/compose-foundation -Material 3 Compose Code - https://github.com/androidx/androidx/tree/androidx-main/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3 -Compose Foundation Examples - https://composables.com/foundation/ -Compose Foundation Official Samples - https://cs.android.com/androidx/platform/frameworks/support/+/androidx-main:compose/foundation/foundation/samples/src/main/java/androidx/compose/foundation/samples/ diff --git a/core/design-system/build.gradle.kts b/core/design-system/build.gradle.kts deleted file mode 100644 index 60a8d40d..00000000 --- a/core/design-system/build.gradle.kts +++ /dev/null @@ -1,24 +0,0 @@ -plugins { - alias(libs.plugins.krail.android.library) - alias(libs.plugins.krail.compose.multiplatform) - alias(libs.plugins.krail.kotlin.multiplatform) -} - -android { - namespace = "xyz.ksharma.krail.design.system" -} - -dependencies { - api(platform(libs.compose.bom)) - implementation(libs.compose.foundation) - implementation(libs.compose.ui) - implementation(libs.compose.ui.graphics) - api(libs.compose.ui.tooling.preview) - implementation(libs.compose.material3) - implementation(libs.core.ktx) // adding for reading code inspiration. - - // Testing - androidTestImplementation(platform(libs.compose.bom)) - debugApi(libs.compose.ui.tooling) - debugApi(libs.test.composeUiTestManifest) -} diff --git a/core/design-system/src/main/kotlin/xyz/.DS_Store b/core/design-system/src/main/kotlin/xyz/.DS_Store deleted file mode 100644 index 3bebff83a4a10dcd0a62a69202eed38905dc93f8..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6148 zcmeHK%}T>S5T0$TO({YT3OxqA7ObrlikDF93mDOZN=<07p=q`xsXdfJ&iX<=iO=KA z?gp&p;7P>J!0tCYKf9R^vOfSI2Fvj&Km!0QY=nZ80wHsut7L)+g_bW zM1Rpl-(H0oBrpXJK7GIR(evgn!XQc8?RQZsSN8U+R@JIo5AIa1+|)~#$-s;6Xml-Q z=;ylU-vpD@sBw59qtpwcNvslr@dQKeZ-ZzoR|C0>#)-;xwZp1ewNazFUUz!!mfh|2 zH!XYJ@1TCx?QJ$S>*)C8{Ax4{=aGETR0?EW$$`NfUQzi{)w3H%p^To;lV|18jLZNt zzzi@0yTyPx2dw&TmCe;M1I)k=7@+;Z#zyEE%rvU413S7tlfObpf;zn=2yKIo!Av81 zP=roJ)TzQeF@#P>yKUkegPBI14nnPr^O%+M$BR&_quo~FARLX{GXu=PHUninY|#0C ziN8$YBY!)EN6Y{-@Xr_!m7zNvU{U65{Z<~GwKld#Y$Ozyk%EHy)+GQ3w2w5EQ~Pbw aG0riVX{1@muF?_tBA^K2jv4p`20j3wdQ67^ diff --git a/core/design-system/src/main/kotlin/xyz/ksharma/.DS_Store b/core/design-system/src/main/kotlin/xyz/ksharma/.DS_Store deleted file mode 100644 index aeb58a8f5e0dab4d67ed31a3e2ceedee463edb4f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6148 zcmeHK%}T>S5Z>*NO(;SS3OxqA7OkxmikDdH!K)EHsMN$14UO57q~=fxIqM7gB)*Q$ z>~5q|y@^Pff!S{|JCkI;4Lcde7$401ea0G$F#{B_WWw-`;5_Pzl(eS|$mKo4(@@-b zq3~8>$?-oKz`d)o35%J_0=B$=_(>dr?!HTrufG)OWyw_U^s6@wgCuRY-*~B9sczJa zno&1yoClFPshiG{fg4}p)wvL%SJ4Oxk8u$;VKnSrE=Y3aFq^zOUKz7bA>9Maov1y{ba5?6fU0* z_qSv?o1jv_Z=nHbYs?jb2ZVkEBn>nW1AofE2fe6PUjP6A diff --git a/core/design-system/src/main/kotlin/xyz/ksharma/krail/.DS_Store b/core/design-system/src/main/kotlin/xyz/ksharma/krail/.DS_Store deleted file mode 100644 index 5d56e9d7e4999b4eea93fc8e20aa9a7f0581d12d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6148 zcmeHKI|>3Z5S>vG!N$@uSMUZw^aNhOLJ>h$P_*94b9pr1d>UQtw2?P3dC6p6LSC`6 zBO*G#Y-S=85gEY^i0p zwti-h{ zq5pp-aYY5Fz+Wk#gT-nw$CI+Qb{=Q7w!qhL%elkNFn0c7o_bQ`6`Nzf VCbof2N8IT^{tTEdG%E0G1s=-46_x-1 diff --git a/core/design-system/src/main/kotlin/xyz/ksharma/krail/design/system/components/Fab.kt b/core/design-system/src/main/kotlin/xyz/ksharma/krail/design/system/components/Fab.kt deleted file mode 100644 index 266b0960..00000000 --- a/core/design-system/src/main/kotlin/xyz/ksharma/krail/design/system/components/Fab.kt +++ /dev/null @@ -1,85 +0,0 @@ -package xyz.ksharma.krail.design.system.components - -import androidx.compose.foundation.Image -import androidx.compose.foundation.background -import androidx.compose.foundation.clickable -import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.size -import androidx.compose.foundation.layout.systemBarsPadding -import androidx.compose.foundation.shape.CircleShape -import androidx.compose.runtime.Composable -import androidx.compose.runtime.CompositionLocalProvider -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.Color -import androidx.compose.ui.graphics.ColorFilter -import androidx.compose.ui.res.painterResource -import androidx.compose.ui.semantics.Role -import androidx.compose.ui.tooling.preview.PreviewLightDark -import androidx.compose.ui.unit.dp -import xyz.ksharma.krail.design.system.LocalContentColor -import xyz.ksharma.krail.design.system.R -import xyz.ksharma.krail.design.system.theme.KrailTheme - -/** - * FAB is a floating action button that represents the primary action of a screen. - * - * @param containerColor The background color of the FAB. - * @param contentColor The color of the content (e.g., icon) inside the FAB. - * @param onClick The callback to be invoked when the FAB is clicked. - * @param modifier The modifier to be applied to the FAB. - * @param content The composable content to be displayed inside the FAB. - */ -@Composable -fun Fab( - containerColor: Color, - contentColor: Color, - onClick: () -> Unit, - modifier: Modifier = Modifier, - content: @Composable () -> Unit, -) { - Box( - modifier = modifier - .fillMaxSize() - .systemBarsPadding() - .padding(24.dp) - .clickable(role = Role.Button, onClick = onClick), - contentAlignment = Alignment.BottomEnd, - ) { - Box( - modifier = Modifier - .size(56.dp) - .background(color = containerColor, shape = CircleShape), - contentAlignment = Alignment.Center, - ) { - CompositionLocalProvider(LocalContentColor provides contentColor) { - content() - } - } - } -} - -// region Preview - -@PreviewLightDark -@Composable -private fun PreviewFab() { - KrailTheme { - Fab( - containerColor = KrailTheme.colors.surface, - contentColor = KrailTheme.colors.onSurface, - onClick = {}, - ) { - Image( - painter = painterResource(R.drawable.star_outline), - contentDescription = null, - colorFilter = ColorFilter.tint(color = LocalContentColor.current), - modifier = Modifier.size(32.dp), - ) - } - } -} - -// endregion diff --git a/core/design-system/src/main/kotlin/xyz/ksharma/krail/design/system/components/TitleBar.kt b/core/design-system/src/main/kotlin/xyz/ksharma/krail/design/system/components/TitleBar.kt index d582ec47..05dbb4c7 100644 --- a/core/design-system/src/main/kotlin/xyz/ksharma/krail/design/system/components/TitleBar.kt +++ b/core/design-system/src/main/kotlin/xyz/ksharma/krail/design/system/components/TitleBar.kt @@ -20,12 +20,10 @@ import androidx.compose.ui.graphics.ColorFilter import androidx.compose.ui.res.painterResource import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp -import xyz.ksharma.krail.design.system.LocalContentColor -import xyz.ksharma.krail.design.system.LocalTextColor -import xyz.ksharma.krail.design.system.LocalTextStyle -import xyz.ksharma.krail.design.system.R -import xyz.ksharma.krail.design.system.preview.PreviewComponent -import xyz.ksharma.krail.design.system.theme.KrailTheme +import xyz.ksharma.krail.taj.LocalContentColor +import xyz.ksharma.krail.taj.LocalTextColor +import xyz.ksharma.krail.taj.LocalTextStyle +import xyz.ksharma.krail.taj.theme.KrailTheme @Composable fun TitleBar( @@ -76,7 +74,6 @@ fun TitleBar( // region Previews -@PreviewComponent @Composable private fun TitleBarPreview() { KrailTheme { @@ -138,7 +135,6 @@ private fun TitleBarPreviewMultipleActions() { } } -@PreviewComponent @Composable private fun TitleBarPreviewNoActions() { KrailTheme { diff --git a/core/design-system/src/main/kotlin/xyz/ksharma/krail/design/system/preview/PreviewComponent.kt b/core/design-system/src/main/kotlin/xyz/ksharma/krail/design/system/preview/PreviewComponent.kt deleted file mode 100644 index 69329eb2..00000000 --- a/core/design-system/src/main/kotlin/xyz/ksharma/krail/design/system/preview/PreviewComponent.kt +++ /dev/null @@ -1,14 +0,0 @@ -package xyz.ksharma.krail.design.system.preview - -import androidx.compose.ui.tooling.preview.Preview -import androidx.compose.ui.tooling.preview.PreviewLightDark - -/** - * A MultiPreview annotation for displaying a component using light and dark themes along with - * large font size. - */ -@Retention(AnnotationRetention.BINARY) -@Target(AnnotationTarget.ANNOTATION_CLASS, AnnotationTarget.FUNCTION) -@PreviewLightDark -@Preview(name = "Large Font", fontScale = 2f) -annotation class PreviewComponent diff --git a/core/design-system/src/main/res/drawable/star_filled.xml b/core/design-system/src/main/res/drawable/star_filled.xml deleted file mode 100644 index 0b4fb8b6..00000000 --- a/core/design-system/src/main/res/drawable/star_filled.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - diff --git a/core/design-system/src/main/res/drawable/star_outline.xml b/core/design-system/src/main/res/drawable/star_outline.xml deleted file mode 100644 index 8e80be47..00000000 --- a/core/design-system/src/main/res/drawable/star_outline.xml +++ /dev/null @@ -1,10 +0,0 @@ - - - diff --git a/core/design-system/src/test/kotlin/xyz/.DS_Store b/core/design-system/src/test/kotlin/xyz/.DS_Store deleted file mode 100644 index 80a3d64a031817c98e9def2141c498aa6194af17..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6148 zcmeHK%}T>S5T3QwrWBzE1-&hJE!bAAh?h|73mDOZN=<07p=q`>tvQrJ?)pN$h|lB9 z?gp&(;7P>J!0tCYKf9R^vOfSI+Ou8*pb7vEHbOzl3L*1K*MSjhbwn zoVFG>hzW%lonl! znL+fR2$PCvQiXkD2$PO>Y2#drnL(2dLa&VT*p>6gi_oj1UFvWUu0igZ0cK#Cfr1%U z>HNRMUuNYae>sIm%m6d+&lnKJj@M~pQTA;8Rvw+T65As-5{k=6K|y`#5`Y8RNA|SS c_$BEW=UU7R(kx`x>4S5Z>*NO({YT3VK`cTC}ZN5ihaUgI6PZP^pP28jRV}q~=fxx$6u0BEF8! z>~5q|y@^PffthbJJCkI-4Z9h}81Ih#7GrhBm<5VhsDk-Ma2|C|3dU0e(s*cA`qdkTL7X(3Z@f?}m6s}3 z#j07?&b>^X#7)L=*BxEr)tQu`myccVJQ$??`pS`v5;uqjBb5;N0|>dg2qIsmT{({Y zSmio;!m3);etms1X?I!;(c0bXOdDdd-)=U<&UR-yty-%aTL-88hww3y&+4TahmTXq zvcVi)U@%kClQW7!8Qp<5&&VSQi2-7O7+5F<%qC~m7OE;Lh8Q3QeuDwr9|S0(Z7|cQ zwhl0=1pq98TM3Nu#}RXcLEB)a5oSQRP6gDd+_V^6r-NUZINM;RQKvI*nh$QC%uR>F z)zjhrLWMJKYa~ky5CdNsz}^p}j_3dJ&;7p&B0~%i1B=N3FZG;W7iu$S>s*z1)(X%Y rP!x>IG=8SQKpn*pi%0Pqs1ooC901w|GmYQ@p&tQ70~uoAPZ{_C4kA|a diff --git a/core/design-system/src/test/kotlin/xyz/ksharma/start/.DS_Store b/core/design-system/src/test/kotlin/xyz/ksharma/start/.DS_Store deleted file mode 100644 index 122de71f9e3f9758a0068f4d5165717387bc1ec8..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6148 zcmeHK!A`&YSteUBc*XOX)1 zBjqndv*SN9Kx>zW0J@Ms2=nVlAH^~1_J z;FO$O??H{dVP`l>nw`NlU0o;@`Ll7yzYM$Mc6seY#lub*cLzEl47wO{a}~yc8aLG_ z4icT~nF*)p6x-#^$)w(>Rb*}dpfRn;$zi=(k$bz1>9pvqZ|xkNw;!XYSiR`i27w=_ zmQ{;0c*Vh7QP18Wj#S)3|14(~%g78c1I)mZFkrVgr?e#Xa2d=1Gw^#1(EcD%30;e+ zLA`aL(JcUC4&7RCOuvnkBQ3fXQ-e5zB1|fxNfoxm5GEb{(#E+KQ-dZQgl#^AeX_6} ziqKET`%4`T!Zpa28DIwH8KB+|rA+7l>CgSY4r0R$FayiUfXKDHRugNpXX`?hbk<7L sJ5&;iOAUUd;6NY67)wX-2C5eHOBx`$7E^=hLE#?(O#>Td;7=L&1YT5FIsgCw diff --git a/core/design-system/src/test/kotlin/xyz/ksharma/start/design/.DS_Store b/core/design-system/src/test/kotlin/xyz/ksharma/start/design/.DS_Store deleted file mode 100644 index 5e966b58ebf9a37392586f02354e318be4c0eb96..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6148 zcmeHKJB|V|47HaLLZYFh%oVsngxC{s0SXY!GD!TS?WW>fdo-Ru4Gh~sgPtYlC5~UB zyka~S5uKmcGm(jijNpcHwV`jeZ{D-7j3^L}Gd8ljED!C^zTNh+p9hRPl%xE@PY-pyTG7}X7izWvu1~)eml-Do-SGgIZ^>CaIe51mJ_T0 z-|&C>|9cWwRDcS6lmgl;mWw%_l(n_@IIFb?.toColors(onSurface: Color): List = when // region Previews -@PreviewLightDark + @Preview(fontScale = 2f) @Composable private fun PreviewJourneyCard() { @@ -556,7 +556,7 @@ private fun PreviewJourneyCard() { } } -@PreviewLightDark + @Preview(fontScale = 2f) @Composable private fun PreviewJourneyCardCollapsed() { diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index dda22521..f6c521c6 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -9,8 +9,6 @@ androidx-test = "1.6.1" androidx-test-ext-junit = "1.2.1" android-lifecycle = "2.8.7" activity-compose = "1.9.3" -compose-bom = "2024.10.01" -compose-navigation = "2.8.3" hilt = "2.52" hiltNavigationCompose = "1.2.0" kotlinxCollectionsImmutable = "0.3.8" @@ -40,25 +38,9 @@ lifecycle-runtime-ktx = { group = "androidx.lifecycle", name = "lifecycle-runtim retrofit2-kotlinx-serialization-converter = { module = "com.jakewharton.retrofit:retrofit2-kotlinx-serialization-converter", version.ref = "retrofit2KotlinxSerializationConverter" } timber = { module = "com.jakewharton.timber:timber", version.ref = "timber" } -#Compose -compose-bom = { group = "androidx.compose", name = "compose-bom", version.ref = "compose-bom" } -compose-foundation = { group = "androidx.compose.foundation", name = "foundation" } -compose-ui = { group = "androidx.compose.ui", name = "ui" } -compose-ui-graphics = { group = "androidx.compose.ui", name = "ui-graphics" } -compose-ui-tooling = { group = "androidx.compose.ui", name = "ui-tooling" } -compose-ui-tooling-preview = { group = "androidx.compose.ui", name = "ui-tooling-preview" } -compose-navigation = { group = "androidx.navigation", name = "navigation-compose", version.ref = "compose-navigation" } -compose-material3 = { group = "androidx.compose.material3", name = "material3", version = "1.3.1" } - androidx-lifecycle-viewmodel = { group = "org.jetbrains.androidx.lifecycle", name = "lifecycle-viewmodel", version.ref = "androidx-lifecycle" } androidx-lifecycle-runtime-compose = { group = "org.jetbrains.androidx.lifecycle", name = "lifecycle-runtime-compose", version.ref = "androidx-lifecycle" } - -#Hilt -hilt-android = { group = "com.google.dagger", name = "hilt-android", version.ref = "hilt" } -hilt-compiler = { group = "com.google.dagger", name = "hilt-compiler", version.ref = "hilt" } -hilt-navigation-compose = { group = "androidx.hilt", name = "hilt-navigation-compose", version.ref = "hiltNavigationCompose" } - #Network kotlinx-serialization-json = { group = "org.jetbrains.kotlinx", name = "kotlinx-serialization-json", version.ref = "kotlinxSerializationJson" } okhttp-logging-interceptor = { module = "com.squareup.okhttp3:logging-interceptor" } diff --git a/settings.gradle.kts b/settings.gradle.kts index 05ae7f7c..3ae8b66e 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -31,7 +31,6 @@ dependencyResolutionManagement { rootProject.name = "Krail" include(":android-app") -include(":core:design-system") include(":composeApp") /* include(":core:coroutines-ext") From 564bc9893f6256d9bc3fd51aff201115f8d3c0d2 Mon Sep 17 00:00:00 2001 From: Karan Sharma <55722391+ksharma-xyz@users.noreply.github.com> Date: Sat, 16 Nov 2024 17:20:56 +1100 Subject: [PATCH 17/67] Add design system module taj --- composeApp/build.gradle.kts | 2 ++ .../xyz/ksharma/krail/common/KrailApp.kt | 4 +-- settings.gradle.kts | 1 + taj/build.gradle.kts | 36 +++++++++++++++++++ .../ksharma/krail/taj}/CompositionLocals.kt | 2 +- .../kotlin/xyz/ksharma/krail/taj}/DpExt.kt | 2 +- .../ksharma/krail/taj}/components/Divider.kt | 6 ++-- .../krail/taj}/components/RoundIconButton.kt | 10 +++--- .../krail/taj}/components/SeparatorIcon.kt | 6 ++-- .../xyz/ksharma/krail/taj}/components/Text.kt | 10 +++--- .../krail/taj}/components/TextField.kt | 19 +++++----- .../krail/taj}/components/TextFieldButton.kt | 14 ++++---- .../ksharma/krail/taj}/components/TitleBar.kt | 10 +++--- .../ksharma/krail/taj}/theme/A11yColors.kt | 2 +- .../xyz/ksharma/krail/taj}/theme/Color.kt | 2 +- .../krail/taj}/theme/KrailTypography.kt | 2 +- .../xyz/ksharma/krail/taj}/theme/Theme.kt | 2 +- .../ksharma/krail/taj}/tokens/ButtonTokens.kt | 2 +- .../krail/taj}/tokens/TextFieldTokens.kt | 2 +- 19 files changed, 86 insertions(+), 48 deletions(-) create mode 100644 taj/build.gradle.kts rename {composeApp/src/commonMain/kotlin/xyz/ksharma/krail/common/designSystem => taj/src/commonMain/kotlin/xyz/ksharma/krail/taj}/CompositionLocals.kt (93%) rename {composeApp/src/commonMain/kotlin/xyz/ksharma/krail/common/designSystem => taj/src/commonMain/kotlin/xyz/ksharma/krail/taj}/DpExt.kt (95%) rename {composeApp/src/commonMain/kotlin/xyz/ksharma/krail/common/designSystem => taj/src/commonMain/kotlin/xyz/ksharma/krail/taj}/components/Divider.kt (92%) rename {composeApp/src/commonMain/kotlin/xyz/ksharma/krail/common/designSystem => taj/src/commonMain/kotlin/xyz/ksharma/krail/taj}/components/RoundIconButton.kt (85%) rename {composeApp/src/commonMain/kotlin/xyz/ksharma/krail/common/designSystem => taj/src/commonMain/kotlin/xyz/ksharma/krail/taj}/components/SeparatorIcon.kt (85%) rename {composeApp/src/commonMain/kotlin/xyz/ksharma/krail/common/designSystem => taj/src/commonMain/kotlin/xyz/ksharma/krail/taj}/components/Text.kt (91%) rename {composeApp/src/commonMain/kotlin/xyz/ksharma/krail/common/designSystem => taj/src/commonMain/kotlin/xyz/ksharma/krail/taj}/components/TextField.kt (90%) rename {composeApp/src/commonMain/kotlin/xyz/ksharma/krail/common/designSystem => taj/src/commonMain/kotlin/xyz/ksharma/krail/taj}/components/TextFieldButton.kt (80%) rename {composeApp/src/commonMain/kotlin/xyz/ksharma/krail/common/designSystem => taj/src/commonMain/kotlin/xyz/ksharma/krail/taj}/components/TitleBar.kt (84%) rename {composeApp/src/commonMain/kotlin/xyz/ksharma/krail/common/designSystem => taj/src/commonMain/kotlin/xyz/ksharma/krail/taj}/theme/A11yColors.kt (94%) rename {composeApp/src/commonMain/kotlin/xyz/ksharma/krail/common/designSystem => taj/src/commonMain/kotlin/xyz/ksharma/krail/taj}/theme/Color.kt (98%) rename {composeApp/src/commonMain/kotlin/xyz/ksharma/krail/common/designSystem => taj/src/commonMain/kotlin/xyz/ksharma/krail/taj}/theme/KrailTypography.kt (99%) rename {composeApp/src/commonMain/kotlin/xyz/ksharma/krail/common/designSystem => taj/src/commonMain/kotlin/xyz/ksharma/krail/taj}/theme/Theme.kt (93%) rename {composeApp/src/commonMain/kotlin/xyz/ksharma/krail/common/designSystem => taj/src/commonMain/kotlin/xyz/ksharma/krail/taj}/tokens/ButtonTokens.kt (65%) rename {composeApp/src/commonMain/kotlin/xyz/ksharma/krail/common/designSystem => taj/src/commonMain/kotlin/xyz/ksharma/krail/taj}/tokens/TextFieldTokens.kt (81%) diff --git a/composeApp/build.gradle.kts b/composeApp/build.gradle.kts index 53731e01..a2d14554 100644 --- a/composeApp/build.gradle.kts +++ b/composeApp/build.gradle.kts @@ -55,6 +55,8 @@ kotlin { } commonMain.dependencies { + implementation(projects.taj) + implementation(compose.runtime) implementation(compose.foundation) implementation(compose.material) diff --git a/composeApp/src/commonMain/kotlin/xyz/ksharma/krail/common/KrailApp.kt b/composeApp/src/commonMain/kotlin/xyz/ksharma/krail/common/KrailApp.kt index eca6b202..234f3a18 100644 --- a/composeApp/src/commonMain/kotlin/xyz/ksharma/krail/common/KrailApp.kt +++ b/composeApp/src/commonMain/kotlin/xyz/ksharma/krail/common/KrailApp.kt @@ -6,8 +6,8 @@ import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.statusBarsPadding import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier -import xyz.ksharma.krail.common.designSystem.components.Text -import xyz.ksharma.krail.common.designSystem.theme.KrailTheme +import xyz.ksharma.krail.taj.components.Text +import xyz.ksharma.krail.taj.theme.KrailTheme @Composable fun KrailApp() { diff --git a/settings.gradle.kts b/settings.gradle.kts index 3ae8b66e..edb267d5 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -32,6 +32,7 @@ dependencyResolutionManagement { rootProject.name = "Krail" include(":android-app") include(":composeApp") +include(":taj") // Design System /* include(":core:coroutines-ext") include(":core:date-time") diff --git a/taj/build.gradle.kts b/taj/build.gradle.kts new file mode 100644 index 00000000..c84628a2 --- /dev/null +++ b/taj/build.gradle.kts @@ -0,0 +1,36 @@ +plugins { + alias(libs.plugins.krail.kotlin.multiplatform) + alias(libs.plugins.krail.compose.multiplatform) + alias(libs.plugins.compose.compiler) + alias(libs.plugins.krail.android.library) +} + +android { + namespace = "xyz.ksharma.krail.taj" +} + +kotlin { + applyDefaultHierarchyTemplate() + + androidTarget() + + iosArm64() + iosSimulatorArm64() + + java { + toolchain { + languageVersion.set(JavaLanguageVersion.of(JavaVersion.VERSION_17.majorVersion)) + } + } + + sourceSets { + commonMain { + dependencies { + implementation(compose.foundation) + implementation(compose.animation) + implementation(compose.ui) + implementation(compose.material3) + } + } + } +} diff --git a/composeApp/src/commonMain/kotlin/xyz/ksharma/krail/common/designSystem/CompositionLocals.kt b/taj/src/commonMain/kotlin/xyz/ksharma/krail/taj/CompositionLocals.kt similarity index 93% rename from composeApp/src/commonMain/kotlin/xyz/ksharma/krail/common/designSystem/CompositionLocals.kt rename to taj/src/commonMain/kotlin/xyz/ksharma/krail/taj/CompositionLocals.kt index 2472c93c..1be538b5 100644 --- a/composeApp/src/commonMain/kotlin/xyz/ksharma/krail/common/designSystem/CompositionLocals.kt +++ b/taj/src/commonMain/kotlin/xyz/ksharma/krail/taj/CompositionLocals.kt @@ -1,4 +1,4 @@ -package xyz.ksharma.krail.common.designSystem +package xyz.ksharma.krail.taj import androidx.compose.runtime.compositionLocalOf import androidx.compose.runtime.mutableStateOf diff --git a/composeApp/src/commonMain/kotlin/xyz/ksharma/krail/common/designSystem/DpExt.kt b/taj/src/commonMain/kotlin/xyz/ksharma/krail/taj/DpExt.kt similarity index 95% rename from composeApp/src/commonMain/kotlin/xyz/ksharma/krail/common/designSystem/DpExt.kt rename to taj/src/commonMain/kotlin/xyz/ksharma/krail/taj/DpExt.kt index 1b6e6510..475dce61 100644 --- a/composeApp/src/commonMain/kotlin/xyz/ksharma/krail/common/designSystem/DpExt.kt +++ b/taj/src/commonMain/kotlin/xyz/ksharma/krail/taj/DpExt.kt @@ -1,4 +1,4 @@ -package xyz.ksharma.krail.common.designSystem +package xyz.ksharma.krail.taj import androidx.compose.runtime.Composable import androidx.compose.ui.platform.LocalDensity diff --git a/composeApp/src/commonMain/kotlin/xyz/ksharma/krail/common/designSystem/components/Divider.kt b/taj/src/commonMain/kotlin/xyz/ksharma/krail/taj/components/Divider.kt similarity index 92% rename from composeApp/src/commonMain/kotlin/xyz/ksharma/krail/common/designSystem/components/Divider.kt rename to taj/src/commonMain/kotlin/xyz/ksharma/krail/taj/components/Divider.kt index ea2011eb..d1ab971e 100644 --- a/composeApp/src/commonMain/kotlin/xyz/ksharma/krail/common/designSystem/components/Divider.kt +++ b/taj/src/commonMain/kotlin/xyz/ksharma/krail/taj/components/Divider.kt @@ -1,4 +1,4 @@ -package xyz.ksharma.krail.common.designSystem.components +package xyz.ksharma.krail.taj.components import androidx.compose.foundation.background import androidx.compose.foundation.layout.Box @@ -13,8 +13,8 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.unit.dp -import xyz.ksharma.krail.common.designSystem.LocalContentColor -import xyz.ksharma.krail.common.designSystem.theme.KrailTheme +import xyz.ksharma.krail.taj.LocalContentColor +import xyz.ksharma.krail.taj.theme.KrailTheme @Composable fun Divider( diff --git a/composeApp/src/commonMain/kotlin/xyz/ksharma/krail/common/designSystem/components/RoundIconButton.kt b/taj/src/commonMain/kotlin/xyz/ksharma/krail/taj/components/RoundIconButton.kt similarity index 85% rename from composeApp/src/commonMain/kotlin/xyz/ksharma/krail/common/designSystem/components/RoundIconButton.kt rename to taj/src/commonMain/kotlin/xyz/ksharma/krail/taj/components/RoundIconButton.kt index 8b5d1c3a..b9545cef 100644 --- a/composeApp/src/commonMain/kotlin/xyz/ksharma/krail/common/designSystem/components/RoundIconButton.kt +++ b/taj/src/commonMain/kotlin/xyz/ksharma/krail/taj/components/RoundIconButton.kt @@ -1,4 +1,4 @@ -package xyz.ksharma.krail.common.designSystem.components +package xyz.ksharma.krail.taj.components import androidx.compose.foundation.background import androidx.compose.foundation.clickable @@ -12,10 +12,10 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.Color import androidx.compose.ui.semantics.Role -import xyz.ksharma.krail.common.designSystem.LocalContentColor -import xyz.ksharma.krail.common.designSystem.LocalOnContentColor -import xyz.ksharma.krail.common.designSystem.theme.KrailTheme -import xyz.ksharma.krail.common.designSystem.tokens.ButtonTokens.RoundButtonSize +import xyz.ksharma.krail.taj.LocalContentColor +import xyz.ksharma.krail.taj.LocalOnContentColor +import xyz.ksharma.krail.taj.theme.KrailTheme +import xyz.ksharma.krail.taj.tokens.ButtonTokens.RoundButtonSize /** * A round icon button with customizable content and colors. diff --git a/composeApp/src/commonMain/kotlin/xyz/ksharma/krail/common/designSystem/components/SeparatorIcon.kt b/taj/src/commonMain/kotlin/xyz/ksharma/krail/taj/components/SeparatorIcon.kt similarity index 85% rename from composeApp/src/commonMain/kotlin/xyz/ksharma/krail/common/designSystem/components/SeparatorIcon.kt rename to taj/src/commonMain/kotlin/xyz/ksharma/krail/taj/components/SeparatorIcon.kt index 0cb62b64..c3037446 100644 --- a/composeApp/src/commonMain/kotlin/xyz/ksharma/krail/common/designSystem/components/SeparatorIcon.kt +++ b/taj/src/commonMain/kotlin/xyz/ksharma/krail/taj/components/SeparatorIcon.kt @@ -1,4 +1,4 @@ -package xyz.ksharma.krail.common.designSystem.components +package xyz.ksharma.krail.taj.components import androidx.compose.foundation.background import androidx.compose.foundation.layout.Box @@ -10,8 +10,8 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.Color import androidx.compose.ui.unit.dp -import xyz.ksharma.krail.common.designSystem.theme.KrailTheme -import xyz.ksharma.krail.common.designSystem.toAdaptiveSize +import xyz.ksharma.krail.taj.theme.KrailTheme +import xyz.ksharma.krail.taj.toAdaptiveSize @Composable fun SeparatorIcon(modifier: Modifier = Modifier, color: Color = KrailTheme.colors.onSurface) { diff --git a/composeApp/src/commonMain/kotlin/xyz/ksharma/krail/common/designSystem/components/Text.kt b/taj/src/commonMain/kotlin/xyz/ksharma/krail/taj/components/Text.kt similarity index 91% rename from composeApp/src/commonMain/kotlin/xyz/ksharma/krail/common/designSystem/components/Text.kt rename to taj/src/commonMain/kotlin/xyz/ksharma/krail/taj/components/Text.kt index 56e88959..1709f38f 100644 --- a/composeApp/src/commonMain/kotlin/xyz/ksharma/krail/common/designSystem/components/Text.kt +++ b/taj/src/commonMain/kotlin/xyz/ksharma/krail/taj/components/Text.kt @@ -1,4 +1,4 @@ -package xyz.ksharma.krail.common.designSystem.components +package xyz.ksharma.krail.taj.components import androidx.compose.foundation.background import androidx.compose.foundation.layout.Column @@ -13,10 +13,10 @@ import androidx.compose.ui.text.TextStyle import androidx.compose.ui.text.font.FontFamily import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.text.style.TextOverflow -import xyz.ksharma.krail.common.designSystem.LocalContentAlpha -import xyz.ksharma.krail.common.designSystem.LocalTextColor -import xyz.ksharma.krail.common.designSystem.LocalTextStyle -import xyz.ksharma.krail.common.designSystem.theme.KrailTheme +import xyz.ksharma.krail.taj.LocalContentAlpha +import xyz.ksharma.krail.taj.LocalTextColor +import xyz.ksharma.krail.taj.LocalTextStyle +import xyz.ksharma.krail.taj.theme.KrailTheme @Composable fun Text( diff --git a/composeApp/src/commonMain/kotlin/xyz/ksharma/krail/common/designSystem/components/TextField.kt b/taj/src/commonMain/kotlin/xyz/ksharma/krail/taj/components/TextField.kt similarity index 90% rename from composeApp/src/commonMain/kotlin/xyz/ksharma/krail/common/designSystem/components/TextField.kt rename to taj/src/commonMain/kotlin/xyz/ksharma/krail/taj/components/TextField.kt index 4fc1eaf6..0b533aff 100644 --- a/composeApp/src/commonMain/kotlin/xyz/ksharma/krail/common/designSystem/components/TextField.kt +++ b/taj/src/commonMain/kotlin/xyz/ksharma/krail/taj/components/TextField.kt @@ -1,4 +1,4 @@ -package xyz.ksharma.krail.design.system.components +package xyz.ksharma.krail.taj.components import androidx.compose.foundation.background import androidx.compose.foundation.interaction.MutableInteractionSource @@ -31,15 +31,14 @@ import androidx.compose.ui.text.input.KeyboardCapitalization import androidx.compose.ui.text.input.KeyboardType import androidx.compose.ui.text.intl.LocaleList import androidx.compose.ui.unit.dp -import xyz.ksharma.krail.common.designSystem.components.Text -import xyz.ksharma.krail.common.designSystem.LocalContentAlpha -import xyz.ksharma.krail.common.designSystem.LocalTextColor -import xyz.ksharma.krail.common.designSystem.LocalTextStyle -import xyz.ksharma.krail.common.designSystem.theme.KrailTheme -import xyz.ksharma.krail.common.designSystem.tokens.TextFieldTokens -import xyz.ksharma.krail.common.designSystem.tokens.TextFieldTokens.PlaceholderOpacity -import xyz.ksharma.krail.common.designSystem.tokens.TextFieldTokens.TextFieldHeight -import xyz.ksharma.krail.common.designSystem.tokens.TextFieldTokens.TextSelectionBackgroundOpacity +import xyz.ksharma.krail.taj.LocalContentAlpha +import xyz.ksharma.krail.taj.LocalTextColor +import xyz.ksharma.krail.taj.LocalTextStyle +import xyz.ksharma.krail.taj.theme.KrailTheme +import xyz.ksharma.krail.taj.tokens.TextFieldTokens +import xyz.ksharma.krail.taj.tokens.TextFieldTokens.PlaceholderOpacity +import xyz.ksharma.krail.taj.tokens.TextFieldTokens.TextFieldHeight +import xyz.ksharma.krail.taj.tokens.TextFieldTokens.TextSelectionBackgroundOpacity /** * Important documentation links: diff --git a/composeApp/src/commonMain/kotlin/xyz/ksharma/krail/common/designSystem/components/TextFieldButton.kt b/taj/src/commonMain/kotlin/xyz/ksharma/krail/taj/components/TextFieldButton.kt similarity index 80% rename from composeApp/src/commonMain/kotlin/xyz/ksharma/krail/common/designSystem/components/TextFieldButton.kt rename to taj/src/commonMain/kotlin/xyz/ksharma/krail/taj/components/TextFieldButton.kt index 82b4899f..26a9105d 100644 --- a/composeApp/src/commonMain/kotlin/xyz/ksharma/krail/common/designSystem/components/TextFieldButton.kt +++ b/taj/src/commonMain/kotlin/xyz/ksharma/krail/taj/components/TextFieldButton.kt @@ -1,4 +1,4 @@ -package xyz.ksharma.krail.common.designSystem.components +package xyz.ksharma.krail.taj.components import androidx.compose.foundation.background import androidx.compose.foundation.clickable @@ -14,12 +14,12 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.semantics.Role import androidx.compose.ui.unit.dp -import xyz.ksharma.krail.common.designSystem.LocalContentAlpha -import xyz.ksharma.krail.common.designSystem.LocalTextColor -import xyz.ksharma.krail.common.designSystem.LocalTextStyle -import xyz.ksharma.krail.common.designSystem.theme.KrailTheme -import xyz.ksharma.krail.common.designSystem.tokens.TextFieldTokens -import xyz.ksharma.krail.common.designSystem.tokens.TextFieldTokens.TextFieldHeight +import xyz.ksharma.krail.taj.LocalContentAlpha +import xyz.ksharma.krail.taj.LocalTextColor +import xyz.ksharma.krail.taj.LocalTextStyle +import xyz.ksharma.krail.taj.theme.KrailTheme +import xyz.ksharma.krail.taj.tokens.TextFieldTokens +import xyz.ksharma.krail.taj.tokens.TextFieldTokens.TextFieldHeight /** * A button that looks like a text field. diff --git a/composeApp/src/commonMain/kotlin/xyz/ksharma/krail/common/designSystem/components/TitleBar.kt b/taj/src/commonMain/kotlin/xyz/ksharma/krail/taj/components/TitleBar.kt similarity index 84% rename from composeApp/src/commonMain/kotlin/xyz/ksharma/krail/common/designSystem/components/TitleBar.kt rename to taj/src/commonMain/kotlin/xyz/ksharma/krail/taj/components/TitleBar.kt index 6f21be04..220b19cc 100644 --- a/composeApp/src/commonMain/kotlin/xyz/ksharma/krail/common/designSystem/components/TitleBar.kt +++ b/taj/src/commonMain/kotlin/xyz/ksharma/krail/taj/components/TitleBar.kt @@ -1,4 +1,4 @@ -package xyz.ksharma.krail.common.designSystem.components +package xyz.ksharma.krail.taj.components import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Row @@ -11,10 +11,10 @@ import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.unit.dp -import xyz.ksharma.krail.common.designSystem.LocalContentColor -import xyz.ksharma.krail.common.designSystem.LocalTextColor -import xyz.ksharma.krail.common.designSystem.LocalTextStyle -import xyz.ksharma.krail.common.designSystem.theme.KrailTheme +import xyz.ksharma.krail.taj.LocalContentColor +import xyz.ksharma.krail.taj.LocalTextColor +import xyz.ksharma.krail.taj.LocalTextStyle +import xyz.ksharma.krail.taj.theme.KrailTheme @Composable fun TitleBar( diff --git a/composeApp/src/commonMain/kotlin/xyz/ksharma/krail/common/designSystem/theme/A11yColors.kt b/taj/src/commonMain/kotlin/xyz/ksharma/krail/taj/theme/A11yColors.kt similarity index 94% rename from composeApp/src/commonMain/kotlin/xyz/ksharma/krail/common/designSystem/theme/A11yColors.kt rename to taj/src/commonMain/kotlin/xyz/ksharma/krail/taj/theme/A11yColors.kt index 3ae059ad..7383239f 100644 --- a/composeApp/src/commonMain/kotlin/xyz/ksharma/krail/common/designSystem/theme/A11yColors.kt +++ b/taj/src/commonMain/kotlin/xyz/ksharma/krail/taj/theme/A11yColors.kt @@ -1,4 +1,4 @@ -package xyz.ksharma.krail.common.designSystem.theme +package xyz.ksharma.krail.taj.theme import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.luminance diff --git a/composeApp/src/commonMain/kotlin/xyz/ksharma/krail/common/designSystem/theme/Color.kt b/taj/src/commonMain/kotlin/xyz/ksharma/krail/taj/theme/Color.kt similarity index 98% rename from composeApp/src/commonMain/kotlin/xyz/ksharma/krail/common/designSystem/theme/Color.kt rename to taj/src/commonMain/kotlin/xyz/ksharma/krail/taj/theme/Color.kt index 75b92d1e..b0114d34 100644 --- a/composeApp/src/commonMain/kotlin/xyz/ksharma/krail/common/designSystem/theme/Color.kt +++ b/taj/src/commonMain/kotlin/xyz/ksharma/krail/taj/theme/Color.kt @@ -1,4 +1,4 @@ -package xyz.ksharma.krail.common.designSystem.theme +package xyz.ksharma.krail.taj.theme import androidx.compose.runtime.Immutable import androidx.compose.runtime.staticCompositionLocalOf diff --git a/composeApp/src/commonMain/kotlin/xyz/ksharma/krail/common/designSystem/theme/KrailTypography.kt b/taj/src/commonMain/kotlin/xyz/ksharma/krail/taj/theme/KrailTypography.kt similarity index 99% rename from composeApp/src/commonMain/kotlin/xyz/ksharma/krail/common/designSystem/theme/KrailTypography.kt rename to taj/src/commonMain/kotlin/xyz/ksharma/krail/taj/theme/KrailTypography.kt index 96305649..2d6c462a 100644 --- a/composeApp/src/commonMain/kotlin/xyz/ksharma/krail/common/designSystem/theme/KrailTypography.kt +++ b/taj/src/commonMain/kotlin/xyz/ksharma/krail/taj/theme/KrailTypography.kt @@ -1,4 +1,4 @@ -package xyz.ksharma.krail.common.designSystem.theme +package xyz.ksharma.krail.taj.theme import androidx.compose.runtime.Immutable import androidx.compose.runtime.staticCompositionLocalOf diff --git a/composeApp/src/commonMain/kotlin/xyz/ksharma/krail/common/designSystem/theme/Theme.kt b/taj/src/commonMain/kotlin/xyz/ksharma/krail/taj/theme/Theme.kt similarity index 93% rename from composeApp/src/commonMain/kotlin/xyz/ksharma/krail/common/designSystem/theme/Theme.kt rename to taj/src/commonMain/kotlin/xyz/ksharma/krail/taj/theme/Theme.kt index 0be93719..28f50b46 100644 --- a/composeApp/src/commonMain/kotlin/xyz/ksharma/krail/common/designSystem/theme/Theme.kt +++ b/taj/src/commonMain/kotlin/xyz/ksharma/krail/taj/theme/Theme.kt @@ -1,4 +1,4 @@ -package xyz.ksharma.krail.common.designSystem.theme +package xyz.ksharma.krail.taj.theme import androidx.compose.foundation.isSystemInDarkTheme import androidx.compose.runtime.Composable diff --git a/composeApp/src/commonMain/kotlin/xyz/ksharma/krail/common/designSystem/tokens/ButtonTokens.kt b/taj/src/commonMain/kotlin/xyz/ksharma/krail/taj/tokens/ButtonTokens.kt similarity index 65% rename from composeApp/src/commonMain/kotlin/xyz/ksharma/krail/common/designSystem/tokens/ButtonTokens.kt rename to taj/src/commonMain/kotlin/xyz/ksharma/krail/taj/tokens/ButtonTokens.kt index 149401be..a88a62e8 100644 --- a/composeApp/src/commonMain/kotlin/xyz/ksharma/krail/common/designSystem/tokens/ButtonTokens.kt +++ b/taj/src/commonMain/kotlin/xyz/ksharma/krail/taj/tokens/ButtonTokens.kt @@ -1,4 +1,4 @@ -package xyz.ksharma.krail.common.designSystem.tokens +package xyz.ksharma.krail.taj.tokens import androidx.compose.ui.unit.dp diff --git a/composeApp/src/commonMain/kotlin/xyz/ksharma/krail/common/designSystem/tokens/TextFieldTokens.kt b/taj/src/commonMain/kotlin/xyz/ksharma/krail/taj/tokens/TextFieldTokens.kt similarity index 81% rename from composeApp/src/commonMain/kotlin/xyz/ksharma/krail/common/designSystem/tokens/TextFieldTokens.kt rename to taj/src/commonMain/kotlin/xyz/ksharma/krail/taj/tokens/TextFieldTokens.kt index a30f6d93..66e2e13c 100644 --- a/composeApp/src/commonMain/kotlin/xyz/ksharma/krail/common/designSystem/tokens/TextFieldTokens.kt +++ b/taj/src/commonMain/kotlin/xyz/ksharma/krail/taj/tokens/TextFieldTokens.kt @@ -1,4 +1,4 @@ -package xyz.ksharma.krail.common.designSystem.tokens +package xyz.ksharma.krail.taj.tokens import androidx.compose.ui.unit.dp From 729b4276af89fc93444caf070677fdac635ff04a Mon Sep 17 00:00:00 2001 From: Karan Sharma <55722391+ksharma-xyz@users.noreply.github.com> Date: Sat, 16 Nov 2024 17:54:14 +1100 Subject: [PATCH 18/67] Add kotlin-inject lib to module :core:di --- core/di/build.gradle.kts | 29 ++++++++++++++++-- .../xyz/ksharma/krail/di/AppDispatchers.kt | 0 .../xyz/ksharma/krail/di/CoroutinesModule.kt | 0 .../xyz/ksharma/krail/di/DispatchersModule.kt | 0 core/di/src/main/kotlin/xyz/.DS_Store | Bin 6148 -> 0 bytes core/di/src/main/kotlin/xyz/ksharma/.DS_Store | Bin 6148 -> 0 bytes gradle/libs.versions.toml | 5 +++ settings.gradle.kts | 2 +- 8 files changed, 33 insertions(+), 3 deletions(-) rename core/di/src/{main => commonMain}/kotlin/xyz/ksharma/krail/di/AppDispatchers.kt (100%) rename core/di/src/{main => commonMain}/kotlin/xyz/ksharma/krail/di/CoroutinesModule.kt (100%) rename core/di/src/{main => commonMain}/kotlin/xyz/ksharma/krail/di/DispatchersModule.kt (100%) delete mode 100644 core/di/src/main/kotlin/xyz/.DS_Store delete mode 100644 core/di/src/main/kotlin/xyz/ksharma/.DS_Store diff --git a/core/di/build.gradle.kts b/core/di/build.gradle.kts index 9bd1f984..8a989fd4 100644 --- a/core/di/build.gradle.kts +++ b/core/di/build.gradle.kts @@ -1,12 +1,37 @@ plugins { alias(libs.plugins.krail.android.library) - alias(libs.plugins.krail.android.hilt) + alias(libs.plugins.krail.kotlin.multiplatform) + alias(libs.plugins.ksp) + } android { namespace = "xyz.ksharma.core.di" } +kotlin { + applyDefaultHierarchyTemplate() + + androidTarget() + iosArm64() + iosSimulatorArm64() + + sourceSets { + commonMain { + dependencies { + implementation(libs.di.kotlinInjectRuntime) + } + } + } +} + dependencies { - + // 1. Configure code generation into the common source set + kspCommonMainMetadata(libs.di.kotlinInjectRuntime) + + // 2. Configure code generation into each KMP target source set + kspAndroid(libs.di.kotlinInjectCompilerKsp) + // kspIosX64("me.tatarka.inject:kotlin-inject-compiler-ksp:0.7.2") + // kspIosArm64("me.tatarka.inject:kotlin-inject-compiler-ksp:0.7.2") + // kspIosSimulatorArm64("me.tatarka.inject:kotlin-inject-compiler-ksp:0.7.2") } diff --git a/core/di/src/main/kotlin/xyz/ksharma/krail/di/AppDispatchers.kt b/core/di/src/commonMain/kotlin/xyz/ksharma/krail/di/AppDispatchers.kt similarity index 100% rename from core/di/src/main/kotlin/xyz/ksharma/krail/di/AppDispatchers.kt rename to core/di/src/commonMain/kotlin/xyz/ksharma/krail/di/AppDispatchers.kt diff --git a/core/di/src/main/kotlin/xyz/ksharma/krail/di/CoroutinesModule.kt b/core/di/src/commonMain/kotlin/xyz/ksharma/krail/di/CoroutinesModule.kt similarity index 100% rename from core/di/src/main/kotlin/xyz/ksharma/krail/di/CoroutinesModule.kt rename to core/di/src/commonMain/kotlin/xyz/ksharma/krail/di/CoroutinesModule.kt diff --git a/core/di/src/main/kotlin/xyz/ksharma/krail/di/DispatchersModule.kt b/core/di/src/commonMain/kotlin/xyz/ksharma/krail/di/DispatchersModule.kt similarity index 100% rename from core/di/src/main/kotlin/xyz/ksharma/krail/di/DispatchersModule.kt rename to core/di/src/commonMain/kotlin/xyz/ksharma/krail/di/DispatchersModule.kt diff --git a/core/di/src/main/kotlin/xyz/.DS_Store b/core/di/src/main/kotlin/xyz/.DS_Store deleted file mode 100644 index 3bebff83a4a10dcd0a62a69202eed38905dc93f8..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6148 zcmeHK%}T>S5T0$TO({YT3OxqA7ObrlikDF93mDOZN=<07p=q`xsXdfJ&iX<=iO=KA z?gp&p;7P>J!0tCYKf9R^vOfSI2Fvj&Km!0QY=nZ80wHsut7L)+g_bW zM1Rpl-(H0oBrpXJK7GIR(evgn!XQc8?RQZsSN8U+R@JIo5AIa1+|)~#$-s;6Xml-Q z=;ylU-vpD@sBw59qtpwcNvslr@dQKeZ-ZzoR|C0>#)-;xwZp1ewNazFUUz!!mfh|2 zH!XYJ@1TCx?QJ$S>*)C8{Ax4{=aGETR0?EW$$`NfUQzi{)w3H%p^To;lV|18jLZNt zzzi@0yTyPx2dw&TmCe;M1I)k=7@+;Z#zyEE%rvU413S7tlfObpf;zn=2yKIo!Av81 zP=roJ)TzQeF@#P>yKUkegPBI14nnPr^O%+M$BR&_quo~FARLX{GXu=PHUninY|#0C ziN8$YBY!)EN6Y{-@Xr_!m7zNvU{U65{Z<~GwKld#Y$Ozyk%EHy)+GQ3w2w5EQ~Pbw aG0riVX{1@muF?_tBA^K2jv4p`20j3wdQ67^ diff --git a/core/di/src/main/kotlin/xyz/ksharma/.DS_Store b/core/di/src/main/kotlin/xyz/ksharma/.DS_Store deleted file mode 100644 index 0597c575289748728410a19ffc534b80bc555773..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6148 zcmeHKyJ`b55Zp~1Ft~B)QeP0rA1KBtT>A%NI}kz+9~hGQI)8RN`!L*b7+j@DW??nb zYF4_NLMw@gF2CNMMCKwgg&WGrnz7lu`N-}vVyM1Gvu@h`u4#88?zMpBKFFsWWh)=j z&$mx{hClwe`2s!W_vUzL+QU2I*qcTLr~nn90#tws+^9g-&FSn$K50~d3j7}h?0hJ2 z!LuJ1NKF*HNp-x9!*3XD|ECx%8k;w5=Cu?viJQQsNs Date: Sat, 16 Nov 2024 18:00:58 +1100 Subject: [PATCH 19/67] Use kotlin-inject instead of hilt --- core/di/build.gradle.kts | 1 + .../xyz/ksharma/krail/di/AppDispatchers.kt | 2 +- .../xyz/ksharma/krail/di/CoroutinesModule.kt | 24 ++++++++++++------- .../xyz/ksharma/krail/di/DispatchersModule.kt | 23 +++++++++++------- gradle/libs.versions.toml | 2 ++ 5 files changed, 33 insertions(+), 19 deletions(-) diff --git a/core/di/build.gradle.kts b/core/di/build.gradle.kts index 8a989fd4..08c1c97a 100644 --- a/core/di/build.gradle.kts +++ b/core/di/build.gradle.kts @@ -20,6 +20,7 @@ kotlin { commonMain { dependencies { implementation(libs.di.kotlinInjectRuntime) + implementation(libs.kotlinx.coroutines.core) } } } diff --git a/core/di/src/commonMain/kotlin/xyz/ksharma/krail/di/AppDispatchers.kt b/core/di/src/commonMain/kotlin/xyz/ksharma/krail/di/AppDispatchers.kt index 43736f21..1fad3849 100644 --- a/core/di/src/commonMain/kotlin/xyz/ksharma/krail/di/AppDispatchers.kt +++ b/core/di/src/commonMain/kotlin/xyz/ksharma/krail/di/AppDispatchers.kt @@ -1,6 +1,6 @@ package xyz.ksharma.krail.di -import javax.inject.Qualifier +import me.tatarka.inject.annotations.Qualifier enum class AppDispatchers { Default, diff --git a/core/di/src/commonMain/kotlin/xyz/ksharma/krail/di/CoroutinesModule.kt b/core/di/src/commonMain/kotlin/xyz/ksharma/krail/di/CoroutinesModule.kt index a92580c1..983e69aa 100644 --- a/core/di/src/commonMain/kotlin/xyz/ksharma/krail/di/CoroutinesModule.kt +++ b/core/di/src/commonMain/kotlin/xyz/ksharma/krail/di/CoroutinesModule.kt @@ -1,18 +1,24 @@ package xyz.ksharma.krail.di -import dagger.Module -import dagger.Provides -import dagger.hilt.InstallIn -import dagger.hilt.components.SingletonComponent +import me.tatarka.inject.annotations.Component +import me.tatarka.inject.annotations.Inject +import me.tatarka.inject.annotations.Provides import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.SupervisorJob -@Module -@InstallIn(SingletonComponent::class) -object CoroutinesModule { +@Component +abstract class CoroutinesComponent { + + abstract val coroutinesModule: CoroutinesModule @Provides - fun provideCoroutineScope(@Dispatcher(AppDispatchers.IO) ioDispatcher: CoroutineDispatcher): CoroutineScope = - CoroutineScope(context = ioDispatcher + SupervisorJob()) + fun provideCoroutineScope( + @Dispatcher(AppDispatchers.IO) ioDispatcher: CoroutineDispatcher, + ): CoroutineScope = CoroutineScope(context = ioDispatcher + SupervisorJob()) } + +@Inject +class CoroutinesModule( + @Dispatcher(AppDispatchers.IO) val ioDispatcher: CoroutineDispatcher, +) diff --git a/core/di/src/commonMain/kotlin/xyz/ksharma/krail/di/DispatchersModule.kt b/core/di/src/commonMain/kotlin/xyz/ksharma/krail/di/DispatchersModule.kt index 654eda4c..f1fe619c 100644 --- a/core/di/src/commonMain/kotlin/xyz/ksharma/krail/di/DispatchersModule.kt +++ b/core/di/src/commonMain/kotlin/xyz/ksharma/krail/di/DispatchersModule.kt @@ -1,21 +1,26 @@ package xyz.ksharma.krail.di -import dagger.Module -import dagger.Provides -import dagger.hilt.InstallIn -import dagger.hilt.components.SingletonComponent +import me.tatarka.inject.annotations.Component +import me.tatarka.inject.annotations.Inject +import me.tatarka.inject.annotations.Provides import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.IO -@Module -@InstallIn(SingletonComponent::class) -object DispatchersModule { +@Component +abstract class DispatchersComponent { + + abstract val dispatchersModule: DispatchersModule @Provides - @Dispatcher(AppDispatchers.Default) fun defaultDispatcher(): CoroutineDispatcher = Dispatchers.Default @Provides - @Dispatcher(AppDispatchers.IO) fun ioDispatcher(): CoroutineDispatcher = Dispatchers.IO } + +@Inject +class DispatchersModule( + @Dispatcher(AppDispatchers.Default) val defaultDispatcher: CoroutineDispatcher, + @Dispatcher(AppDispatchers.IO) val ioDispatcher: CoroutineDispatcher +) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 0d693666..2dc74fd2 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -24,6 +24,7 @@ timber = "5.0.1" compose-multiplatform = "1.7.0" ktor = "3.0.0" androidx-lifecycle = "2.8.3" +kotlinxCoroutines = "1.9.0" #SDK minSdk = "26" @@ -38,6 +39,7 @@ kotlinx-datetime = { module = "org.jetbrains.kotlinx:kotlinx-datetime", version. lifecycle-runtime-ktx = { group = "androidx.lifecycle", name = "lifecycle-runtime-ktx", version.ref = "android-lifecycle" } retrofit2-kotlinx-serialization-converter = { module = "com.jakewharton.retrofit:retrofit2-kotlinx-serialization-converter", version.ref = "retrofit2KotlinxSerializationConverter" } timber = { module = "com.jakewharton.timber:timber", version.ref = "timber" } +kotlinx-coroutines-core = { group = "org.jetbrains.kotlinx", name = "kotlinx-coroutines-core", version.ref = "kotlinxCoroutines" } androidx-lifecycle-viewmodel = { group = "org.jetbrains.androidx.lifecycle", name = "lifecycle-viewmodel", version.ref = "androidx-lifecycle" } androidx-lifecycle-runtime-compose = { group = "org.jetbrains.androidx.lifecycle", name = "lifecycle-runtime-compose", version.ref = "androidx-lifecycle" } From 7b5c8b2c61efd2d10676296f028617fb49d674cd Mon Sep 17 00:00:00 2001 From: Karan Sharma <55722391+ksharma-xyz@users.noreply.github.com> Date: Sat, 16 Nov 2024 18:29:04 +1100 Subject: [PATCH 20/67] Make date-time module multiplatform --- core/date-time/build.gradle.kts | 29 +++++- .../krail/core/datetime/DateTimeHelper.kt | 81 +++++++++++++++ .../krail/core/datetime/DateTimeHelperTest.kt | 98 +++++++++++++++++++ .../krail/core/datetime/DateTimeHelperTest.kt | 35 ------- core/di/build.gradle.kts | 1 - core/network/build.gradle.kts | 2 +- settings.gradle.kts | 2 +- 7 files changed, 206 insertions(+), 42 deletions(-) create mode 100644 core/date-time/src/commonMain/kotlin/xyz/ksharma/krail/core/datetime/DateTimeHelper.kt create mode 100644 core/date-time/src/commonTest/kotlin/xyz/ksharma/krail/core/datetime/DateTimeHelperTest.kt delete mode 100644 core/date-time/src/test/kotlin/xyz/ksharma/krail/core/datetime/DateTimeHelperTest.kt diff --git a/core/date-time/build.gradle.kts b/core/date-time/build.gradle.kts index 34359049..59230f24 100644 --- a/core/date-time/build.gradle.kts +++ b/core/date-time/build.gradle.kts @@ -1,12 +1,33 @@ plugins { alias(libs.plugins.krail.android.library) + alias(libs.plugins.krail.kotlin.multiplatform) + alias(libs.plugins.krail.compose.multiplatform) + alias(libs.plugins.compose.compiler) } android { namespace = "xyz.ksharma.krail.core.datetime" } -dependencies { - testImplementation(libs.test.kotlin) - testImplementation(libs.test.googleTruth) - implementation(libs.kotlinx.datetime) + +kotlin { + applyDefaultHierarchyTemplate() + + androidTarget() + iosArm64() + iosSimulatorArm64() + + sourceSets { + commonMain { + dependencies { + implementation(libs.kotlinx.datetime) + implementation(compose.runtime) + } + } + + commonTest { + dependencies { + implementation(libs.test.kotlin) + } + } + } } diff --git a/core/date-time/src/commonMain/kotlin/xyz/ksharma/krail/core/datetime/DateTimeHelper.kt b/core/date-time/src/commonMain/kotlin/xyz/ksharma/krail/core/datetime/DateTimeHelper.kt new file mode 100644 index 00000000..42289436 --- /dev/null +++ b/core/date-time/src/commonMain/kotlin/xyz/ksharma/krail/core/datetime/DateTimeHelper.kt @@ -0,0 +1,81 @@ +package xyz.ksharma.krail.core.datetime + +import kotlinx.datetime.Clock +import kotlinx.datetime.Instant +import kotlinx.datetime.TimeZone +import kotlinx.datetime.toLocalDateTime +import kotlin.math.absoluteValue +import kotlin.time.Duration +import kotlin.time.Duration.Companion.minutes +import kotlin.time.DurationUnit + +object DateTimeHelper { + + fun String.formatTo12HourTime(): String { + val localDateTime = Instant.parse(this).toLocalDateTime(TimeZone.UTC) + val hour = if (localDateTime.hour % 12 == 0) 12 else localDateTime.hour % 12 // Ensure 12-hour format + val minute = localDateTime.minute.toString().padStart(2, '0') + val amPm = if (localDateTime.hour < 12) "am" else "pm" + return "$hour:$minute $amPm" + } + + fun String.utcToAEST(): String { + val instant = Instant.parse(this) + val aestZone = TimeZone.of("Australia/Sydney") + val localDateTime = instant.toLocalDateTime(aestZone) + return localDateTime.toString() + } + + + fun String.aestToHHMM(): String { + val localDateTime = Instant.parse(this).toLocalDateTime(TimeZone.of("Australia/Sydney")) + val hour = if (localDateTime.hour % 12 == 0) 12 else localDateTime.hour % 12 // Ensure 12-hour format + val minute = localDateTime.minute.toString().padStart(2, '0') + val amPm = if (localDateTime.hour < 12) "AM" else "PM" + return "$hour:$minute $amPm" + } + + + fun calculateTimeDifferenceFromNow( + utcDateString: String, + now: Instant = Clock.System.now(), + ): Duration { + val instant = Instant.parse(utcDateString) + return instant - now + } + + fun Duration.toGenericFormattedTimeString(): String { + val totalMinutes = this.toLong(DurationUnit.MINUTES) + val hours = this.toLong(DurationUnit.HOURS) + val partialMinutes = totalMinutes - (hours * 60.minutes.inWholeMinutes) + + return when { + totalMinutes < 0 -> "${totalMinutes.absoluteValue} ${if (totalMinutes.absoluteValue == 1L) "min" else "mins"} ago" + totalMinutes == 0L -> "Now" + hours == 1L -> "In ${hours.absoluteValue}h ${partialMinutes.absoluteValue}m" + hours >= 2 -> "In ${hours.absoluteValue}h" + else -> "In ${totalMinutes.absoluteValue} ${if (totalMinutes.absoluteValue == 1L) "min" else "mins"}" + } + } + + fun Duration.toFormattedDurationTimeString(): String { + val totalMinutes = this.toLong(DurationUnit.MINUTES) + val hours = this.toLong(DurationUnit.HOURS) + val partialMinutes = totalMinutes - (hours * 60.minutes.inWholeMinutes) + + return when { + hours >= 1 && partialMinutes == 0L -> "${hours.absoluteValue}h" + hours >= 1 -> "${hours.absoluteValue}h ${partialMinutes.absoluteValue}m" + else -> "${totalMinutes.absoluteValue} ${if (totalMinutes.absoluteValue == 1L) "min" else "mins"}" + } + } + + fun calculateTimeDifference( + utcDateString1: String, + utcDateString2: String, + ): Duration { + val instant1 = Instant.parse(utcDateString1) + val instant2 = Instant.parse(utcDateString2) + return (instant1 - instant2).absoluteValue + } +} diff --git a/core/date-time/src/commonTest/kotlin/xyz/ksharma/krail/core/datetime/DateTimeHelperTest.kt b/core/date-time/src/commonTest/kotlin/xyz/ksharma/krail/core/datetime/DateTimeHelperTest.kt new file mode 100644 index 00000000..fcfd477d --- /dev/null +++ b/core/date-time/src/commonTest/kotlin/xyz/ksharma/krail/core/datetime/DateTimeHelperTest.kt @@ -0,0 +1,98 @@ +package xyz.ksharma.krail.core.datetime + +import kotlinx.datetime.Instant +import xyz.ksharma.krail.core.datetime.DateTimeHelper.aestToHHMM +import xyz.ksharma.krail.core.datetime.DateTimeHelper.formatTo12HourTime +import xyz.ksharma.krail.core.datetime.DateTimeHelper.toFormattedDurationTimeString +import xyz.ksharma.krail.core.datetime.DateTimeHelper.toGenericFormattedTimeString +import xyz.ksharma.krail.core.datetime.DateTimeHelper.utcToAEST +import kotlin.test.Test +import kotlin.test.assertEquals +import kotlin.time.Duration.Companion.minutes + +class DateTimeHelperTest { + + @Test + fun testCalculateTimeDifferenceFromNow() { + val difference = DateTimeHelper.calculateTimeDifferenceFromNow( + utcDateString = "2024-10-07T09:00:00Z", + now = Instant.parse("2024-10-07T08:20:00Z"), + ) + assertEquals(40L, difference.inWholeMinutes) + } + + @Test + fun testCalculateTimeDifferenceNextDay() { + val difference = DateTimeHelper.calculateTimeDifferenceFromNow( + utcDateString = "2024-10-08T09:00:00Z", + now = Instant.parse("2024-10-07T08:20:00Z"), + ) + assertEquals(1480L, difference.inWholeMinutes) + } + + @Test + fun testCalculateTimeDifferencePreviousDay() { + val difference = DateTimeHelper.calculateTimeDifferenceFromNow( + utcDateString = "2024-10-06T09:00:00Z", + now = Instant.parse("2024-10-07T08:20:00Z"), + ) + assertEquals(-1400L, difference.inWholeMinutes) + } + + @Test + fun testFormatTo12HourTime() { + assertEquals("12:00 am", "2024-10-07T00:00:00Z".formatTo12HourTime()) + assertEquals("1:00 am", "2024-10-07T01:00:00Z".formatTo12HourTime()) + assertEquals("12:00 pm", "2024-10-07T12:00:00Z".formatTo12HourTime()) + assertEquals("1:00 pm", "2024-10-07T13:00:00Z".formatTo12HourTime()) + } + + @Test + fun testUtcToAEST() { + assertEquals("2024-10-07T11:00:10", "2024-10-07T00:00:10Z".utcToAEST()) + assertEquals("2024-10-07T12:00", "2024-10-07T01:00:00Z".utcToAEST()) + assertEquals("2024-10-07T12:00:23", "2024-10-07T01:00:23Z".utcToAEST()) + } + + @Test + fun testAestToHHMM() { + assertEquals("11:00 AM", "2024-10-07T00:00:00Z".aestToHHMM()) + assertEquals("12:00 PM", "2024-10-07T01:00:00Z".aestToHHMM()) + } + + @Test + fun testToGenericFormattedTimeString() { + assertEquals("40 mins ago", (-40).minutes.toGenericFormattedTimeString()) + assertEquals("Now", 0.minutes.toGenericFormattedTimeString()) + assertEquals("In 1h 20m", 80.minutes.toGenericFormattedTimeString()) + assertEquals("In 2h", 120.minutes.toGenericFormattedTimeString()) + } + + @Test + fun testToFormattedDurationTimeString() { + assertEquals("1h 20m", 80.minutes.toFormattedDurationTimeString()) + assertEquals("2h", 120.minutes.toFormattedDurationTimeString()) + assertEquals("40 mins", 40.minutes.toFormattedDurationTimeString()) + } + + @Test + fun testCalculateTimeDifference() { + val difference1 = DateTimeHelper.calculateTimeDifference( + utcDateString1 = "2024-10-07T09:00:00Z", + utcDateString2 = "2024-10-07T08:20:00Z", + ) + assertEquals(40L, difference1.inWholeMinutes) + + val difference2 = DateTimeHelper.calculateTimeDifference( + utcDateString1 = "2024-10-07T09:00:00Z", + utcDateString2 = "2024-10-06T09:00:00Z", + ) + assertEquals(1440L, difference2.inWholeMinutes) + + val difference3 = DateTimeHelper.calculateTimeDifference( + utcDateString1 = "2024-10-07T09:00:00Z", + utcDateString2 = "2024-10-07T09:00:00Z", + ) + assertEquals(0L, difference3.inWholeMinutes) + } +} diff --git a/core/date-time/src/test/kotlin/xyz/ksharma/krail/core/datetime/DateTimeHelperTest.kt b/core/date-time/src/test/kotlin/xyz/ksharma/krail/core/datetime/DateTimeHelperTest.kt deleted file mode 100644 index 4b35d437..00000000 --- a/core/date-time/src/test/kotlin/xyz/ksharma/krail/core/datetime/DateTimeHelperTest.kt +++ /dev/null @@ -1,35 +0,0 @@ -package xyz.ksharma.krail.core.datetime - -import com.google.common.truth.Truth.assertThat -import kotlinx.datetime.Instant -import kotlin.test.Test - -class DateTimeHelperTest { - - @Test - fun testCalculateTimeDifference() { - val difference = DateTimeHelper.calculateTimeDifferenceFromNow( - utcDateString = "2024-10-07T09:00:00Z", - now = Instant.parse("2024-10-07T08:20:00Z"), - ) - assertThat(difference.inWholeMinutes).isEqualTo(40L) - } - - @Test - fun testCalculateTimeDifferenceNextDay() { - val difference = DateTimeHelper.calculateTimeDifferenceFromNow( - utcDateString = "2024-10-08T09:00:00Z", - now = Instant.parse("2024-10-07T08:20:00Z"), - ) - assertThat(difference.inWholeMinutes).isEqualTo(1480L) - } - - @Test - fun testCalculateTimeDifferencePreviousDay() { - val difference = DateTimeHelper.calculateTimeDifferenceFromNow( - utcDateString = "2024-10-06T09:00:00Z", - now = Instant.parse("2024-10-07T08:20:00Z"), - ) - assertThat(difference.inWholeMinutes).isEqualTo(-1400L) - } -} diff --git a/core/di/build.gradle.kts b/core/di/build.gradle.kts index 08c1c97a..d13deb47 100644 --- a/core/di/build.gradle.kts +++ b/core/di/build.gradle.kts @@ -2,7 +2,6 @@ plugins { alias(libs.plugins.krail.android.library) alias(libs.plugins.krail.kotlin.multiplatform) alias(libs.plugins.ksp) - } android { diff --git a/core/network/build.gradle.kts b/core/network/build.gradle.kts index 8c5e92b3..b2e6fbff 100644 --- a/core/network/build.gradle.kts +++ b/core/network/build.gradle.kts @@ -34,7 +34,7 @@ wire { javaInterop = true } sourcePath { - srcDir(files("src/main/proto")) + srcDir(files("src/commonMain/proto")) } } diff --git a/settings.gradle.kts b/settings.gradle.kts index bf316a0b..ec1870c1 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -34,9 +34,9 @@ include(":android-app") include(":composeApp") include(":taj") // Design System include(":core:di") +include(":core:date-time") /* include(":core:coroutines-ext") -include(":core:date-time") include(":core:network") include(":feature:trip-planner:network:api") include(":feature:trip-planner:network:real") From 560f7dd1270b32a173d72812212c67a17d3b1d0b Mon Sep 17 00:00:00 2001 From: Karan Sharma <55722391+ksharma-xyz@users.noreply.github.com> Date: Sat, 16 Nov 2024 19:13:54 +1100 Subject: [PATCH 21/67] Make :core:coroutines-ext module multiplatform --- core/coroutines-ext/build.gradle.kts | 17 +++++++++++++++-- .../krail/coroutines/ext/CoroutinesExt.kt | 0 settings.gradle.kts | 2 +- 3 files changed, 16 insertions(+), 3 deletions(-) rename core/coroutines-ext/src/{main => commonMain}/kotlin/xyz/ksharma/krail/coroutines/ext/CoroutinesExt.kt (100%) diff --git a/core/coroutines-ext/build.gradle.kts b/core/coroutines-ext/build.gradle.kts index fb5ba3e0..bc9ade18 100644 --- a/core/coroutines-ext/build.gradle.kts +++ b/core/coroutines-ext/build.gradle.kts @@ -1,11 +1,24 @@ plugins { alias(libs.plugins.krail.android.library) + alias(libs.plugins.krail.kotlin.multiplatform) } android { namespace = "xyz.ksharma.krail.coroutines.ext" } -dependencies { - implementation(libs.test.androidxCoreKtx) +kotlin { + applyDefaultHierarchyTemplate() + + androidTarget() + iosArm64() + iosSimulatorArm64() + + sourceSets { + commonMain { + dependencies { + implementation(libs.kotlinx.coroutines.core) + } + } + } } diff --git a/core/coroutines-ext/src/main/kotlin/xyz/ksharma/krail/coroutines/ext/CoroutinesExt.kt b/core/coroutines-ext/src/commonMain/kotlin/xyz/ksharma/krail/coroutines/ext/CoroutinesExt.kt similarity index 100% rename from core/coroutines-ext/src/main/kotlin/xyz/ksharma/krail/coroutines/ext/CoroutinesExt.kt rename to core/coroutines-ext/src/commonMain/kotlin/xyz/ksharma/krail/coroutines/ext/CoroutinesExt.kt diff --git a/settings.gradle.kts b/settings.gradle.kts index ec1870c1..b8212eef 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -35,8 +35,8 @@ include(":composeApp") include(":taj") // Design System include(":core:di") include(":core:date-time") -/* include(":core:coroutines-ext") +/* include(":core:network") include(":feature:trip-planner:network:api") include(":feature:trip-planner:network:real") From 9f862164ced13b60eaad14fe2011a87acd905714 Mon Sep 17 00:00:00 2001 From: Karan Sharma <55722391+ksharma-xyz@users.noreply.github.com> Date: Sat, 16 Nov 2024 19:35:45 +1100 Subject: [PATCH 22/67] Make :Core:network multiplatform and use ktor --- core/network/build.gradle.kts | 50 ++++++++++++---- .../xyz/ksharma/krail/network/ResponseExt.kt | 0 .../ksharma/krail/network/di/NetworkModule.kt | 40 +++++++++++++ .../network/interceptor/AuthInterceptor.kt | 0 .../ksharma/krail/network/di/NetworkModule.kt | 58 ------------------- gradle/libs.versions.toml | 7 +-- settings.gradle.kts | 2 +- 7 files changed, 82 insertions(+), 75 deletions(-) rename core/network/src/{main => commonMain}/kotlin/xyz/ksharma/krail/network/ResponseExt.kt (100%) create mode 100644 core/network/src/commonMain/kotlin/xyz/ksharma/krail/network/di/NetworkModule.kt rename core/network/src/{main => commonMain}/kotlin/xyz/ksharma/krail/network/interceptor/AuthInterceptor.kt (100%) delete mode 100644 core/network/src/main/kotlin/xyz/ksharma/krail/network/di/NetworkModule.kt diff --git a/core/network/build.gradle.kts b/core/network/build.gradle.kts index b2e6fbff..f5ade1be 100644 --- a/core/network/build.gradle.kts +++ b/core/network/build.gradle.kts @@ -2,9 +2,9 @@ import java.util.Properties plugins { alias(libs.plugins.krail.android.library) - alias(libs.plugins.krail.android.hilt) + alias(libs.plugins.krail.kotlin.multiplatform) alias(libs.plugins.kotlin.serialization) - alias(libs.plugins.wire) + alias(libs.plugins.ksp) } // Get local.properties values @@ -29,15 +29,7 @@ android { } } -wire { - kotlin { - javaInterop = true - } - sourcePath { - srcDir(files("src/commonMain/proto")) - } -} - +/* dependencies { api(projects.core.di) implementation(projects.core.coroutinesExt) @@ -49,3 +41,39 @@ dependencies { implementation(libs.retrofit) implementation(libs.retrofit2.kotlinx.serialization.converter) } +*/ + +kotlin { + applyDefaultHierarchyTemplate() + + androidTarget() + iosArm64() + iosSimulatorArm64() + + sourceSets { + commonMain { + dependencies { + implementation(projects.core.coroutinesExt) + api(projects.core.di) + + implementation(libs.di.kotlinInjectRuntime) + implementation(libs.kotlinx.serialization.json) + implementation(libs.ktor.client.core) + implementation(libs.ktor.client.logging) + implementation(libs.ktor.serialization.kotlinx.json) + //implementation(libs.kotlinx.coroutines.core) + } + } + } +} + +dependencies { + // 1. Configure code generation into the common source set + kspCommonMainMetadata(libs.di.kotlinInjectRuntime) + + // 2. Configure code generation into each KMP target source set + //kspAndroid(libs.di.kotlinInjectCompilerKsp) + // kspIosX64("me.tatarka.inject:kotlin-inject-compiler-ksp:0.7.2") + // kspIosArm64("me.tatarka.inject:kotlin-inject-compiler-ksp:0.7.2") + // kspIosSimulatorArm64("me.tatarka.inject:kotlin-inject-compiler-ksp:0.7.2") +} diff --git a/core/network/src/main/kotlin/xyz/ksharma/krail/network/ResponseExt.kt b/core/network/src/commonMain/kotlin/xyz/ksharma/krail/network/ResponseExt.kt similarity index 100% rename from core/network/src/main/kotlin/xyz/ksharma/krail/network/ResponseExt.kt rename to core/network/src/commonMain/kotlin/xyz/ksharma/krail/network/ResponseExt.kt diff --git a/core/network/src/commonMain/kotlin/xyz/ksharma/krail/network/di/NetworkModule.kt b/core/network/src/commonMain/kotlin/xyz/ksharma/krail/network/di/NetworkModule.kt new file mode 100644 index 00000000..18616b1d --- /dev/null +++ b/core/network/src/commonMain/kotlin/xyz/ksharma/krail/network/di/NetworkModule.kt @@ -0,0 +1,40 @@ +package xyz.ksharma.krail.network.di + +import io.ktor.client.* +import io.ktor.client.engine.cio.* +import io.ktor.client.features.json.* +import io.ktor.client.features.json.serializer.* +import io.ktor.client.features.logging.* +import me.tatarka.inject.annotations.Component +import me.tatarka.inject.annotations.Provides +import xyz.ksharma.krail.network.BuildConfig +import xyz.ksharma.krail.network.interceptor.AuthInterceptor + +@Component +abstract class NetworkModule { + + @Provides + fun provideHttpClient(): HttpClient { + return HttpClient(CIO) { + install(JsonFeature) { + serializer = KotlinxSerializer(kotlinx.serialization.json.Json { + ignoreUnknownKeys = true + }) + } + if (BuildConfig.DEBUG) { + install(Logging) { + level = LogLevel.BODY + } + } + install(AuthInterceptor) + engine { + requestTimeout = 30000 + connectTimeout = 10000 + } + } + } + + companion object { + const val BASE_URL = "https://api.transport.nsw.gov.au" + } +} diff --git a/core/network/src/main/kotlin/xyz/ksharma/krail/network/interceptor/AuthInterceptor.kt b/core/network/src/commonMain/kotlin/xyz/ksharma/krail/network/interceptor/AuthInterceptor.kt similarity index 100% rename from core/network/src/main/kotlin/xyz/ksharma/krail/network/interceptor/AuthInterceptor.kt rename to core/network/src/commonMain/kotlin/xyz/ksharma/krail/network/interceptor/AuthInterceptor.kt diff --git a/core/network/src/main/kotlin/xyz/ksharma/krail/network/di/NetworkModule.kt b/core/network/src/main/kotlin/xyz/ksharma/krail/network/di/NetworkModule.kt deleted file mode 100644 index 039e874f..00000000 --- a/core/network/src/main/kotlin/xyz/ksharma/krail/network/di/NetworkModule.kt +++ /dev/null @@ -1,58 +0,0 @@ -package xyz.ksharma.krail.network.di - -import com.jakewharton.retrofit2.converter.kotlinx.serialization.asConverterFactory -import dagger.Module -import dagger.Provides -import dagger.hilt.InstallIn -import dagger.hilt.components.SingletonComponent -import kotlinx.serialization.json.Json -import okhttp3.MediaType.Companion.toMediaType -import okhttp3.OkHttpClient -import okhttp3.logging.HttpLoggingInterceptor -import retrofit2.Retrofit -import xyz.ksharma.krail.network.BuildConfig -import xyz.ksharma.krail.network.interceptor.AuthInterceptor -import java.util.concurrent.TimeUnit - -@Module -@InstallIn(SingletonComponent::class) -object NetworkModule { - - const val BASE_URL = "https://api.transport.nsw.gov.au" - - @Provides - fun provideOkHttpClient(): OkHttpClient { - val okhttpBuilder = OkHttpClient.Builder() - - if (BuildConfig.DEBUG) { - okhttpBuilder.addInterceptor( - HttpLoggingInterceptor().setLevel(HttpLoggingInterceptor.Level.BODY), - ) - } - okhttpBuilder.addInterceptor(AuthInterceptor()) - - // Add Timeouts - okhttpBuilder - .connectTimeout(10, TimeUnit.SECONDS) - .readTimeout(30, TimeUnit.SECONDS) - .writeTimeout(30, TimeUnit.SECONDS) - - return okhttpBuilder.build() - } - - @Provides - fun provideRetrofit(okHttpClient: OkHttpClient): Retrofit { - val json = Json { ignoreUnknownKeys = true } - - val retrofit: Retrofit = Retrofit.Builder() - .baseUrl(BASE_URL) - .client(okHttpClient) - .addConverterFactory( - json.asConverterFactory( - "application/json; charset=UTF8".toMediaType(), - ), - ) - .build() - return retrofit - } -} diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 2dc74fd2..f0c5a8a5 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -14,6 +14,7 @@ hiltNavigationCompose = "1.2.0" kotlinInject = "0.7.2" kotlinxCollectionsImmutable = "0.3.8" kotlinxDatetime = "0.6.1" +ktorClientLogging = "2.3.12" okhttpBom = "4.12.0" kotlinxSerializationJson = "1.7.3" ksp = "2.0.21-1.0.27" # ksp to kotlin version mapping https://github.com/google/ksp/releases @@ -50,15 +51,11 @@ di-kotlinInjectCompilerKsp = { module = "me.tatarka.inject:kotlin-inject-compile #Network kotlinx-serialization-json = { group = "org.jetbrains.kotlinx", name = "kotlinx-serialization-json", version.ref = "kotlinxSerializationJson" } -okhttp-logging-interceptor = { module = "com.squareup.okhttp3:logging-interceptor" } -okhttp = { module = "com.squareup.okhttp3:okhttp" } -okhttp-bom = { module = "com.squareup.okhttp3:okhttp-bom", version.ref = "okhttpBom" } -retrofit = { module = "com.squareup.retrofit2:retrofit", version.ref = "retrofit" } ktor-client-content-negotiation = { module = "io.ktor:ktor-client-content-negotiation", version.ref = "ktor" } ktor-client-core = { module = "io.ktor:ktor-client-core", version.ref = "ktor" } ktor-client-darwin = { module = "io.ktor:ktor-client-darwin", version.ref = "ktor" } -ktor-client-okhttp = { module = "io.ktor:ktor-client-okhttp", version.ref = "ktor" } ktor-serialization-kotlinx-json = { module = "io.ktor:ktor-serialization-kotlinx-json", version.ref = "ktor" } +ktor-client-logging = { module = "io.ktor:ktor-client-logging", version.ref = "ktor" } #Test test-composeUiTestManifest = { group = "androidx.compose.ui", name = "ui-test-manifest" } diff --git a/settings.gradle.kts b/settings.gradle.kts index b8212eef..f1f29269 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -36,8 +36,8 @@ include(":taj") // Design System include(":core:di") include(":core:date-time") include(":core:coroutines-ext") -/* include(":core:network") +/* include(":feature:trip-planner:network:api") include(":feature:trip-planner:network:real") include(":feature:trip-planner:state") From 48847cbc4154f7b1fc946cda6e4ba5f3338b3e46 Mon Sep 17 00:00:00 2001 From: Karan Sharma <55722391+ksharma-xyz@users.noreply.github.com> Date: Sat, 16 Nov 2024 19:53:42 +1100 Subject: [PATCH 23/67] Ktor sample --- core/network/build.gradle.kts | 12 ++++++ .../xyz/ksharma/krail/network/APIClient.kt | 42 +++++++++++++++++++ .../xyz/ksharma/krail/network/ResponseExt.kt | 21 ---------- .../ksharma/krail/network/di/NetworkModule.kt | 40 ------------------ .../network/interceptor/AuthInterceptor.kt | 22 ---------- gradle/libs.versions.toml | 4 +- 6 files changed, 57 insertions(+), 84 deletions(-) create mode 100644 core/network/src/commonMain/kotlin/xyz/ksharma/krail/network/APIClient.kt delete mode 100644 core/network/src/commonMain/kotlin/xyz/ksharma/krail/network/ResponseExt.kt delete mode 100644 core/network/src/commonMain/kotlin/xyz/ksharma/krail/network/di/NetworkModule.kt delete mode 100644 core/network/src/commonMain/kotlin/xyz/ksharma/krail/network/interceptor/AuthInterceptor.kt diff --git a/core/network/build.gradle.kts b/core/network/build.gradle.kts index f5ade1be..c500fab5 100644 --- a/core/network/build.gradle.kts +++ b/core/network/build.gradle.kts @@ -51,6 +51,10 @@ kotlin { iosSimulatorArm64() sourceSets { + androidMain.dependencies { + implementation(libs.ktor.client.okhttp) + } + commonMain { dependencies { implementation(projects.core.coroutinesExt) @@ -59,11 +63,19 @@ kotlin { implementation(libs.di.kotlinInjectRuntime) implementation(libs.kotlinx.serialization.json) implementation(libs.ktor.client.core) + implementation(libs.ktor.client.cio) + implementation(libs.ktor.client.content.negotiation) implementation(libs.ktor.client.logging) implementation(libs.ktor.serialization.kotlinx.json) //implementation(libs.kotlinx.coroutines.core) } } + + iosMain { + dependencies { + implementation(libs.ktor.client.darwin) + } + } } } diff --git a/core/network/src/commonMain/kotlin/xyz/ksharma/krail/network/APIClient.kt b/core/network/src/commonMain/kotlin/xyz/ksharma/krail/network/APIClient.kt new file mode 100644 index 00000000..87a41f19 --- /dev/null +++ b/core/network/src/commonMain/kotlin/xyz/ksharma/krail/network/APIClient.kt @@ -0,0 +1,42 @@ +package xyz.ksharma.krail.network + +import io.ktor.client.HttpClient +import io.ktor.client.call.body +import io.ktor.client.plugins.contentnegotiation.ContentNegotiation +import io.ktor.client.request.get +import io.ktor.client.request.headers +import io.ktor.http.HttpHeaders +import io.ktor.serialization.kotlinx.json.json +import kotlinx.serialization.Serializable +import kotlinx.serialization.json.Json + +class ApiClient { + private val client = HttpClient { + install(ContentNegotiation) { + json(Json { + ignoreUnknownKeys = true // To avoid crashes if the API sends extra fields + }) + } + } + + suspend fun getApiResponse(apiKey: String): ApiResponse { + return client.get("https://api.example.com/data") { + headers { + append("Authorization", "apikey $apiKey") // Example of Authorization header + append(HttpHeaders.Accept, "application/json") // Accept JSON response + } + + url { + parameters.append("key", "value") + parameters.append("search", "query") + } + }.body() + } +} + +@Serializable +data class ApiResponse( + val id: Int, + val name: String, + val details: String, +) diff --git a/core/network/src/commonMain/kotlin/xyz/ksharma/krail/network/ResponseExt.kt b/core/network/src/commonMain/kotlin/xyz/ksharma/krail/network/ResponseExt.kt deleted file mode 100644 index 6cd709ba..00000000 --- a/core/network/src/commonMain/kotlin/xyz/ksharma/krail/network/ResponseExt.kt +++ /dev/null @@ -1,21 +0,0 @@ -package xyz.ksharma.krail.network - -import retrofit2.Response - -/** - * Processes a Retrofit response and returns a [Result] object. - * - * If the response is successful, the body is extracted and wrapped in a [Result.success]. - * If the response is unsuccessful, an [Exception] is created with the error code and wrapped in a [Result.failure]. - * - * @return A [Result] object containing the response body or an error. - */ -fun Response.toSafeResult(): Result { - return if (this.isSuccessful) { - this.body()?.let { body -> - Result.success(body) - } ?: Result.failure(Exception("Response body is null")) - } else { - Result.failure(Exception("API call failed with error: ${this.code()}")) - } -} diff --git a/core/network/src/commonMain/kotlin/xyz/ksharma/krail/network/di/NetworkModule.kt b/core/network/src/commonMain/kotlin/xyz/ksharma/krail/network/di/NetworkModule.kt deleted file mode 100644 index 18616b1d..00000000 --- a/core/network/src/commonMain/kotlin/xyz/ksharma/krail/network/di/NetworkModule.kt +++ /dev/null @@ -1,40 +0,0 @@ -package xyz.ksharma.krail.network.di - -import io.ktor.client.* -import io.ktor.client.engine.cio.* -import io.ktor.client.features.json.* -import io.ktor.client.features.json.serializer.* -import io.ktor.client.features.logging.* -import me.tatarka.inject.annotations.Component -import me.tatarka.inject.annotations.Provides -import xyz.ksharma.krail.network.BuildConfig -import xyz.ksharma.krail.network.interceptor.AuthInterceptor - -@Component -abstract class NetworkModule { - - @Provides - fun provideHttpClient(): HttpClient { - return HttpClient(CIO) { - install(JsonFeature) { - serializer = KotlinxSerializer(kotlinx.serialization.json.Json { - ignoreUnknownKeys = true - }) - } - if (BuildConfig.DEBUG) { - install(Logging) { - level = LogLevel.BODY - } - } - install(AuthInterceptor) - engine { - requestTimeout = 30000 - connectTimeout = 10000 - } - } - } - - companion object { - const val BASE_URL = "https://api.transport.nsw.gov.au" - } -} diff --git a/core/network/src/commonMain/kotlin/xyz/ksharma/krail/network/interceptor/AuthInterceptor.kt b/core/network/src/commonMain/kotlin/xyz/ksharma/krail/network/interceptor/AuthInterceptor.kt deleted file mode 100644 index 3363ebe2..00000000 --- a/core/network/src/commonMain/kotlin/xyz/ksharma/krail/network/interceptor/AuthInterceptor.kt +++ /dev/null @@ -1,22 +0,0 @@ -package xyz.ksharma.krail.network.interceptor - -import okhttp3.Interceptor -import okhttp3.Request -import okhttp3.Response -import xyz.ksharma.krail.network.BuildConfig.NSW_TRANSPORT_API_KEY -import javax.inject.Singleton - -@Singleton -class AuthInterceptor : Interceptor { - - override fun intercept(chain: Interceptor.Chain): Response { - return chain.proceed( - Request - .Builder() - .header("Authorization", "apikey $NSW_TRANSPORT_API_KEY") - .header("accept", "application/x-google-protobuf") - .url(chain.request().url) - .build(), - ) - } -} diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index f0c5a8a5..153b631e 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -23,7 +23,7 @@ retrofit = "2.11.0" retrofit2KotlinxSerializationConverter = "1.0.0" timber = "5.0.1" compose-multiplatform = "1.7.0" -ktor = "3.0.0" +ktor = "3.0.1" androidx-lifecycle = "2.8.3" kotlinxCoroutines = "1.9.0" @@ -53,6 +53,8 @@ di-kotlinInjectCompilerKsp = { module = "me.tatarka.inject:kotlin-inject-compile kotlinx-serialization-json = { group = "org.jetbrains.kotlinx", name = "kotlinx-serialization-json", version.ref = "kotlinxSerializationJson" } ktor-client-content-negotiation = { module = "io.ktor:ktor-client-content-negotiation", version.ref = "ktor" } ktor-client-core = { module = "io.ktor:ktor-client-core", version.ref = "ktor" } +ktor-client-cio = { module = "io.ktor:ktor-client-cio", version.ref = "ktor" } +ktor-client-okhttp = { module = "io.ktor:ktor-client-okhttp", version.ref = "ktor" } ktor-client-darwin = { module = "io.ktor:ktor-client-darwin", version.ref = "ktor" } ktor-serialization-kotlinx-json = { module = "io.ktor:ktor-serialization-kotlinx-json", version.ref = "ktor" } ktor-client-logging = { module = "io.ktor:ktor-client-logging", version.ref = "ktor" } From 8057c5af834001d3a54e8fc898295dea7711ee22 Mon Sep 17 00:00:00 2001 From: Karan Sharma <55722391+ksharma-xyz@users.noreply.github.com> Date: Sat, 16 Nov 2024 21:54:52 +1100 Subject: [PATCH 24/67] Make tripplanner:ui Multiplatform --- feature/trip-planner/ui/build.gradle.kts | 55 +++++++++++++++---- .../krail/trip/planner/ui/ContextExt.kt | 0 .../planner/ui/alerts/AlertsDestination.kt | 3 +- .../planner/ui/alerts/CollapsibleAlert.kt | 2 +- .../krail/trip/planner/ui/alerts/HtmlText.kt | 0 .../planner/ui/alerts/ServiceAlertScreen.kt | 8 +-- .../trip/planner/ui/components/A11yExt.kt | 0 .../trip/planner/ui/components/ColorExt.kt | 4 +- .../planner/ui/components/ErrorMessage.kt | 0 .../trip/planner/ui/components/JourneyCard.kt | 19 +++---- .../planner/ui/components/JourneyCardState.kt | 0 .../trip/planner/ui/components/LegView.kt | 0 .../ui/components/OriginDestination.kt | 0 .../planner/ui/components/SavedTripCard.kt | 0 .../planner/ui/components/SearchStopRow.kt | 0 .../ui/components/StopSearchListItem.kt | 0 .../ui/components/TimelineModifiers.kt | 0 .../ui/components/TransportModeBadge.kt | 0 .../ui/components/TransportModeIcon.kt | 0 .../ui/components/TransportModeInfo.kt | 0 .../trip/planner/ui/components/WalkingLeg.kt | 0 .../ui/components/loading/FestivalType.kt | 0 .../ui/components/loading/LoadingEmojiAnim.kt | 5 +- .../components/loading/LoadingEmojiManager.kt | 0 .../ui/navigation/TripPlannerDestinations.kt | 0 .../ui/savedtrips/SavedTripsDestination.kt | 0 .../planner/ui/savedtrips/SavedTripsScreen.kt | 0 .../ui/savedtrips/SavedTripsViewModel.kt | 0 .../ui/searchstop/SearchStopDestination.kt | 0 .../planner/ui/searchstop/SearchStopScreen.kt | 0 .../ui/searchstop/SearchStopViewModel.kt | 0 .../planner/ui/searchstop/StopResultMapper.kt | 0 .../ui/timetable/TimeTableDestination.kt | 0 .../planner/ui/timetable/TimeTableScreen.kt | 0 .../ui/timetable/TimeTableViewModel.kt | 0 .../ui/timetable/business/TripResponseExt.kt | 0 .../timetable/business/TripResponseMapper.kt | 0 .../ui/usualride/UsualRideDestination.kt | 0 .../planner/ui/usualride/UsualRideScreen.kt | 0 .../ui/usualride/UsualRideViewModel.kt | 0 .../res/drawable/ic_a11y.xml | 0 .../res/drawable/ic_alert.xml | 0 .../res/drawable/ic_arrow_down.xml | 0 .../res/drawable/ic_arrow_right.xml | 0 .../res/drawable/ic_clock.xml | 0 .../res/drawable/ic_loc.xml | 0 .../res/drawable/ic_location.xml | 0 .../res/drawable/ic_mode_ferry.xml | 0 .../res/drawable/ic_reverse.xml | 0 .../res/drawable/ic_search.xml | 0 .../res/drawable/ic_star.xml | 0 .../res/drawable/ic_star_filled.xml | 0 .../res/drawable/ic_walk.xml | 0 .../res/values/strings.xml | 0 .../trip/planner/ui/components/ColorsTest.kt | 3 +- .../ui/timetable/business/PlatformTextTest.kt | 0 gradle/libs.versions.toml | 3 + settings.gradle.kts | 2 +- 58 files changed, 69 insertions(+), 35 deletions(-) rename feature/trip-planner/ui/src/{main => commonMain}/kotlin/xyz/ksharma/krail/trip/planner/ui/ContextExt.kt (100%) rename feature/trip-planner/ui/src/{main => commonMain}/kotlin/xyz/ksharma/krail/trip/planner/ui/alerts/AlertsDestination.kt (93%) rename feature/trip-planner/ui/src/{main => commonMain}/kotlin/xyz/ksharma/krail/trip/planner/ui/alerts/CollapsibleAlert.kt (99%) rename feature/trip-planner/ui/src/{main => commonMain}/kotlin/xyz/ksharma/krail/trip/planner/ui/alerts/HtmlText.kt (100%) rename feature/trip-planner/ui/src/{main => commonMain}/kotlin/xyz/ksharma/krail/trip/planner/ui/alerts/ServiceAlertScreen.kt (96%) rename feature/trip-planner/ui/src/{main => commonMain}/kotlin/xyz/ksharma/krail/trip/planner/ui/components/A11yExt.kt (100%) rename feature/trip-planner/ui/src/{main => commonMain}/kotlin/xyz/ksharma/krail/trip/planner/ui/components/ColorExt.kt (95%) rename feature/trip-planner/ui/src/{main => commonMain}/kotlin/xyz/ksharma/krail/trip/planner/ui/components/ErrorMessage.kt (100%) rename feature/trip-planner/ui/src/{main => commonMain}/kotlin/xyz/ksharma/krail/trip/planner/ui/components/JourneyCard.kt (98%) rename feature/trip-planner/ui/src/{main => commonMain}/kotlin/xyz/ksharma/krail/trip/planner/ui/components/JourneyCardState.kt (100%) rename feature/trip-planner/ui/src/{main => commonMain}/kotlin/xyz/ksharma/krail/trip/planner/ui/components/LegView.kt (100%) rename feature/trip-planner/ui/src/{main => commonMain}/kotlin/xyz/ksharma/krail/trip/planner/ui/components/OriginDestination.kt (100%) rename feature/trip-planner/ui/src/{main => commonMain}/kotlin/xyz/ksharma/krail/trip/planner/ui/components/SavedTripCard.kt (100%) rename feature/trip-planner/ui/src/{main => commonMain}/kotlin/xyz/ksharma/krail/trip/planner/ui/components/SearchStopRow.kt (100%) rename feature/trip-planner/ui/src/{main => commonMain}/kotlin/xyz/ksharma/krail/trip/planner/ui/components/StopSearchListItem.kt (100%) rename feature/trip-planner/ui/src/{main => commonMain}/kotlin/xyz/ksharma/krail/trip/planner/ui/components/TimelineModifiers.kt (100%) rename feature/trip-planner/ui/src/{main => commonMain}/kotlin/xyz/ksharma/krail/trip/planner/ui/components/TransportModeBadge.kt (100%) rename feature/trip-planner/ui/src/{main => commonMain}/kotlin/xyz/ksharma/krail/trip/planner/ui/components/TransportModeIcon.kt (100%) rename feature/trip-planner/ui/src/{main => commonMain}/kotlin/xyz/ksharma/krail/trip/planner/ui/components/TransportModeInfo.kt (100%) rename feature/trip-planner/ui/src/{main => commonMain}/kotlin/xyz/ksharma/krail/trip/planner/ui/components/WalkingLeg.kt (100%) rename feature/trip-planner/ui/src/{main => commonMain}/kotlin/xyz/ksharma/krail/trip/planner/ui/components/loading/FestivalType.kt (100%) rename feature/trip-planner/ui/src/{main => commonMain}/kotlin/xyz/ksharma/krail/trip/planner/ui/components/loading/LoadingEmojiAnim.kt (95%) rename feature/trip-planner/ui/src/{main => commonMain}/kotlin/xyz/ksharma/krail/trip/planner/ui/components/loading/LoadingEmojiManager.kt (100%) rename feature/trip-planner/ui/src/{main => commonMain}/kotlin/xyz/ksharma/krail/trip/planner/ui/navigation/TripPlannerDestinations.kt (100%) rename feature/trip-planner/ui/src/{main => commonMain}/kotlin/xyz/ksharma/krail/trip/planner/ui/savedtrips/SavedTripsDestination.kt (100%) rename feature/trip-planner/ui/src/{main => commonMain}/kotlin/xyz/ksharma/krail/trip/planner/ui/savedtrips/SavedTripsScreen.kt (100%) rename feature/trip-planner/ui/src/{main => commonMain}/kotlin/xyz/ksharma/krail/trip/planner/ui/savedtrips/SavedTripsViewModel.kt (100%) rename feature/trip-planner/ui/src/{main => commonMain}/kotlin/xyz/ksharma/krail/trip/planner/ui/searchstop/SearchStopDestination.kt (100%) rename feature/trip-planner/ui/src/{main => commonMain}/kotlin/xyz/ksharma/krail/trip/planner/ui/searchstop/SearchStopScreen.kt (100%) rename feature/trip-planner/ui/src/{main => commonMain}/kotlin/xyz/ksharma/krail/trip/planner/ui/searchstop/SearchStopViewModel.kt (100%) rename feature/trip-planner/ui/src/{main => commonMain}/kotlin/xyz/ksharma/krail/trip/planner/ui/searchstop/StopResultMapper.kt (100%) rename feature/trip-planner/ui/src/{main => commonMain}/kotlin/xyz/ksharma/krail/trip/planner/ui/timetable/TimeTableDestination.kt (100%) rename feature/trip-planner/ui/src/{main => commonMain}/kotlin/xyz/ksharma/krail/trip/planner/ui/timetable/TimeTableScreen.kt (100%) rename feature/trip-planner/ui/src/{main => commonMain}/kotlin/xyz/ksharma/krail/trip/planner/ui/timetable/TimeTableViewModel.kt (100%) rename feature/trip-planner/ui/src/{main => commonMain}/kotlin/xyz/ksharma/krail/trip/planner/ui/timetable/business/TripResponseExt.kt (100%) rename feature/trip-planner/ui/src/{main => commonMain}/kotlin/xyz/ksharma/krail/trip/planner/ui/timetable/business/TripResponseMapper.kt (100%) rename feature/trip-planner/ui/src/{main => commonMain}/kotlin/xyz/ksharma/krail/trip/planner/ui/usualride/UsualRideDestination.kt (100%) rename feature/trip-planner/ui/src/{main => commonMain}/kotlin/xyz/ksharma/krail/trip/planner/ui/usualride/UsualRideScreen.kt (100%) rename feature/trip-planner/ui/src/{main => commonMain}/kotlin/xyz/ksharma/krail/trip/planner/ui/usualride/UsualRideViewModel.kt (100%) rename feature/trip-planner/ui/src/{main => commonMain}/res/drawable/ic_a11y.xml (100%) rename feature/trip-planner/ui/src/{main => commonMain}/res/drawable/ic_alert.xml (100%) rename feature/trip-planner/ui/src/{main => commonMain}/res/drawable/ic_arrow_down.xml (100%) rename feature/trip-planner/ui/src/{main => commonMain}/res/drawable/ic_arrow_right.xml (100%) rename feature/trip-planner/ui/src/{main => commonMain}/res/drawable/ic_clock.xml (100%) rename feature/trip-planner/ui/src/{main => commonMain}/res/drawable/ic_loc.xml (100%) rename feature/trip-planner/ui/src/{main => commonMain}/res/drawable/ic_location.xml (100%) rename feature/trip-planner/ui/src/{main => commonMain}/res/drawable/ic_mode_ferry.xml (100%) rename feature/trip-planner/ui/src/{main => commonMain}/res/drawable/ic_reverse.xml (100%) rename feature/trip-planner/ui/src/{main => commonMain}/res/drawable/ic_search.xml (100%) rename feature/trip-planner/ui/src/{main => commonMain}/res/drawable/ic_star.xml (100%) rename feature/trip-planner/ui/src/{main => commonMain}/res/drawable/ic_star_filled.xml (100%) rename feature/trip-planner/ui/src/{main => commonMain}/res/drawable/ic_walk.xml (100%) rename feature/trip-planner/ui/src/{main => commonMain}/res/values/strings.xml (100%) rename feature/trip-planner/ui/src/{test => commonTest}/kotlin/xyz/ksharma/krail/trip/planner/ui/components/ColorsTest.kt (97%) rename feature/trip-planner/ui/src/{test => commonTest}/kotlin/xyz/ksharma/krail/trip/planner/ui/timetable/business/PlatformTextTest.kt (100%) diff --git a/feature/trip-planner/ui/build.gradle.kts b/feature/trip-planner/ui/build.gradle.kts index 347729bb..c0ad6fb8 100644 --- a/feature/trip-planner/ui/build.gradle.kts +++ b/feature/trip-planner/ui/build.gradle.kts @@ -1,13 +1,55 @@ +import com.android.build.gradle.internal.ide.kmp.KotlinAndroidSourceSetMarker.Companion.android + plugins { - alias(libs.plugins.krail.android.library.compose) - alias(libs.plugins.krail.android.hilt) + alias(libs.plugins.krail.kotlin.multiplatform) + alias(libs.plugins.krail.compose.multiplatform) + alias(libs.plugins.compose.compiler) + alias(libs.plugins.krail.android.library) alias(libs.plugins.kotlin.serialization) } +kotlin { + applyDefaultHierarchyTemplate() + + androidTarget() + + iosArm64() + iosSimulatorArm64() + + java { + toolchain { + languageVersion.set(JavaLanguageVersion.of(JavaVersion.VERSION_17.majorVersion)) + } + } + + sourceSets { + commonMain { + dependencies { + implementation(projects.taj) + + implementation(compose.foundation) + implementation(compose.animation) + implementation(compose.ui) + + implementation(libs.kotlinx.datetime) + implementation(libs.kotlinx.collections.immutable) + implementation(libs.kotlinx.serialization.json) + implementation(libs.navigation.compose) + } + } + commonTest { + dependencies { + implementation(libs.test.kotlin) + } + } + } +} + android { namespace = "xyz.ksharma.krail.trip.planner.ui" } +/* dependencies { implementation(projects.core.dateTime) implementation(projects.core.designSystem) @@ -15,17 +57,10 @@ dependencies { implementation(projects.feature.tripPlanner.state) implementation(projects.sandook.api) - implementation(libs.compose.ui) - implementation(libs.compose.foundation) - implementation(libs.compose.navigation) - implementation(libs.hilt.navigation.compose) - implementation(libs.kotlinx.collections.immutable) - implementation(libs.kotlinx.serialization.json) - implementation(libs.timber) - implementation(libs.kotlinx.datetime) implementation(projects.sandook.real) implementation(libs.compose.material3) testImplementation(libs.test.composeUiTestJunit4) testImplementation(libs.test.kotlin) } +*/ diff --git a/feature/trip-planner/ui/src/main/kotlin/xyz/ksharma/krail/trip/planner/ui/ContextExt.kt b/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/ContextExt.kt similarity index 100% rename from feature/trip-planner/ui/src/main/kotlin/xyz/ksharma/krail/trip/planner/ui/ContextExt.kt rename to feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/ContextExt.kt diff --git a/feature/trip-planner/ui/src/main/kotlin/xyz/ksharma/krail/trip/planner/ui/alerts/AlertsDestination.kt b/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/alerts/AlertsDestination.kt similarity index 93% rename from feature/trip-planner/ui/src/main/kotlin/xyz/ksharma/krail/trip/planner/ui/alerts/AlertsDestination.kt rename to feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/alerts/AlertsDestination.kt index cc8e7c8a..40e4d55e 100644 --- a/feature/trip-planner/ui/src/main/kotlin/xyz/ksharma/krail/trip/planner/ui/alerts/AlertsDestination.kt +++ b/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/alerts/AlertsDestination.kt @@ -6,7 +6,6 @@ import androidx.navigation.NavHostController import androidx.navigation.compose.composable import androidx.navigation.toRoute import kotlinx.collections.immutable.toImmutableSet -import timber.log.Timber import xyz.ksharma.krail.trip.planner.ui.navigation.ServiceAlertRoute import xyz.ksharma.krail.trip.planner.ui.state.alerts.ServiceAlert @@ -22,7 +21,7 @@ internal fun NavGraphBuilder.alertsDestination(navController: NavHostController) route.alertsJsonList.forEach { ServiceAlert.fromJsonString(it)?.let { alert -> // Timber.d("Alert Heading: ${alert.heading}") - Timber.d("Alert Message: ${alert.message}") + //Timber.d("Alert Message: ${alert.message}") } } } diff --git a/feature/trip-planner/ui/src/main/kotlin/xyz/ksharma/krail/trip/planner/ui/alerts/CollapsibleAlert.kt b/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/alerts/CollapsibleAlert.kt similarity index 99% rename from feature/trip-planner/ui/src/main/kotlin/xyz/ksharma/krail/trip/planner/ui/alerts/CollapsibleAlert.kt rename to feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/alerts/CollapsibleAlert.kt index a193d8e7..0fcc5e0e 100644 --- a/feature/trip-planner/ui/src/main/kotlin/xyz/ksharma/krail/trip/planner/ui/alerts/CollapsibleAlert.kt +++ b/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/alerts/CollapsibleAlert.kt @@ -106,7 +106,7 @@ fun CollapsibleAlert( // region Previews -@Preview(fontScale = 2f) +//@Preview(fontScale = 2f) @Composable private fun PreviewCollapsibleAlertCollapsed() { KrailTheme { diff --git a/feature/trip-planner/ui/src/main/kotlin/xyz/ksharma/krail/trip/planner/ui/alerts/HtmlText.kt b/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/alerts/HtmlText.kt similarity index 100% rename from feature/trip-planner/ui/src/main/kotlin/xyz/ksharma/krail/trip/planner/ui/alerts/HtmlText.kt rename to feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/alerts/HtmlText.kt diff --git a/feature/trip-planner/ui/src/main/kotlin/xyz/ksharma/krail/trip/planner/ui/alerts/ServiceAlertScreen.kt b/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/alerts/ServiceAlertScreen.kt similarity index 96% rename from feature/trip-planner/ui/src/main/kotlin/xyz/ksharma/krail/trip/planner/ui/alerts/ServiceAlertScreen.kt rename to feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/alerts/ServiceAlertScreen.kt index 44efaf31..aff03486 100644 --- a/feature/trip-planner/ui/src/main/kotlin/xyz/ksharma/krail/trip/planner/ui/alerts/ServiceAlertScreen.kt +++ b/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/alerts/ServiceAlertScreen.kt @@ -25,9 +25,9 @@ import androidx.compose.ui.unit.dp import kotlinx.collections.immutable.ImmutableSet import kotlinx.collections.immutable.persistentSetOf import kotlinx.collections.immutable.toImmutableList -import xyz.ksharma.krail.design.system.components.Text -import xyz.ksharma.krail.design.system.components.TitleBar -import xyz.ksharma.krail.design.system.theme.KrailTheme +import xyz.ksharma.krail.taj.components.Text +import xyz.ksharma.krail.taj.components.TitleBar +import xyz.ksharma.krail.taj.theme.KrailTheme import xyz.ksharma.krail.trip.planner.ui.DefaultSystemBarColors import xyz.ksharma.krail.trip.planner.ui.state.alerts.ServiceAlert import xyz.ksharma.krail.trip.planner.ui.state.alerts.ServiceAlertPriority @@ -95,7 +95,7 @@ fun ServiceAlertScreen( } } -@Preview +//@Preview @Composable private fun PreviewServiceAlertScreen() { KrailTheme { diff --git a/feature/trip-planner/ui/src/main/kotlin/xyz/ksharma/krail/trip/planner/ui/components/A11yExt.kt b/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/components/A11yExt.kt similarity index 100% rename from feature/trip-planner/ui/src/main/kotlin/xyz/ksharma/krail/trip/planner/ui/components/A11yExt.kt rename to feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/components/A11yExt.kt diff --git a/feature/trip-planner/ui/src/main/kotlin/xyz/ksharma/krail/trip/planner/ui/components/ColorExt.kt b/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/components/ColorExt.kt similarity index 95% rename from feature/trip-planner/ui/src/main/kotlin/xyz/ksharma/krail/trip/planner/ui/components/ColorExt.kt rename to feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/components/ColorExt.kt index 996c1460..abe20744 100644 --- a/feature/trip-planner/ui/src/main/kotlin/xyz/ksharma/krail/trip/planner/ui/components/ColorExt.kt +++ b/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/components/ColorExt.kt @@ -91,13 +91,13 @@ fun Color.toHex(): String { /** * The default light scrim, as defined by androidx and the platform: - * https://cs.android.com/androidx/platform/frameworks/support/+/androidx-main:activity/activity/src/main/java/androidx/activity/EdgeToEdge.kt;l=35-38;drc=27e7d52e8604a080133e8b842db10c89b4482598 + * https://cs.android.com/androidx/platform/frameworks/support/+/androidx-commonMain:activity/activity/src/commonMain/java/androidx/activity/EdgeToEdge.kt;l=35-38;drc=27e7d52e8604a080133e8b842db10c89b4482598 */ val lightScrim = android.graphics.Color.argb(0xe6, 0xFF, 0xFF, 0xFF) /** * The default dark scrim, as defined by androidx and the platform: - * https://cs.android.com/androidx/platform/frameworks/support/+/androidx-main:activity/activity/src/main/java/androidx/activity/EdgeToEdge.kt;l=40-44;drc=27e7d52e8604a080133e8b842db10c89b4482598 + * https://cs.android.com/androidx/platform/frameworks/support/+/androidx-commonMain:activity/activity/src/commonMain/java/androidx/activity/EdgeToEdge.kt;l=40-44;drc=27e7d52e8604a080133e8b842db10c89b4482598 */ val darkScrim = android.graphics.Color.argb(0x80, 0x1b, 0x1b, 0x1b) diff --git a/feature/trip-planner/ui/src/main/kotlin/xyz/ksharma/krail/trip/planner/ui/components/ErrorMessage.kt b/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/components/ErrorMessage.kt similarity index 100% rename from feature/trip-planner/ui/src/main/kotlin/xyz/ksharma/krail/trip/planner/ui/components/ErrorMessage.kt rename to feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/components/ErrorMessage.kt diff --git a/feature/trip-planner/ui/src/main/kotlin/xyz/ksharma/krail/trip/planner/ui/components/JourneyCard.kt b/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/components/JourneyCard.kt similarity index 98% rename from feature/trip-planner/ui/src/main/kotlin/xyz/ksharma/krail/trip/planner/ui/components/JourneyCard.kt rename to feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/components/JourneyCard.kt index a23d2296..735b5930 100644 --- a/feature/trip-planner/ui/src/main/kotlin/xyz/ksharma/krail/trip/planner/ui/components/JourneyCard.kt +++ b/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/components/JourneyCard.kt @@ -46,14 +46,13 @@ import androidx.compose.ui.unit.sp import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.persistentListOf import kotlinx.collections.immutable.toImmutableList -import xyz.ksharma.krail.design.system.LocalContentAlpha -import xyz.ksharma.krail.design.system.components.SeparatorIcon -import xyz.ksharma.krail.design.system.components.Text -import xyz.ksharma.krail.design.system.theme.KrailTheme -import xyz.ksharma.krail.design.system.theme.getForegroundColor -import xyz.ksharma.krail.design.system.toAdaptiveDecorativeIconSize -import xyz.ksharma.krail.design.system.toAdaptiveSize -import xyz.ksharma.krail.trip.planner.ui.R +import xyz.ksharma.krail.taj.LocalContentAlpha +import xyz.ksharma.krail.taj.components.SeparatorIcon +import xyz.ksharma.krail.taj.components.Text +import xyz.ksharma.krail.taj.theme.KrailTheme +import xyz.ksharma.krail.taj.theme.getForegroundColor +import xyz.ksharma.krail.taj.toAdaptiveDecorativeIconSize +import xyz.ksharma.krail.taj.toAdaptiveSize import xyz.ksharma.krail.trip.planner.ui.state.TransportMode import xyz.ksharma.krail.trip.planner.ui.state.TransportModeLine import xyz.ksharma.krail.trip.planner.ui.state.timetable.TimeTableState @@ -531,7 +530,7 @@ internal fun List?.toColors(onSurface: Color): List = when // region Previews -@Preview(fontScale = 2f) +//@Preview(fontScale = 2f) @Composable private fun PreviewJourneyCard() { KrailTheme { @@ -557,7 +556,7 @@ private fun PreviewJourneyCard() { } -@Preview(fontScale = 2f) +//@Preview(fontScale = 2f) @Composable private fun PreviewJourneyCardCollapsed() { KrailTheme { diff --git a/feature/trip-planner/ui/src/main/kotlin/xyz/ksharma/krail/trip/planner/ui/components/JourneyCardState.kt b/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/components/JourneyCardState.kt similarity index 100% rename from feature/trip-planner/ui/src/main/kotlin/xyz/ksharma/krail/trip/planner/ui/components/JourneyCardState.kt rename to feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/components/JourneyCardState.kt diff --git a/feature/trip-planner/ui/src/main/kotlin/xyz/ksharma/krail/trip/planner/ui/components/LegView.kt b/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/components/LegView.kt similarity index 100% rename from feature/trip-planner/ui/src/main/kotlin/xyz/ksharma/krail/trip/planner/ui/components/LegView.kt rename to feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/components/LegView.kt diff --git a/feature/trip-planner/ui/src/main/kotlin/xyz/ksharma/krail/trip/planner/ui/components/OriginDestination.kt b/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/components/OriginDestination.kt similarity index 100% rename from feature/trip-planner/ui/src/main/kotlin/xyz/ksharma/krail/trip/planner/ui/components/OriginDestination.kt rename to feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/components/OriginDestination.kt diff --git a/feature/trip-planner/ui/src/main/kotlin/xyz/ksharma/krail/trip/planner/ui/components/SavedTripCard.kt b/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/components/SavedTripCard.kt similarity index 100% rename from feature/trip-planner/ui/src/main/kotlin/xyz/ksharma/krail/trip/planner/ui/components/SavedTripCard.kt rename to feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/components/SavedTripCard.kt diff --git a/feature/trip-planner/ui/src/main/kotlin/xyz/ksharma/krail/trip/planner/ui/components/SearchStopRow.kt b/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/components/SearchStopRow.kt similarity index 100% rename from feature/trip-planner/ui/src/main/kotlin/xyz/ksharma/krail/trip/planner/ui/components/SearchStopRow.kt rename to feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/components/SearchStopRow.kt diff --git a/feature/trip-planner/ui/src/main/kotlin/xyz/ksharma/krail/trip/planner/ui/components/StopSearchListItem.kt b/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/components/StopSearchListItem.kt similarity index 100% rename from feature/trip-planner/ui/src/main/kotlin/xyz/ksharma/krail/trip/planner/ui/components/StopSearchListItem.kt rename to feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/components/StopSearchListItem.kt diff --git a/feature/trip-planner/ui/src/main/kotlin/xyz/ksharma/krail/trip/planner/ui/components/TimelineModifiers.kt b/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/components/TimelineModifiers.kt similarity index 100% rename from feature/trip-planner/ui/src/main/kotlin/xyz/ksharma/krail/trip/planner/ui/components/TimelineModifiers.kt rename to feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/components/TimelineModifiers.kt diff --git a/feature/trip-planner/ui/src/main/kotlin/xyz/ksharma/krail/trip/planner/ui/components/TransportModeBadge.kt b/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/components/TransportModeBadge.kt similarity index 100% rename from feature/trip-planner/ui/src/main/kotlin/xyz/ksharma/krail/trip/planner/ui/components/TransportModeBadge.kt rename to feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/components/TransportModeBadge.kt diff --git a/feature/trip-planner/ui/src/main/kotlin/xyz/ksharma/krail/trip/planner/ui/components/TransportModeIcon.kt b/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/components/TransportModeIcon.kt similarity index 100% rename from feature/trip-planner/ui/src/main/kotlin/xyz/ksharma/krail/trip/planner/ui/components/TransportModeIcon.kt rename to feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/components/TransportModeIcon.kt diff --git a/feature/trip-planner/ui/src/main/kotlin/xyz/ksharma/krail/trip/planner/ui/components/TransportModeInfo.kt b/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/components/TransportModeInfo.kt similarity index 100% rename from feature/trip-planner/ui/src/main/kotlin/xyz/ksharma/krail/trip/planner/ui/components/TransportModeInfo.kt rename to feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/components/TransportModeInfo.kt diff --git a/feature/trip-planner/ui/src/main/kotlin/xyz/ksharma/krail/trip/planner/ui/components/WalkingLeg.kt b/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/components/WalkingLeg.kt similarity index 100% rename from feature/trip-planner/ui/src/main/kotlin/xyz/ksharma/krail/trip/planner/ui/components/WalkingLeg.kt rename to feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/components/WalkingLeg.kt diff --git a/feature/trip-planner/ui/src/main/kotlin/xyz/ksharma/krail/trip/planner/ui/components/loading/FestivalType.kt b/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/components/loading/FestivalType.kt similarity index 100% rename from feature/trip-planner/ui/src/main/kotlin/xyz/ksharma/krail/trip/planner/ui/components/loading/FestivalType.kt rename to feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/components/loading/FestivalType.kt diff --git a/feature/trip-planner/ui/src/main/kotlin/xyz/ksharma/krail/trip/planner/ui/components/loading/LoadingEmojiAnim.kt b/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/components/loading/LoadingEmojiAnim.kt similarity index 95% rename from feature/trip-planner/ui/src/main/kotlin/xyz/ksharma/krail/trip/planner/ui/components/loading/LoadingEmojiAnim.kt rename to feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/components/loading/LoadingEmojiAnim.kt index e171c4dd..1295765c 100644 --- a/feature/trip-planner/ui/src/main/kotlin/xyz/ksharma/krail/trip/planner/ui/components/loading/LoadingEmojiAnim.kt +++ b/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/components/loading/LoadingEmojiAnim.kt @@ -16,10 +16,9 @@ import androidx.compose.runtime.getValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.graphicsLayer -import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.sp -import xyz.ksharma.krail.design.system.components.Text -import xyz.ksharma.krail.design.system.theme.KrailTheme +import xyz.ksharma.krail.taj.components.Text +import xyz.ksharma.krail.taj.theme.KrailTheme import xyz.ksharma.krail.trip.planner.ui.components.loading.LoadingEmojiManager.getRandomEmoji @Composable diff --git a/feature/trip-planner/ui/src/main/kotlin/xyz/ksharma/krail/trip/planner/ui/components/loading/LoadingEmojiManager.kt b/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/components/loading/LoadingEmojiManager.kt similarity index 100% rename from feature/trip-planner/ui/src/main/kotlin/xyz/ksharma/krail/trip/planner/ui/components/loading/LoadingEmojiManager.kt rename to feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/components/loading/LoadingEmojiManager.kt diff --git a/feature/trip-planner/ui/src/main/kotlin/xyz/ksharma/krail/trip/planner/ui/navigation/TripPlannerDestinations.kt b/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/navigation/TripPlannerDestinations.kt similarity index 100% rename from feature/trip-planner/ui/src/main/kotlin/xyz/ksharma/krail/trip/planner/ui/navigation/TripPlannerDestinations.kt rename to feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/navigation/TripPlannerDestinations.kt diff --git a/feature/trip-planner/ui/src/main/kotlin/xyz/ksharma/krail/trip/planner/ui/savedtrips/SavedTripsDestination.kt b/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/savedtrips/SavedTripsDestination.kt similarity index 100% rename from feature/trip-planner/ui/src/main/kotlin/xyz/ksharma/krail/trip/planner/ui/savedtrips/SavedTripsDestination.kt rename to feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/savedtrips/SavedTripsDestination.kt diff --git a/feature/trip-planner/ui/src/main/kotlin/xyz/ksharma/krail/trip/planner/ui/savedtrips/SavedTripsScreen.kt b/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/savedtrips/SavedTripsScreen.kt similarity index 100% rename from feature/trip-planner/ui/src/main/kotlin/xyz/ksharma/krail/trip/planner/ui/savedtrips/SavedTripsScreen.kt rename to feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/savedtrips/SavedTripsScreen.kt diff --git a/feature/trip-planner/ui/src/main/kotlin/xyz/ksharma/krail/trip/planner/ui/savedtrips/SavedTripsViewModel.kt b/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/savedtrips/SavedTripsViewModel.kt similarity index 100% rename from feature/trip-planner/ui/src/main/kotlin/xyz/ksharma/krail/trip/planner/ui/savedtrips/SavedTripsViewModel.kt rename to feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/savedtrips/SavedTripsViewModel.kt diff --git a/feature/trip-planner/ui/src/main/kotlin/xyz/ksharma/krail/trip/planner/ui/searchstop/SearchStopDestination.kt b/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/searchstop/SearchStopDestination.kt similarity index 100% rename from feature/trip-planner/ui/src/main/kotlin/xyz/ksharma/krail/trip/planner/ui/searchstop/SearchStopDestination.kt rename to feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/searchstop/SearchStopDestination.kt diff --git a/feature/trip-planner/ui/src/main/kotlin/xyz/ksharma/krail/trip/planner/ui/searchstop/SearchStopScreen.kt b/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/searchstop/SearchStopScreen.kt similarity index 100% rename from feature/trip-planner/ui/src/main/kotlin/xyz/ksharma/krail/trip/planner/ui/searchstop/SearchStopScreen.kt rename to feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/searchstop/SearchStopScreen.kt diff --git a/feature/trip-planner/ui/src/main/kotlin/xyz/ksharma/krail/trip/planner/ui/searchstop/SearchStopViewModel.kt b/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/searchstop/SearchStopViewModel.kt similarity index 100% rename from feature/trip-planner/ui/src/main/kotlin/xyz/ksharma/krail/trip/planner/ui/searchstop/SearchStopViewModel.kt rename to feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/searchstop/SearchStopViewModel.kt diff --git a/feature/trip-planner/ui/src/main/kotlin/xyz/ksharma/krail/trip/planner/ui/searchstop/StopResultMapper.kt b/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/searchstop/StopResultMapper.kt similarity index 100% rename from feature/trip-planner/ui/src/main/kotlin/xyz/ksharma/krail/trip/planner/ui/searchstop/StopResultMapper.kt rename to feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/searchstop/StopResultMapper.kt diff --git a/feature/trip-planner/ui/src/main/kotlin/xyz/ksharma/krail/trip/planner/ui/timetable/TimeTableDestination.kt b/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/timetable/TimeTableDestination.kt similarity index 100% rename from feature/trip-planner/ui/src/main/kotlin/xyz/ksharma/krail/trip/planner/ui/timetable/TimeTableDestination.kt rename to feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/timetable/TimeTableDestination.kt diff --git a/feature/trip-planner/ui/src/main/kotlin/xyz/ksharma/krail/trip/planner/ui/timetable/TimeTableScreen.kt b/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/timetable/TimeTableScreen.kt similarity index 100% rename from feature/trip-planner/ui/src/main/kotlin/xyz/ksharma/krail/trip/planner/ui/timetable/TimeTableScreen.kt rename to feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/timetable/TimeTableScreen.kt diff --git a/feature/trip-planner/ui/src/main/kotlin/xyz/ksharma/krail/trip/planner/ui/timetable/TimeTableViewModel.kt b/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/timetable/TimeTableViewModel.kt similarity index 100% rename from feature/trip-planner/ui/src/main/kotlin/xyz/ksharma/krail/trip/planner/ui/timetable/TimeTableViewModel.kt rename to feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/timetable/TimeTableViewModel.kt diff --git a/feature/trip-planner/ui/src/main/kotlin/xyz/ksharma/krail/trip/planner/ui/timetable/business/TripResponseExt.kt b/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/timetable/business/TripResponseExt.kt similarity index 100% rename from feature/trip-planner/ui/src/main/kotlin/xyz/ksharma/krail/trip/planner/ui/timetable/business/TripResponseExt.kt rename to feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/timetable/business/TripResponseExt.kt diff --git a/feature/trip-planner/ui/src/main/kotlin/xyz/ksharma/krail/trip/planner/ui/timetable/business/TripResponseMapper.kt b/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/timetable/business/TripResponseMapper.kt similarity index 100% rename from feature/trip-planner/ui/src/main/kotlin/xyz/ksharma/krail/trip/planner/ui/timetable/business/TripResponseMapper.kt rename to feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/timetable/business/TripResponseMapper.kt diff --git a/feature/trip-planner/ui/src/main/kotlin/xyz/ksharma/krail/trip/planner/ui/usualride/UsualRideDestination.kt b/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/usualride/UsualRideDestination.kt similarity index 100% rename from feature/trip-planner/ui/src/main/kotlin/xyz/ksharma/krail/trip/planner/ui/usualride/UsualRideDestination.kt rename to feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/usualride/UsualRideDestination.kt diff --git a/feature/trip-planner/ui/src/main/kotlin/xyz/ksharma/krail/trip/planner/ui/usualride/UsualRideScreen.kt b/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/usualride/UsualRideScreen.kt similarity index 100% rename from feature/trip-planner/ui/src/main/kotlin/xyz/ksharma/krail/trip/planner/ui/usualride/UsualRideScreen.kt rename to feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/usualride/UsualRideScreen.kt diff --git a/feature/trip-planner/ui/src/main/kotlin/xyz/ksharma/krail/trip/planner/ui/usualride/UsualRideViewModel.kt b/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/usualride/UsualRideViewModel.kt similarity index 100% rename from feature/trip-planner/ui/src/main/kotlin/xyz/ksharma/krail/trip/planner/ui/usualride/UsualRideViewModel.kt rename to feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/usualride/UsualRideViewModel.kt diff --git a/feature/trip-planner/ui/src/main/res/drawable/ic_a11y.xml b/feature/trip-planner/ui/src/commonMain/res/drawable/ic_a11y.xml similarity index 100% rename from feature/trip-planner/ui/src/main/res/drawable/ic_a11y.xml rename to feature/trip-planner/ui/src/commonMain/res/drawable/ic_a11y.xml diff --git a/feature/trip-planner/ui/src/main/res/drawable/ic_alert.xml b/feature/trip-planner/ui/src/commonMain/res/drawable/ic_alert.xml similarity index 100% rename from feature/trip-planner/ui/src/main/res/drawable/ic_alert.xml rename to feature/trip-planner/ui/src/commonMain/res/drawable/ic_alert.xml diff --git a/feature/trip-planner/ui/src/main/res/drawable/ic_arrow_down.xml b/feature/trip-planner/ui/src/commonMain/res/drawable/ic_arrow_down.xml similarity index 100% rename from feature/trip-planner/ui/src/main/res/drawable/ic_arrow_down.xml rename to feature/trip-planner/ui/src/commonMain/res/drawable/ic_arrow_down.xml diff --git a/feature/trip-planner/ui/src/main/res/drawable/ic_arrow_right.xml b/feature/trip-planner/ui/src/commonMain/res/drawable/ic_arrow_right.xml similarity index 100% rename from feature/trip-planner/ui/src/main/res/drawable/ic_arrow_right.xml rename to feature/trip-planner/ui/src/commonMain/res/drawable/ic_arrow_right.xml diff --git a/feature/trip-planner/ui/src/main/res/drawable/ic_clock.xml b/feature/trip-planner/ui/src/commonMain/res/drawable/ic_clock.xml similarity index 100% rename from feature/trip-planner/ui/src/main/res/drawable/ic_clock.xml rename to feature/trip-planner/ui/src/commonMain/res/drawable/ic_clock.xml diff --git a/feature/trip-planner/ui/src/main/res/drawable/ic_loc.xml b/feature/trip-planner/ui/src/commonMain/res/drawable/ic_loc.xml similarity index 100% rename from feature/trip-planner/ui/src/main/res/drawable/ic_loc.xml rename to feature/trip-planner/ui/src/commonMain/res/drawable/ic_loc.xml diff --git a/feature/trip-planner/ui/src/main/res/drawable/ic_location.xml b/feature/trip-planner/ui/src/commonMain/res/drawable/ic_location.xml similarity index 100% rename from feature/trip-planner/ui/src/main/res/drawable/ic_location.xml rename to feature/trip-planner/ui/src/commonMain/res/drawable/ic_location.xml diff --git a/feature/trip-planner/ui/src/main/res/drawable/ic_mode_ferry.xml b/feature/trip-planner/ui/src/commonMain/res/drawable/ic_mode_ferry.xml similarity index 100% rename from feature/trip-planner/ui/src/main/res/drawable/ic_mode_ferry.xml rename to feature/trip-planner/ui/src/commonMain/res/drawable/ic_mode_ferry.xml diff --git a/feature/trip-planner/ui/src/main/res/drawable/ic_reverse.xml b/feature/trip-planner/ui/src/commonMain/res/drawable/ic_reverse.xml similarity index 100% rename from feature/trip-planner/ui/src/main/res/drawable/ic_reverse.xml rename to feature/trip-planner/ui/src/commonMain/res/drawable/ic_reverse.xml diff --git a/feature/trip-planner/ui/src/main/res/drawable/ic_search.xml b/feature/trip-planner/ui/src/commonMain/res/drawable/ic_search.xml similarity index 100% rename from feature/trip-planner/ui/src/main/res/drawable/ic_search.xml rename to feature/trip-planner/ui/src/commonMain/res/drawable/ic_search.xml diff --git a/feature/trip-planner/ui/src/main/res/drawable/ic_star.xml b/feature/trip-planner/ui/src/commonMain/res/drawable/ic_star.xml similarity index 100% rename from feature/trip-planner/ui/src/main/res/drawable/ic_star.xml rename to feature/trip-planner/ui/src/commonMain/res/drawable/ic_star.xml diff --git a/feature/trip-planner/ui/src/main/res/drawable/ic_star_filled.xml b/feature/trip-planner/ui/src/commonMain/res/drawable/ic_star_filled.xml similarity index 100% rename from feature/trip-planner/ui/src/main/res/drawable/ic_star_filled.xml rename to feature/trip-planner/ui/src/commonMain/res/drawable/ic_star_filled.xml diff --git a/feature/trip-planner/ui/src/main/res/drawable/ic_walk.xml b/feature/trip-planner/ui/src/commonMain/res/drawable/ic_walk.xml similarity index 100% rename from feature/trip-planner/ui/src/main/res/drawable/ic_walk.xml rename to feature/trip-planner/ui/src/commonMain/res/drawable/ic_walk.xml diff --git a/feature/trip-planner/ui/src/main/res/values/strings.xml b/feature/trip-planner/ui/src/commonMain/res/values/strings.xml similarity index 100% rename from feature/trip-planner/ui/src/main/res/values/strings.xml rename to feature/trip-planner/ui/src/commonMain/res/values/strings.xml diff --git a/feature/trip-planner/ui/src/test/kotlin/xyz/ksharma/krail/trip/planner/ui/components/ColorsTest.kt b/feature/trip-planner/ui/src/commonTest/kotlin/xyz/ksharma/krail/trip/planner/ui/components/ColorsTest.kt similarity index 97% rename from feature/trip-planner/ui/src/test/kotlin/xyz/ksharma/krail/trip/planner/ui/components/ColorsTest.kt rename to feature/trip-planner/ui/src/commonTest/kotlin/xyz/ksharma/krail/trip/planner/ui/components/ColorsTest.kt index dd547f37..2951a99b 100644 --- a/feature/trip-planner/ui/src/test/kotlin/xyz/ksharma/krail/trip/planner/ui/components/ColorsTest.kt +++ b/feature/trip-planner/ui/src/commonTest/kotlin/xyz/ksharma/krail/trip/planner/ui/components/ColorsTest.kt @@ -2,8 +2,7 @@ package xyz.ksharma.krail.trip.planner.ui.components import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.luminance -import org.junit.Assert.assertEquals -import org.junit.Test + // Define the colors private val myWhiteLightMode = Color(0xFFFFFBFF) diff --git a/feature/trip-planner/ui/src/test/kotlin/xyz/ksharma/krail/trip/planner/ui/timetable/business/PlatformTextTest.kt b/feature/trip-planner/ui/src/commonTest/kotlin/xyz/ksharma/krail/trip/planner/ui/timetable/business/PlatformTextTest.kt similarity index 100% rename from feature/trip-planner/ui/src/test/kotlin/xyz/ksharma/krail/trip/planner/ui/timetable/business/PlatformTextTest.kt rename to feature/trip-planner/ui/src/commonTest/kotlin/xyz/ksharma/krail/trip/planner/ui/timetable/business/PlatformTextTest.kt diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 153b631e..eba69ae3 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -15,6 +15,7 @@ kotlinInject = "0.7.2" kotlinxCollectionsImmutable = "0.3.8" kotlinxDatetime = "0.6.1" ktorClientLogging = "2.3.12" +navigationCompose = "2.8.0-alpha10" okhttpBom = "4.12.0" kotlinxSerializationJson = "1.7.3" ksp = "2.0.21-1.0.27" # ksp to kotlin version mapping https://github.com/google/ksp/releases @@ -38,6 +39,7 @@ activity-compose = { group = "androidx.activity", name = "activity-compose", ver kotlinx-collections-immutable = { module = "org.jetbrains.kotlinx:kotlinx-collections-immutable", version.ref = "kotlinxCollectionsImmutable" } kotlinx-datetime = { module = "org.jetbrains.kotlinx:kotlinx-datetime", version.ref = "kotlinxDatetime" } lifecycle-runtime-ktx = { group = "androidx.lifecycle", name = "lifecycle-runtime-ktx", version.ref = "android-lifecycle" } +navigation-compose = { module = "org.jetbrains.androidx.navigation:navigation-compose", version.ref = "navigationCompose" } retrofit2-kotlinx-serialization-converter = { module = "com.jakewharton.retrofit:retrofit2-kotlinx-serialization-converter", version.ref = "retrofit2KotlinxSerializationConverter" } timber = { module = "com.jakewharton.timber:timber", version.ref = "timber" } kotlinx-coroutines-core = { group = "org.jetbrains.kotlinx", name = "kotlinx-coroutines-core", version.ref = "kotlinxCoroutines" } @@ -45,6 +47,7 @@ kotlinx-coroutines-core = { group = "org.jetbrains.kotlinx", name = "kotlinx-cor androidx-lifecycle-viewmodel = { group = "org.jetbrains.androidx.lifecycle", name = "lifecycle-viewmodel", version.ref = "androidx-lifecycle" } androidx-lifecycle-runtime-compose = { group = "org.jetbrains.androidx.lifecycle", name = "lifecycle-runtime-compose", version.ref = "androidx-lifecycle" } + # DI di-kotlinInjectRuntime = { module = "me.tatarka.inject:kotlin-inject-runtime-kmp", version.ref = "kotlinInject" } di-kotlinInjectCompilerKsp = { module = "me.tatarka.inject:kotlin-inject-compiler-ksp", version.ref = "kotlinInject" } diff --git a/settings.gradle.kts b/settings.gradle.kts index f1f29269..7249f9dd 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -37,11 +37,11 @@ include(":core:di") include(":core:date-time") include(":core:coroutines-ext") include(":core:network") +include(":feature:trip-planner:ui") /* include(":feature:trip-planner:network:api") include(":feature:trip-planner:network:real") include(":feature:trip-planner:state") -include(":feature:trip-planner:ui") include(":sandook:api") include(":sandook:real") */ From cf2e56a18682f1927884455dd561d5b26a5d212c Mon Sep 17 00:00:00 2001 From: Karan Sharma <55722391+ksharma-xyz@users.noreply.github.com> Date: Sat, 16 Nov 2024 21:57:55 +1100 Subject: [PATCH 25/67] Make trip-planner:state multiplatform --- feature/trip-planner/state/build.gradle.kts | 1 + .../xyz/ksharma/krail/trip/planner/ui/state/TransportMode.kt | 0 .../ksharma/krail/trip/planner/ui/state/TransportModeLine.kt | 0 .../ksharma/krail/trip/planner/ui/state/alerts/ServiceAlert.kt | 0 .../krail/trip/planner/ui/state/savedtrip/SavedTripUiEvent.kt | 0 .../krail/trip/planner/ui/state/savedtrip/SavedTripsState.kt | 0 .../krail/trip/planner/ui/state/searchstop/SearchStopState.kt | 0 .../krail/trip/planner/ui/state/searchstop/SearchStopUiEvent.kt | 0 .../krail/trip/planner/ui/state/searchstop/model/StopItem.kt | 0 .../krail/trip/planner/ui/state/timetable/TimeTableState.kt | 0 .../krail/trip/planner/ui/state/timetable/TimeTableUiEvent.kt | 0 .../xyz/ksharma/krail/trip/planner/ui/state/timetable/Trip.kt | 0 .../krail/trip/planner/ui/state/usualride/UsualRideEvent.kt | 0 .../krail/trip/planner/ui/state/usualride/UsualRideState.kt | 0 feature/trip-planner/ui/build.gradle.kts | 1 + settings.gradle.kts | 2 +- 16 files changed, 3 insertions(+), 1 deletion(-) rename feature/trip-planner/state/src/{main => commonMain}/kotlin/xyz/ksharma/krail/trip/planner/ui/state/TransportMode.kt (100%) rename feature/trip-planner/state/src/{main => commonMain}/kotlin/xyz/ksharma/krail/trip/planner/ui/state/TransportModeLine.kt (100%) rename feature/trip-planner/state/src/{main => commonMain}/kotlin/xyz/ksharma/krail/trip/planner/ui/state/alerts/ServiceAlert.kt (100%) rename feature/trip-planner/state/src/{main => commonMain}/kotlin/xyz/ksharma/krail/trip/planner/ui/state/savedtrip/SavedTripUiEvent.kt (100%) rename feature/trip-planner/state/src/{main => commonMain}/kotlin/xyz/ksharma/krail/trip/planner/ui/state/savedtrip/SavedTripsState.kt (100%) rename feature/trip-planner/state/src/{main => commonMain}/kotlin/xyz/ksharma/krail/trip/planner/ui/state/searchstop/SearchStopState.kt (100%) rename feature/trip-planner/state/src/{main => commonMain}/kotlin/xyz/ksharma/krail/trip/planner/ui/state/searchstop/SearchStopUiEvent.kt (100%) rename feature/trip-planner/state/src/{main => commonMain}/kotlin/xyz/ksharma/krail/trip/planner/ui/state/searchstop/model/StopItem.kt (100%) rename feature/trip-planner/state/src/{main => commonMain}/kotlin/xyz/ksharma/krail/trip/planner/ui/state/timetable/TimeTableState.kt (100%) rename feature/trip-planner/state/src/{main => commonMain}/kotlin/xyz/ksharma/krail/trip/planner/ui/state/timetable/TimeTableUiEvent.kt (100%) rename feature/trip-planner/state/src/{main => commonMain}/kotlin/xyz/ksharma/krail/trip/planner/ui/state/timetable/Trip.kt (100%) rename feature/trip-planner/state/src/{main => commonMain}/kotlin/xyz/ksharma/krail/trip/planner/ui/state/usualride/UsualRideEvent.kt (100%) rename feature/trip-planner/state/src/{main => commonMain}/kotlin/xyz/ksharma/krail/trip/planner/ui/state/usualride/UsualRideState.kt (100%) diff --git a/feature/trip-planner/state/build.gradle.kts b/feature/trip-planner/state/build.gradle.kts index 4c63505b..56762eb5 100644 --- a/feature/trip-planner/state/build.gradle.kts +++ b/feature/trip-planner/state/build.gradle.kts @@ -1,6 +1,7 @@ plugins { alias(libs.plugins.krail.android.library) alias(libs.plugins.kotlin.serialization) + alias(libs.plugins.krail.kotlin.multiplatform) } android { diff --git a/feature/trip-planner/state/src/main/kotlin/xyz/ksharma/krail/trip/planner/ui/state/TransportMode.kt b/feature/trip-planner/state/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/state/TransportMode.kt similarity index 100% rename from feature/trip-planner/state/src/main/kotlin/xyz/ksharma/krail/trip/planner/ui/state/TransportMode.kt rename to feature/trip-planner/state/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/state/TransportMode.kt diff --git a/feature/trip-planner/state/src/main/kotlin/xyz/ksharma/krail/trip/planner/ui/state/TransportModeLine.kt b/feature/trip-planner/state/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/state/TransportModeLine.kt similarity index 100% rename from feature/trip-planner/state/src/main/kotlin/xyz/ksharma/krail/trip/planner/ui/state/TransportModeLine.kt rename to feature/trip-planner/state/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/state/TransportModeLine.kt diff --git a/feature/trip-planner/state/src/main/kotlin/xyz/ksharma/krail/trip/planner/ui/state/alerts/ServiceAlert.kt b/feature/trip-planner/state/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/state/alerts/ServiceAlert.kt similarity index 100% rename from feature/trip-planner/state/src/main/kotlin/xyz/ksharma/krail/trip/planner/ui/state/alerts/ServiceAlert.kt rename to feature/trip-planner/state/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/state/alerts/ServiceAlert.kt diff --git a/feature/trip-planner/state/src/main/kotlin/xyz/ksharma/krail/trip/planner/ui/state/savedtrip/SavedTripUiEvent.kt b/feature/trip-planner/state/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/state/savedtrip/SavedTripUiEvent.kt similarity index 100% rename from feature/trip-planner/state/src/main/kotlin/xyz/ksharma/krail/trip/planner/ui/state/savedtrip/SavedTripUiEvent.kt rename to feature/trip-planner/state/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/state/savedtrip/SavedTripUiEvent.kt diff --git a/feature/trip-planner/state/src/main/kotlin/xyz/ksharma/krail/trip/planner/ui/state/savedtrip/SavedTripsState.kt b/feature/trip-planner/state/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/state/savedtrip/SavedTripsState.kt similarity index 100% rename from feature/trip-planner/state/src/main/kotlin/xyz/ksharma/krail/trip/planner/ui/state/savedtrip/SavedTripsState.kt rename to feature/trip-planner/state/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/state/savedtrip/SavedTripsState.kt diff --git a/feature/trip-planner/state/src/main/kotlin/xyz/ksharma/krail/trip/planner/ui/state/searchstop/SearchStopState.kt b/feature/trip-planner/state/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/state/searchstop/SearchStopState.kt similarity index 100% rename from feature/trip-planner/state/src/main/kotlin/xyz/ksharma/krail/trip/planner/ui/state/searchstop/SearchStopState.kt rename to feature/trip-planner/state/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/state/searchstop/SearchStopState.kt diff --git a/feature/trip-planner/state/src/main/kotlin/xyz/ksharma/krail/trip/planner/ui/state/searchstop/SearchStopUiEvent.kt b/feature/trip-planner/state/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/state/searchstop/SearchStopUiEvent.kt similarity index 100% rename from feature/trip-planner/state/src/main/kotlin/xyz/ksharma/krail/trip/planner/ui/state/searchstop/SearchStopUiEvent.kt rename to feature/trip-planner/state/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/state/searchstop/SearchStopUiEvent.kt diff --git a/feature/trip-planner/state/src/main/kotlin/xyz/ksharma/krail/trip/planner/ui/state/searchstop/model/StopItem.kt b/feature/trip-planner/state/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/state/searchstop/model/StopItem.kt similarity index 100% rename from feature/trip-planner/state/src/main/kotlin/xyz/ksharma/krail/trip/planner/ui/state/searchstop/model/StopItem.kt rename to feature/trip-planner/state/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/state/searchstop/model/StopItem.kt diff --git a/feature/trip-planner/state/src/main/kotlin/xyz/ksharma/krail/trip/planner/ui/state/timetable/TimeTableState.kt b/feature/trip-planner/state/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/state/timetable/TimeTableState.kt similarity index 100% rename from feature/trip-planner/state/src/main/kotlin/xyz/ksharma/krail/trip/planner/ui/state/timetable/TimeTableState.kt rename to feature/trip-planner/state/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/state/timetable/TimeTableState.kt diff --git a/feature/trip-planner/state/src/main/kotlin/xyz/ksharma/krail/trip/planner/ui/state/timetable/TimeTableUiEvent.kt b/feature/trip-planner/state/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/state/timetable/TimeTableUiEvent.kt similarity index 100% rename from feature/trip-planner/state/src/main/kotlin/xyz/ksharma/krail/trip/planner/ui/state/timetable/TimeTableUiEvent.kt rename to feature/trip-planner/state/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/state/timetable/TimeTableUiEvent.kt diff --git a/feature/trip-planner/state/src/main/kotlin/xyz/ksharma/krail/trip/planner/ui/state/timetable/Trip.kt b/feature/trip-planner/state/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/state/timetable/Trip.kt similarity index 100% rename from feature/trip-planner/state/src/main/kotlin/xyz/ksharma/krail/trip/planner/ui/state/timetable/Trip.kt rename to feature/trip-planner/state/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/state/timetable/Trip.kt diff --git a/feature/trip-planner/state/src/main/kotlin/xyz/ksharma/krail/trip/planner/ui/state/usualride/UsualRideEvent.kt b/feature/trip-planner/state/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/state/usualride/UsualRideEvent.kt similarity index 100% rename from feature/trip-planner/state/src/main/kotlin/xyz/ksharma/krail/trip/planner/ui/state/usualride/UsualRideEvent.kt rename to feature/trip-planner/state/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/state/usualride/UsualRideEvent.kt diff --git a/feature/trip-planner/state/src/main/kotlin/xyz/ksharma/krail/trip/planner/ui/state/usualride/UsualRideState.kt b/feature/trip-planner/state/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/state/usualride/UsualRideState.kt similarity index 100% rename from feature/trip-planner/state/src/main/kotlin/xyz/ksharma/krail/trip/planner/ui/state/usualride/UsualRideState.kt rename to feature/trip-planner/state/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/state/usualride/UsualRideState.kt diff --git a/feature/trip-planner/ui/build.gradle.kts b/feature/trip-planner/ui/build.gradle.kts index c0ad6fb8..516cd461 100644 --- a/feature/trip-planner/ui/build.gradle.kts +++ b/feature/trip-planner/ui/build.gradle.kts @@ -26,6 +26,7 @@ kotlin { commonMain { dependencies { implementation(projects.taj) + implementation(projects.feature.tripPlanner.state) implementation(compose.foundation) implementation(compose.animation) diff --git a/settings.gradle.kts b/settings.gradle.kts index 7249f9dd..d2b31359 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -38,10 +38,10 @@ include(":core:date-time") include(":core:coroutines-ext") include(":core:network") include(":feature:trip-planner:ui") +include(":feature:trip-planner:state") /* include(":feature:trip-planner:network:api") include(":feature:trip-planner:network:real") -include(":feature:trip-planner:state") include(":sandook:api") include(":sandook:real") */ From ee4c6e71427f31c6bca4bd42ab4c38affb1c607c Mon Sep 17 00:00:00 2001 From: Karan Sharma <55722391+ksharma-xyz@users.noreply.github.com> Date: Sat, 16 Nov 2024 22:17:11 +1100 Subject: [PATCH 26/67] update trip-planner:ui - multiplatform --- .../krail/trip/planner/ui/alerts/CollapsibleAlert.kt | 10 ++++------ .../ksharma/krail/trip/planner/ui/alerts/HtmlText.kt | 2 +- .../planner/ui/components/loading/LoadingEmojiAnim.kt | 2 +- .../ui/components/loading/LoadingEmojiManager.kt | 11 +++++++++-- .../{res => resources}/drawable/ic_a11y.xml | 0 .../{res => resources}/drawable/ic_alert.xml | 0 .../{res => resources}/drawable/ic_arrow_down.xml | 0 .../{res => resources}/drawable/ic_arrow_right.xml | 0 .../{res => resources}/drawable/ic_clock.xml | 0 .../commonMain/{res => resources}/drawable/ic_loc.xml | 0 .../{res => resources}/drawable/ic_location.xml | 0 .../{res => resources}/drawable/ic_mode_ferry.xml | 0 .../{res => resources}/drawable/ic_reverse.xml | 0 .../{res => resources}/drawable/ic_search.xml | 0 .../{res => resources}/drawable/ic_star.xml | 0 .../{res => resources}/drawable/ic_star_filled.xml | 0 .../{res => resources}/drawable/ic_walk.xml | 0 .../commonMain/{res => resources}/values/strings.xml | 0 18 files changed, 15 insertions(+), 10 deletions(-) rename feature/trip-planner/ui/src/commonMain/{res => resources}/drawable/ic_a11y.xml (100%) rename feature/trip-planner/ui/src/commonMain/{res => resources}/drawable/ic_alert.xml (100%) rename feature/trip-planner/ui/src/commonMain/{res => resources}/drawable/ic_arrow_down.xml (100%) rename feature/trip-planner/ui/src/commonMain/{res => resources}/drawable/ic_arrow_right.xml (100%) rename feature/trip-planner/ui/src/commonMain/{res => resources}/drawable/ic_clock.xml (100%) rename feature/trip-planner/ui/src/commonMain/{res => resources}/drawable/ic_loc.xml (100%) rename feature/trip-planner/ui/src/commonMain/{res => resources}/drawable/ic_location.xml (100%) rename feature/trip-planner/ui/src/commonMain/{res => resources}/drawable/ic_mode_ferry.xml (100%) rename feature/trip-planner/ui/src/commonMain/{res => resources}/drawable/ic_reverse.xml (100%) rename feature/trip-planner/ui/src/commonMain/{res => resources}/drawable/ic_search.xml (100%) rename feature/trip-planner/ui/src/commonMain/{res => resources}/drawable/ic_star.xml (100%) rename feature/trip-planner/ui/src/commonMain/{res => resources}/drawable/ic_star_filled.xml (100%) rename feature/trip-planner/ui/src/commonMain/{res => resources}/drawable/ic_walk.xml (100%) rename feature/trip-planner/ui/src/commonMain/{res => resources}/values/strings.xml (100%) diff --git a/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/alerts/CollapsibleAlert.kt b/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/alerts/CollapsibleAlert.kt index 0fcc5e0e..716dcc74 100644 --- a/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/alerts/CollapsibleAlert.kt +++ b/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/alerts/CollapsibleAlert.kt @@ -21,13 +21,11 @@ import androidx.compose.runtime.remember import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip -import androidx.compose.ui.tooling.preview.Preview -import androidx.compose.ui.tooling.preview.PreviewLightDark import androidx.compose.ui.unit.dp -import xyz.ksharma.krail.design.system.LocalThemeColor -import xyz.ksharma.krail.design.system.components.Text -import xyz.ksharma.krail.design.system.theme.KrailTheme -import xyz.ksharma.krail.design.system.toAdaptiveSize +import xyz.ksharma.krail.taj.LocalThemeColor +import xyz.ksharma.krail.taj.components.Text +import xyz.ksharma.krail.taj.theme.KrailTheme +import xyz.ksharma.krail.taj.toAdaptiveSize import xyz.ksharma.krail.trip.planner.ui.components.themeBackgroundColor import xyz.ksharma.krail.trip.planner.ui.state.TransportMode import xyz.ksharma.krail.trip.planner.ui.state.alerts.ServiceAlert diff --git a/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/alerts/HtmlText.kt b/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/alerts/HtmlText.kt index 7dea84c5..805ddfff 100644 --- a/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/alerts/HtmlText.kt +++ b/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/alerts/HtmlText.kt @@ -14,7 +14,7 @@ import androidx.compose.ui.text.font.FontSynthesis import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.viewinterop.AndroidView import androidx.core.text.HtmlCompat -import xyz.ksharma.krail.design.system.theme.KrailTheme +import xyz.ksharma.krail.taj.theme.KrailTheme /** * Reference - https://developer.android.com/codelabs/jetpack-compose-migration#8 diff --git a/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/components/loading/LoadingEmojiAnim.kt b/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/components/loading/LoadingEmojiAnim.kt index 1295765c..bbf13cd0 100644 --- a/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/components/loading/LoadingEmojiAnim.kt +++ b/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/components/loading/LoadingEmojiAnim.kt @@ -69,7 +69,7 @@ fun LoadingEmojiAnim(modifier: Modifier = Modifier, emoji: String? = null) { } } -@Preview +//@Preview @Composable private fun Preview() { KrailTheme { diff --git a/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/components/loading/LoadingEmojiManager.kt b/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/components/loading/LoadingEmojiManager.kt index 574022f3..d27d98fd 100644 --- a/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/components/loading/LoadingEmojiManager.kt +++ b/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/components/loading/LoadingEmojiManager.kt @@ -3,8 +3,8 @@ package xyz.ksharma.krail.trip.planner.ui.components.loading import kotlinx.collections.immutable.persistentListOf import kotlinx.datetime.Clock import kotlinx.datetime.TimeZone +import kotlinx.datetime.number import kotlinx.datetime.todayIn -import java.time.MonthDay import kotlin.random.Random object LoadingEmojiManager { @@ -35,6 +35,13 @@ object LoadingEmojiManager { FestivalType.CHINESE_NEW_YEAR to listOf("🧧"), ) + // TODO - test logic + data class MonthDay(val month: Int, val dayOfMonth: Int) { + companion object { + fun of(month: Int, dayOfMonth: Int) = MonthDay(month, dayOfMonth) + } + } + private val knownFestivalDates = mapOf( FestivalType.CHRISTMAS to MonthDay.of(12, 25), FestivalType.NEW_YEAR to MonthDay.of(1, 1), @@ -48,7 +55,7 @@ object LoadingEmojiManager { val today = Clock.System.todayIn(TimeZone.currentSystemDefault()) val festivalEmoji = knownFestivalDates.entries - .firstOrNull { it.value.month == today.month && it.value.dayOfMonth == today.dayOfMonth } + .firstOrNull { it.value.month == today.month.number && it.value.dayOfMonth == today.dayOfMonth } ?.let { festivalEmojiMap[it.key]?.randomOrNull() } if (festivalEmoji != null) return festivalEmoji diff --git a/feature/trip-planner/ui/src/commonMain/res/drawable/ic_a11y.xml b/feature/trip-planner/ui/src/commonMain/resources/drawable/ic_a11y.xml similarity index 100% rename from feature/trip-planner/ui/src/commonMain/res/drawable/ic_a11y.xml rename to feature/trip-planner/ui/src/commonMain/resources/drawable/ic_a11y.xml diff --git a/feature/trip-planner/ui/src/commonMain/res/drawable/ic_alert.xml b/feature/trip-planner/ui/src/commonMain/resources/drawable/ic_alert.xml similarity index 100% rename from feature/trip-planner/ui/src/commonMain/res/drawable/ic_alert.xml rename to feature/trip-planner/ui/src/commonMain/resources/drawable/ic_alert.xml diff --git a/feature/trip-planner/ui/src/commonMain/res/drawable/ic_arrow_down.xml b/feature/trip-planner/ui/src/commonMain/resources/drawable/ic_arrow_down.xml similarity index 100% rename from feature/trip-planner/ui/src/commonMain/res/drawable/ic_arrow_down.xml rename to feature/trip-planner/ui/src/commonMain/resources/drawable/ic_arrow_down.xml diff --git a/feature/trip-planner/ui/src/commonMain/res/drawable/ic_arrow_right.xml b/feature/trip-planner/ui/src/commonMain/resources/drawable/ic_arrow_right.xml similarity index 100% rename from feature/trip-planner/ui/src/commonMain/res/drawable/ic_arrow_right.xml rename to feature/trip-planner/ui/src/commonMain/resources/drawable/ic_arrow_right.xml diff --git a/feature/trip-planner/ui/src/commonMain/res/drawable/ic_clock.xml b/feature/trip-planner/ui/src/commonMain/resources/drawable/ic_clock.xml similarity index 100% rename from feature/trip-planner/ui/src/commonMain/res/drawable/ic_clock.xml rename to feature/trip-planner/ui/src/commonMain/resources/drawable/ic_clock.xml diff --git a/feature/trip-planner/ui/src/commonMain/res/drawable/ic_loc.xml b/feature/trip-planner/ui/src/commonMain/resources/drawable/ic_loc.xml similarity index 100% rename from feature/trip-planner/ui/src/commonMain/res/drawable/ic_loc.xml rename to feature/trip-planner/ui/src/commonMain/resources/drawable/ic_loc.xml diff --git a/feature/trip-planner/ui/src/commonMain/res/drawable/ic_location.xml b/feature/trip-planner/ui/src/commonMain/resources/drawable/ic_location.xml similarity index 100% rename from feature/trip-planner/ui/src/commonMain/res/drawable/ic_location.xml rename to feature/trip-planner/ui/src/commonMain/resources/drawable/ic_location.xml diff --git a/feature/trip-planner/ui/src/commonMain/res/drawable/ic_mode_ferry.xml b/feature/trip-planner/ui/src/commonMain/resources/drawable/ic_mode_ferry.xml similarity index 100% rename from feature/trip-planner/ui/src/commonMain/res/drawable/ic_mode_ferry.xml rename to feature/trip-planner/ui/src/commonMain/resources/drawable/ic_mode_ferry.xml diff --git a/feature/trip-planner/ui/src/commonMain/res/drawable/ic_reverse.xml b/feature/trip-planner/ui/src/commonMain/resources/drawable/ic_reverse.xml similarity index 100% rename from feature/trip-planner/ui/src/commonMain/res/drawable/ic_reverse.xml rename to feature/trip-planner/ui/src/commonMain/resources/drawable/ic_reverse.xml diff --git a/feature/trip-planner/ui/src/commonMain/res/drawable/ic_search.xml b/feature/trip-planner/ui/src/commonMain/resources/drawable/ic_search.xml similarity index 100% rename from feature/trip-planner/ui/src/commonMain/res/drawable/ic_search.xml rename to feature/trip-planner/ui/src/commonMain/resources/drawable/ic_search.xml diff --git a/feature/trip-planner/ui/src/commonMain/res/drawable/ic_star.xml b/feature/trip-planner/ui/src/commonMain/resources/drawable/ic_star.xml similarity index 100% rename from feature/trip-planner/ui/src/commonMain/res/drawable/ic_star.xml rename to feature/trip-planner/ui/src/commonMain/resources/drawable/ic_star.xml diff --git a/feature/trip-planner/ui/src/commonMain/res/drawable/ic_star_filled.xml b/feature/trip-planner/ui/src/commonMain/resources/drawable/ic_star_filled.xml similarity index 100% rename from feature/trip-planner/ui/src/commonMain/res/drawable/ic_star_filled.xml rename to feature/trip-planner/ui/src/commonMain/resources/drawable/ic_star_filled.xml diff --git a/feature/trip-planner/ui/src/commonMain/res/drawable/ic_walk.xml b/feature/trip-planner/ui/src/commonMain/resources/drawable/ic_walk.xml similarity index 100% rename from feature/trip-planner/ui/src/commonMain/res/drawable/ic_walk.xml rename to feature/trip-planner/ui/src/commonMain/resources/drawable/ic_walk.xml diff --git a/feature/trip-planner/ui/src/commonMain/res/values/strings.xml b/feature/trip-planner/ui/src/commonMain/resources/values/strings.xml similarity index 100% rename from feature/trip-planner/ui/src/commonMain/res/values/strings.xml rename to feature/trip-planner/ui/src/commonMain/resources/values/strings.xml From b69193e65309f04a010c6946514d5ee1bb6ae2c3 Mon Sep 17 00:00:00 2001 From: Karan Sharma <55722391+ksharma-xyz@users.noreply.github.com> Date: Sun, 17 Nov 2024 12:44:17 +1100 Subject: [PATCH 27/67] update ui, -icons / resources --- feature/trip-planner/ui/build.gradle.kts | 3 +- .../planner/ui/alerts/AlertsDestination.kt | 10 - .../trip/planner/ui/components/ColorExt.kt | 173 +++++++++++------- .../planner/ui/components/ErrorMessage.kt | 7 +- .../trip/planner/ui/components/JourneyCard.kt | 43 ++--- .../trip/planner/ui/components/LegView.kt | 38 +--- .../ui/components/OriginDestination.kt | 4 +- .../planner/ui/components/SavedTripCard.kt | 17 +- .../planner/ui/components/SearchStopRow.kt | 29 ++- .../ui/components/StopSearchListItem.kt | 8 +- .../ui/components/TransportModeBadge.kt | 8 +- .../ui/components/TransportModeIcon.kt | 26 ++- .../ui/components/TransportModeInfo.kt | 7 +- .../trip/planner/ui/components/WalkingLeg.kt | 14 +- .../ui/timetable/business/PlatformTextTest.kt | 2 - 15 files changed, 180 insertions(+), 209 deletions(-) diff --git a/feature/trip-planner/ui/build.gradle.kts b/feature/trip-planner/ui/build.gradle.kts index 516cd461..6f5b1084 100644 --- a/feature/trip-planner/ui/build.gradle.kts +++ b/feature/trip-planner/ui/build.gradle.kts @@ -1,5 +1,3 @@ -import com.android.build.gradle.internal.ide.kmp.KotlinAndroidSourceSetMarker.Companion.android - plugins { alias(libs.plugins.krail.kotlin.multiplatform) alias(libs.plugins.krail.compose.multiplatform) @@ -31,6 +29,7 @@ kotlin { implementation(compose.foundation) implementation(compose.animation) implementation(compose.ui) + implementation(compose.material3) implementation(libs.kotlinx.datetime) implementation(libs.kotlinx.collections.immutable) diff --git a/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/alerts/AlertsDestination.kt b/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/alerts/AlertsDestination.kt index 40e4d55e..011554d2 100644 --- a/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/alerts/AlertsDestination.kt +++ b/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/alerts/AlertsDestination.kt @@ -1,6 +1,5 @@ package xyz.ksharma.krail.trip.planner.ui.alerts -import androidx.compose.runtime.LaunchedEffect import androidx.navigation.NavGraphBuilder import androidx.navigation.NavHostController import androidx.navigation.compose.composable @@ -17,15 +16,6 @@ internal fun NavGraphBuilder.alertsDestination(navController: NavHostController) ServiceAlert.fromJsonString(alertJson) }.toImmutableSet() - LaunchedEffect(route.alertsJsonList) { - route.alertsJsonList.forEach { - ServiceAlert.fromJsonString(it)?.let { alert -> - // Timber.d("Alert Heading: ${alert.heading}") - //Timber.d("Alert Message: ${alert.message}") - } - } - } - ServiceAlertScreen(serviceAlerts = serviceAlerts, onBackClick = { navController.popBackStack() }) diff --git a/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/components/ColorExt.kt b/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/components/ColorExt.kt index abe20744..8609d4bc 100644 --- a/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/components/ColorExt.kt +++ b/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/components/ColorExt.kt @@ -5,26 +5,38 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.toArgb -import xyz.ksharma.krail.design.system.LocalThemeColor -import xyz.ksharma.krail.design.system.LocalThemeContentColor +import xyz.ksharma.krail.taj.LocalThemeColor +import xyz.ksharma.krail.taj.LocalThemeContentColor import xyz.ksharma.krail.trip.planner.ui.state.TransportMode +import kotlin.math.absoluteValue -/** - * Converts a hexadecimal color string to a Compose Color object. - * - * This function takes a string representing a hexadecimal color code - * (e.g., "#FF0000" for red) and attempts to convert it into a Compose Color - * object. - * - * @throws IllegalArgumentException if the provided string is not a valid - * hexadecimal color code. A valid code must start with "#" followed by - * either 6 or 8 hexadecimal digits (0-9, A-F, a-f). - * - * @return A Compose Color object representing the provided hex color code. - */ fun String.hexToComposeColor(): Color { - require(this.isValidHexColorCode()) { "Invalid hex color code: $this" } - return Color(android.graphics.Color.parseColor(this)) + require(isValidHexColorCode()) { + "Invalid hex color code: $this. Hex color codes must be in the format #RRGGBB or #AARRGGBB." + } + + // Remove the leading '#' if present + val hex = removePrefix("#") + + // Parse the hex value + return when (hex.length) { + 6 -> { + // If the string is in the format RRGGBB, add full opacity (FF) at the start + val r = hex.substring(0, 2).toInt(16) + val g = hex.substring(2, 4).toInt(16) + val b = hex.substring(4, 6).toInt(16) + Color(red = r / 255f, green = g / 255f, blue = b / 255f) + } + 8 -> { + // If the string is in the format AARRGGBB + val a = hex.substring(0, 2).toInt(16) + val r = hex.substring(2, 4).toInt(16) + val g = hex.substring(4, 6).toInt(16) + val b = hex.substring(6, 8).toInt(16) + Color(alpha = a / 255f, red = r / 255f, green = g / 255f, blue = b / 255f) + } + else -> throw IllegalArgumentException("Invalid hex color format. Use #RRGGBB or #AARRGGBB.") + } } /** @@ -80,26 +92,17 @@ internal fun themeContentColor(): Color { * * @return A string representing the hexadecimal color code (e.g., "#FF0000" for red). */ -@Suppress("ImplicitDefaultLocale") fun Color.toHex(): String { val red = (this.red * 255).toInt() val green = (this.green * 255).toInt() val blue = (this.blue * 255).toInt() val alpha = (this.alpha * 255).toInt() - return String.format("#%02X%02X%02X%02X", alpha, red, green, blue) + return "#${alpha.toHex()}${red.toHex()}${green.toHex()}${blue.toHex()}" } -/** - * The default light scrim, as defined by androidx and the platform: - * https://cs.android.com/androidx/platform/frameworks/support/+/androidx-commonMain:activity/activity/src/commonMain/java/androidx/activity/EdgeToEdge.kt;l=35-38;drc=27e7d52e8604a080133e8b842db10c89b4482598 - */ -val lightScrim = android.graphics.Color.argb(0xe6, 0xFF, 0xFF, 0xFF) - -/** - * The default dark scrim, as defined by androidx and the platform: - * https://cs.android.com/androidx/platform/frameworks/support/+/androidx-commonMain:activity/activity/src/commonMain/java/androidx/activity/EdgeToEdge.kt;l=40-44;drc=27e7d52e8604a080133e8b842db10c89b4482598 - */ -val darkScrim = android.graphics.Color.argb(0x80, 0x1b, 0x1b, 0x1b) +private fun Int.toHex(): String { + return this.toString(16).padStart(2, '0').uppercase() +} /** * Update the theme color to make text more readable on top of it. @@ -115,26 +118,63 @@ val darkScrim = android.graphics.Color.argb(0x80, 0x1b, 0x1b, 0x1b) * * @return The brightened color in ARGB format */ -private fun brightenColor(color: Int, factor: Float = 0.2f): Int { - // Convert the color to RGB components - val red = android.graphics.Color.red(color) / 255f - val green = android.graphics.Color.green(color) / 255f - val blue = android.graphics.Color.blue(color) / 255f - - // Convert RGB to HSL - val hsl = FloatArray(3) - android.graphics.Color.RGBToHSV( - (red * 255).toInt(), - (green * 255).toInt(), - (blue * 255).toInt(), - hsl, - ) - - // Adjust lightness (value) within bounds - hsl[2] = (hsl[2] + factor).coerceIn(0f, 1f) - - // Convert back to RGB - return android.graphics.Color.HSVToColor(hsl) +fun brightenColor(color: Int, factor: Float = 0.2f): Int { + // Extract RGB components from the color + val red = (color shr 16 and 0xFF) / 255f + val green = (color shr 8 and 0xFF) / 255f + val blue = (color and 0xFF) / 255f + + // Convert RGB to HSV + val hsv = rgbToHsv(red, green, blue) + + // Adjust brightness (value) within bounds + hsv[2] = (hsv[2] + factor).coerceIn(0f, 1f) + + // Convert back to RGB and return the color as an Int + return hsvToColor(hsv) +} + +private fun rgbToHsv(r: Float, g: Float, b: Float): FloatArray { + val max = maxOf(r, g, b) + val min = minOf(r, g, b) + val delta = max - min + + val h: Float = when { + delta == 0f -> 0f + max == r -> ((g - b) / delta + (if (g < b) 6 else 0)) % 6 + max == g -> (b - r) / delta + 2 + else -> (r - g) / delta + 4 + } * 60 + + val s: Float = if (max == 0f) 0f else delta / max + val v: Float = max + + return floatArrayOf(h, s, v) +} + +private fun hsvToColor(hsv: FloatArray): Int { + val h = hsv[0] + val s = hsv[1] + val v = hsv[2] + + val c = v * s + val x = c * (1 - ((h / 60) % 2 - 1).absoluteValue) + val m = v - c + + val (r, g, b) = when { + h < 60 -> Triple(c, x, 0f) + h < 120 -> Triple(x, c, 0f) + h < 180 -> Triple(0f, c, x) + h < 240 -> Triple(0f, x, c) + h < 300 -> Triple(x, 0f, c) + else -> Triple(c, 0f, x) + } + + val red = ((r + m) * 255).toInt() + val green = ((g + m) * 255).toInt() + val blue = ((b + m) * 255).toInt() + + return (255 shl 24) or (red shl 16) or (green shl 8) or blue } /** @@ -152,27 +192,20 @@ private fun Color.brighten(factor: Float = 0.2f): Color { val brightenedArgb = brightenColor(argb, factor) return Color(brightenedArgb) } +fun darkenColor(color: Int, factor: Float = 0.2f): Int { + // Extract RGB components from the color + val red = (color shr 16 and 0xFF) / 255f + val green = (color shr 8 and 0xFF) / 255f + val blue = (color and 0xFF) / 255f + + // Convert RGB to HSV + val hsv = rgbToHsv(red, green, blue) + + // Adjust brightness (value) within bounds + hsv[2] = (hsv[2] - factor).coerceIn(0f, 1f) -private fun darkenColor(color: Int, factor: Float = 0.2f): Int { - // Convert the color to RGB components - val red = android.graphics.Color.red(color) / 255f - val green = android.graphics.Color.green(color) / 255f - val blue = android.graphics.Color.blue(color) / 255f - - // Convert RGB to HSL - val hsl = FloatArray(3) - android.graphics.Color.RGBToHSV( - (red * 255).toInt(), - (green * 255).toInt(), - (blue * 255).toInt(), - hsl, - ) - - // Adjust lightness (value) within bounds - hsl[2] = (hsl[2] - factor).coerceIn(0f, 1f) - - // Convert back to RGB - return android.graphics.Color.HSVToColor(hsl) + // Convert back to RGB and return the color as an Int + return hsvToColor(hsv) } private fun Color.darken(factor: Float = 0.2f): Color { diff --git a/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/components/ErrorMessage.kt b/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/components/ErrorMessage.kt index 7144a4b7..57f76067 100644 --- a/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/components/ErrorMessage.kt +++ b/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/components/ErrorMessage.kt @@ -13,12 +13,11 @@ import androidx.compose.runtime.remember import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.text.style.TextAlign -import androidx.compose.ui.tooling.preview.PreviewLightDark import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp -import xyz.ksharma.krail.design.system.LocalThemeColor -import xyz.ksharma.krail.design.system.components.Text -import xyz.ksharma.krail.design.system.theme.KrailTheme +import xyz.ksharma.krail.taj.LocalThemeColor +import xyz.ksharma.krail.taj.components.Text +import xyz.ksharma.krail.taj.theme.KrailTheme import xyz.ksharma.krail.trip.planner.ui.state.TransportMode @Composable diff --git a/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/components/JourneyCard.kt b/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/components/JourneyCard.kt index 735b5930..ffbd2472 100644 --- a/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/components/JourneyCard.kt +++ b/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/components/JourneyCard.kt @@ -20,6 +20,10 @@ import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.shape.CircleShape import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Call +import androidx.compose.material.icons.filled.ShoppingCart +import androidx.compose.material.icons.outlined.Add import androidx.compose.runtime.Composable import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.runtime.getValue @@ -31,15 +35,12 @@ import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.Brush import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.ColorFilter -import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.platform.LocalDensity -import androidx.compose.ui.res.painterResource import androidx.compose.ui.semantics.Role import androidx.compose.ui.semantics.semantics import androidx.compose.ui.text.TextStyle import androidx.compose.ui.text.style.TextAlign -import androidx.compose.ui.tooling.preview.Preview -import androidx.compose.ui.tooling.preview.PreviewLightDark import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp @@ -195,8 +196,6 @@ fun ExpandedJourneyCardContent( onAlertClick: () -> Unit, modifier: Modifier = Modifier, ) { - val context = LocalContext.current - Column(modifier = modifier) { FlowRow( modifier = Modifier @@ -231,12 +230,12 @@ fun ExpandedJourneyCardContent( ) { if (totalUniqueServiceAlerts > 0) { SmallButton( - icon = R.drawable.ic_alert, - text = context.resources.getQuantityString( - R.plurals.alerts, - totalUniqueServiceAlerts, - totalUniqueServiceAlerts, - ), + imageVector = Icons.Filled.Call, // TODO - R.drawable.ic_alert, + text = if(totalUniqueServiceAlerts > 1) { + "$totalUniqueServiceAlerts Alerts" + } else { + "$totalUniqueServiceAlerts Alert" + }, color = getForegroundColor(KrailTheme.colors.alert), iconSize = iconSize, onClick = onAlertClick, @@ -245,7 +244,7 @@ fun ExpandedJourneyCardContent( } TextWithIcon( - icon = R.drawable.ic_clock, + imageVector = Icons.Filled.ShoppingCart,//TODO - R.drawable.ic_clock, text = totalTravelTime, textStyle = KrailTheme.typography.bodyLarge, iconSize = iconSize, @@ -408,7 +407,7 @@ fun DefaultJourneyCardContent( modifier = Modifier.padding(end = 10.dp), ) TextWithIcon( - icon = R.drawable.ic_clock, + imageVector = Icons.Filled.ShoppingCart, //TODO - R.drawable.ic_clock, text = totalTravelTime, modifier = Modifier .align(Alignment.CenterVertically) @@ -416,7 +415,7 @@ fun DefaultJourneyCardContent( ) totalWalkTime?.let { TextWithIcon( - icon = R.drawable.ic_walk, + imageVector = Icons.Filled.ShoppingCart,// TODO - R.drawable.ic_walk, text = totalWalkTime, modifier = Modifier .align(Alignment.CenterVertically) @@ -426,7 +425,7 @@ fun DefaultJourneyCardContent( Spacer(modifier = Modifier.weight(1f)) if (isWheelchairAccessible) { Image( - painter = painterResource(R.drawable.ic_a11y), + imageVector = Icons.Outlined.Add, contentDescription = null, colorFilter = ColorFilter.tint(color = KrailTheme.colors.onSurface), modifier = Modifier @@ -440,7 +439,7 @@ fun DefaultJourneyCardContent( @Composable private fun TextWithIcon( - icon: Int, + imageVector: ImageVector, text: String, modifier: Modifier = Modifier, textStyle: TextStyle = KrailTheme.typography.bodyMedium, @@ -455,7 +454,7 @@ private fun TextWithIcon( horizontalArrangement = Arrangement.spacedBy(4.dp), ) { Image( - painter = painterResource(icon), + imageVector = imageVector, contentDescription = null, colorFilter = ColorFilter.tint(color = color.copy(alpha = contentAlpha)), modifier = Modifier @@ -472,7 +471,7 @@ private fun TextWithIcon( @Composable private fun SmallButton( - icon: Int, + imageVector: ImageVector, text: String, modifier: Modifier = Modifier, textStyle: TextStyle = KrailTheme.typography.bodyMedium, @@ -502,7 +501,7 @@ private fun SmallButton( horizontalArrangement = Arrangement.spacedBy(4.dp), ) { Image( - painter = painterResource(icon), + imageVector = imageVector, contentDescription = null, colorFilter = ColorFilter.tint(color = color.copy(alpha = contentAlpha)), modifier = Modifier @@ -604,8 +603,6 @@ private fun PreviewJourneyCardCollapsed() { } } -@PreviewLightDark -@Preview(fontScale = 2f) @Composable private fun PreviewJourneyCardExpanded() { KrailTheme { @@ -670,8 +667,6 @@ private val PREVIEW_STOPS = persistentListOf( ), ) -@Preview -@Preview(fontScale = 2f) @Composable private fun PreviewJourneyCardLongData() { KrailTheme { diff --git a/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/components/LegView.kt b/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/components/LegView.kt index d6708741..995a122b 100644 --- a/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/components/LegView.kt +++ b/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/components/LegView.kt @@ -17,6 +17,8 @@ import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Add import androidx.compose.runtime.Composable import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.runtime.getValue @@ -28,24 +30,19 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.ColorFilter -import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalDensity -import androidx.compose.ui.res.painterResource import androidx.compose.ui.semantics.Role import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextAlign -import androidx.compose.ui.tooling.preview.Preview -import androidx.compose.ui.tooling.preview.PreviewLightDark import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.toImmutableList -import xyz.ksharma.krail.design.system.LocalContentAlpha -import xyz.ksharma.krail.design.system.components.Text -import xyz.ksharma.krail.design.system.theme.KrailTheme -import xyz.ksharma.krail.design.system.toAdaptiveDecorativeIconSize -import xyz.ksharma.krail.trip.planner.ui.R +import xyz.ksharma.krail.taj.LocalContentAlpha +import xyz.ksharma.krail.taj.components.Text +import xyz.ksharma.krail.taj.theme.KrailTheme +import xyz.ksharma.krail.taj.toAdaptiveDecorativeIconSize import xyz.ksharma.krail.trip.planner.ui.state.TransportMode import xyz.ksharma.krail.trip.planner.ui.state.TransportModeLine import xyz.ksharma.krail.trip.planner.ui.state.timetable.TimeTableState @@ -120,14 +117,8 @@ fun LegView( .padding(start = 16.dp, top = 12.dp), ) { if (stops.size > 2) { - val context = LocalContext.current StopsRow( - // Need to pass count twice - https://developer.android.com/guide/topics/resources/string-resource#Plurals - stops = context.resources.getQuantityString( - R.plurals.stops, - stops.size - 2, - stops.size - 2, - ), + stops = if (stops.size == 1) "${stops.size} stop" else "${stops.size} stops", line = transportModeLine, ) } else { @@ -208,7 +199,7 @@ private fun RouteSummary( if (displayDuration) { Row(horizontalArrangement = Arrangement.End) { Image( - painter = painterResource(R.drawable.ic_clock), + imageVector = Icons.Filled.Add, //painterResource(R.drawable.ic_clock), // TODO replace with clock icon contentDescription = null, colorFilter = ColorFilter.tint(color = KrailTheme.colors.onSurface), modifier = Modifier @@ -253,7 +244,7 @@ private fun StopInfo( ) if (isWheelchairAccessible) { Image( - painter = painterResource(R.drawable.ic_a11y), + imageVector = Icons.Filled.Add, // TODO - //painterResource(R.drawable.ic_a11y), contentDescription = null, colorFilter = ColorFilter.tint( color = if (isProminent) { @@ -309,8 +300,6 @@ private fun StopsRow(stops: String, line: TransportModeLine, modifier: Modifier // region Previews -@PreviewLightDark -@Preview(fontScale = 2f) @Composable private fun PreviewLegView() { KrailTheme { @@ -344,8 +333,6 @@ private fun PreviewLegView() { } } -@PreviewLightDark -@Preview(fontScale = 2f) @Composable private fun PreviewLegViewTwoStops() { KrailTheme { @@ -374,7 +361,6 @@ private fun PreviewLegViewTwoStops() { } } -@PreviewLightDark @Composable private fun PreviewLegViewMetro() { KrailTheme { @@ -403,7 +389,6 @@ private fun PreviewLegViewMetro() { } } -@PreviewLightDark @Composable private fun PreviewLegViewFerry() { KrailTheme { @@ -432,7 +417,6 @@ private fun PreviewLegViewFerry() { } } -@PreviewLightDark @Composable private fun PreviewLegViewLightRail() { KrailTheme { @@ -461,7 +445,6 @@ private fun PreviewLegViewLightRail() { } } -@PreviewLightDark @Composable private fun PreviewStopsRow() { KrailTheme { @@ -475,7 +458,6 @@ private fun PreviewStopsRow() { } } -@PreviewLightDark @Composable private fun PreviewProminentStopInfo() { KrailTheme { @@ -489,8 +471,6 @@ private fun PreviewProminentStopInfo() { } } -@Preview -@Preview(fontScale = 2f) @Composable private fun PreviewRouteSummary() { KrailTheme { diff --git a/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/components/OriginDestination.kt b/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/components/OriginDestination.kt index c2c4494c..f7cdd1be 100644 --- a/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/components/OriginDestination.kt +++ b/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/components/OriginDestination.kt @@ -19,8 +19,8 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.dp -import xyz.ksharma.krail.design.system.components.Text -import xyz.ksharma.krail.design.system.theme.KrailTheme +import xyz.ksharma.krail.taj.components.Text +import xyz.ksharma.krail.taj.theme.KrailTheme import xyz.ksharma.krail.trip.planner.ui.state.timetable.Trip @Composable diff --git a/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/components/SavedTripCard.kt b/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/components/SavedTripCard.kt index 58afffb3..5d85e61a 100644 --- a/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/components/SavedTripCard.kt +++ b/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/components/SavedTripCard.kt @@ -12,6 +12,8 @@ import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.shape.CircleShape import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Star import androidx.compose.runtime.Composable import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.runtime.getValue @@ -21,19 +23,14 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.ColorFilter -import androidx.compose.ui.graphics.vector.ImageVector -import androidx.compose.ui.res.vectorResource import androidx.compose.ui.semantics.Role import androidx.compose.ui.semantics.semantics -import androidx.compose.ui.tooling.preview.PreviewLightDark import androidx.compose.ui.unit.dp -import xyz.ksharma.krail.design.system.LocalThemeColor -import xyz.ksharma.krail.design.system.components.Text -import xyz.ksharma.krail.design.system.preview.PreviewComponent -import xyz.ksharma.krail.design.system.theme.KrailTheme +import xyz.ksharma.krail.taj.LocalThemeColor +import xyz.ksharma.krail.taj.components.Text +import xyz.ksharma.krail.taj.theme.KrailTheme import xyz.ksharma.krail.trip.planner.ui.state.TransportMode import xyz.ksharma.krail.trip.planner.ui.state.timetable.Trip -import xyz.ksharma.krail.design.system.R as DSR @Composable fun SavedTripCard( @@ -92,7 +89,7 @@ fun SavedTripCard( contentAlignment = Alignment.Center, ) { Image( - imageVector = ImageVector.vectorResource(DSR.drawable.star_filled), + imageVector = Icons.Filled.Star, contentDescription = "Save Trip", colorFilter = ColorFilter.tint( primaryTransportMode?.colorCode @@ -105,7 +102,6 @@ fun SavedTripCard( // region Previews -@PreviewComponent @Composable private fun SavedTripCardPreview() { KrailTheme { @@ -127,7 +123,6 @@ private fun SavedTripCardPreview() { } } -@PreviewLightDark @Composable private fun SavedTripCardListPreview() { KrailTheme { diff --git a/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/components/SearchStopRow.kt b/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/components/SearchStopRow.kt index d73f9866..88282382 100644 --- a/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/components/SearchStopRow.kt +++ b/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/components/SearchStopRow.kt @@ -10,6 +10,9 @@ import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.navigationBars import androidx.compose.foundation.layout.padding import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Edit +import androidx.compose.material.icons.filled.Search import androidx.compose.runtime.Composable import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.runtime.getValue @@ -17,21 +20,16 @@ import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.ColorFilter -import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.platform.LocalDensity -import androidx.compose.ui.res.stringResource -import androidx.compose.ui.res.vectorResource import androidx.compose.ui.unit.dp -import xyz.ksharma.krail.design.system.LocalOnContentColor -import xyz.ksharma.krail.design.system.LocalThemeColor -import xyz.ksharma.krail.design.system.components.RoundIconButton -import xyz.ksharma.krail.design.system.components.Text -import xyz.ksharma.krail.design.system.components.TextFieldButton -import xyz.ksharma.krail.design.system.preview.PreviewComponent -import xyz.ksharma.krail.design.system.theme.KrailTheme +import xyz.ksharma.krail.taj.LocalOnContentColor +import xyz.ksharma.krail.taj.LocalThemeColor +import xyz.ksharma.krail.taj.components.RoundIconButton +import xyz.ksharma.krail.taj.components.Text +import xyz.ksharma.krail.taj.components.TextFieldButton +import xyz.ksharma.krail.taj.theme.KrailTheme import xyz.ksharma.krail.trip.planner.ui.state.TransportMode import xyz.ksharma.krail.trip.planner.ui.state.searchstop.model.StopItem -import xyz.ksharma.krail.trip.planner.ui.R as TripPlannerUiR @Composable fun SearchStopRow( @@ -69,14 +67,14 @@ fun SearchStopRow( TextFieldButton(onClick = fromButtonClick) { Text( text = fromStopItem?.stopName - ?: stringResource(TripPlannerUiR.string.from_text_field_placeholder), + ?: "Where from", maxLines = 1, ) } TextFieldButton(onClick = toButtonClick) { Text( text = toStopItem?.stopName - ?: stringResource(TripPlannerUiR.string.to_text_field_placeholder), + ?: "Where to", maxLines = 1, ) } @@ -90,7 +88,7 @@ fun SearchStopRow( RoundIconButton( content = { Image( - imageVector = ImageVector.vectorResource(TripPlannerUiR.drawable.ic_reverse), + imageVector = Icons.Filled.Edit, //todo 0 //.vectorResource(TripPlannerUiR.drawable.ic_reverse), contentDescription = "Reverse", colorFilter = ColorFilter.tint(LocalOnContentColor.current), ) @@ -101,7 +99,7 @@ fun SearchStopRow( RoundIconButton( content = { Image( - imageVector = ImageVector.vectorResource(TripPlannerUiR.drawable.ic_search), + imageVector = Icons.Filled.Search, // TODO - ImageVector.vectorResource(TripPlannerUiR.drawable.ic_search), contentDescription = "Search", colorFilter = ColorFilter.tint(LocalOnContentColor.current), ) @@ -114,7 +112,6 @@ fun SearchStopRow( // region Previews -@PreviewComponent @Composable private fun SearchStopColumnPreview() { KrailTheme { diff --git a/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/components/StopSearchListItem.kt b/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/components/StopSearchListItem.kt index 88997717..93c44b0b 100644 --- a/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/components/StopSearchListItem.kt +++ b/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/components/StopSearchListItem.kt @@ -11,13 +11,11 @@ import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.semantics.Role -import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import kotlinx.collections.immutable.ImmutableSet import kotlinx.collections.immutable.persistentSetOf -import xyz.ksharma.krail.design.system.components.Text -import xyz.ksharma.krail.design.system.preview.PreviewComponent -import xyz.ksharma.krail.design.system.theme.KrailTheme +import xyz.ksharma.krail.taj.components.Text +import xyz.ksharma.krail.taj.theme.KrailTheme import xyz.ksharma.krail.trip.planner.ui.state.TransportMode import xyz.ksharma.krail.trip.planner.ui.state.searchstop.model.StopItem @@ -64,7 +62,6 @@ fun StopSearchListItem( // region Preview -@PreviewComponent @Composable private fun StopSearchListItemPreview() { KrailTheme { @@ -81,7 +78,6 @@ private fun StopSearchListItemPreview() { } } -@Preview @Composable private fun StopSearchListItemLongNamePreview() { KrailTheme { diff --git a/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/components/TransportModeBadge.kt b/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/components/TransportModeBadge.kt index 2e32e30d..a6f90458 100644 --- a/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/components/TransportModeBadge.kt +++ b/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/components/TransportModeBadge.kt @@ -15,9 +15,8 @@ import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp -import xyz.ksharma.krail.design.system.components.Text -import xyz.ksharma.krail.design.system.preview.PreviewComponent -import xyz.ksharma.krail.design.system.theme.KrailTheme +import xyz.ksharma.krail.taj.components.Text +import xyz.ksharma.krail.taj.theme.KrailTheme @Composable fun TransportModeBadge( @@ -48,7 +47,6 @@ fun TransportModeBadge( // region Previews -@PreviewComponent @Composable private fun TransportModeBadgeBusPreview() { KrailTheme { @@ -59,7 +57,6 @@ private fun TransportModeBadgeBusPreview() { } } -@PreviewComponent @Composable private fun TransportModeBadgeTrainPreview() { KrailTheme { @@ -70,7 +67,6 @@ private fun TransportModeBadgeTrainPreview() { } } -@PreviewComponent @Composable private fun TransportModeBadgeFerryPreview() { KrailTheme { diff --git a/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/components/TransportModeIcon.kt b/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/components/TransportModeIcon.kt index cf709e45..249c17a1 100644 --- a/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/components/TransportModeIcon.kt +++ b/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/components/TransportModeIcon.kt @@ -17,10 +17,9 @@ import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.TextUnit import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp -import xyz.ksharma.krail.design.system.LocalContentAlpha -import xyz.ksharma.krail.design.system.components.Text -import xyz.ksharma.krail.design.system.preview.PreviewComponent -import xyz.ksharma.krail.design.system.theme.KrailTheme +import xyz.ksharma.krail.taj.LocalContentAlpha +import xyz.ksharma.krail.taj.components.Text +import xyz.ksharma.krail.taj.theme.KrailTheme @Composable fun TransportModeIcon( @@ -67,7 +66,7 @@ private fun Modifier.borderIfEnabled(enabled: Boolean): Modifier = // region Previews -@PreviewComponent + @Composable private fun TrainPreview() { KrailTheme { @@ -75,7 +74,7 @@ private fun TrainPreview() { } } -@PreviewComponent + @Composable private fun BusPreview() { KrailTheme { @@ -86,7 +85,7 @@ private fun BusPreview() { } } -@PreviewComponent + @Composable private fun MetroPreview() { KrailTheme { @@ -97,7 +96,7 @@ private fun MetroPreview() { } } -@PreviewComponent + @Composable private fun LightRailPreview() { KrailTheme { @@ -108,7 +107,7 @@ private fun LightRailPreview() { } } -@PreviewComponent + @Composable private fun FerryPreview() { KrailTheme { @@ -119,7 +118,7 @@ private fun FerryPreview() { } } -@PreviewComponent + @Composable private fun TrainWithBackgroundPreview() { KrailTheme { @@ -131,7 +130,6 @@ private fun TrainWithBackgroundPreview() { } } -@PreviewComponent @Composable private fun BusWithBackgroundPreview() { KrailTheme { @@ -143,7 +141,7 @@ private fun BusWithBackgroundPreview() { } } -@PreviewComponent + @Composable private fun MetroWithBackgroundPreview() { KrailTheme { @@ -155,7 +153,7 @@ private fun MetroWithBackgroundPreview() { } } -@PreviewComponent + @Composable private fun LightRailWithBackgroundPreview() { KrailTheme { @@ -167,7 +165,7 @@ private fun LightRailWithBackgroundPreview() { } } -@PreviewComponent + @Composable private fun FerryWithBackgroundPreview() { KrailTheme { diff --git a/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/components/TransportModeInfo.kt b/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/components/TransportModeInfo.kt index 61f728cd..7cb379eb 100644 --- a/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/components/TransportModeInfo.kt +++ b/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/components/TransportModeInfo.kt @@ -6,10 +6,9 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color -import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp -import xyz.ksharma.krail.design.system.LocalContentAlpha -import xyz.ksharma.krail.design.system.theme.KrailTheme +import xyz.ksharma.krail.taj.LocalContentAlpha +import xyz.ksharma.krail.taj.theme.KrailTheme @Composable fun TransportModeInfo( @@ -39,8 +38,6 @@ fun TransportModeInfo( // region Previews -@Preview -@Preview(fontScale = 2.0f) @Composable private fun TransportModeInfoPreview() { KrailTheme { diff --git a/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/components/WalkingLeg.kt b/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/components/WalkingLeg.kt index a67d4224..241c4a55 100644 --- a/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/components/WalkingLeg.kt +++ b/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/components/WalkingLeg.kt @@ -6,18 +6,17 @@ import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.size +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Add import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.ColorFilter import androidx.compose.ui.platform.LocalDensity -import androidx.compose.ui.res.painterResource -import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp -import xyz.ksharma.krail.design.system.LocalContentAlpha -import xyz.ksharma.krail.design.system.components.Text -import xyz.ksharma.krail.design.system.theme.KrailTheme -import xyz.ksharma.krail.trip.planner.ui.R +import xyz.ksharma.krail.taj.LocalContentAlpha +import xyz.ksharma.krail.taj.components.Text +import xyz.ksharma.krail.taj.theme.KrailTheme @Composable fun WalkingLeg( @@ -36,7 +35,7 @@ fun WalkingLeg( horizontalArrangement = Arrangement.spacedBy(8.dp), ) { Image( - painter = painterResource(id = R.drawable.ic_walk), + imageVector = Icons.Filled.Add,// TODO - painterResource(id = R.drawable.ic_walk), contentDescription = null, colorFilter = ColorFilter.tint( color = KrailTheme.colors.onSurface.copy(alpha = contentAlpha), @@ -47,7 +46,6 @@ fun WalkingLeg( } } -@Preview(showBackground = true) @Composable private fun PreviewWalkingLeg() { KrailTheme { diff --git a/feature/trip-planner/ui/src/commonTest/kotlin/xyz/ksharma/krail/trip/planner/ui/timetable/business/PlatformTextTest.kt b/feature/trip-planner/ui/src/commonTest/kotlin/xyz/ksharma/krail/trip/planner/ui/timetable/business/PlatformTextTest.kt index f558b460..058e2c7a 100644 --- a/feature/trip-planner/ui/src/commonTest/kotlin/xyz/ksharma/krail/trip/planner/ui/timetable/business/PlatformTextTest.kt +++ b/feature/trip-planner/ui/src/commonTest/kotlin/xyz/ksharma/krail/trip/planner/ui/timetable/business/PlatformTextTest.kt @@ -1,7 +1,5 @@ package xyz.ksharma.krail.trip.planner.ui.timetable.business -import org.junit.Assert.assertEquals -import org.junit.Test import xyz.ksharma.krail.trip.planner.network.api.model.TripResponse class PlatformTextTest { From bfddce06ec5956e54c441ce792d9cd520de7e020 Mon Sep 17 00:00:00 2001 From: Karan Sharma <55722391+ksharma-xyz@users.noreply.github.com> Date: Sun, 17 Nov 2024 15:04:15 +1100 Subject: [PATCH 28/67] Create SavedTripsViewModelFactory --- composeApp/build.gradle.kts | 2 -- feature/trip-planner/ui/build.gradle.kts | 3 ++ .../ui/savedtrips/SavedTripsDestination.kt | 29 +++++++++++++------ .../ui/savedtrips/SavedTripsViewModel.kt | 24 ++++++--------- .../planner/ui/viewmodel/ViewModelFactory.kt | 17 +++++++++++ gradle/libs.versions.toml | 3 +- 6 files changed, 51 insertions(+), 27 deletions(-) create mode 100644 feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/viewmodel/ViewModelFactory.kt diff --git a/composeApp/build.gradle.kts b/composeApp/build.gradle.kts index a2d14554..1515c8de 100644 --- a/composeApp/build.gradle.kts +++ b/composeApp/build.gradle.kts @@ -63,9 +63,7 @@ kotlin { implementation(compose.ui) implementation(compose.components.resources) implementation(compose.components.uiToolingPreview) - implementation(libs.androidx.lifecycle.viewmodel) implementation(libs.androidx.lifecycle.runtime.compose) - } } } diff --git a/feature/trip-planner/ui/build.gradle.kts b/feature/trip-planner/ui/build.gradle.kts index 6f5b1084..013c867e 100644 --- a/feature/trip-planner/ui/build.gradle.kts +++ b/feature/trip-planner/ui/build.gradle.kts @@ -24,7 +24,9 @@ kotlin { commonMain { dependencies { implementation(projects.taj) + implementation(projects.core.di) implementation(projects.feature.tripPlanner.state) + implementation(projects.core.dateTime) implementation(compose.foundation) implementation(compose.animation) @@ -35,6 +37,7 @@ kotlin { implementation(libs.kotlinx.collections.immutable) implementation(libs.kotlinx.serialization.json) implementation(libs.navigation.compose) + implementation(libs.lifecycle.viewmodel.compose) } } commonTest { diff --git a/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/savedtrips/SavedTripsDestination.kt b/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/savedtrips/SavedTripsDestination.kt index 5f47aaa6..29d33b68 100644 --- a/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/savedtrips/SavedTripsDestination.kt +++ b/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/savedtrips/SavedTripsDestination.kt @@ -5,13 +5,15 @@ import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue -import androidx.hilt.navigation.compose.hiltViewModel import androidx.lifecycle.compose.collectAsStateWithLifecycle +import androidx.lifecycle.viewmodel.compose.viewModel import androidx.navigation.NavGraphBuilder import androidx.navigation.NavHostController import androidx.navigation.NavOptions import androidx.navigation.compose.composable -import timber.log.Timber +import kotlinx.coroutines.CoroutineDispatcher +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.IO import xyz.ksharma.krail.trip.planner.ui.navigation.SavedTripsRoute import xyz.ksharma.krail.trip.planner.ui.navigation.SearchStopFieldType import xyz.ksharma.krail.trip.planner.ui.navigation.SearchStopRoute @@ -19,11 +21,12 @@ import xyz.ksharma.krail.trip.planner.ui.navigation.TimeTableRoute import xyz.ksharma.krail.trip.planner.ui.state.savedtrip.SavedTripUiEvent import xyz.ksharma.krail.trip.planner.ui.state.searchstop.model.StopItem import xyz.ksharma.krail.trip.planner.ui.state.searchstop.model.StopItem.Companion.fromJsonString +import xyz.ksharma.krail.trip.planner.ui.viewmodel.SavedTripsViewModelFactory @Suppress("LongMethod") internal fun NavGraphBuilder.savedTripsDestination(navController: NavHostController) { composable { backStackEntry -> - val viewModel = hiltViewModel() + val viewModel = viewModel { createSavedTripsViewModel() } val savedTripState by viewModel.uiState.collectAsStateWithLifecycle() val fromArg: String? = @@ -45,12 +48,12 @@ internal fun NavGraphBuilder.savedTripsDestination(navController: NavHostControl LaunchedEffect(fromArg) { fromArg?.let { fromStopItem = fromJsonString(it) } - Timber.d("Change fromStopItem: $fromStopItem") +// Timber.d("Change fromStopItem: $fromStopItem") } LaunchedEffect(toArg) { toArg?.let { toStopItem = fromJsonString(it) } - Timber.d("Change toStopItem: $toStopItem") +// Timber.d("Change toStopItem: $toStopItem") } SavedTripsScreen( @@ -58,18 +61,18 @@ internal fun NavGraphBuilder.savedTripsDestination(navController: NavHostControl fromStopItem = fromStopItem, toStopItem = toStopItem, fromButtonClick = { - Timber.d("fromButtonClick - nav: ${SearchStopRoute(fieldType = SearchStopFieldType.FROM)}") + // Timber.d("fromButtonClick - nav: ${SearchStopRoute(fieldType = SearchStopFieldType.FROM)}") navController.navigate(SearchStopRoute(fieldType = SearchStopFieldType.FROM)) }, toButtonClick = { - Timber.d("toButtonClick - nav: ${SearchStopRoute(fieldType = SearchStopFieldType.TO)}") + // Timber.d("toButtonClick - nav: ${SearchStopRoute(fieldType = SearchStopFieldType.TO)}") navController.navigate( route = SearchStopRoute(fieldType = SearchStopFieldType.TO), navOptions = NavOptions.Builder().setLaunchSingleTop(true).build(), ) }, onReverseButtonClick = { - Timber.d("onReverseButtonClick:") + // Timber.d("onReverseButtonClick:") val bufferStop = fromStopItem backStackEntry.savedStateHandle[SearchStopFieldType.FROM.key] = toStopItem?.toJsonString() @@ -102,10 +105,18 @@ internal fun NavGraphBuilder.savedTripsDestination(navController: NavHostControl ) } else { // TODO - show message - to select both stops - Timber.e("Select both stops") + // Timber.e("Select both stops") } }, onEvent = { event -> viewModel.onEvent(event) }, ) } } + +// Create an instance of ViewModelFactory with injected dependencies +fun createSavedTripsViewModel( + ioDispatcher: CoroutineDispatcher = Dispatchers.IO +): SavedTripsViewModel { + val factory = SavedTripsViewModelFactory(ioDispatcher) + return factory.create() // Instantiate the ViewModel +} diff --git a/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/savedtrips/SavedTripsViewModel.kt b/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/savedtrips/SavedTripsViewModel.kt index 5e81dd15..411f0794 100644 --- a/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/savedtrips/SavedTripsViewModel.kt +++ b/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/savedtrips/SavedTripsViewModel.kt @@ -2,45 +2,39 @@ package xyz.ksharma.krail.trip.planner.ui.savedtrips import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope -import dagger.hilt.android.lifecycle.HiltViewModel -import kotlinx.collections.immutable.toImmutableList +import kotlinx.collections.immutable.persistentListOf import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch -import timber.log.Timber import xyz.ksharma.krail.di.AppDispatchers import xyz.ksharma.krail.di.Dispatcher -import xyz.ksharma.krail.sandook.Sandook -import xyz.ksharma.krail.sandook.di.SandookFactory import xyz.ksharma.krail.trip.planner.ui.state.savedtrip.SavedTripUiEvent import xyz.ksharma.krail.trip.planner.ui.state.savedtrip.SavedTripsState import xyz.ksharma.krail.trip.planner.ui.state.timetable.Trip -import javax.inject.Inject -@HiltViewModel -class SavedTripsViewModel @Inject constructor( - sandookFactory: SandookFactory, +class SavedTripsViewModel ( +// sandookFactory: SandookFactory, @Dispatcher(AppDispatchers.IO) private val ioDispatcher: CoroutineDispatcher, ) : ViewModel() { - private val sandook: Sandook = sandookFactory.create(SandookFactory.SandookKey.SAVED_TRIP) +// private val sandook: Sandook = sandookFactory.create(SandookFactory.SandookKey.SAVED_TRIP) private val _uiState: MutableStateFlow = MutableStateFlow(SavedTripsState()) val uiState: StateFlow = _uiState private fun loadSavedTrips() { viewModelScope.launch(context = ioDispatcher) { - val trips = sandook.keys().mapNotNull { key -> + val trips = persistentListOf() /*sandook.keys().mapNotNull { key -> val tripString = sandook.getString(key, null) tripString?.let { tripJsonString -> Trip.fromJsonString(tripJsonString) } - }.toImmutableList() + }.toImmutableList()*/ trips.forEachIndexed { index, trip -> - Timber.d("\t SavedTrip: #$index ${trip.fromStopName} -> ${trip.toStopName}") + //Timber.d("\t SavedTrip: #$index ${trip.fromStopName} -> ${trip.toStopName}") } updateUiState { copy(savedTrips = trips) } @@ -55,9 +49,9 @@ class SavedTripsViewModel @Inject constructor( } private fun onDeleteSavedTrip(savedTrip: Trip) { - Timber.d("onDeleteSavedTrip: $savedTrip") +// Timber.d("onDeleteSavedTrip: $savedTrip") viewModelScope.launch(context = ioDispatcher) { - sandook.remove(key = savedTrip.tripId) +// sandook.remove(key = savedTrip.tripId) loadSavedTrips() } } diff --git a/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/viewmodel/ViewModelFactory.kt b/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/viewmodel/ViewModelFactory.kt new file mode 100644 index 00000000..5302054f --- /dev/null +++ b/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/viewmodel/ViewModelFactory.kt @@ -0,0 +1,17 @@ +package xyz.ksharma.krail.trip.planner.ui.viewmodel + +import kotlinx.coroutines.CoroutineDispatcher +import xyz.ksharma.krail.trip.planner.ui.savedtrips.SavedTripsViewModel + +interface ViewModelFactory { + fun create(): T +} + +// Factory for SavedTripsViewModel +class SavedTripsViewModelFactory( + private val ioDispatcher: CoroutineDispatcher, +) : ViewModelFactory { + override fun create(): SavedTripsViewModel { + return SavedTripsViewModel(ioDispatcher) + } +} diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index eba69ae3..9d33a679 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -15,6 +15,7 @@ kotlinInject = "0.7.2" kotlinxCollectionsImmutable = "0.3.8" kotlinxDatetime = "0.6.1" ktorClientLogging = "2.3.12" +lifecycleViewmodelCompose = "2.8.2" navigationCompose = "2.8.0-alpha10" okhttpBom = "4.12.0" kotlinxSerializationJson = "1.7.3" @@ -44,7 +45,7 @@ retrofit2-kotlinx-serialization-converter = { module = "com.jakewharton.retrofit timber = { module = "com.jakewharton.timber:timber", version.ref = "timber" } kotlinx-coroutines-core = { group = "org.jetbrains.kotlinx", name = "kotlinx-coroutines-core", version.ref = "kotlinxCoroutines" } -androidx-lifecycle-viewmodel = { group = "org.jetbrains.androidx.lifecycle", name = "lifecycle-viewmodel", version.ref = "androidx-lifecycle" } +lifecycle-viewmodel-compose = { module = "org.jetbrains.androidx.lifecycle:lifecycle-viewmodel-compose", version.ref = "lifecycleViewmodelCompose" } androidx-lifecycle-runtime-compose = { group = "org.jetbrains.androidx.lifecycle", name = "lifecycle-runtime-compose", version.ref = "androidx-lifecycle" } From c85e38185743a8dc6610560f77e829c999d5dd5b Mon Sep 17 00:00:00 2001 From: Karan Sharma <55722391+ksharma-xyz@users.noreply.github.com> Date: Sun, 17 Nov 2024 17:40:52 +1100 Subject: [PATCH 29/67] Make :state multiplatform --- feature/trip-planner/state/build.gradle.kts | 25 +++++++++++++-- .../planner/ui/state/alerts/ServiceAlert.kt | 2 +- .../ui/state/searchstop/model/StopItem.kt | 3 +- .../ui/savedtrips/SavedTripsDestination.kt | 2 +- .../planner/ui/savedtrips/SavedTripsScreen.kt | 31 ++++++------------- 5 files changed, 34 insertions(+), 29 deletions(-) diff --git a/feature/trip-planner/state/build.gradle.kts b/feature/trip-planner/state/build.gradle.kts index 56762eb5..488cc809 100644 --- a/feature/trip-planner/state/build.gradle.kts +++ b/feature/trip-planner/state/build.gradle.kts @@ -8,7 +8,26 @@ android { namespace = "xyz.ksharma.krail.trip.planner.state" } -dependencies { - implementation(libs.kotlinx.collections.immutable) - implementation(libs.kotlinx.serialization.json) +kotlin { + applyDefaultHierarchyTemplate() + + androidTarget() + + iosArm64() + iosSimulatorArm64() + + java { + toolchain { + languageVersion.set(JavaLanguageVersion.of(JavaVersion.VERSION_17.majorVersion)) + } + } + + sourceSets { + commonMain { + dependencies { + implementation(libs.kotlinx.collections.immutable) + implementation(libs.kotlinx.serialization.json) + } + } + } } diff --git a/feature/trip-planner/state/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/state/alerts/ServiceAlert.kt b/feature/trip-planner/state/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/state/alerts/ServiceAlert.kt index a6ff02b8..fbc7b7b1 100644 --- a/feature/trip-planner/state/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/state/alerts/ServiceAlert.kt +++ b/feature/trip-planner/state/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/state/alerts/ServiceAlert.kt @@ -8,7 +8,7 @@ data class ServiceAlert( val heading: String, val message: String, val priority: ServiceAlertPriority, -) : java.io.Serializable { +) { fun toJsonString() = Json.encodeToString(serializer(), this) diff --git a/feature/trip-planner/state/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/state/searchstop/model/StopItem.kt b/feature/trip-planner/state/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/state/searchstop/model/StopItem.kt index d90b3b2e..2c8a9a74 100644 --- a/feature/trip-planner/state/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/state/searchstop/model/StopItem.kt +++ b/feature/trip-planner/state/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/state/searchstop/model/StopItem.kt @@ -4,7 +4,6 @@ import kotlinx.collections.immutable.ImmutableSet import kotlinx.collections.immutable.persistentSetOf import kotlinx.serialization.json.Json import xyz.ksharma.krail.trip.planner.ui.state.TransportMode -import java.io.Serializable /** * Represents a Stop item in the search results when searching for stops. @@ -15,7 +14,7 @@ data class StopItem( val stopName: String, val transportModes: ImmutableSet = persistentSetOf(), val stopId: String, -) : Serializable { +) { fun toJsonString() = Json.encodeToString(serializer(), this) @Suppress("ConstPropertyName") diff --git a/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/savedtrips/SavedTripsDestination.kt b/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/savedtrips/SavedTripsDestination.kt index 29d33b68..9aaea775 100644 --- a/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/savedtrips/SavedTripsDestination.kt +++ b/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/savedtrips/SavedTripsDestination.kt @@ -26,7 +26,7 @@ import xyz.ksharma.krail.trip.planner.ui.viewmodel.SavedTripsViewModelFactory @Suppress("LongMethod") internal fun NavGraphBuilder.savedTripsDestination(navController: NavHostController) { composable { backStackEntry -> - val viewModel = viewModel { createSavedTripsViewModel() } + val viewModel: SavedTripsViewModel = viewModel() val savedTripState by viewModel.uiState.collectAsStateWithLifecycle() val fromArg: String? = diff --git a/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/savedtrips/SavedTripsScreen.kt b/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/savedtrips/SavedTripsScreen.kt index 4ced2e6d..b245136c 100644 --- a/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/savedtrips/SavedTripsScreen.kt +++ b/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/savedtrips/SavedTripsScreen.kt @@ -1,8 +1,5 @@ package xyz.ksharma.krail.trip.planner.ui.savedtrips -import androidx.activity.ComponentActivity -import androidx.activity.SystemBarStyle -import androidx.activity.enableEdgeToEdge import androidx.compose.foundation.background import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column @@ -15,25 +12,17 @@ import androidx.compose.foundation.layout.statusBarsPadding import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.items import androidx.compose.runtime.Composable -import androidx.compose.runtime.DisposableEffect import androidx.compose.runtime.getValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.toArgb -import androidx.compose.ui.platform.LocalContext -import androidx.compose.ui.res.stringResource -import androidx.compose.ui.tooling.preview.PreviewLightDark import androidx.compose.ui.unit.dp -import xyz.ksharma.krail.design.system.LocalThemeContentColor -import xyz.ksharma.krail.design.system.components.Text -import xyz.ksharma.krail.design.system.components.TitleBar -import xyz.ksharma.krail.design.system.theme.KrailTheme -import xyz.ksharma.krail.trip.planner.ui.R +import xyz.ksharma.krail.taj.LocalThemeContentColor +import xyz.ksharma.krail.taj.components.Text +import xyz.ksharma.krail.taj.components.TitleBar +import xyz.ksharma.krail.taj.theme.KrailTheme import xyz.ksharma.krail.trip.planner.ui.components.ErrorMessage import xyz.ksharma.krail.trip.planner.ui.components.SavedTripCard import xyz.ksharma.krail.trip.planner.ui.components.SearchStopRow -import xyz.ksharma.krail.trip.planner.ui.components.hexToComposeColor -import xyz.ksharma.krail.trip.planner.ui.getActivityOrNull import xyz.ksharma.krail.trip.planner.ui.state.savedtrip.SavedTripUiEvent import xyz.ksharma.krail.trip.planner.ui.state.savedtrip.SavedTripsState import xyz.ksharma.krail.trip.planner.ui.state.searchstop.model.StopItem @@ -51,8 +40,8 @@ fun SavedTripsScreen( onEvent: (SavedTripUiEvent) -> Unit = {}, ) { val themeContentColor by LocalThemeContentColor.current - val context = LocalContext.current - DisposableEffect(themeContentColor) { + // TODO - handle colors of status bar +/* DisposableEffect(themeContentColor) { context.getActivityOrNull()?.let { activity -> (activity as ComponentActivity).enableEdgeToEdge( navigationBarStyle = SystemBarStyle.auto( @@ -62,7 +51,7 @@ fun SavedTripsScreen( ) } onDispose {} - } + }*/ Box( modifier = modifier @@ -72,7 +61,7 @@ fun SavedTripsScreen( ) { Column { TitleBar(title = { - Text(text = stringResource(R.string.saved_trips_screen_title)) + Text(text = "Saved Trips") }) LazyColumn( @@ -116,8 +105,7 @@ fun SavedTripsScreen( }, primaryTransportMode = null, // TODO modifier = Modifier - .padding(horizontal = 16.dp) - .animateItem(), + .padding(horizontal = 16.dp), ) Spacer(modifier = Modifier.height(12.dp)) @@ -140,7 +128,6 @@ fun SavedTripsScreen( // region Previews -@PreviewLightDark @Composable private fun SavedTripsScreenPreview() { KrailTheme { From 1ba4c28b9c03a64470d2c69da883acee6eec0e5f Mon Sep 17 00:00:00 2001 From: Karan Sharma <55722391+ksharma-xyz@users.noreply.github.com> Date: Sun, 17 Nov 2024 17:48:47 +1100 Subject: [PATCH 30/67] Make searchstop multiplatform --- .../network/real/ratelimit/APIRateLimiter.kt | 2 +- .../ui/searchstop/SearchStopDestination.kt | 3 +- .../planner/ui/searchstop/SearchStopScreen.kt | 30 +++++++------------ .../ui/searchstop/SearchStopViewModel.kt | 6 +--- .../ui/timetable/TimeTableDestination.kt | 2 +- .../planner/ui/timetable/TimeTableScreen.kt | 14 ++++----- .../ui/timetable/TimeTableViewModel.kt | 2 +- .../timetable/business/TripResponseMapper.kt | 2 +- .../ui/usualride/UsualRideDestination.kt | 6 ++-- .../planner/ui/usualride/UsualRideScreen.kt | 4 +-- .../ui/usualride/UsualRideViewModel.kt | 2 +- 11 files changed, 30 insertions(+), 43 deletions(-) diff --git a/feature/trip-planner/network/real/src/main/kotlin/xyz/ksharma/krail/trip/planner/network/real/ratelimit/APIRateLimiter.kt b/feature/trip-planner/network/real/src/main/kotlin/xyz/ksharma/krail/trip/planner/network/real/ratelimit/APIRateLimiter.kt index b8403aa6..b3a38e6f 100644 --- a/feature/trip-planner/network/real/src/main/kotlin/xyz/ksharma/krail/trip/planner/network/real/ratelimit/APIRateLimiter.kt +++ b/feature/trip-planner/network/real/src/main/kotlin/xyz/ksharma/krail/trip/planner/network/real/ratelimit/APIRateLimiter.kt @@ -7,7 +7,7 @@ import kotlinx.coroutines.flow.debounce import kotlinx.coroutines.flow.flatMapLatest import kotlinx.coroutines.flow.flow import kotlinx.coroutines.flow.update -import timber.log.Timber + import xyz.ksharma.krail.trip.planner.network.api.RateLimiter import javax.inject.Inject import kotlin.time.Duration.Companion.milliseconds diff --git a/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/searchstop/SearchStopDestination.kt b/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/searchstop/SearchStopDestination.kt index 774cbbed..f8bcc4b7 100644 --- a/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/searchstop/SearchStopDestination.kt +++ b/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/searchstop/SearchStopDestination.kt @@ -7,7 +7,6 @@ import androidx.navigation.NavGraphBuilder import androidx.navigation.NavHostController import androidx.navigation.compose.composable import androidx.navigation.toRoute -import timber.log.Timber import xyz.ksharma.krail.trip.planner.ui.navigation.SearchStopRoute fun NavGraphBuilder.searchStopDestination(navController: NavHostController) { @@ -19,7 +18,7 @@ fun NavGraphBuilder.searchStopDestination(navController: NavHostController) { SearchStopScreen( searchStopState = searchStopState, onStopSelect = { stopItem -> - Timber.d("onStopSelected: fieldTypeKey=${route.fieldType.key} and stopItem: $stopItem") + //Timber.d("onStopSelected: fieldTypeKey=${route.fieldType.key} and stopItem: $stopItem") navController.previousBackStackEntry?.savedStateHandle?.set( route.fieldType.key, diff --git a/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/searchstop/SearchStopScreen.kt b/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/searchstop/SearchStopScreen.kt index bffc7bbb..ca05c977 100644 --- a/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/searchstop/SearchStopScreen.kt +++ b/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/searchstop/SearchStopScreen.kt @@ -25,22 +25,22 @@ import androidx.compose.ui.focus.FocusRequester import androidx.compose.ui.focus.focusRequester import androidx.compose.ui.graphics.Brush import androidx.compose.ui.platform.LocalSoftwareKeyboardController -import androidx.compose.ui.tooling.preview.Preview -import androidx.compose.ui.tooling.preview.PreviewLightDark import androidx.compose.ui.unit.dp import kotlinx.collections.immutable.persistentListOf import kotlinx.collections.immutable.toImmutableSet +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.FlowPreview import kotlinx.coroutines.delay import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.flow.debounce import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.filter import kotlinx.coroutines.flow.mapLatest -import timber.log.Timber -import xyz.ksharma.krail.design.system.LocalThemeColor -import xyz.ksharma.krail.design.system.components.Divider -import xyz.ksharma.krail.design.system.components.TextField -import xyz.ksharma.krail.design.system.theme.KrailTheme +import kotlinx.datetime.Clock +import xyz.ksharma.krail.taj.LocalThemeColor +import xyz.ksharma.krail.taj.components.Divider +import xyz.ksharma.krail.taj.components.TextField +import xyz.ksharma.krail.taj.theme.KrailTheme import xyz.ksharma.krail.trip.planner.ui.components.ErrorMessage import xyz.ksharma.krail.trip.planner.ui.components.StopSearchListItem import xyz.ksharma.krail.trip.planner.ui.components.backgroundColorOf @@ -50,6 +50,7 @@ import xyz.ksharma.krail.trip.planner.ui.state.searchstop.SearchStopState import xyz.ksharma.krail.trip.planner.ui.state.searchstop.SearchStopUiEvent import xyz.ksharma.krail.trip.planner.ui.state.searchstop.model.StopItem +@OptIn(FlowPreview::class, ExperimentalCoroutinesApi::class) @Composable fun SearchStopScreen( searchStopState: SearchStopState, @@ -76,7 +77,7 @@ fun SearchStopScreen( .debounce(250) .filter { it.isNotBlank() } .mapLatest { text -> - Timber.d("Query - $text") + // Timber.d("Query - $text") onEvent(SearchStopUiEvent.SearchTextChanged(text)) }.collectLatest {} } @@ -93,7 +94,7 @@ fun SearchStopScreen( // track the time of the last query. If new results come in during the delay period, // then lastQueryTime will be different, therefore, it will prevent // "No match found" message from being displayed. - val currentQueryTime = System.currentTimeMillis() + val currentQueryTime = Clock.System.now().toEpochMilliseconds() lastQueryTime = currentQueryTime delay(1000) if (lastQueryTime == currentQueryTime && searchStopState.stops.isEmpty()) { @@ -130,7 +131,7 @@ fun SearchStopScreen( input.filter { it.isLetterOrDigit() || it.isWhitespace() } }, ) { value -> - Timber.d("value: $value") + //Timber.d("value: $value") textFieldText = value.toString() } @@ -188,7 +189,6 @@ fun SearchStopScreen( // region Previews -@Preview @Composable private fun PreviewSearchStopScreenLoading() { KrailTheme { @@ -202,7 +202,6 @@ private fun PreviewSearchStopScreenLoading() { } } -@Preview @Composable private fun PreviewSearchStopScreenError() { KrailTheme { @@ -216,7 +215,6 @@ private fun PreviewSearchStopScreenError() { } } -@Preview @Composable private fun PreviewSearchStopScreenEmpty() { KrailTheme { @@ -234,7 +232,6 @@ private fun PreviewSearchStopScreenEmpty() { } } -@PreviewLightDark @Composable private fun PreviewSearchStopScreenTrain() { KrailTheme { @@ -248,7 +245,6 @@ private fun PreviewSearchStopScreenTrain() { } } -@PreviewLightDark @Composable private fun PreviewSearchStopScreenCoach() { KrailTheme { @@ -262,7 +258,6 @@ private fun PreviewSearchStopScreenCoach() { } } -@PreviewLightDark @Composable private fun PreviewSearchStopScreenFerry() { KrailTheme { @@ -276,7 +271,6 @@ private fun PreviewSearchStopScreenFerry() { } } -@PreviewLightDark @Composable private fun PreviewSearchStopScreenMetro() { KrailTheme { @@ -290,7 +284,6 @@ private fun PreviewSearchStopScreenMetro() { } } -@PreviewLightDark @Composable private fun PreviewSearchStopScreenLightRail() { KrailTheme { @@ -304,7 +297,6 @@ private fun PreviewSearchStopScreenLightRail() { } } -@PreviewLightDark @Composable private fun PreviewSearchStopScreenBus() { KrailTheme { diff --git a/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/searchstop/SearchStopViewModel.kt b/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/searchstop/SearchStopViewModel.kt index 2861edb0..6fc3cf30 100644 --- a/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/searchstop/SearchStopViewModel.kt +++ b/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/searchstop/SearchStopViewModel.kt @@ -2,22 +2,18 @@ package xyz.ksharma.krail.trip.planner.ui.searchstop import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope -import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.collections.immutable.persistentListOf import kotlinx.collections.immutable.toImmutableList import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch -import timber.log.Timber import xyz.ksharma.krail.trip.planner.network.api.model.StopFinderResponse import xyz.ksharma.krail.trip.planner.network.api.repository.TripPlanningRepository import xyz.ksharma.krail.trip.planner.ui.searchstop.StopResultMapper.toStopResults import xyz.ksharma.krail.trip.planner.ui.state.searchstop.SearchStopState import xyz.ksharma.krail.trip.planner.ui.state.searchstop.SearchStopUiEvent -import javax.inject.Inject -@HiltViewModel class SearchStopViewModel @Inject constructor( private val tripPlanningRepository: TripPlanningRepository, ) : ViewModel() { @@ -32,7 +28,7 @@ class SearchStopViewModel @Inject constructor( } private fun onSearchTextChanged(query: String) { - Timber.d("onSearchTextChanged: $query") + //Timber.d("onSearchTextChanged: $query") updateUiState { displayLoading() } viewModelScope.launch { diff --git a/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/timetable/TimeTableDestination.kt b/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/timetable/TimeTableDestination.kt index dbefc756..f29fef5b 100644 --- a/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/timetable/TimeTableDestination.kt +++ b/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/timetable/TimeTableDestination.kt @@ -8,7 +8,7 @@ import androidx.navigation.NavHostController import androidx.navigation.NavOptions import androidx.navigation.compose.composable import androidx.navigation.toRoute -import timber.log.Timber + import xyz.ksharma.krail.trip.planner.ui.navigation.ServiceAlertRoute import xyz.ksharma.krail.trip.planner.ui.navigation.TimeTableRoute import xyz.ksharma.krail.trip.planner.ui.state.timetable.TimeTableUiEvent diff --git a/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/timetable/TimeTableScreen.kt b/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/timetable/TimeTableScreen.kt index fc1e723a..823c989b 100644 --- a/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/timetable/TimeTableScreen.kt +++ b/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/timetable/TimeTableScreen.kt @@ -45,12 +45,12 @@ import androidx.compose.ui.unit.dp import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.persistentListOf import kotlinx.collections.immutable.toImmutableList -import xyz.ksharma.krail.design.system.LocalThemeColor -import xyz.ksharma.krail.design.system.LocalThemeContentColor -import xyz.ksharma.krail.design.system.components.Text -import xyz.ksharma.krail.design.system.components.TitleBar -import xyz.ksharma.krail.design.system.theme.KrailTheme -import xyz.ksharma.krail.design.system.theme.shouldUseDarkIcons +import xyz.ksharma.krail.taj.LocalThemeColor +import xyz.ksharma.krail.taj.LocalThemeContentColor +import xyz.ksharma.krail.taj.components.Text +import xyz.ksharma.krail.taj.components.TitleBar +import xyz.ksharma.krail.taj.theme.KrailTheme +import xyz.ksharma.krail.taj.theme.shouldUseDarkIcons import xyz.ksharma.krail.trip.planner.ui.R import xyz.ksharma.krail.trip.planner.ui.components.ActionData import xyz.ksharma.krail.trip.planner.ui.components.ErrorMessage @@ -65,7 +65,7 @@ import xyz.ksharma.krail.trip.planner.ui.state.TransportModeLine import xyz.ksharma.krail.trip.planner.ui.state.timetable.TimeTableState import xyz.ksharma.krail.trip.planner.ui.state.timetable.TimeTableUiEvent import xyz.ksharma.krail.trip.planner.ui.state.timetable.Trip -import xyz.ksharma.krail.design.system.R as DesignSystemR +import xyz.ksharma.krail.taj.R as DesignSystemR @Composable fun TimeTableScreen( diff --git a/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/timetable/TimeTableViewModel.kt b/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/timetable/TimeTableViewModel.kt index 29faab37..c41ad573 100644 --- a/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/timetable/TimeTableViewModel.kt +++ b/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/timetable/TimeTableViewModel.kt @@ -17,7 +17,7 @@ import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch import kotlinx.coroutines.withContext -import timber.log.Timber + import xyz.ksharma.krail.core.datetime.DateTimeHelper.calculateTimeDifferenceFromNow import xyz.ksharma.krail.core.datetime.DateTimeHelper.toGenericFormattedTimeString import xyz.ksharma.krail.di.AppDispatchers diff --git a/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/timetable/business/TripResponseMapper.kt b/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/timetable/business/TripResponseMapper.kt index 8e7099c8..ab51ffad 100644 --- a/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/timetable/business/TripResponseMapper.kt +++ b/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/timetable/business/TripResponseMapper.kt @@ -2,7 +2,7 @@ package xyz.ksharma.krail.trip.planner.ui.timetable.business import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.toImmutableList -import timber.log.Timber + import xyz.ksharma.krail.core.datetime.DateTimeHelper.aestToHHMM import xyz.ksharma.krail.core.datetime.DateTimeHelper.calculateTimeDifference import xyz.ksharma.krail.core.datetime.DateTimeHelper.calculateTimeDifferenceFromNow diff --git a/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/usualride/UsualRideDestination.kt b/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/usualride/UsualRideDestination.kt index be366eb2..14f0500b 100644 --- a/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/usualride/UsualRideDestination.kt +++ b/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/usualride/UsualRideDestination.kt @@ -10,9 +10,9 @@ import androidx.navigation.NavHostController import androidx.navigation.NavOptions import androidx.navigation.compose.composable import kotlinx.collections.immutable.toImmutableSet -import xyz.ksharma.krail.design.system.LocalThemeColor -import xyz.ksharma.krail.design.system.LocalThemeContentColor -import xyz.ksharma.krail.design.system.theme.getForegroundColor +import xyz.ksharma.krail.taj.LocalThemeColor +import xyz.ksharma.krail.taj.LocalThemeContentColor +import xyz.ksharma.krail.taj.theme.getForegroundColor import xyz.ksharma.krail.trip.planner.ui.components.hexToComposeColor import xyz.ksharma.krail.trip.planner.ui.components.toHex import xyz.ksharma.krail.trip.planner.ui.navigation.SavedTripsRoute diff --git a/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/usualride/UsualRideScreen.kt b/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/usualride/UsualRideScreen.kt index 351b91e9..a95dd0c8 100644 --- a/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/usualride/UsualRideScreen.kt +++ b/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/usualride/UsualRideScreen.kt @@ -36,8 +36,8 @@ import androidx.compose.ui.unit.sp import kotlinx.collections.immutable.ImmutableSet import kotlinx.collections.immutable.toImmutableList import kotlinx.collections.immutable.toImmutableSet -import xyz.ksharma.krail.design.system.components.Text -import xyz.ksharma.krail.design.system.theme.KrailTheme +import xyz.ksharma.krail.taj.components.Text +import xyz.ksharma.krail.taj.theme.KrailTheme import xyz.ksharma.krail.trip.planner.ui.components.TransportModeIcon import xyz.ksharma.krail.trip.planner.ui.components.hexToComposeColor import xyz.ksharma.krail.trip.planner.ui.components.transportModeBackgroundColor diff --git a/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/usualride/UsualRideViewModel.kt b/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/usualride/UsualRideViewModel.kt index f9c988e5..71950edd 100644 --- a/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/usualride/UsualRideViewModel.kt +++ b/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/usualride/UsualRideViewModel.kt @@ -7,7 +7,7 @@ import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.launch -import timber.log.Timber + import xyz.ksharma.krail.di.AppDispatchers import xyz.ksharma.krail.di.Dispatcher import xyz.ksharma.krail.sandook.Sandook From 3ccd0dd331d0911784e3d09452a45e55ce70cef9 Mon Sep 17 00:00:00 2001 From: Karan Sharma <55722391+ksharma-xyz@users.noreply.github.com> Date: Sun, 17 Nov 2024 17:59:39 +1100 Subject: [PATCH 31/67] Make timetable multiplatform --- feature/trip-planner/ui/build.gradle.kts | 16 ++++++++ .../ui/searchstop/SearchStopDestination.kt | 4 +- .../ui/timetable/TimeTableDestination.kt | 4 +- .../planner/ui/timetable/TimeTableScreen.kt | 41 ++++++------------- .../ui/timetable/TimeTableViewModel.kt | 34 +++++++-------- .../ui/timetable/TimeTableViewModelFactory.kt | 18 ++++++++ .../timetable/business/TripResponseMapper.kt | 8 ++-- 7 files changed, 70 insertions(+), 55 deletions(-) create mode 100644 feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/timetable/TimeTableViewModelFactory.kt diff --git a/feature/trip-planner/ui/build.gradle.kts b/feature/trip-planner/ui/build.gradle.kts index 013c867e..27b1550e 100644 --- a/feature/trip-planner/ui/build.gradle.kts +++ b/feature/trip-planner/ui/build.gradle.kts @@ -4,6 +4,7 @@ plugins { alias(libs.plugins.compose.compiler) alias(libs.plugins.krail.android.library) alias(libs.plugins.kotlin.serialization) + alias(libs.plugins.ksp) } kotlin { @@ -38,6 +39,8 @@ kotlin { implementation(libs.kotlinx.serialization.json) implementation(libs.navigation.compose) implementation(libs.lifecycle.viewmodel.compose) + + implementation(libs.di.kotlinInjectRuntime) } } commonTest { @@ -52,6 +55,19 @@ android { namespace = "xyz.ksharma.krail.trip.planner.ui" } + +dependencies { + // 1. Configure code generation into the common source set + kspCommonMainMetadata(libs.di.kotlinInjectRuntime) + + // 2. Configure code generation into each KMP target source set + //kspAndroid(libs.di.kotlinInjectCompilerKsp) + // kspIosX64("me.tatarka.inject:kotlin-inject-compiler-ksp:0.7.2") + // kspIosArm64("me.tatarka.inject:kotlin-inject-compiler-ksp:0.7.2") + // kspIosSimulatorArm64("me.tatarka.inject:kotlin-inject-compiler-ksp:0.7.2") +} + + /* dependencies { implementation(projects.core.dateTime) diff --git a/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/searchstop/SearchStopDestination.kt b/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/searchstop/SearchStopDestination.kt index f8bcc4b7..944081b7 100644 --- a/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/searchstop/SearchStopDestination.kt +++ b/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/searchstop/SearchStopDestination.kt @@ -1,8 +1,8 @@ package xyz.ksharma.krail.trip.planner.ui.searchstop import androidx.compose.runtime.getValue -import androidx.hilt.navigation.compose.hiltViewModel import androidx.lifecycle.compose.collectAsStateWithLifecycle +import androidx.lifecycle.viewmodel.compose.viewModel import androidx.navigation.NavGraphBuilder import androidx.navigation.NavHostController import androidx.navigation.compose.composable @@ -11,7 +11,7 @@ import xyz.ksharma.krail.trip.planner.ui.navigation.SearchStopRoute fun NavGraphBuilder.searchStopDestination(navController: NavHostController) { composable { backStackEntry -> - val viewModel = hiltViewModel() + val viewModel: SearchStopViewModel = viewModel() val searchStopState by viewModel.uiState.collectAsStateWithLifecycle() val route: SearchStopRoute = backStackEntry.toRoute() diff --git a/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/timetable/TimeTableDestination.kt b/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/timetable/TimeTableDestination.kt index f29fef5b..9e248847 100644 --- a/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/timetable/TimeTableDestination.kt +++ b/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/timetable/TimeTableDestination.kt @@ -1,8 +1,8 @@ package xyz.ksharma.krail.trip.planner.ui.timetable import androidx.compose.runtime.getValue -import androidx.hilt.navigation.compose.hiltViewModel import androidx.lifecycle.compose.collectAsStateWithLifecycle +import androidx.lifecycle.viewmodel.compose.viewModel import androidx.navigation.NavGraphBuilder import androidx.navigation.NavHostController import androidx.navigation.NavOptions @@ -16,7 +16,7 @@ import xyz.ksharma.krail.trip.planner.ui.state.timetable.Trip internal fun NavGraphBuilder.timeTableDestination(navController: NavHostController) { composable { backStackEntry -> - val viewModel = hiltViewModel() + val viewModel: TimeTableViewModel = viewModel() val timeTableState by viewModel.uiState.collectAsStateWithLifecycle() val route: TimeTableRoute = backStackEntry.toRoute() val isLoading by viewModel.isLoading.collectAsStateWithLifecycle() diff --git a/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/timetable/TimeTableScreen.kt b/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/timetable/TimeTableScreen.kt index 823c989b..1f5642c8 100644 --- a/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/timetable/TimeTableScreen.kt +++ b/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/timetable/TimeTableScreen.kt @@ -1,8 +1,5 @@ package xyz.ksharma.krail.trip.planner.ui.timetable -import androidx.activity.ComponentActivity -import androidx.activity.SystemBarStyle -import androidx.activity.enableEdgeToEdge import androidx.compose.foundation.Image import androidx.compose.foundation.background import androidx.compose.foundation.clickable @@ -21,9 +18,12 @@ import androidx.compose.foundation.lazy.items import androidx.compose.foundation.shape.CircleShape import androidx.compose.material.icons.Icons import androidx.compose.material.icons.automirrored.filled.ArrowBack +import androidx.compose.material.icons.filled.Edit +import androidx.compose.material.icons.filled.Star +import androidx.compose.material.icons.outlined.Star +>>>>>>> 142d9d4b (Make timetable multiplatform) import androidx.compose.runtime.Composable import androidx.compose.runtime.CompositionLocalProvider -import androidx.compose.runtime.DisposableEffect import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember @@ -31,16 +31,10 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.ColorFilter -import androidx.compose.ui.graphics.toArgb -import androidx.compose.ui.graphics.vector.ImageVector -import androidx.compose.ui.platform.LocalContext -import androidx.compose.ui.res.stringResource -import androidx.compose.ui.res.vectorResource import androidx.compose.ui.semantics.Role import androidx.compose.ui.semantics.contentDescription import androidx.compose.ui.semantics.semantics import androidx.compose.ui.text.style.TextAlign -import androidx.compose.ui.tooling.preview.PreviewLightDark import androidx.compose.ui.unit.dp import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.persistentListOf @@ -51,7 +45,6 @@ import xyz.ksharma.krail.taj.components.Text import xyz.ksharma.krail.taj.components.TitleBar import xyz.ksharma.krail.taj.theme.KrailTheme import xyz.ksharma.krail.taj.theme.shouldUseDarkIcons -import xyz.ksharma.krail.trip.planner.ui.R import xyz.ksharma.krail.trip.planner.ui.components.ActionData import xyz.ksharma.krail.trip.planner.ui.components.ErrorMessage import xyz.ksharma.krail.trip.planner.ui.components.JourneyCard @@ -59,13 +52,11 @@ import xyz.ksharma.krail.trip.planner.ui.components.JourneyCardState import xyz.ksharma.krail.trip.planner.ui.components.OriginDestination import xyz.ksharma.krail.trip.planner.ui.components.hexToComposeColor import xyz.ksharma.krail.trip.planner.ui.components.loading.LoadingEmojiAnim -import xyz.ksharma.krail.trip.planner.ui.getActivityOrNull import xyz.ksharma.krail.trip.planner.ui.state.TransportMode import xyz.ksharma.krail.trip.planner.ui.state.TransportModeLine import xyz.ksharma.krail.trip.planner.ui.state.timetable.TimeTableState import xyz.ksharma.krail.trip.planner.ui.state.timetable.TimeTableUiEvent import xyz.ksharma.krail.trip.planner.ui.state.timetable.Trip -import xyz.ksharma.krail.taj.R as DesignSystemR @Composable fun TimeTableScreen( @@ -80,9 +71,8 @@ fun TimeTableScreen( val themeContentColorHex by LocalThemeContentColor.current val themeColor by remember { mutableStateOf(themeColorHex.hexToComposeColor()) } val themeContentColor by remember { mutableStateOf(themeContentColorHex.hexToComposeColor()) } - val context = LocalContext.current val darkIcons = shouldUseDarkIcons(themeColor) - DisposableEffect(darkIcons) { + /*DisposableEffect(darkIcons) { context.getActivityOrNull()?.let { activity -> (activity as ComponentActivity).enableEdgeToEdge( statusBarStyle = SystemBarStyle.auto( @@ -96,7 +86,7 @@ fun TimeTableScreen( ) } onDispose {} - } + }*///TODO - Fix status bar colors Column( modifier = modifier @@ -124,7 +114,7 @@ fun TimeTableScreen( }, title = { Text( - text = stringResource(R.string.time_table_screen_title), + text = "TimeTable", color = themeContentColor, ) }, @@ -136,7 +126,7 @@ fun TimeTableScreen( contentDescription = "Reverse Trip Search", ) { Image( - imageVector = ImageVector.vectorResource(R.drawable.ic_reverse), + imageVector = Icons.Filled.Edit,// TODO - icons ImageVector.vectorResource(R.drawable.ic_reverse), contentDescription = null, colorFilter = ColorFilter.tint(themeContentColor), modifier = Modifier.size(24.dp), @@ -151,13 +141,11 @@ fun TimeTableScreen( }, ) { Image( - imageVector = ImageVector.vectorResource( - if (timeTableState.isTripSaved) { - DesignSystemR.drawable.star_filled - } else { - DesignSystemR.drawable.star_outline - }, - ), + imageVector = if (timeTableState.isTripSaved) { + Icons.Filled.Star + } else { + Icons.Outlined.Star + }, contentDescription = null, colorFilter = ColorFilter.tint(themeContentColor), modifier = Modifier.size(24.dp), @@ -336,7 +324,6 @@ fun ActionButton( // region Preview -@PreviewLightDark @Composable private fun PreviewTimeTableScreen() { KrailTheme { @@ -379,7 +366,6 @@ private fun PreviewTimeTableScreen() { } } -@PreviewLightDark @Composable private fun PreviewTimeTableScreenError() { KrailTheme { @@ -405,7 +391,6 @@ private fun PreviewTimeTableScreenError() { } } -@PreviewLightDark @Composable private fun PreviewTimeTableScreenNoResults() { KrailTheme { diff --git a/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/timetable/TimeTableViewModel.kt b/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/timetable/TimeTableViewModel.kt index c41ad573..66114b0f 100644 --- a/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/timetable/TimeTableViewModel.kt +++ b/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/timetable/TimeTableViewModel.kt @@ -2,7 +2,6 @@ package xyz.ksharma.krail.trip.planner.ui.timetable import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope -import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.collections.immutable.persistentListOf import kotlinx.collections.immutable.toImmutableList import kotlinx.coroutines.CoroutineDispatcher @@ -17,7 +16,6 @@ import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch import kotlinx.coroutines.withContext - import xyz.ksharma.krail.core.datetime.DateTimeHelper.calculateTimeDifferenceFromNow import xyz.ksharma.krail.core.datetime.DateTimeHelper.toGenericFormattedTimeString import xyz.ksharma.krail.di.AppDispatchers @@ -33,11 +31,9 @@ import xyz.ksharma.krail.trip.planner.ui.state.timetable.TimeTableUiEvent import xyz.ksharma.krail.trip.planner.ui.state.timetable.Trip import xyz.ksharma.krail.trip.planner.ui.timetable.business.buildJourneyList import xyz.ksharma.krail.trip.planner.ui.timetable.business.logForUnderstandingData -import javax.inject.Inject import kotlin.time.Duration.Companion.seconds -@HiltViewModel -class TimeTableViewModel @Inject constructor( +class TimeTableViewModel ( private val tripRepository: TripPlanningRepository, sandookFactory: SandookFactory, @Dispatcher(AppDispatchers.IO) private val ioDispatcher: CoroutineDispatcher, @@ -55,7 +51,7 @@ class TimeTableViewModel @Inject constructor( // to background and come back up again, the API call will be made. // Probably good to have data up to date. .onStart { - Timber.d("onStart: Fetching Trip") + // Timber.d("onStart: Fetching Trip") fetchTrip() }.stateIn(viewModelScope, SharingStarted.WhileSubscribed(ANR_TIMEOUT), true) @@ -89,17 +85,17 @@ class TimeTableViewModel @Inject constructor( } private fun fetchTrip() { - Timber.d("fetchTrip API Call") + // Timber.d("fetchTrip API Call") viewModelScope.launch(ioDispatcher) { // TODO - silent refresh here, UI to display loading but silent one. rateLimiter.rateLimitFlow { - Timber.d("rateLimitFlow block") + // Timber.d("rateLimitFlow block") loadTrip() }.catch { e -> - Timber.e("Error while fetching trip: $e") + println("Error while fetching trip: $e") }.collectLatest { result -> result.onSuccess { response -> - Timber.d("Success API response") + // Timber.d("Success API response") updateUiState { copy( isLoading = false, @@ -109,7 +105,7 @@ class TimeTableViewModel @Inject constructor( } response.logForUnderstandingData() }.onFailure { - Timber.e("Error while fetching trip: $it") + // Timber.e("Error while fetching trip: $it") updateUiState { copy(isLoading = false, isError = true) } } } @@ -117,7 +113,7 @@ class TimeTableViewModel @Inject constructor( } private suspend fun loadTrip(): Result = withContext(ioDispatcher) { - Timber.d("loadTrip API Call") + // Timber.d("loadTrip API Call") require( tripInfo != null && tripInfo!!.fromStopId.isNotEmpty() && tripInfo!!.toStopId.isNotEmpty(), ) { "Trip Info is null or empty" } @@ -129,20 +125,20 @@ class TimeTableViewModel @Inject constructor( } private fun onSaveTripButtonClicked() { - Timber.d("Save Trip Button Clicked") + //Timber.d("Save Trip Button Clicked") viewModelScope.launch(ioDispatcher) { tripInfo?.let { trip -> - Timber.d("Toggle Save Trip: $trip") + // Timber.d("Toggle Save Trip: $trip") val savedTrip = sandook.getString(key = trip.tripId) if (savedTrip != null) { // Trip is already saved, so delete it sandook.remove(key = trip.tripId) - Timber.d("Deleted Trip (Pref): ${Trip.fromJsonString(savedTrip)}") + // Timber.d("Deleted Trip (Pref): ${Trip.fromJsonString(savedTrip)}") updateUiState { copy(isTripSaved = false) } } else { // Trip is not saved, so save it sandook.putString(key = trip.tripId, value = trip.toJsonString()) - Timber.d("Saved Trip (Pref): $trip") + // Timber.d("Saved Trip (Pref): $trip") updateUiState { copy(isTripSaved = true) } } } @@ -150,12 +146,12 @@ class TimeTableViewModel @Inject constructor( } private fun onJourneyCardClicked(journeyId: String) { - Timber.d("Journey Card Clicked(JourneyId): $journeyId") + //Timber.d("Journey Card Clicked(JourneyId): $journeyId") _expandedJourneyId.update { if (it == journeyId) null else journeyId } } private fun onLoadTimeTable(trip: Trip) { - Timber.d("onLoadTimeTable -- Trigger fromStopItem: ${trip.fromStopId}, toStopItem: ${trip.toStopId}") + //Timber.d("onLoadTimeTable -- Trigger fromStopItem: ${trip.fromStopId}, toStopItem: ${trip.toStopId}") tripInfo = trip val savedTrip = sandook.getString(key = trip.tripId) updateUiState { @@ -170,7 +166,7 @@ class TimeTableViewModel @Inject constructor( } private fun onReverseTripButtonClicked() { - Timber.d("Reverse Trip Button Clicked -- Trigger") + // Timber.d("Reverse Trip Button Clicked -- Trigger") require(tripInfo != null) { "Trip Info is null" } val reverseTrip = Trip( fromStopId = tripInfo!!.toStopId, diff --git a/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/timetable/TimeTableViewModelFactory.kt b/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/timetable/TimeTableViewModelFactory.kt new file mode 100644 index 00000000..1e9d8dae --- /dev/null +++ b/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/timetable/TimeTableViewModelFactory.kt @@ -0,0 +1,18 @@ +package xyz.ksharma.krail.trip.planner.ui.timetable + +import kotlinx.coroutines.CoroutineDispatcher +import me.tatarka.inject.annotations.Inject +import xyz.ksharma.krail.di.AppDispatchers +import xyz.ksharma.krail.di.Dispatcher + +@Inject +class TimeTableViewModelFactory( + private val tripRepository: TripPlanningRepository, + private val sandookFactory: SandookFactory, + @Dispatcher(AppDispatchers.IO) private val ioDispatcher: CoroutineDispatcher, + private val rateLimiter: RateLimiter +) { + fun create(): TimeTableViewModel { + return TimeTableViewModel(tripRepository, sandookFactory, ioDispatcher, rateLimiter) + } +} diff --git a/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/timetable/business/TripResponseMapper.kt b/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/timetable/business/TripResponseMapper.kt index ab51ffad..548d2dc6 100644 --- a/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/timetable/business/TripResponseMapper.kt +++ b/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/timetable/business/TripResponseMapper.kt @@ -68,7 +68,7 @@ internal fun TripResponse.buildJourneyList(): ImmutableList leg.infos.orEmpty() }.toSet().size, ).also { - Timber.d("\tJourneyId: ${it.journeyId}") + //Timber.d("\tJourneyId: ${it.journeyId}") } } else { null @@ -137,7 +137,7 @@ private fun List.getTransportModeLines() = mapNotNull { leg -> private fun List.getLegsList() = mapNotNull { it.toUiModel() }.toImmutableList() private fun String.getTimeText() = let { - Timber.d("originTime: $it :- ${calculateTimeDifferenceFromNow(utcDateString = it)}") + // Timber.d("originTime: $it :- ${calculateTimeDifferenceFromNow(utcDateString = it)}") calculateTimeDifferenceFromNow(utcDateString = it).toGenericFormattedTimeString() } @@ -161,9 +161,9 @@ private fun TripResponse.Leg.toUiModel(): TimeTableState.JourneyCardInfo.Leg? { val stops = stopSequence?.mapNotNull { it.toUiModel() }?.toImmutableList() val alerts = infos?.mapNotNull { it.toAlert() }?.toImmutableList() alerts?.forEach { - Timber.d("\t Alert: ${it.heading.take(5)}, ${it.message.take(5)}, ${it.priority}") + //Timber.d("\t Alert: ${it.heading.take(5)}, ${it.message.take(5)}, ${it.priority}") } - Timber.d("Alert---") + // Timber.d("Alert---") return when { // Walking Leg - Always check before public transport leg From 8df008dfc3de49e63c8cf06aa052be36ae7db0e8 Mon Sep 17 00:00:00 2001 From: Karan Sharma <55722391+ksharma-xyz@users.noreply.github.com> Date: Sun, 17 Nov 2024 18:01:05 +1100 Subject: [PATCH 32/67] Make usualride multiplatform --- .../xyz/ksharma/krail/trip/planner/ui/ContextExt.kt | 6 ++---- .../trip/planner/ui/usualride/UsualRideDestination.kt | 4 ++-- .../krail/trip/planner/ui/usualride/UsualRideScreen.kt | 2 -- .../krail/trip/planner/ui/usualride/UsualRideViewModel.kt | 8 ++------ 4 files changed, 6 insertions(+), 14 deletions(-) diff --git a/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/ContextExt.kt b/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/ContextExt.kt index cb096bdd..b073d7de 100644 --- a/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/ContextExt.kt +++ b/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/ContextExt.kt @@ -1,9 +1,6 @@ package xyz.ksharma.krail.trip.planner.ui +/* -import android.app.Activity -import android.content.Context -import android.content.ContextWrapper -import android.graphics.Color import androidx.activity.ComponentActivity import androidx.activity.SystemBarStyle import androidx.activity.enableEdgeToEdge @@ -39,3 +36,4 @@ internal fun DefaultSystemBarColors() { onDispose {} } } +*/ diff --git a/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/usualride/UsualRideDestination.kt b/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/usualride/UsualRideDestination.kt index 14f0500b..ba62997e 100644 --- a/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/usualride/UsualRideDestination.kt +++ b/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/usualride/UsualRideDestination.kt @@ -4,7 +4,7 @@ import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue -import androidx.hilt.navigation.compose.hiltViewModel +import androidx.lifecycle.viewmodel.compose.viewModel import androidx.navigation.NavGraphBuilder import androidx.navigation.NavHostController import androidx.navigation.NavOptions @@ -23,7 +23,7 @@ import xyz.ksharma.krail.trip.planner.ui.state.usualride.UsualRideEvent internal fun NavGraphBuilder.usualRideDestination(navController: NavHostController) { composable { - val viewModel = hiltViewModel() + val viewModel:UsualRideViewModel = viewModel() var themeColor by LocalThemeColor.current var themeContentColor by LocalThemeContentColor.current var mode: TransportMode? by remember { mutableStateOf(null) } diff --git a/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/usualride/UsualRideScreen.kt b/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/usualride/UsualRideScreen.kt index a95dd0c8..08a351b2 100644 --- a/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/usualride/UsualRideScreen.kt +++ b/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/usualride/UsualRideScreen.kt @@ -30,7 +30,6 @@ import androidx.compose.ui.graphics.Color import androidx.compose.ui.semantics.Role import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextAlign -import androidx.compose.ui.tooling.preview.PreviewLightDark import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import kotlinx.collections.immutable.ImmutableSet @@ -184,7 +183,6 @@ private fun TransportModeRadioButton( } } -@PreviewLightDark @Composable private fun PreviewUsualRideScreen() { KrailTheme { diff --git a/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/usualride/UsualRideViewModel.kt b/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/usualride/UsualRideViewModel.kt index 71950edd..0ac9fa73 100644 --- a/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/usualride/UsualRideViewModel.kt +++ b/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/usualride/UsualRideViewModel.kt @@ -2,12 +2,10 @@ package xyz.ksharma.krail.trip.planner.ui.usualride import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope -import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.launch - import xyz.ksharma.krail.di.AppDispatchers import xyz.ksharma.krail.di.Dispatcher import xyz.ksharma.krail.sandook.Sandook @@ -15,10 +13,8 @@ import xyz.ksharma.krail.sandook.di.SandookFactory import xyz.ksharma.krail.trip.planner.ui.state.TransportMode import xyz.ksharma.krail.trip.planner.ui.state.usualride.UsualRideEvent import xyz.ksharma.krail.trip.planner.ui.state.usualride.UsualRideState -import javax.inject.Inject -@HiltViewModel -class UsualRideViewModel @Inject constructor( +class UsualRideViewModel( sandookFactory: SandookFactory, @Dispatcher(AppDispatchers.IO) private val ioDispatcher: CoroutineDispatcher, ) : ViewModel() { @@ -37,7 +33,7 @@ class UsualRideViewModel @Inject constructor( private fun onTransportModeSelected(productClass: Int) { viewModelScope.launch(ioDispatcher) { TransportMode.toTransportModeType(productClass)?.let { mode -> - Timber.d("onTransportModeSelected: $mode") + //Timber.d("onTransportModeSelected: $mode") sandook.putInt("selectedMode", mode.productClass) } } From bdd9086a18d87e8a2d972589e2a69974a61646c2 Mon Sep 17 00:00:00 2001 From: Karan Sharma <55722391+ksharma-xyz@users.noreply.github.com> Date: Sun, 17 Nov 2024 18:45:36 +1100 Subject: [PATCH 33/67] Make :network api and real multiplatform --- .../trip-planner/network/api/build.gradle.kts | 57 ++++++++++++++--- .../trip/planner/network/api/RateLimiter.kt | 0 .../planner/network/api/di/ServiceModule.kt | 0 .../network/api/model/StopFinderResponse.kt | 0 .../planner/network/api/model/StopType.kt | 0 .../planner/network/api/model/TripResponse.kt | 0 .../api/repository/TripPlanningRepository.kt | 10 +-- .../api/service/TripPlanningService.kt | 24 +++---- .../assets/hige_multiple_legs_trip.json | 0 .../{test => commonTest}/assets/multiple_legs | 0 .../assets/stop_finder_any.json | 0 .../assets/stop_finder_stop.json | 0 .../src/{test => commonTest}/assets/trip.json | 0 .../assets/trip_metro_bus.json | 0 .../assets/trip_occupancy_platform.json | 0 .../assets/trip_sevenhills_townhall.json | 0 .../network/real/build.gradle.kts | 64 +++++++++++++++---- .../network/real/di/TripPlanningModule.kt | 31 +++++++++ .../network/real/ratelimit/APIRateLimiter.kt | 12 ++-- .../repository/RealTripPlanningRepository.kt | 9 +-- .../real/ratelimit/APIRateLimiterTest.kt | 0 .../network/real/di/TripPlanningModule.kt | 24 ------- settings.gradle.kts | 2 +- 23 files changed, 158 insertions(+), 75 deletions(-) rename feature/trip-planner/network/api/src/{main => commonMain}/kotlin/xyz/ksharma/krail/trip/planner/network/api/RateLimiter.kt (100%) rename feature/trip-planner/network/api/src/{main => commonMain}/kotlin/xyz/ksharma/krail/trip/planner/network/api/di/ServiceModule.kt (100%) rename feature/trip-planner/network/api/src/{main => commonMain}/kotlin/xyz/ksharma/krail/trip/planner/network/api/model/StopFinderResponse.kt (100%) rename feature/trip-planner/network/api/src/{main => commonMain}/kotlin/xyz/ksharma/krail/trip/planner/network/api/model/StopType.kt (100%) rename feature/trip-planner/network/api/src/{main => commonMain}/kotlin/xyz/ksharma/krail/trip/planner/network/api/model/TripResponse.kt (100%) rename feature/trip-planner/network/api/src/{main => commonMain}/kotlin/xyz/ksharma/krail/trip/planner/network/api/repository/TripPlanningRepository.kt (72%) rename feature/trip-planner/network/api/src/{main => commonMain}/kotlin/xyz/ksharma/krail/trip/planner/network/api/service/TripPlanningService.kt (95%) rename feature/trip-planner/network/api/src/{test => commonTest}/assets/hige_multiple_legs_trip.json (100%) rename feature/trip-planner/network/api/src/{test => commonTest}/assets/multiple_legs (100%) rename feature/trip-planner/network/api/src/{test => commonTest}/assets/stop_finder_any.json (100%) rename feature/trip-planner/network/api/src/{test => commonTest}/assets/stop_finder_stop.json (100%) rename feature/trip-planner/network/api/src/{test => commonTest}/assets/trip.json (100%) rename feature/trip-planner/network/api/src/{test => commonTest}/assets/trip_metro_bus.json (100%) rename feature/trip-planner/network/api/src/{test => commonTest}/assets/trip_occupancy_platform.json (100%) rename feature/trip-planner/network/api/src/{test => commonTest}/assets/trip_sevenhills_townhall.json (100%) create mode 100644 feature/trip-planner/network/real/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/network/real/di/TripPlanningModule.kt rename feature/trip-planner/network/real/src/{main => commonMain}/kotlin/xyz/ksharma/krail/trip/planner/network/real/ratelimit/APIRateLimiter.kt (84%) rename feature/trip-planner/network/real/src/{main => commonMain}/kotlin/xyz/ksharma/krail/trip/planner/network/real/repository/RealTripPlanningRepository.kt (78%) rename feature/trip-planner/network/real/src/{test => commonTest}/kotlin/xyz/ksharma/krail/trip/planner/network/real/ratelimit/APIRateLimiterTest.kt (100%) delete mode 100644 feature/trip-planner/network/real/src/main/kotlin/xyz/ksharma/krail/trip/planner/network/real/di/TripPlanningModule.kt diff --git a/feature/trip-planner/network/api/build.gradle.kts b/feature/trip-planner/network/api/build.gradle.kts index 4d445b50..2c4ce51f 100644 --- a/feature/trip-planner/network/api/build.gradle.kts +++ b/feature/trip-planner/network/api/build.gradle.kts @@ -1,17 +1,58 @@ +android { + namespace = "xyz.ksharma.krail.trip.planner.network.api" +} + plugins { alias(libs.plugins.krail.android.library) - alias(libs.plugins.krail.android.hilt) + alias(libs.plugins.krail.kotlin.multiplatform) alias(libs.plugins.kotlin.serialization) + alias(libs.plugins.ksp) } -android { - namespace = "xyz.ksharma.krail.trip.planner.network.api" +kotlin { + applyDefaultHierarchyTemplate() + + androidTarget() + iosArm64() + iosSimulatorArm64() + + sourceSets { + androidMain.dependencies { + implementation(libs.ktor.client.okhttp) + } + + commonMain { + dependencies { + implementation(projects.core.coroutinesExt) + api(projects.core.di) + api(projects.core.network) + + implementation(libs.di.kotlinInjectRuntime) + implementation(libs.kotlinx.serialization.json) + implementation(libs.ktor.client.core) + implementation(libs.ktor.client.cio) + implementation(libs.ktor.client.content.negotiation) + implementation(libs.ktor.client.logging) + implementation(libs.ktor.serialization.kotlinx.json) + implementation(libs.kotlinx.datetime) + } + } + + iosMain { + dependencies { + implementation(libs.ktor.client.darwin) + } + } + } } dependencies { - api(projects.core.network) - implementation(platform(libs.okhttp.bom)) - implementation(libs.okhttp) - implementation(libs.retrofit) - implementation(libs.kotlinx.serialization.json) + // 1. Configure code generation into the common source set + kspCommonMainMetadata(libs.di.kotlinInjectRuntime) + + // 2. Configure code generation into each KMP target source set + //kspAndroid(libs.di.kotlinInjectCompilerKsp) + // kspIosX64("me.tatarka.inject:kotlin-inject-compiler-ksp:0.7.2") + // kspIosArm64("me.tatarka.inject:kotlin-inject-compiler-ksp:0.7.2") + // kspIosSimulatorArm64("me.tatarka.inject:kotlin-inject-compiler-ksp:0.7.2") } diff --git a/feature/trip-planner/network/api/src/main/kotlin/xyz/ksharma/krail/trip/planner/network/api/RateLimiter.kt b/feature/trip-planner/network/api/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/network/api/RateLimiter.kt similarity index 100% rename from feature/trip-planner/network/api/src/main/kotlin/xyz/ksharma/krail/trip/planner/network/api/RateLimiter.kt rename to feature/trip-planner/network/api/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/network/api/RateLimiter.kt diff --git a/feature/trip-planner/network/api/src/main/kotlin/xyz/ksharma/krail/trip/planner/network/api/di/ServiceModule.kt b/feature/trip-planner/network/api/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/network/api/di/ServiceModule.kt similarity index 100% rename from feature/trip-planner/network/api/src/main/kotlin/xyz/ksharma/krail/trip/planner/network/api/di/ServiceModule.kt rename to feature/trip-planner/network/api/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/network/api/di/ServiceModule.kt diff --git a/feature/trip-planner/network/api/src/main/kotlin/xyz/ksharma/krail/trip/planner/network/api/model/StopFinderResponse.kt b/feature/trip-planner/network/api/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/network/api/model/StopFinderResponse.kt similarity index 100% rename from feature/trip-planner/network/api/src/main/kotlin/xyz/ksharma/krail/trip/planner/network/api/model/StopFinderResponse.kt rename to feature/trip-planner/network/api/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/network/api/model/StopFinderResponse.kt diff --git a/feature/trip-planner/network/api/src/main/kotlin/xyz/ksharma/krail/trip/planner/network/api/model/StopType.kt b/feature/trip-planner/network/api/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/network/api/model/StopType.kt similarity index 100% rename from feature/trip-planner/network/api/src/main/kotlin/xyz/ksharma/krail/trip/planner/network/api/model/StopType.kt rename to feature/trip-planner/network/api/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/network/api/model/StopType.kt diff --git a/feature/trip-planner/network/api/src/main/kotlin/xyz/ksharma/krail/trip/planner/network/api/model/TripResponse.kt b/feature/trip-planner/network/api/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/network/api/model/TripResponse.kt similarity index 100% rename from feature/trip-planner/network/api/src/main/kotlin/xyz/ksharma/krail/trip/planner/network/api/model/TripResponse.kt rename to feature/trip-planner/network/api/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/network/api/model/TripResponse.kt diff --git a/feature/trip-planner/network/api/src/main/kotlin/xyz/ksharma/krail/trip/planner/network/api/repository/TripPlanningRepository.kt b/feature/trip-planner/network/api/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/network/api/repository/TripPlanningRepository.kt similarity index 72% rename from feature/trip-planner/network/api/src/main/kotlin/xyz/ksharma/krail/trip/planner/network/api/repository/TripPlanningRepository.kt rename to feature/trip-planner/network/api/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/network/api/repository/TripPlanningRepository.kt index e4cba9d7..385565bb 100644 --- a/feature/trip-planner/network/api/src/main/kotlin/xyz/ksharma/krail/trip/planner/network/api/repository/TripPlanningRepository.kt +++ b/feature/trip-planner/network/api/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/network/api/repository/TripPlanningRepository.kt @@ -3,9 +3,9 @@ package xyz.ksharma.krail.trip.planner.network.api.repository import xyz.ksharma.krail.trip.planner.network.api.model.StopFinderResponse import xyz.ksharma.krail.trip.planner.network.api.model.StopType import xyz.ksharma.krail.trip.planner.network.api.model.TripResponse -import java.time.Instant -import java.time.ZoneId -import java.time.format.DateTimeFormatter +import kotlinx.datetime.Instant +import kotlinx.datetime.TimeZone +import kotlinx.datetime.toLocalDateTime interface TripPlanningRepository { @@ -26,6 +26,6 @@ interface TripPlanningRepository { * // todo - move to another module for time related functions */ fun Instant.toItdTime(): String { - val formatter = DateTimeFormatter.ofPattern("HHMM").withZone(ZoneId.of("Australia/Sydney")) - return formatter.format(this) + val localDateTime = this.toLocalDateTime(TimeZone.of("Australia/Sydney")) + return localDateTime.hour.toString().padStart(2, '0') + localDateTime.minute.toString().padStart(2, '0') } diff --git a/feature/trip-planner/network/api/src/main/kotlin/xyz/ksharma/krail/trip/planner/network/api/service/TripPlanningService.kt b/feature/trip-planner/network/api/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/network/api/service/TripPlanningService.kt similarity index 95% rename from feature/trip-planner/network/api/src/main/kotlin/xyz/ksharma/krail/trip/planner/network/api/service/TripPlanningService.kt rename to feature/trip-planner/network/api/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/network/api/service/TripPlanningService.kt index f71febd7..b2ed8382 100644 --- a/feature/trip-planner/network/api/src/main/kotlin/xyz/ksharma/krail/trip/planner/network/api/service/TripPlanningService.kt +++ b/feature/trip-planner/network/api/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/network/api/service/TripPlanningService.kt @@ -1,9 +1,5 @@ package xyz.ksharma.krail.trip.planner.network.api.service -import retrofit2.Response -import retrofit2.http.GET -import retrofit2.http.Query -import xyz.ksharma.krail.trip.planner.network.api.model.StopFinderResponse import xyz.ksharma.krail.trip.planner.network.api.model.TripResponse /** @@ -20,7 +16,7 @@ interface TripPlanningService { * points of interest and known addresses to be used for auto-suggest/auto-complete (to be * used with the Trip planner and Departure board APIs). */ - @GET("v1/tp/stop_finder") + //@GET("v1/tp/stop_finder") suspend fun stopFinder( /** * Used to set the response data type. This documentation only covers responses that use the JSON format. @@ -28,7 +24,7 @@ interface TripPlanningService { * * Available values : rapidJSON */ - @Query("outputFormat") outputFormat: String = "rapidJSON", + // @Query("outputFormat") outputFormat: String = "rapidJSON", /** * This specifies the type of results expected in the list of returned stops. @@ -39,7 +35,7 @@ interface TripPlanningService { * * Available values : any, coord, poi, stop */ - @Query("type_sf") typeSf: String = "stop", + // @Query("type_sf") typeSf: String = "stop", /** * This is the search term that will be used to find locations. @@ -49,7 +45,7 @@ interface TripPlanningService { * * Default value : Circular Quay */ - @Query("name_sf") nameSf: String, + // @Query("name_sf") nameSf: String, /** * This specifies the format the coordinates are returned in. @@ -57,7 +53,7 @@ interface TripPlanningService { * * Available values : EPSG:4326 */ - @Query("coordOutputFormat") coordOutputFormat: String = "EPSG:4326", + // @Query("coordOutputFormat") coordOutputFormat: String = "EPSG:4326", /** * Including this parameter enables a number of options that result in the stop finder @@ -67,7 +63,7 @@ interface TripPlanningService { * * Default value : true */ - @Query("TfNSWSF") tfNSWSF: String = "true", + // @Query("TfNSWSF") tfNSWSF: String = "true", /** * Indicates which version of the API the caller is expecting for both request and response @@ -76,8 +72,8 @@ interface TripPlanningService { * * Default value : 10.2.1.42 */ - @Query("version") version: String? = null, - ): Response + // @Query("version") version: String? = null, + )//: Response /** * This endpoint is used to find a list of journeys between two locations at the specified @@ -89,13 +85,13 @@ interface TripPlanningService { * Provides capability to provide NSW public transport trip plan options, * including walking and driving legs, real-time and Opal fare information. */ - @GET("v1/tp/trip") + // @GET("v1/tp/trip") suspend fun trip( /** * Used to set the response data type. This documentation only covers responses that use * the JSON format. Setting the outputFormat value to rapidJSON is required to enable JSON output. */ - @Query("outputFormat") outputFormat: String = "rapidJSON", + // @Query("outputFormat") outputFormat: String = "rapidJSON", /** * This specifies the format the coordinates are returned in. While other variations are diff --git a/feature/trip-planner/network/api/src/test/assets/hige_multiple_legs_trip.json b/feature/trip-planner/network/api/src/commonTest/assets/hige_multiple_legs_trip.json similarity index 100% rename from feature/trip-planner/network/api/src/test/assets/hige_multiple_legs_trip.json rename to feature/trip-planner/network/api/src/commonTest/assets/hige_multiple_legs_trip.json diff --git a/feature/trip-planner/network/api/src/test/assets/multiple_legs b/feature/trip-planner/network/api/src/commonTest/assets/multiple_legs similarity index 100% rename from feature/trip-planner/network/api/src/test/assets/multiple_legs rename to feature/trip-planner/network/api/src/commonTest/assets/multiple_legs diff --git a/feature/trip-planner/network/api/src/test/assets/stop_finder_any.json b/feature/trip-planner/network/api/src/commonTest/assets/stop_finder_any.json similarity index 100% rename from feature/trip-planner/network/api/src/test/assets/stop_finder_any.json rename to feature/trip-planner/network/api/src/commonTest/assets/stop_finder_any.json diff --git a/feature/trip-planner/network/api/src/test/assets/stop_finder_stop.json b/feature/trip-planner/network/api/src/commonTest/assets/stop_finder_stop.json similarity index 100% rename from feature/trip-planner/network/api/src/test/assets/stop_finder_stop.json rename to feature/trip-planner/network/api/src/commonTest/assets/stop_finder_stop.json diff --git a/feature/trip-planner/network/api/src/test/assets/trip.json b/feature/trip-planner/network/api/src/commonTest/assets/trip.json similarity index 100% rename from feature/trip-planner/network/api/src/test/assets/trip.json rename to feature/trip-planner/network/api/src/commonTest/assets/trip.json diff --git a/feature/trip-planner/network/api/src/test/assets/trip_metro_bus.json b/feature/trip-planner/network/api/src/commonTest/assets/trip_metro_bus.json similarity index 100% rename from feature/trip-planner/network/api/src/test/assets/trip_metro_bus.json rename to feature/trip-planner/network/api/src/commonTest/assets/trip_metro_bus.json diff --git a/feature/trip-planner/network/api/src/test/assets/trip_occupancy_platform.json b/feature/trip-planner/network/api/src/commonTest/assets/trip_occupancy_platform.json similarity index 100% rename from feature/trip-planner/network/api/src/test/assets/trip_occupancy_platform.json rename to feature/trip-planner/network/api/src/commonTest/assets/trip_occupancy_platform.json diff --git a/feature/trip-planner/network/api/src/test/assets/trip_sevenhills_townhall.json b/feature/trip-planner/network/api/src/commonTest/assets/trip_sevenhills_townhall.json similarity index 100% rename from feature/trip-planner/network/api/src/test/assets/trip_sevenhills_townhall.json rename to feature/trip-planner/network/api/src/commonTest/assets/trip_sevenhills_townhall.json diff --git a/feature/trip-planner/network/real/build.gradle.kts b/feature/trip-planner/network/real/build.gradle.kts index e1ded5f3..4e74e344 100644 --- a/feature/trip-planner/network/real/build.gradle.kts +++ b/feature/trip-planner/network/real/build.gradle.kts @@ -1,22 +1,64 @@ plugins { alias(libs.plugins.krail.android.library) - alias(libs.plugins.krail.android.hilt) + alias(libs.plugins.krail.kotlin.multiplatform) + alias(libs.plugins.kotlin.serialization) + alias(libs.plugins.ksp) } android { namespace = "xyz.ksharma.krail.trip.planner.network.real" } -dependencies { - implementation(projects.core.coroutinesExt) - implementation(projects.feature.tripPlanner.network.api) +kotlin { + applyDefaultHierarchyTemplate() + + androidTarget() + iosArm64() + iosSimulatorArm64() + + sourceSets { + androidMain.dependencies { + implementation(libs.ktor.client.okhttp) + } + + commonMain { + dependencies { + implementation(projects.core.coroutinesExt) + implementation(projects.feature.tripPlanner.network.api) + + implementation(libs.di.kotlinInjectRuntime) + implementation(libs.kotlinx.serialization.json) + implementation(libs.ktor.client.core) + implementation(libs.ktor.client.cio) + implementation(libs.ktor.client.content.negotiation) + implementation(libs.ktor.client.logging) + implementation(libs.ktor.serialization.kotlinx.json) + } + } - implementation(platform(libs.okhttp.bom)) - implementation(libs.okhttp) - implementation(libs.retrofit) + commonTest { + dependencies { + implementation(libs.test.kotlin) + implementation(libs.test.turbine) + implementation(libs.test.kotlinxCoroutineTest) + } + } + + iosMain { + dependencies { + implementation(libs.ktor.client.darwin) + } + } + } +} + +dependencies { + // 1. Configure code generation into the common source set + kspCommonMainMetadata(libs.di.kotlinInjectRuntime) - testImplementation(libs.test.junit) - testImplementation(libs.test.turbine) - testImplementation(libs.test.kotlin) - testImplementation(libs.test.kotlinxCoroutineTest) + // 2. Configure code generation into each KMP target source set + //kspAndroid(libs.di.kotlinInjectCompilerKsp) + // kspIosX64("me.tatarka.inject:kotlin-inject-compiler-ksp:0.7.2") + // kspIosArm64("me.tatarka.inject:kotlin-inject-compiler-ksp:0.7.2") + // kspIosSimulatorArm64("me.tatarka.inject:kotlin-inject-compiler-ksp:0.7.2") } diff --git a/feature/trip-planner/network/real/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/network/real/di/TripPlanningModule.kt b/feature/trip-planner/network/real/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/network/real/di/TripPlanningModule.kt new file mode 100644 index 00000000..204b96a1 --- /dev/null +++ b/feature/trip-planner/network/real/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/network/real/di/TripPlanningModule.kt @@ -0,0 +1,31 @@ +package xyz.ksharma.krail.trip.planner.network.real.di + +import kotlinx.coroutines.CoroutineDispatcher +import me.tatarka.inject.annotations.Component +import me.tatarka.inject.annotations.Provides +import xyz.ksharma.krail.di.AppDispatchers +import xyz.ksharma.krail.di.Dispatcher +import xyz.ksharma.krail.trip.planner.network.api.RateLimiter +import xyz.ksharma.krail.trip.planner.network.api.repository.TripPlanningRepository +import xyz.ksharma.krail.trip.planner.network.real.ratelimit.APIRateLimiter +import xyz.ksharma.krail.trip.planner.network.real.repository.RealTripPlanningRepository + +@Component +abstract class TripPlanningComponent { + + abstract val tripPlanningRepository: TripPlanningRepository + + abstract val rateLimiter: RateLimiter + + @Provides + fun provideTripPlanningRepository( + @Dispatcher(AppDispatchers.IO) ioDispatcher: CoroutineDispatcher, + ): TripPlanningRepository { + return RealTripPlanningRepository(ioDispatcher = ioDispatcher) + } + + @Provides + fun provideRateLimiter(): RateLimiter { + return APIRateLimiter() + } +} diff --git a/feature/trip-planner/network/real/src/main/kotlin/xyz/ksharma/krail/trip/planner/network/real/ratelimit/APIRateLimiter.kt b/feature/trip-planner/network/real/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/network/real/ratelimit/APIRateLimiter.kt similarity index 84% rename from feature/trip-planner/network/real/src/main/kotlin/xyz/ksharma/krail/trip/planner/network/real/ratelimit/APIRateLimiter.kt rename to feature/trip-planner/network/real/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/network/real/ratelimit/APIRateLimiter.kt index b3a38e6f..d28f78ea 100644 --- a/feature/trip-planner/network/real/src/main/kotlin/xyz/ksharma/krail/trip/planner/network/real/ratelimit/APIRateLimiter.kt +++ b/feature/trip-planner/network/real/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/network/real/ratelimit/APIRateLimiter.kt @@ -1,5 +1,7 @@ package xyz.ksharma.krail.trip.planner.network.real.ratelimit +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.FlowPreview import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.MutableStateFlow @@ -7,9 +9,8 @@ import kotlinx.coroutines.flow.debounce import kotlinx.coroutines.flow.flatMapLatest import kotlinx.coroutines.flow.flow import kotlinx.coroutines.flow.update - +import me.tatarka.inject.annotations.Inject import xyz.ksharma.krail.trip.planner.network.api.RateLimiter -import javax.inject.Inject import kotlin.time.Duration.Companion.milliseconds import kotlin.time.Duration.Companion.seconds @@ -30,19 +31,20 @@ class APIRateLimiter @Inject constructor() : RateLimiter { * @param block A suspend function representing the API call to be rate-limited. * @return A Flow that emits the result of the API call. */ + @OptIn(FlowPreview::class, ExperimentalCoroutinesApi::class) override fun rateLimitFlow(block: suspend () -> T): Flow { return triggerFlow .debounce { // First time the block should be executed immediately and subsequent must be rate limited. val interval = if (isFirstTime.value) rateLimitInterval else 0.milliseconds - Timber.d("state: ${isFirstTime.value} and interval: $interval") + // Timber.d("state: ${isFirstTime.value} and interval: $interval") interval } .flatMapLatest { - Timber.d("flatmapLatest: Triggered") + //Timber.d("flatmapLatest: Triggered") isFirstTime.update { true } // Mark the first trigger flow { - Timber.d("Inside flow -emitting block") + // Timber.d("Inside flow -emitting block") emit(block()) } } diff --git a/feature/trip-planner/network/real/src/main/kotlin/xyz/ksharma/krail/trip/planner/network/real/repository/RealTripPlanningRepository.kt b/feature/trip-planner/network/real/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/network/real/repository/RealTripPlanningRepository.kt similarity index 78% rename from feature/trip-planner/network/real/src/main/kotlin/xyz/ksharma/krail/trip/planner/network/real/repository/RealTripPlanningRepository.kt rename to feature/trip-planner/network/real/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/network/real/repository/RealTripPlanningRepository.kt index fb05ec4b..7ebc976e 100644 --- a/feature/trip-planner/network/real/src/main/kotlin/xyz/ksharma/krail/trip/planner/network/real/repository/RealTripPlanningRepository.kt +++ b/feature/trip-planner/network/real/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/network/real/repository/RealTripPlanningRepository.kt @@ -1,20 +1,15 @@ package xyz.ksharma.krail.trip.planner.network.real.repository import kotlinx.coroutines.CoroutineDispatcher +import me.tatarka.inject.annotations.Inject import xyz.ksharma.krail.coroutines.ext.suspendSafeResult -import xyz.ksharma.krail.di.AppDispatchers -import xyz.ksharma.krail.di.Dispatcher -import xyz.ksharma.krail.network.toSafeResult import xyz.ksharma.krail.trip.planner.network.api.model.StopFinderResponse import xyz.ksharma.krail.trip.planner.network.api.model.StopType import xyz.ksharma.krail.trip.planner.network.api.model.TripResponse import xyz.ksharma.krail.trip.planner.network.api.repository.TripPlanningRepository -import xyz.ksharma.krail.trip.planner.network.api.service.TripPlanningService -import javax.inject.Inject class RealTripPlanningRepository @Inject constructor( - private val tripPlanningService: TripPlanningService, - @Dispatcher(AppDispatchers.IO) private val ioDispatcher: CoroutineDispatcher, + val ioDispatcher: CoroutineDispatcher, ) : TripPlanningRepository { override suspend fun stopFinder( diff --git a/feature/trip-planner/network/real/src/test/kotlin/xyz/ksharma/krail/trip/planner/network/real/ratelimit/APIRateLimiterTest.kt b/feature/trip-planner/network/real/src/commonTest/kotlin/xyz/ksharma/krail/trip/planner/network/real/ratelimit/APIRateLimiterTest.kt similarity index 100% rename from feature/trip-planner/network/real/src/test/kotlin/xyz/ksharma/krail/trip/planner/network/real/ratelimit/APIRateLimiterTest.kt rename to feature/trip-planner/network/real/src/commonTest/kotlin/xyz/ksharma/krail/trip/planner/network/real/ratelimit/APIRateLimiterTest.kt diff --git a/feature/trip-planner/network/real/src/main/kotlin/xyz/ksharma/krail/trip/planner/network/real/di/TripPlanningModule.kt b/feature/trip-planner/network/real/src/main/kotlin/xyz/ksharma/krail/trip/planner/network/real/di/TripPlanningModule.kt deleted file mode 100644 index faa6bbe3..00000000 --- a/feature/trip-planner/network/real/src/main/kotlin/xyz/ksharma/krail/trip/planner/network/real/di/TripPlanningModule.kt +++ /dev/null @@ -1,24 +0,0 @@ -package xyz.ksharma.krail.trip.planner.network.real.di - -import dagger.Binds -import dagger.Module -import dagger.hilt.InstallIn -import dagger.hilt.components.SingletonComponent -import xyz.ksharma.krail.trip.planner.network.api.RateLimiter -import xyz.ksharma.krail.trip.planner.network.api.repository.TripPlanningRepository -import xyz.ksharma.krail.trip.planner.network.real.ratelimit.APIRateLimiter -import xyz.ksharma.krail.trip.planner.network.real.repository.RealTripPlanningRepository -import javax.inject.Singleton - -@Module -@InstallIn(SingletonComponent::class) -abstract class TripPlanningModule { - - @Binds - @Singleton - abstract fun bindTripPlanningRepository(impl: RealTripPlanningRepository): TripPlanningRepository - - @Binds - // This clas should not be Singleton. It should be created per use-case. - abstract fun bindRateLimiter(impl: APIRateLimiter): RateLimiter -} diff --git a/settings.gradle.kts b/settings.gradle.kts index d2b31359..70fe0863 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -39,9 +39,9 @@ include(":core:coroutines-ext") include(":core:network") include(":feature:trip-planner:ui") include(":feature:trip-planner:state") -/* include(":feature:trip-planner:network:api") include(":feature:trip-planner:network:real") +/* include(":sandook:api") include(":sandook:real") */ From cbc3ea4f66a86210f27cba486913d47e2d1962a5 Mon Sep 17 00:00:00 2001 From: Karan Sharma <55722391+ksharma-xyz@users.noreply.github.com> Date: Sun, 17 Nov 2024 19:02:27 +1100 Subject: [PATCH 34/67] Make sandook multiplatform --- .github/workflows/ci.yml | 4 +- .../xyz/ksharma/krail/KrailApplication.kt | 7 +-- build.gradle.kts | 1 - composeApp/build.gradle.kts | 4 +- .../xyz/ksharma/krail/common/KrailApp.kt | 6 ++ .../planner/network/api/di/ServiceModule.kt | 20 ------- gradle/libs.versions.toml | 13 +---- gradlew | 2 +- sandook/api/build.gradle.kts | 3 - sandook/build.gradle.kts | 50 ++++++++++++++++ sandook/real/build.gradle.kts | 14 ----- .../xyz/ksharma/krail/sandook/RealSandook.kt | 57 ------------------- .../krail/sandook/di/SandookFactory.kt | 23 -------- .../ksharma/krail/sandook/di/SandookModule.kt | 18 ------ .../xyz/ksharma/krail/sandook/RealSandook.kt | 57 +++++++++++++++++++ .../xyz/ksharma/krail/sandook/Sandook.kt | 23 ++++++-- .../krail/sandook/di/SandookComponent.kt | 19 +++++++ settings.gradle.kts | 5 +- taj/build.gradle.kts | 1 + 19 files changed, 158 insertions(+), 169 deletions(-) delete mode 100644 feature/trip-planner/network/api/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/network/api/di/ServiceModule.kt delete mode 100644 sandook/api/build.gradle.kts create mode 100644 sandook/build.gradle.kts delete mode 100644 sandook/real/build.gradle.kts delete mode 100644 sandook/real/src/main/kotlin/xyz/ksharma/krail/sandook/RealSandook.kt delete mode 100644 sandook/real/src/main/kotlin/xyz/ksharma/krail/sandook/di/SandookFactory.kt delete mode 100644 sandook/real/src/main/kotlin/xyz/ksharma/krail/sandook/di/SandookModule.kt create mode 100644 sandook/src/commonMain/kotlin/xyz/ksharma/krail/sandook/RealSandook.kt rename sandook/{real/src/main => src/commonMain}/kotlin/xyz/ksharma/krail/sandook/Sandook.kt (98%) create mode 100644 sandook/src/commonMain/kotlin/xyz/ksharma/krail/sandook/di/SandookComponent.kt diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 1f0c243e..fe58573c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -2,9 +2,9 @@ name: Android CI on: push: - branches: [ main ] + branches: [ commonMain ] pull_request: - branches: [ main ] + branches: [ commonMain ] jobs: build: diff --git a/android-app/src/main/kotlin/xyz/ksharma/krail/KrailApplication.kt b/android-app/src/main/kotlin/xyz/ksharma/krail/KrailApplication.kt index 48d1ce94..e66c82fb 100644 --- a/android-app/src/main/kotlin/xyz/ksharma/krail/KrailApplication.kt +++ b/android-app/src/main/kotlin/xyz/ksharma/krail/KrailApplication.kt @@ -2,9 +2,4 @@ package xyz.ksharma.krail import android.app.Application -class KrailApplication : Application() { - - override fun onCreate() { - super.onCreate() - } -} +class KrailApplication : Application() diff --git a/build.gradle.kts b/build.gradle.kts index b6aa3f9e..f6ab05be 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -4,7 +4,6 @@ plugins { alias(libs.plugins.androidApplication) apply false alias(libs.plugins.kotlinAndroid) apply false - alias(libs.plugins.hilt) apply false alias(libs.plugins.ksp) apply false alias(libs.plugins.kotlin.serialization) apply false alias(libs.plugins.compose.compiler) apply false diff --git a/composeApp/build.gradle.kts b/composeApp/build.gradle.kts index 1515c8de..10da5592 100644 --- a/composeApp/build.gradle.kts +++ b/composeApp/build.gradle.kts @@ -56,6 +56,7 @@ kotlin { commonMain.dependencies { implementation(projects.taj) + implementation(projects.sandook) implementation(compose.runtime) implementation(compose.foundation) @@ -67,6 +68,3 @@ kotlin { } } } - -dependencies { -} diff --git a/composeApp/src/commonMain/kotlin/xyz/ksharma/krail/common/KrailApp.kt b/composeApp/src/commonMain/kotlin/xyz/ksharma/krail/common/KrailApp.kt index 234f3a18..a28c09b8 100644 --- a/composeApp/src/commonMain/kotlin/xyz/ksharma/krail/common/KrailApp.kt +++ b/composeApp/src/commonMain/kotlin/xyz/ksharma/krail/common/KrailApp.kt @@ -5,13 +5,19 @@ import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.statusBarsPadding import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect import androidx.compose.ui.Modifier +import xyz.ksharma.krail.sandook.di.SandookComponent import xyz.ksharma.krail.taj.components.Text import xyz.ksharma.krail.taj.theme.KrailTheme @Composable fun KrailApp() { KrailTheme { + + LaunchedEffect(Unit) { + } + Column(modifier = Modifier.fillMaxSize().background(color = KrailTheme.colors.surface)) { Text( "Hello, Krail!", diff --git a/feature/trip-planner/network/api/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/network/api/di/ServiceModule.kt b/feature/trip-planner/network/api/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/network/api/di/ServiceModule.kt deleted file mode 100644 index dc0f6ddc..00000000 --- a/feature/trip-planner/network/api/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/network/api/di/ServiceModule.kt +++ /dev/null @@ -1,20 +0,0 @@ -package xyz.ksharma.krail.trip.planner.network.api.di - -import dagger.Module -import dagger.Provides -import dagger.hilt.InstallIn -import dagger.hilt.components.SingletonComponent -import retrofit2.Retrofit -import xyz.ksharma.krail.trip.planner.network.api.service.TripPlanningService -import javax.inject.Singleton - -@Module -@InstallIn(SingletonComponent::class) -object ServiceModule { - - @Provides - @Singleton - fun provideTripPlanningService(retrofit: Retrofit): TripPlanningService { - return retrofit.create(TripPlanningService::class.java) - } -} diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 9d33a679..9ec3aa33 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -9,21 +9,15 @@ androidx-test = "1.6.1" androidx-test-ext-junit = "1.2.1" android-lifecycle = "2.8.7" activity-compose = "1.9.3" -hilt = "2.52" -hiltNavigationCompose = "1.2.0" kotlinInject = "0.7.2" kotlinxCollectionsImmutable = "0.3.8" kotlinxDatetime = "0.6.1" -ktorClientLogging = "2.3.12" lifecycleViewmodelCompose = "2.8.2" +multiplatformSettings = "1.2.0" navigationCompose = "2.8.0-alpha10" -okhttpBom = "4.12.0" kotlinxSerializationJson = "1.7.3" ksp = "2.0.21-1.0.27" # ksp to kotlin version mapping https://github.com/google/ksp/releases paparazzi = "1.3.5" -retrofit = "2.11.0" -retrofit2KotlinxSerializationConverter = "1.0.0" -timber = "5.0.1" compose-multiplatform = "1.7.0" ktor = "3.0.1" androidx-lifecycle = "2.8.3" @@ -41,13 +35,11 @@ kotlinx-collections-immutable = { module = "org.jetbrains.kotlinx:kotlinx-collec kotlinx-datetime = { module = "org.jetbrains.kotlinx:kotlinx-datetime", version.ref = "kotlinxDatetime" } lifecycle-runtime-ktx = { group = "androidx.lifecycle", name = "lifecycle-runtime-ktx", version.ref = "android-lifecycle" } navigation-compose = { module = "org.jetbrains.androidx.navigation:navigation-compose", version.ref = "navigationCompose" } -retrofit2-kotlinx-serialization-converter = { module = "com.jakewharton.retrofit:retrofit2-kotlinx-serialization-converter", version.ref = "retrofit2KotlinxSerializationConverter" } -timber = { module = "com.jakewharton.timber:timber", version.ref = "timber" } kotlinx-coroutines-core = { group = "org.jetbrains.kotlinx", name = "kotlinx-coroutines-core", version.ref = "kotlinxCoroutines" } lifecycle-viewmodel-compose = { module = "org.jetbrains.androidx.lifecycle:lifecycle-viewmodel-compose", version.ref = "lifecycleViewmodelCompose" } androidx-lifecycle-runtime-compose = { group = "org.jetbrains.androidx.lifecycle", name = "lifecycle-runtime-compose", version.ref = "androidx-lifecycle" } - +multiplatform-settings = { module = "com.russhwolf:multiplatform-settings-no-arg", version.ref = "multiplatformSettings" } # DI di-kotlinInjectRuntime = { module = "me.tatarka.inject:kotlin-inject-runtime-kmp", version.ref = "kotlinInject" } @@ -87,7 +79,6 @@ composeCompiler-gradlePlugin = { module = "org.jetbrains.kotlin:compose-compiler [plugins] androidApplication = { id = "com.android.application", version.ref = "agp" } kotlinAndroid = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" } -hilt = { id = "com.google.dagger.hilt.android", version.ref = "hilt" } kotlin-serialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlin" } cash-paparazzi = { id = "app.cash.paparazzi", version.ref = "paparazzi" } ksp = { id = "com.google.devtools.ksp", version.ref = "ksp" } diff --git a/gradlew b/gradlew index f5feea6d..b87e4980 100755 --- a/gradlew +++ b/gradlew @@ -164,7 +164,7 @@ fi # Collect all arguments for the java command, stacking in reverse order: # * args from the command line -# * the main class name +# * the commonMain class name # * -classpath # * -D...appname settings # * --module-path (only if needed) diff --git a/sandook/api/build.gradle.kts b/sandook/api/build.gradle.kts deleted file mode 100644 index 749b3eb4..00000000 --- a/sandook/api/build.gradle.kts +++ /dev/null @@ -1,3 +0,0 @@ -plugins { - alias(libs.plugins.krail.jvm.library) -} diff --git a/sandook/build.gradle.kts b/sandook/build.gradle.kts new file mode 100644 index 00000000..ac296682 --- /dev/null +++ b/sandook/build.gradle.kts @@ -0,0 +1,50 @@ +plugins { + alias(libs.plugins.krail.android.library) + alias(libs.plugins.krail.kotlin.multiplatform) + alias(libs.plugins.krail.compose.multiplatform) + alias(libs.plugins.kotlin.serialization) + alias(libs.plugins.ksp) + alias(libs.plugins.compose.compiler) +} + +android { + namespace = "xyz.ksharma.krail.sandook" +} + +kotlin { + applyDefaultHierarchyTemplate() + + androidTarget() + + iosArm64() + iosSimulatorArm64() + + java { + toolchain { + languageVersion.set(JavaLanguageVersion.of(JavaVersion.VERSION_17.majorVersion)) + } + } + + sourceSets { + commonMain { + dependencies { + implementation(libs.kotlinx.serialization.json) + implementation(libs.di.kotlinInjectRuntime) + implementation(libs.multiplatform.settings) + + implementation(compose.runtime) + } + } + } +} + +dependencies { + // 1. Configure code generation into the common source set + kspCommonMainMetadata(libs.di.kotlinInjectRuntime) + + // 2. Configure code generation into each KMP target source set + //kspAndroid(libs.di.kotlinInjectCompilerKsp) + // kspIosX64("me.tatarka.inject:kotlin-inject-compiler-ksp:0.7.2") + // kspIosArm64("me.tatarka.inject:kotlin-inject-compiler-ksp:0.7.2") + // kspIosSimulatorArm64("me.tatarka.inject:kotlin-inject-compiler-ksp:0.7.2") +} diff --git a/sandook/real/build.gradle.kts b/sandook/real/build.gradle.kts deleted file mode 100644 index 2166ad06..00000000 --- a/sandook/real/build.gradle.kts +++ /dev/null @@ -1,14 +0,0 @@ -plugins { - alias(libs.plugins.krail.android.library) - alias(libs.plugins.krail.android.hilt) -} - -android { - namespace = "xyz.ksharma.krail.sandook.real" -} - -dependencies { - implementation(projects.sandook.api) - - implementation(libs.kotlinx.serialization.json) -} diff --git a/sandook/real/src/main/kotlin/xyz/ksharma/krail/sandook/RealSandook.kt b/sandook/real/src/main/kotlin/xyz/ksharma/krail/sandook/RealSandook.kt deleted file mode 100644 index a0400356..00000000 --- a/sandook/real/src/main/kotlin/xyz/ksharma/krail/sandook/RealSandook.kt +++ /dev/null @@ -1,57 +0,0 @@ -package xyz.ksharma.krail.sandook - -import android.content.SharedPreferences -import javax.inject.Inject - -internal class RealSandook @Inject constructor(private val sharedPreferences: SharedPreferences) : Sandook { - - override fun keys(): Set = sharedPreferences.all.keys - - override fun putString(key: String, value: String) { - sharedPreferences.edit().putString(key, value).apply() - } - - override fun getString(key: String, defaultValue: String?): String? { - return sharedPreferences.getString(key, defaultValue) - } - - override fun putInt(key: String, value: Int) { - sharedPreferences.edit().putInt(key, value).apply() - } - - override fun getInt(key: String, defaultValue: Int): Int { - return sharedPreferences.getInt(key, defaultValue) - } - - override fun putBoolean(key: String, value: Boolean) { - sharedPreferences.edit().putBoolean(key, value).apply() - } - - override fun getBoolean(key: String, defaultValue: Boolean): Boolean { - return sharedPreferences.getBoolean(key, defaultValue) - } - - override fun putFloat(key: String, value: Float) { - sharedPreferences.edit().putFloat(key, value).apply() - } - - override fun getFloat(key: String, defaultValue: Float): Float { - return sharedPreferences.getFloat(key, defaultValue) - } - - override fun putLong(key: String, value: Long) { - sharedPreferences.edit().putLong(key, value).apply() - } - - override fun getLong(key: String, defaultValue: Long): Long { - return sharedPreferences.getLong(key, defaultValue) - } - - override fun remove(key: String) { - sharedPreferences.edit().remove(key).apply() - } - - override fun clear() { - sharedPreferences.edit().clear().apply() - } -} diff --git a/sandook/real/src/main/kotlin/xyz/ksharma/krail/sandook/di/SandookFactory.kt b/sandook/real/src/main/kotlin/xyz/ksharma/krail/sandook/di/SandookFactory.kt deleted file mode 100644 index 329d03a8..00000000 --- a/sandook/real/src/main/kotlin/xyz/ksharma/krail/sandook/di/SandookFactory.kt +++ /dev/null @@ -1,23 +0,0 @@ -package xyz.ksharma.krail.sandook.di - -import android.content.Context -import dagger.hilt.android.qualifiers.ApplicationContext -import xyz.ksharma.krail.sandook.RealSandook -import xyz.ksharma.krail.sandook.Sandook -import javax.inject.Inject -import javax.inject.Singleton - -@Singleton -class SandookFactory @Inject constructor( - @ApplicationContext private val context: Context, -) { - fun create(sandookKey: SandookKey): Sandook { - val sharedPreferences = context.getSharedPreferences(sandookKey.fileName, Context.MODE_PRIVATE) - return RealSandook(sharedPreferences) - } - - enum class SandookKey(val fileName: String) { - SAVED_TRIP("saved_trip"), - THEME("theme"), - } -} diff --git a/sandook/real/src/main/kotlin/xyz/ksharma/krail/sandook/di/SandookModule.kt b/sandook/real/src/main/kotlin/xyz/ksharma/krail/sandook/di/SandookModule.kt deleted file mode 100644 index 2d76da29..00000000 --- a/sandook/real/src/main/kotlin/xyz/ksharma/krail/sandook/di/SandookModule.kt +++ /dev/null @@ -1,18 +0,0 @@ -package xyz.ksharma.krail.sandook.di - -import dagger.Binds -import dagger.Module -import dagger.hilt.InstallIn -import dagger.hilt.components.SingletonComponent -import xyz.ksharma.krail.sandook.RealSandook -import xyz.ksharma.krail.sandook.Sandook -import javax.inject.Singleton - -@Module -@InstallIn(SingletonComponent::class) -abstract class SandookModule { - - @Binds - @Singleton - internal abstract fun bindSandook(impl: RealSandook): Sandook -} diff --git a/sandook/src/commonMain/kotlin/xyz/ksharma/krail/sandook/RealSandook.kt b/sandook/src/commonMain/kotlin/xyz/ksharma/krail/sandook/RealSandook.kt new file mode 100644 index 00000000..43446390 --- /dev/null +++ b/sandook/src/commonMain/kotlin/xyz/ksharma/krail/sandook/RealSandook.kt @@ -0,0 +1,57 @@ +package xyz.ksharma.krail.sandook + +import com.russhwolf.settings.Settings +import me.tatarka.inject.annotations.Inject + +internal class RealSandook @Inject constructor(private val settings: Settings) : Sandook { + + override fun keys(): Set = settings.keys + + override fun putString(key: String, value: String) { + settings.putString(key, value) + } + + override fun getString(key: String, defaultValue: String?): String? { + return settings.getStringOrNull(key) ?: defaultValue + } + + override fun putInt(key: String, value: Int) { + settings.putInt(key, value) + } + + override fun getInt(key: String, defaultValue: Int): Int { + return settings.getInt(key, defaultValue) + } + + override fun putBoolean(key: String, value: Boolean) { + settings.putBoolean(key, value) + } + + override fun getBoolean(key: String, defaultValue: Boolean): Boolean { + return settings.getBoolean(key, defaultValue) + } + + override fun putFloat(key: String, value: Float) { + settings.putFloat(key, value) + } + + override fun getFloat(key: String, defaultValue: Float): Float { + return settings.getFloat(key, defaultValue) + } + + override fun putLong(key: String, value: Long) { + settings.putLong(key, value) + } + + override fun getLong(key: String, defaultValue: Long): Long { + return settings.getLong(key, defaultValue) + } + + override fun remove(key: String) { + settings.remove(key) + } + + override fun clear() { + settings.clear() + } +} diff --git a/sandook/real/src/main/kotlin/xyz/ksharma/krail/sandook/Sandook.kt b/sandook/src/commonMain/kotlin/xyz/ksharma/krail/sandook/Sandook.kt similarity index 98% rename from sandook/real/src/main/kotlin/xyz/ksharma/krail/sandook/Sandook.kt rename to sandook/src/commonMain/kotlin/xyz/ksharma/krail/sandook/Sandook.kt index 719435a3..d9c134ca 100644 --- a/sandook/real/src/main/kotlin/xyz/ksharma/krail/sandook/Sandook.kt +++ b/sandook/src/commonMain/kotlin/xyz/ksharma/krail/sandook/Sandook.kt @@ -2,21 +2,32 @@ package xyz.ksharma.krail.sandook interface Sandook { - fun clear() - fun remove(key: String) + /** + * Returns a set of all keys in the Sandook. + */ + fun keys(): Set + fun getLong(key: String, defaultValue: Long = 0L): Long + fun putLong(key: String, value: Long) + fun getFloat(key: String, defaultValue: Float = 0f): Float + fun putFloat(key: String, value: Float) + fun getBoolean(key: String, defaultValue: Boolean = false): Boolean + fun putBoolean(key: String, value: Boolean) + fun getInt(key: String, defaultValue: Int = 0): Int + fun putInt(key: String, value: Int) + fun getString(key: String, defaultValue: String? = null): String? + fun putString(key: String, value: String) - /** - * Returns a set of all keys in the Sandook. - */ - fun keys(): Set + fun clear() + + fun remove(key: String) } diff --git a/sandook/src/commonMain/kotlin/xyz/ksharma/krail/sandook/di/SandookComponent.kt b/sandook/src/commonMain/kotlin/xyz/ksharma/krail/sandook/di/SandookComponent.kt new file mode 100644 index 00000000..2cbdce2a --- /dev/null +++ b/sandook/src/commonMain/kotlin/xyz/ksharma/krail/sandook/di/SandookComponent.kt @@ -0,0 +1,19 @@ +package xyz.ksharma.krail.sandook.di + +import com.russhwolf.settings.Settings +import me.tatarka.inject.annotations.Component +import me.tatarka.inject.annotations.Provides +import xyz.ksharma.krail.sandook.RealSandook +import xyz.ksharma.krail.sandook.Sandook + +@Component +abstract class SandookComponent { + abstract val sandook: Sandook + + // Settings() does not provide encryption use dependency without no-arg if encryption is required. + @Provides + fun provideSettings(): Settings = Settings() + + @Provides + fun provideSandook(settings: Settings): Sandook = RealSandook(settings) +} diff --git a/settings.gradle.kts b/settings.gradle.kts index 70fe0863..f4bf466d 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -41,7 +41,4 @@ include(":feature:trip-planner:ui") include(":feature:trip-planner:state") include(":feature:trip-planner:network:api") include(":feature:trip-planner:network:real") -/* -include(":sandook:api") -include(":sandook:real") -*/ +include(":sandook") diff --git a/taj/build.gradle.kts b/taj/build.gradle.kts index c84628a2..422d673f 100644 --- a/taj/build.gradle.kts +++ b/taj/build.gradle.kts @@ -30,6 +30,7 @@ kotlin { implementation(compose.animation) implementation(compose.ui) implementation(compose.material3) + implementation(compose.runtime) } } } From 4fd01f5e7cfe2e4e04f599d095daa7d78bf4f160 Mon Sep 17 00:00:00 2001 From: Karan Sharma <55722391+ksharma-xyz@users.noreply.github.com> Date: Sun, 17 Nov 2024 22:06:37 +1100 Subject: [PATCH 35/67] Using RealSandook directly -temp --- composeApp/build.gradle.kts | 2 ++ .../kotlin/xyz/ksharma/krail/common/KrailApp.kt | 17 ++++++++++++++++- .../xyz/ksharma/krail/network/APIClient.kt | 2 ++ sandook/build.gradle.kts | 4 ++++ .../xyz/ksharma/krail/sandook/RealSandook.kt | 3 +-- .../krail/sandook/di/SandookComponent.kt | 2 ++ 6 files changed, 27 insertions(+), 3 deletions(-) diff --git a/composeApp/build.gradle.kts b/composeApp/build.gradle.kts index 10da5592..29be056a 100644 --- a/composeApp/build.gradle.kts +++ b/composeApp/build.gradle.kts @@ -65,6 +65,8 @@ kotlin { implementation(compose.components.resources) implementation(compose.components.uiToolingPreview) implementation(libs.androidx.lifecycle.runtime.compose) + + implementation(libs.multiplatform.settings) } } } diff --git a/composeApp/src/commonMain/kotlin/xyz/ksharma/krail/common/KrailApp.kt b/composeApp/src/commonMain/kotlin/xyz/ksharma/krail/common/KrailApp.kt index a28c09b8..65d8bdc6 100644 --- a/composeApp/src/commonMain/kotlin/xyz/ksharma/krail/common/KrailApp.kt +++ b/composeApp/src/commonMain/kotlin/xyz/ksharma/krail/common/KrailApp.kt @@ -7,7 +7,9 @@ import androidx.compose.foundation.layout.statusBarsPadding import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.ui.Modifier -import xyz.ksharma.krail.sandook.di.SandookComponent +import com.russhwolf.settings.Settings +import kotlinx.coroutines.delay +import xyz.ksharma.krail.sandook.RealSandook import xyz.ksharma.krail.taj.components.Text import xyz.ksharma.krail.taj.theme.KrailTheme @@ -16,8 +18,21 @@ fun KrailApp() { KrailTheme { LaunchedEffect(Unit) { + val sandook = RealSandook(Settings()) + println("sandook: " + sandook) + sandook.putString("key", "value") + + delay(2000) + val sandook1 = RealSandook(Settings()) + println("sandook1: " + sandook1) + var x = sandook1.getString("key") + println("Sandook1 value: x: $x") + x = sandook.getString("key") + println("Sandook value: x: $x") } + + Column(modifier = Modifier.fillMaxSize().background(color = KrailTheme.colors.surface)) { Text( "Hello, Krail!", diff --git a/core/network/src/commonMain/kotlin/xyz/ksharma/krail/network/APIClient.kt b/core/network/src/commonMain/kotlin/xyz/ksharma/krail/network/APIClient.kt index 87a41f19..8fcffcd1 100644 --- a/core/network/src/commonMain/kotlin/xyz/ksharma/krail/network/APIClient.kt +++ b/core/network/src/commonMain/kotlin/xyz/ksharma/krail/network/APIClient.kt @@ -14,6 +14,8 @@ class ApiClient { private val client = HttpClient { install(ContentNegotiation) { json(Json { + prettyPrint = true + isLenient = true ignoreUnknownKeys = true // To avoid crashes if the API sends extra fields }) } diff --git a/sandook/build.gradle.kts b/sandook/build.gradle.kts index ac296682..6ad2030b 100644 --- a/sandook/build.gradle.kts +++ b/sandook/build.gradle.kts @@ -48,3 +48,7 @@ dependencies { // kspIosArm64("me.tatarka.inject:kotlin-inject-compiler-ksp:0.7.2") // kspIosSimulatorArm64("me.tatarka.inject:kotlin-inject-compiler-ksp:0.7.2") } + +ksp { + arg("me.tatarka.inject.generateCompanionExtensions", "true") +} diff --git a/sandook/src/commonMain/kotlin/xyz/ksharma/krail/sandook/RealSandook.kt b/sandook/src/commonMain/kotlin/xyz/ksharma/krail/sandook/RealSandook.kt index 43446390..39912730 100644 --- a/sandook/src/commonMain/kotlin/xyz/ksharma/krail/sandook/RealSandook.kt +++ b/sandook/src/commonMain/kotlin/xyz/ksharma/krail/sandook/RealSandook.kt @@ -1,9 +1,8 @@ package xyz.ksharma.krail.sandook import com.russhwolf.settings.Settings -import me.tatarka.inject.annotations.Inject -internal class RealSandook @Inject constructor(private val settings: Settings) : Sandook { +class RealSandook (private val settings: Settings) : Sandook { override fun keys(): Set = settings.keys diff --git a/sandook/src/commonMain/kotlin/xyz/ksharma/krail/sandook/di/SandookComponent.kt b/sandook/src/commonMain/kotlin/xyz/ksharma/krail/sandook/di/SandookComponent.kt index 2cbdce2a..7f770585 100644 --- a/sandook/src/commonMain/kotlin/xyz/ksharma/krail/sandook/di/SandookComponent.kt +++ b/sandook/src/commonMain/kotlin/xyz/ksharma/krail/sandook/di/SandookComponent.kt @@ -16,4 +16,6 @@ abstract class SandookComponent { @Provides fun provideSandook(settings: Settings): Sandook = RealSandook(settings) + + companion object } From eef292956b1a818302bcdce3a89e74d709661fb5 Mon Sep 17 00:00:00 2001 From: Karan Sharma <55722391+ksharma-xyz@users.noreply.github.com> Date: Mon, 18 Nov 2024 11:23:59 +1100 Subject: [PATCH 36/67] Update TitleBar - rebase main --- .../design/system/components/TitleBar.kt | 150 ------------------ .../ksharma/krail/taj/components/TitleBar.kt | 13 +- 2 files changed, 11 insertions(+), 152 deletions(-) delete mode 100644 core/design-system/src/main/kotlin/xyz/ksharma/krail/design/system/components/TitleBar.kt diff --git a/core/design-system/src/main/kotlin/xyz/ksharma/krail/design/system/components/TitleBar.kt b/core/design-system/src/main/kotlin/xyz/ksharma/krail/design/system/components/TitleBar.kt deleted file mode 100644 index 05dbb4c7..00000000 --- a/core/design-system/src/main/kotlin/xyz/ksharma/krail/design/system/components/TitleBar.kt +++ /dev/null @@ -1,150 +0,0 @@ -package xyz.ksharma.krail.design.system.components - -import androidx.compose.foundation.Image -import androidx.compose.foundation.background -import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.heightIn -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.size -import androidx.compose.foundation.layout.statusBarsPadding -import androidx.compose.foundation.shape.CircleShape -import androidx.compose.runtime.Composable -import androidx.compose.runtime.CompositionLocalProvider -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.draw.clip -import androidx.compose.ui.graphics.ColorFilter -import androidx.compose.ui.res.painterResource -import androidx.compose.ui.tooling.preview.Preview -import androidx.compose.ui.unit.dp -import xyz.ksharma.krail.taj.LocalContentColor -import xyz.ksharma.krail.taj.LocalTextColor -import xyz.ksharma.krail.taj.LocalTextStyle -import xyz.ksharma.krail.taj.theme.KrailTheme - -@Composable -fun TitleBar( - title: @Composable () -> Unit, - modifier: Modifier = Modifier, - navAction: @Composable (() -> Unit)? = null, - actions: @Composable (() -> Unit)? = null, -) { - Row( - modifier = modifier - .statusBarsPadding() - .fillMaxWidth() - .heightIn(min = 56.dp) - .padding(end = 16.dp, start = 8.dp) - .padding(vertical = 4.dp), - horizontalArrangement = Arrangement.SpaceBetween, - verticalAlignment = Alignment.CenterVertically, - ) { - navAction?.let { - navAction() - } - Row( - modifier = Modifier - .weight(1f) - .padding(start = 10.dp), - ) { - CompositionLocalProvider( - LocalTextColor provides KrailTheme.colors.onSurface, - LocalTextStyle provides KrailTheme.typography.headlineMedium, - ) { - title() - } - } - actions?.let { - Row( - modifier = Modifier.padding(start = 16.dp), - horizontalArrangement = Arrangement.spacedBy(8.dp), - ) { - CompositionLocalProvider( - LocalContentColor provides KrailTheme.colors.onSurface, - ) { - actions() - } - } - } - } -} - -// region Previews - -@Composable -private fun TitleBarPreview() { - KrailTheme { - TitleBar( - title = { - Text(text = "Saved Trips Screen Title") - }, - actions = { - Image( - painter = painterResource(id = R.drawable.star_outline), - contentDescription = null, - modifier = Modifier.size(24.dp), - colorFilter = ColorFilter.tint(LocalContentColor.current), - ) - }, - modifier = Modifier.background(color = KrailTheme.colors.surface), - ) - } -} - -@Preview -@Composable -private fun TitleBarPreviewMultipleActions() { - KrailTheme { - TitleBar( - title = { - Text(text = "Saved Trips Screen Title") - }, - actions = { - Box( - modifier = Modifier - .size(40.dp) - .clip(CircleShape), - contentAlignment = Alignment.Center, - ) { - Image( - painter = painterResource(id = R.drawable.star_outline), - contentDescription = null, - modifier = Modifier.size(24.dp), - colorFilter = ColorFilter.tint(LocalContentColor.current), - ) - } - Box( - modifier = Modifier - .size(40.dp) - .clip(CircleShape), - contentAlignment = Alignment.Center, - ) { - Image( - painter = painterResource(id = R.drawable.star_outline), - contentDescription = null, - modifier = Modifier.size(24.dp), - colorFilter = ColorFilter.tint(LocalContentColor.current), - ) - } - }, - modifier = Modifier.background(color = KrailTheme.colors.surface), - ) - } -} - -@Composable -private fun TitleBarPreviewNoActions() { - KrailTheme { - TitleBar( - title = { - Text(text = "Saved Trips Screen Title") - }, - modifier = Modifier.background(color = KrailTheme.colors.surface), - ) - } -} - -// endregion diff --git a/taj/src/commonMain/kotlin/xyz/ksharma/krail/taj/components/TitleBar.kt b/taj/src/commonMain/kotlin/xyz/ksharma/krail/taj/components/TitleBar.kt index 220b19cc..c0ad1772 100644 --- a/taj/src/commonMain/kotlin/xyz/ksharma/krail/taj/components/TitleBar.kt +++ b/taj/src/commonMain/kotlin/xyz/ksharma/krail/taj/components/TitleBar.kt @@ -20,6 +20,7 @@ import xyz.ksharma.krail.taj.theme.KrailTheme fun TitleBar( title: @Composable () -> Unit, modifier: Modifier = Modifier, + navAction: @Composable (() -> Unit)? = null, actions: @Composable (() -> Unit)? = null, ) { Row( @@ -27,11 +28,19 @@ fun TitleBar( .statusBarsPadding() .fillMaxWidth() .heightIn(min = 56.dp) - .padding(horizontal = 16.dp, vertical = 4.dp), + .padding(end = 16.dp, start = 8.dp) + .padding(vertical = 4.dp), horizontalArrangement = Arrangement.SpaceBetween, verticalAlignment = Alignment.CenterVertically, ) { - Row(modifier = Modifier.weight(1f)) { + navAction?.let { + navAction() + } + Row( + modifier = Modifier + .weight(1f) + .padding(start = 10.dp), + ) { CompositionLocalProvider( LocalTextColor provides KrailTheme.colors.onSurface, LocalTextStyle provides KrailTheme.typography.headlineMedium, From 8e83e5eaf97977ed10bfe0248e97ba2e604080c6 Mon Sep 17 00:00:00 2001 From: Karan Sharma <55722391+ksharma-xyz@users.noreply.github.com> Date: Mon, 18 Nov 2024 23:05:21 +1100 Subject: [PATCH 37/67] Add HttpClient and TripRequest --- .../xyz/ksharma/krail/network/APIClient.kt | 44 --- .../trip-planner/network/api/build.gradle.kts | 58 --- .../api/repository/TripPlanningRepository.kt | 31 -- .../api/service/TripPlanningService.kt | 349 ------------------ .../trip-planner}/network/build.gradle.kts | 48 +-- .../network/real/build.gradle.kts | 64 ---- .../network/real/di/TripPlanningModule.kt | 31 -- .../repository/RealTripPlanningRepository.kt | 45 --- .../network/api/model/StopFinderResponse.kt | 0 .../planner/network/api/model/StopType.kt | 0 .../planner/network/api/model/TripResponse.kt | 0 .../api/ratelimit/NetworkRateLimiter.kt} | 10 +- .../network/api/ratelimit}/RateLimiter.kt | 2 +- .../planner/network/api/service/HttpClient.kt | 31 ++ .../api/service/TripPlanningService.kt | 33 ++ .../service/stop_finder/StopFinderRequest.kt | 37 ++ .../stop_finder/StopFinderRequestParams.kt | 60 +++ .../network/api/service/trip/TripRequest.kt | 56 +++ .../api/service/trip/TripRequestParams.kt | 237 ++++++++++++ .../assets/hige_multiple_legs_trip.json | 0 .../src/commonTest/assets/multiple_legs | 0 .../commonTest/assets/stop_finder_any.json | 0 .../commonTest/assets/stop_finder_stop.json | 0 .../{api => }/src/commonTest/assets/trip.json | 0 .../src/commonTest/assets/trip_metro_bus.json | 0 .../assets/trip_occupancy_platform.json | 0 .../assets/trip_sevenhills_townhall.json | 0 .../kotlin}/ratelimit/APIRateLimiterTest.kt | 2 +- settings.gradle.kts | 4 +- 29 files changed, 482 insertions(+), 660 deletions(-) delete mode 100644 core/network/src/commonMain/kotlin/xyz/ksharma/krail/network/APIClient.kt delete mode 100644 feature/trip-planner/network/api/build.gradle.kts delete mode 100644 feature/trip-planner/network/api/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/network/api/repository/TripPlanningRepository.kt delete mode 100644 feature/trip-planner/network/api/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/network/api/service/TripPlanningService.kt rename {core => feature/trip-planner}/network/build.gradle.kts (80%) delete mode 100644 feature/trip-planner/network/real/build.gradle.kts delete mode 100644 feature/trip-planner/network/real/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/network/real/di/TripPlanningModule.kt delete mode 100644 feature/trip-planner/network/real/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/network/real/repository/RealTripPlanningRepository.kt rename feature/trip-planner/network/{api => }/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/network/api/model/StopFinderResponse.kt (100%) rename feature/trip-planner/network/{api => }/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/network/api/model/StopType.kt (100%) rename feature/trip-planner/network/{api => }/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/network/api/model/TripResponse.kt (100%) rename feature/trip-planner/network/{real/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/network/real/ratelimit/APIRateLimiter.kt => src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/network/api/ratelimit/NetworkRateLimiter.kt} (87%) rename feature/trip-planner/network/{api/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/network/api => src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/network/api/ratelimit}/RateLimiter.kt (92%) create mode 100644 feature/trip-planner/network/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/network/api/service/HttpClient.kt create mode 100644 feature/trip-planner/network/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/network/api/service/TripPlanningService.kt create mode 100644 feature/trip-planner/network/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/network/api/service/stop_finder/StopFinderRequest.kt create mode 100644 feature/trip-planner/network/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/network/api/service/stop_finder/StopFinderRequestParams.kt create mode 100644 feature/trip-planner/network/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/network/api/service/trip/TripRequest.kt create mode 100644 feature/trip-planner/network/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/network/api/service/trip/TripRequestParams.kt rename feature/trip-planner/network/{api => }/src/commonTest/assets/hige_multiple_legs_trip.json (100%) rename feature/trip-planner/network/{api => }/src/commonTest/assets/multiple_legs (100%) rename feature/trip-planner/network/{api => }/src/commonTest/assets/stop_finder_any.json (100%) rename feature/trip-planner/network/{api => }/src/commonTest/assets/stop_finder_stop.json (100%) rename feature/trip-planner/network/{api => }/src/commonTest/assets/trip.json (100%) rename feature/trip-planner/network/{api => }/src/commonTest/assets/trip_metro_bus.json (100%) rename feature/trip-planner/network/{api => }/src/commonTest/assets/trip_occupancy_platform.json (100%) rename feature/trip-planner/network/{api => }/src/commonTest/assets/trip_sevenhills_townhall.json (100%) rename feature/trip-planner/network/{real/src/commonTest/kotlin/xyz/ksharma/krail/trip/planner/network/real => src/commonTest/kotlin}/ratelimit/APIRateLimiterTest.kt (96%) diff --git a/core/network/src/commonMain/kotlin/xyz/ksharma/krail/network/APIClient.kt b/core/network/src/commonMain/kotlin/xyz/ksharma/krail/network/APIClient.kt deleted file mode 100644 index 8fcffcd1..00000000 --- a/core/network/src/commonMain/kotlin/xyz/ksharma/krail/network/APIClient.kt +++ /dev/null @@ -1,44 +0,0 @@ -package xyz.ksharma.krail.network - -import io.ktor.client.HttpClient -import io.ktor.client.call.body -import io.ktor.client.plugins.contentnegotiation.ContentNegotiation -import io.ktor.client.request.get -import io.ktor.client.request.headers -import io.ktor.http.HttpHeaders -import io.ktor.serialization.kotlinx.json.json -import kotlinx.serialization.Serializable -import kotlinx.serialization.json.Json - -class ApiClient { - private val client = HttpClient { - install(ContentNegotiation) { - json(Json { - prettyPrint = true - isLenient = true - ignoreUnknownKeys = true // To avoid crashes if the API sends extra fields - }) - } - } - - suspend fun getApiResponse(apiKey: String): ApiResponse { - return client.get("https://api.example.com/data") { - headers { - append("Authorization", "apikey $apiKey") // Example of Authorization header - append(HttpHeaders.Accept, "application/json") // Accept JSON response - } - - url { - parameters.append("key", "value") - parameters.append("search", "query") - } - }.body() - } -} - -@Serializable -data class ApiResponse( - val id: Int, - val name: String, - val details: String, -) diff --git a/feature/trip-planner/network/api/build.gradle.kts b/feature/trip-planner/network/api/build.gradle.kts deleted file mode 100644 index 2c4ce51f..00000000 --- a/feature/trip-planner/network/api/build.gradle.kts +++ /dev/null @@ -1,58 +0,0 @@ -android { - namespace = "xyz.ksharma.krail.trip.planner.network.api" -} - -plugins { - alias(libs.plugins.krail.android.library) - alias(libs.plugins.krail.kotlin.multiplatform) - alias(libs.plugins.kotlin.serialization) - alias(libs.plugins.ksp) -} - -kotlin { - applyDefaultHierarchyTemplate() - - androidTarget() - iosArm64() - iosSimulatorArm64() - - sourceSets { - androidMain.dependencies { - implementation(libs.ktor.client.okhttp) - } - - commonMain { - dependencies { - implementation(projects.core.coroutinesExt) - api(projects.core.di) - api(projects.core.network) - - implementation(libs.di.kotlinInjectRuntime) - implementation(libs.kotlinx.serialization.json) - implementation(libs.ktor.client.core) - implementation(libs.ktor.client.cio) - implementation(libs.ktor.client.content.negotiation) - implementation(libs.ktor.client.logging) - implementation(libs.ktor.serialization.kotlinx.json) - implementation(libs.kotlinx.datetime) - } - } - - iosMain { - dependencies { - implementation(libs.ktor.client.darwin) - } - } - } -} - -dependencies { - // 1. Configure code generation into the common source set - kspCommonMainMetadata(libs.di.kotlinInjectRuntime) - - // 2. Configure code generation into each KMP target source set - //kspAndroid(libs.di.kotlinInjectCompilerKsp) - // kspIosX64("me.tatarka.inject:kotlin-inject-compiler-ksp:0.7.2") - // kspIosArm64("me.tatarka.inject:kotlin-inject-compiler-ksp:0.7.2") - // kspIosSimulatorArm64("me.tatarka.inject:kotlin-inject-compiler-ksp:0.7.2") -} diff --git a/feature/trip-planner/network/api/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/network/api/repository/TripPlanningRepository.kt b/feature/trip-planner/network/api/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/network/api/repository/TripPlanningRepository.kt deleted file mode 100644 index 385565bb..00000000 --- a/feature/trip-planner/network/api/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/network/api/repository/TripPlanningRepository.kt +++ /dev/null @@ -1,31 +0,0 @@ -package xyz.ksharma.krail.trip.planner.network.api.repository - -import xyz.ksharma.krail.trip.planner.network.api.model.StopFinderResponse -import xyz.ksharma.krail.trip.planner.network.api.model.StopType -import xyz.ksharma.krail.trip.planner.network.api.model.TripResponse -import kotlinx.datetime.Instant -import kotlinx.datetime.TimeZone -import kotlinx.datetime.toLocalDateTime - -interface TripPlanningRepository { - - suspend fun stopFinder( - stopType: StopType = StopType.STOP, - stopSearchQuery: String, - ): Result - - suspend fun trip( - originStopId: String, - destinationStopId: String, - journeyTime: String? = null, - ): Result -} - -/** - * Converts Instant to ITD Time in HHMM 24-hour format. - * // todo - move to another module for time related functions - */ -fun Instant.toItdTime(): String { - val localDateTime = this.toLocalDateTime(TimeZone.of("Australia/Sydney")) - return localDateTime.hour.toString().padStart(2, '0') + localDateTime.minute.toString().padStart(2, '0') -} diff --git a/feature/trip-planner/network/api/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/network/api/service/TripPlanningService.kt b/feature/trip-planner/network/api/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/network/api/service/TripPlanningService.kt deleted file mode 100644 index b2ed8382..00000000 --- a/feature/trip-planner/network/api/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/network/api/service/TripPlanningService.kt +++ /dev/null @@ -1,349 +0,0 @@ -package xyz.ksharma.krail.trip.planner.network.api.service - -import xyz.ksharma.krail.trip.planner.network.api.model.TripResponse - -/** - * Swagger: https://opendata.transport.nsw.gov.au/dataset/trip-planner-apis/resource/917c66c3-8123-4a0f-b1b1-b4220f32585d - */ -interface TripPlanningService { - - /** - * This endpoint returns info about stops that match the search criteria. Matches can be - * sorted on matchQuality to determine the best matches for the given input, while the best - * match will be indicated by the isBest value. - * - * Provides capability to return all NSW public transport stop, station, wharf, - * points of interest and known addresses to be used for auto-suggest/auto-complete (to be - * used with the Trip planner and Departure board APIs). - */ - //@GET("v1/tp/stop_finder") - suspend fun stopFinder( - /** - * Used to set the response data type. This documentation only covers responses that use the JSON format. - * Setting the outputFormat value to rapidJSON is required to enable JSON output. - * - * Available values : rapidJSON - */ - // @Query("outputFormat") outputFormat: String = "rapidJSON", - - /** - * This specifies the type of results expected in the list of returned stops. - * By specifying `any`, locations of all types can be returned. - * If you specifically know that you're searching using a coord, specify `coord`. - * Likewise, if you're using a stop ID or global stop ID as an input, use `stop` - * for more accurate results. - * - * Available values : any, coord, poi, stop - */ - // @Query("type_sf") typeSf: String = "stop", - - /** - * This is the search term that will be used to find locations. - * To lookup a coordinate, set type_sf to coord, and use the following format: - * LONGITUDE:LATITUDE:EPSG:4326 (Note that longitude is first). For example, 151.206290:-33.884080:EPSG:4326. - * To lookup a stop set type_sf to stop and enter the stop id or global stop ID. For example, 10101100 - * - * Default value : Circular Quay - */ - // @Query("name_sf") nameSf: String, - - /** - * This specifies the format the coordinates are returned in. - * While other variations are available, the EPSG:4326 format will return the widely-used format. - * - * Available values : EPSG:4326 - */ - // @Query("coordOutputFormat") coordOutputFormat: String = "EPSG:4326", - - /** - * Including this parameter enables a number of options that result in the stop finder - * operating in the same way as the Transport for NSW Trip Planner web site. - * - * Available values : true - * - * Default value : true - */ - // @Query("TfNSWSF") tfNSWSF: String = "true", - - /** - * Indicates which version of the API the caller is expecting for both request and response - * data. Note that if this version differs - * from the version listed above then the returned data may not be as expected. - * - * Default value : 10.2.1.42 - */ - // @Query("version") version: String? = null, - )//: Response - - /** - * This endpoint is used to find a list of journeys between two locations at the specified - * date and time. For example, if the user is at the Airport and wants to get to Manly using - * public transport but isn't sure how exactly, this call will tell them exactly which train, - * bus, ferry or light rail to catch, and between which stops. It is extremely detailed, - * and includes the the specific path the vehicle(s) will take. - * - * Provides capability to provide NSW public transport trip plan options, - * including walking and driving legs, real-time and Opal fare information. - */ - // @GET("v1/tp/trip") - suspend fun trip( - /** - * Used to set the response data type. This documentation only covers responses that use - * the JSON format. Setting the outputFormat value to rapidJSON is required to enable JSON output. - */ - // @Query("outputFormat") outputFormat: String = "rapidJSON", - - /** - * This specifies the format the coordinates are returned in. While other variations are - * available, the EPSG:4326 format will return the widely-used format. - */ - @Query("coordOutputFormat") coordOutputFormat: String = "EPSG:4326", - - /** - * This value anchors the requested date time. If set to dep, then trips departing after - * the specified date/time at the specified location are included. - * - * If set to arr, then trips arriving before the specified time at its destination stop are included. - * Works in conjunctions with the [itdDate] and [itdTime] values. - * - * "dep" or "arr" - */ - @Query("depArrMacro") depArrMacro: String, - - /** - * The reference date used when searching trips, in YYYYMMDD format. - * For instance, 20160901 refers to 1 September 2016. Works in conjunction with the - * [itdTime] and [depArrMacro] values. If not specified, the current server date is used. - * - * Optional, default to null if not provided - */ - @Query("itdDate") itdDate: String? = null, - - /** - * The reference time used when searching trips, in HHMM 24-hour format. - * For instance, 2215 refers to 10:15 PM. - * Works in conjunction with the [itdDate] and [depArrMacro] values. - * - * If not specified, the current server time is used. - * - * Optional, default to null if not provided - */ - @Query("itdTime") itdTime: String? = null, - - /** - * This is the type of data specified in the name_origin field. The origin indicates the - * starting point when searching for journeys. - * The best way to use the trip planner is to use use any for this field then specify a - * valid location ID in type_origin, or to use coord - * in this field and a correctly formatted coordinate in type_origin. - * - * Available values : any, coord - * - * Default value : any - */ - @Query("type_origin") typeOrigin: String = "any", - - /** - * This value is used to indicate the starting point when searching for journeys. - * This value can be one of three things: - * A valid location/stop ID (for example, 10101100 indicates Central Station - this can be - * determined using stop_finder). - * - * A valid global stop ID (for example, 200060 indicates Central Station - this can be - * determined using stop_finder) Coordinates in the format LONGITUDE:LATITUDE:EPSG:4326 - * (Note that longitude is first). - * - * Default value : 10101331 - */ - @Query("name_origin") nameOrigin: String, - - /** - * This is the type of data specified in the name_destination field. The origin indicates - * the finishing point when searching for journeys. The best way to use the trip planner - * is to use use any for this field then specify a valid location ID in type_destination, - * or to use coord in this field and a correctly formatted coordinate in type_destination. - * - * Available values : any, coord - * - * Default value : any - */ - @Query("type_destination") typeDestination: String = "any", - - /** - * his value is used to indicate the finishing point when searching for journeys. - * This value can be one of three things: - * A valid location/stop ID (for example, 10101100 indicates Central Station - this can be - * determined using [stopFinder]). - * - * A valid global stop ID (for example, 200060 indicates Central Station - this can be - * determined using [stopFinder]) - * Coordinates in the format LONGITUDE:LATITUDE:EPSG:4326 (Note that longitude is first). - * - * Default value : 10102027 - */ - @Query("name_destination") nameDestination: String, // Destination location or coordinates - - /** - * This parameter indicates the maximum number of trips to returned. Fewer trips may be returned anyway, - * depending on the available public transport services. - * - * Default value : 6 - */ - @Query("calcNumberOfTrips") calcNumberOfTrips: Int = 6, - - /** - * Including this parameter (regardless of its value) ensures that only wheelchair-accessible - * options are returned. - * - * Available values : on - */ - @Query("wheelchair") wheelchair: String? = null, // Optional, used for wheelchair accessible trips - - /** - * This parameter which means of transport to exclude from the trip plan. - * To exclude one means, - * select one of the following: 1 = train, 2 = metro, 4 = light rail, 5 = bus, 7 = coach, - * 9 = ferry, 11 = school bus. - * `checkbox` allows you to exclude more than one means of transport when used in conjunction - * with the exclMOT_ parameters. - * - * Available values : checkbox, 1, 2, 4, 5, 7, 9, 11 - */ - @Query("excludedMeans") excludedMeans: String? = null, // Optional, to exclude specific transport modes - - /** - * Excludes train services from the trip plan. Must be used in conjunction with - * [excludedMeans]=checkbox - * - * Available values : 1 - */ - @Query("exclMOT_1") exclMOT1: String? = null, // Optional, to exclude trains - - /** - * Excludes metro services from the trip plan. Must be used in conjunction with - * [excludedMeans]=checkbox - * - * Available values : 1 - */ - @Query("exclMOT_2") exclMOT2: String? = null, // Optional, to exclude metro - - /** - * Excludes light rail services from the trip plan. Must be used in conjunction with - * [excludedMeans]=checkbox - * - * Available values : 1 - */ - @Query("exclMOT_4") exclMOT4: String? = null, // Optional, to exclude light rail - - /** - * Excludes bus services from the trip plan. Must be used in conjunction with - * [excludedMeans]=checkbox - * - * Available values : 1 - */ - @Query("exclMOT_5") exclMOT5: String? = null, // Optional, to exclude bus - - /** - * Excludes coach services from the trip plan. Must be used in conjunction with - * [excludedMeans]=checkbox - * - * Available values : 1 - */ - @Query("exclMOT_7") exclMOT7: String? = null, // Optional, to exclude bus - - /** - * Excludes ferry services from the trip plan. Must be used in conjunction with - * [excludedMeans]=checkbox - * - * Available values : 1 - */ - @Query("exclMOT_9") exclMOT9: String? = null, // Optional, to exclude bus - - /** - * Excludes school bus services from the trip plan. Must be used in conjunction with - * [excludedMeans]=checkbox - * - * Available values : 1 - */ - @Query("exclMOT_11") exclMOT11: String? = null, // Optional, to exclude bus - - /** - * Including this parameter enables a number of options that result in this API call - * operating in the same way as the Transport for NSW Trip Planner web site, - * including enabling real-time data. - * - * Available values : true - * - * Default value : true - */ - @Query("TfNSWTR") tfNSWTR: String = "true", // Enables real-time data - - /** - * Indicates which version of the API the caller is expecting for both request and response - * data. Note that if this version differs from the version listed above then the returned - * data may not be as expected. - * - * Default value : 10.2.1.42 - */ - @Query("version") version: String = "10.2.1.42", // API version - - /** - * This parameter activates the options for individual transport. If the parameter is - * disabled, the parameters concerning individual transport will not be taken into account. - * possible values are 0 and 1 - * - * Default value : 1 - */ - @Query("itOptionsActive") itOptionsActive: Int = 1, // Activates individual transport options - - /** - * Activates the calculation of a monomodal trip, i.e., a trip that takes place exclusively - * with the means of transport , e.g., with bicycle. - * - * Note 1: In order to use this parameter, the options for individual transport must be - * activated with itOptionsActive=1. - * - * Note 2: If no monomodal trip with the means of transport is calculated despite the - * parameter, the maximum time is often set too low. The parameter MaxITTime applies to - * all means of transport, the parameter MaxITTimeto the means of transport (e.g., MaxITTime107). - * These parameters are located in the [Parameters] section or are added to it. - * The configuration can be alternatively overridden by the [maxTime] parameter. - * - */ - @Query("computeMonomodalTripBicycle") computeMonomodalTripBicycle: Boolean = false, // Bike only trip - - @Query("cycleSpeed") cycleSpeed: Int = 16, // Cycling speed in km/h - - @Query("useElevationData") useElevationData: Int = 1, // Takes elevation data into account - - @Query("elevFac") elevFac: Int? = null, // Optional elevation factor - ): Response - - /** - * This endpoint returns a list of departures for a given location based on the date and time - * specified. This data can be used to display a "upcoming departures" board for a stop. - * - * Provides capability to provide NSW public transport departure information - * from a stop, station or wharf including real-time. - */ - @GET("v1/tp/departure_mon") - suspend fun departure() - - /** - * This endpoint returns a list of service alerts or additional information about travelling - * on the public transport network. This list can be filtered by date, route type, route, - * operator or stop. - * - * Provides capability to display all public transport service status and - * incident information (as published from the Incident Capture System). - */ - @GET("v1/tp/add_info") - suspend fun serviceAlert() - - /** - * When given a specific geographical location, this API finds public transport stops, - * stations, wharfs and points of interest around that location. - */ - @GET("v1/tp/coord") - suspend fun coordinateRequest() -} diff --git a/core/network/build.gradle.kts b/feature/trip-planner/network/build.gradle.kts similarity index 80% rename from core/network/build.gradle.kts rename to feature/trip-planner/network/build.gradle.kts index c500fab5..679a3e7a 100644 --- a/core/network/build.gradle.kts +++ b/feature/trip-planner/network/build.gradle.kts @@ -1,12 +1,29 @@ import java.util.Properties +android { + namespace = "xyz.ksharma.krail.trip.planner.network" + + buildTypes { + debug { + buildConfigField("String", "NSW_TRANSPORT_API_KEY", "\"$nswTransportApiKey\"") + } + + release { + buildConfigField("String", "NSW_TRANSPORT_API_KEY", "\"$nswTransportApiKey\"") + } + } +} + plugins { alias(libs.plugins.krail.android.library) alias(libs.plugins.krail.kotlin.multiplatform) + alias(libs.plugins.krail.compose.multiplatform) + alias(libs.plugins.compose.compiler) alias(libs.plugins.kotlin.serialization) alias(libs.plugins.ksp) } + // Get local.properties values val localProperties = Properties() val localPropertiesFile = rootProject.file("local.properties") @@ -15,33 +32,6 @@ if (localPropertiesFile.exists()) { } val nswTransportApiKey: String = localProperties.getProperty("NSW_TRANSPORT_API_KEY", "") -android { - namespace = "xyz.ksharma.krail.network" - - buildTypes { - debug { - buildConfigField("String", "NSW_TRANSPORT_API_KEY", "\"$nswTransportApiKey\"") - } - - release { - buildConfigField("String", "NSW_TRANSPORT_API_KEY", "\"$nswTransportApiKey\"") - } - } -} - -/* -dependencies { - api(projects.core.di) - implementation(projects.core.coroutinesExt) - implementation(libs.test.androidxCoreKtx) - implementation(libs.kotlinx.serialization.json) - implementation(platform(libs.okhttp.bom)) - implementation(libs.okhttp) - implementation(libs.okhttp.logging.interceptor) - implementation(libs.retrofit) - implementation(libs.retrofit2.kotlinx.serialization.converter) -} -*/ kotlin { applyDefaultHierarchyTemplate() @@ -67,7 +57,9 @@ kotlin { implementation(libs.ktor.client.content.negotiation) implementation(libs.ktor.client.logging) implementation(libs.ktor.serialization.kotlinx.json) - //implementation(libs.kotlinx.coroutines.core) + implementation(libs.kotlinx.datetime) + + implementation(compose.runtime) } } diff --git a/feature/trip-planner/network/real/build.gradle.kts b/feature/trip-planner/network/real/build.gradle.kts deleted file mode 100644 index 4e74e344..00000000 --- a/feature/trip-planner/network/real/build.gradle.kts +++ /dev/null @@ -1,64 +0,0 @@ -plugins { - alias(libs.plugins.krail.android.library) - alias(libs.plugins.krail.kotlin.multiplatform) - alias(libs.plugins.kotlin.serialization) - alias(libs.plugins.ksp) -} - -android { - namespace = "xyz.ksharma.krail.trip.planner.network.real" -} - -kotlin { - applyDefaultHierarchyTemplate() - - androidTarget() - iosArm64() - iosSimulatorArm64() - - sourceSets { - androidMain.dependencies { - implementation(libs.ktor.client.okhttp) - } - - commonMain { - dependencies { - implementation(projects.core.coroutinesExt) - implementation(projects.feature.tripPlanner.network.api) - - implementation(libs.di.kotlinInjectRuntime) - implementation(libs.kotlinx.serialization.json) - implementation(libs.ktor.client.core) - implementation(libs.ktor.client.cio) - implementation(libs.ktor.client.content.negotiation) - implementation(libs.ktor.client.logging) - implementation(libs.ktor.serialization.kotlinx.json) - } - } - - commonTest { - dependencies { - implementation(libs.test.kotlin) - implementation(libs.test.turbine) - implementation(libs.test.kotlinxCoroutineTest) - } - } - - iosMain { - dependencies { - implementation(libs.ktor.client.darwin) - } - } - } -} - -dependencies { - // 1. Configure code generation into the common source set - kspCommonMainMetadata(libs.di.kotlinInjectRuntime) - - // 2. Configure code generation into each KMP target source set - //kspAndroid(libs.di.kotlinInjectCompilerKsp) - // kspIosX64("me.tatarka.inject:kotlin-inject-compiler-ksp:0.7.2") - // kspIosArm64("me.tatarka.inject:kotlin-inject-compiler-ksp:0.7.2") - // kspIosSimulatorArm64("me.tatarka.inject:kotlin-inject-compiler-ksp:0.7.2") -} diff --git a/feature/trip-planner/network/real/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/network/real/di/TripPlanningModule.kt b/feature/trip-planner/network/real/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/network/real/di/TripPlanningModule.kt deleted file mode 100644 index 204b96a1..00000000 --- a/feature/trip-planner/network/real/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/network/real/di/TripPlanningModule.kt +++ /dev/null @@ -1,31 +0,0 @@ -package xyz.ksharma.krail.trip.planner.network.real.di - -import kotlinx.coroutines.CoroutineDispatcher -import me.tatarka.inject.annotations.Component -import me.tatarka.inject.annotations.Provides -import xyz.ksharma.krail.di.AppDispatchers -import xyz.ksharma.krail.di.Dispatcher -import xyz.ksharma.krail.trip.planner.network.api.RateLimiter -import xyz.ksharma.krail.trip.planner.network.api.repository.TripPlanningRepository -import xyz.ksharma.krail.trip.planner.network.real.ratelimit.APIRateLimiter -import xyz.ksharma.krail.trip.planner.network.real.repository.RealTripPlanningRepository - -@Component -abstract class TripPlanningComponent { - - abstract val tripPlanningRepository: TripPlanningRepository - - abstract val rateLimiter: RateLimiter - - @Provides - fun provideTripPlanningRepository( - @Dispatcher(AppDispatchers.IO) ioDispatcher: CoroutineDispatcher, - ): TripPlanningRepository { - return RealTripPlanningRepository(ioDispatcher = ioDispatcher) - } - - @Provides - fun provideRateLimiter(): RateLimiter { - return APIRateLimiter() - } -} diff --git a/feature/trip-planner/network/real/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/network/real/repository/RealTripPlanningRepository.kt b/feature/trip-planner/network/real/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/network/real/repository/RealTripPlanningRepository.kt deleted file mode 100644 index 7ebc976e..00000000 --- a/feature/trip-planner/network/real/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/network/real/repository/RealTripPlanningRepository.kt +++ /dev/null @@ -1,45 +0,0 @@ -package xyz.ksharma.krail.trip.planner.network.real.repository - -import kotlinx.coroutines.CoroutineDispatcher -import me.tatarka.inject.annotations.Inject -import xyz.ksharma.krail.coroutines.ext.suspendSafeResult -import xyz.ksharma.krail.trip.planner.network.api.model.StopFinderResponse -import xyz.ksharma.krail.trip.planner.network.api.model.StopType -import xyz.ksharma.krail.trip.planner.network.api.model.TripResponse -import xyz.ksharma.krail.trip.planner.network.api.repository.TripPlanningRepository - -class RealTripPlanningRepository @Inject constructor( - val ioDispatcher: CoroutineDispatcher, -) : TripPlanningRepository { - - override suspend fun stopFinder( - stopType: StopType, - stopSearchQuery: String, - ): Result = suspendSafeResult(ioDispatcher) { - tripPlanningService.stopFinder( - typeSf = stopType.type, - nameSf = stopSearchQuery, - ).toSafeResult() - } - - override suspend fun trip( - originStopId: String, - destinationStopId: String, - journeyTime: String?, - ): Result = suspendSafeResult(ioDispatcher) { - tripPlanningService.trip( - depArrMacro = "dep", - nameOrigin = originStopId, - nameDestination = destinationStopId, - itdTime = journeyTime, - ).toSafeResult() - } -} - -/** Stop Ids - * Rockdale - 221620 - * Central - 200060 - * TownHall - 200070 - * SevenHills - 214710 - * NewTown - 204210 - */ diff --git a/feature/trip-planner/network/api/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/network/api/model/StopFinderResponse.kt b/feature/trip-planner/network/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/network/api/model/StopFinderResponse.kt similarity index 100% rename from feature/trip-planner/network/api/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/network/api/model/StopFinderResponse.kt rename to feature/trip-planner/network/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/network/api/model/StopFinderResponse.kt diff --git a/feature/trip-planner/network/api/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/network/api/model/StopType.kt b/feature/trip-planner/network/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/network/api/model/StopType.kt similarity index 100% rename from feature/trip-planner/network/api/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/network/api/model/StopType.kt rename to feature/trip-planner/network/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/network/api/model/StopType.kt diff --git a/feature/trip-planner/network/api/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/network/api/model/TripResponse.kt b/feature/trip-planner/network/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/network/api/model/TripResponse.kt similarity index 100% rename from feature/trip-planner/network/api/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/network/api/model/TripResponse.kt rename to feature/trip-planner/network/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/network/api/model/TripResponse.kt diff --git a/feature/trip-planner/network/real/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/network/real/ratelimit/APIRateLimiter.kt b/feature/trip-planner/network/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/network/api/ratelimit/NetworkRateLimiter.kt similarity index 87% rename from feature/trip-planner/network/real/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/network/real/ratelimit/APIRateLimiter.kt rename to feature/trip-planner/network/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/network/api/ratelimit/NetworkRateLimiter.kt index d28f78ea..cc615ad0 100644 --- a/feature/trip-planner/network/real/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/network/real/ratelimit/APIRateLimiter.kt +++ b/feature/trip-planner/network/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/network/api/ratelimit/NetworkRateLimiter.kt @@ -1,4 +1,4 @@ -package xyz.ksharma.krail.trip.planner.network.real.ratelimit +package xyz.ksharma.krail.trip.planner.network.api.ratelimit import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.FlowPreview @@ -10,7 +10,6 @@ import kotlinx.coroutines.flow.flatMapLatest import kotlinx.coroutines.flow.flow import kotlinx.coroutines.flow.update import me.tatarka.inject.annotations.Inject -import xyz.ksharma.krail.trip.planner.network.api.RateLimiter import kotlin.time.Duration.Companion.milliseconds import kotlin.time.Duration.Companion.seconds @@ -20,7 +19,8 @@ import kotlin.time.Duration.Companion.seconds * * Note: This class should not be Singleton. It should be created per use-case. */ -class APIRateLimiter @Inject constructor() : RateLimiter { +@Inject +internal class NetworkRateLimiter : RateLimiter { private val triggerFlow = MutableSharedFlow(replay = 1) private val isFirstTime = MutableStateFlow(false) @@ -37,14 +37,14 @@ class APIRateLimiter @Inject constructor() : RateLimiter { .debounce { // First time the block should be executed immediately and subsequent must be rate limited. val interval = if (isFirstTime.value) rateLimitInterval else 0.milliseconds - // Timber.d("state: ${isFirstTime.value} and interval: $interval") + // Timber.d("state: ${isFirstTime.value} and interval: $interval") interval } .flatMapLatest { //Timber.d("flatmapLatest: Triggered") isFirstTime.update { true } // Mark the first trigger flow { - // Timber.d("Inside flow -emitting block") + // Timber.d("Inside flow -emitting block") emit(block()) } } diff --git a/feature/trip-planner/network/api/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/network/api/RateLimiter.kt b/feature/trip-planner/network/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/network/api/ratelimit/RateLimiter.kt similarity index 92% rename from feature/trip-planner/network/api/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/network/api/RateLimiter.kt rename to feature/trip-planner/network/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/network/api/ratelimit/RateLimiter.kt index 17c172c5..b40d2302 100644 --- a/feature/trip-planner/network/api/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/network/api/RateLimiter.kt +++ b/feature/trip-planner/network/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/network/api/ratelimit/RateLimiter.kt @@ -1,4 +1,4 @@ -package xyz.ksharma.krail.trip.planner.network.api +package xyz.ksharma.krail.trip.planner.network.api.ratelimit import kotlinx.coroutines.flow.Flow diff --git a/feature/trip-planner/network/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/network/api/service/HttpClient.kt b/feature/trip-planner/network/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/network/api/service/HttpClient.kt new file mode 100644 index 00000000..93da9f6b --- /dev/null +++ b/feature/trip-planner/network/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/network/api/service/HttpClient.kt @@ -0,0 +1,31 @@ +package xyz.ksharma.krail.trip.planner.network.api.service + +import androidx.compose.runtime.Composable +import androidx.compose.runtime.remember +import io.ktor.client.HttpClient +import io.ktor.client.plugins.contentnegotiation.ContentNegotiation +import io.ktor.client.plugins.logging.LogLevel +import io.ktor.client.plugins.logging.Logging +import io.ktor.serialization.kotlinx.json.json +import kotlinx.serialization.json.Json + +fun getHttpClient(): HttpClient { + return HttpClient { + install(ContentNegotiation) { + json(Json { + ignoreUnknownKeys = true + isLenient = true + prettyPrint = true + }) + } + install(Logging) { +// if(debug) - TODO + level = LogLevel.BODY + } + } +} + +@Composable +fun rememberHttpClient() = remember { getHttpClient() } + +internal const val NSW_TRANSPORT_BASE_URL = "https://api.transport.nsw.gov.au" diff --git a/feature/trip-planner/network/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/network/api/service/TripPlanningService.kt b/feature/trip-planner/network/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/network/api/service/TripPlanningService.kt new file mode 100644 index 00000000..ea47eb60 --- /dev/null +++ b/feature/trip-planner/network/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/network/api/service/TripPlanningService.kt @@ -0,0 +1,33 @@ +package xyz.ksharma.krail.trip.planner.network.api.service + +/** + * Swagger: https://opendata.transport.nsw.gov.au/dataset/trip-planner-apis/resource/917c66c3-8123-4a0f-b1b1-b4220f32585d + */ + +/** + * This endpoint returns a list of departures for a given location based on the date and time + * specified. This data can be used to display a "upcoming departures" board for a stop. + * + * Provides capability to provide NSW public transport departure information + * from a stop, station or wharf including real-time. + */ +//("v1/tp/departure_mon") +//suspend fun departure() + +/** + * This endpoint returns a list of service alerts or additional information about travelling + * on the public transport network. This list can be filtered by date, route type, route, + * operator or stop. + * + * Provides capability to display all public transport service status and + * incident information (as published from the Incident Capture System). + */ +//"v1/tp/add_info") +//suspend fun serviceAlert() + +/** + * When given a specific geographical location, this API finds public transport stops, + * stations, wharfs and points of interest around that location. + */ +//"v1/tp/coord") +//suspend fun coordinateRequest() diff --git a/feature/trip-planner/network/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/network/api/service/stop_finder/StopFinderRequest.kt b/feature/trip-planner/network/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/network/api/service/stop_finder/StopFinderRequest.kt new file mode 100644 index 00000000..9cf62c97 --- /dev/null +++ b/feature/trip-planner/network/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/network/api/service/stop_finder/StopFinderRequest.kt @@ -0,0 +1,37 @@ +package xyz.ksharma.krail.trip.planner.network.api.service.stop_finder + +import io.ktor.client.HttpClient +import io.ktor.client.call.body +import io.ktor.client.request.get +import io.ktor.http.HttpHeaders +import io.ktor.http.headers +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.IO +import kotlinx.coroutines.withContext +import xyz.ksharma.krail.trip.planner.network.api.model.StopType +import xyz.ksharma.krail.trip.planner.network.api.model.TripResponse +import xyz.ksharma.krail.trip.planner.network.api.service.NSW_TRANSPORT_BASE_URL + +suspend fun fetchStop( + httpClient: HttpClient, + apiKey: String, + stopType: StopType, + stopSearchQuery: String, +): TripResponse = withContext(Dispatchers.IO) { + httpClient.get("${NSW_TRANSPORT_BASE_URL}/v1/tp/stop_finder") { + headers { + append("Authorization", "apikey $apiKey") // Example of Authorization header + append(HttpHeaders.Accept, "application/json") // Accept JSON response + } + + url { + parameters.append(StopFinderRequestParams.nameSf, stopSearchQuery) + + parameters.append(StopFinderRequestParams.typeSf, stopType.type) + parameters.append(StopFinderRequestParams.coordOutputFormat, "EPSG:4326") + parameters.append(StopFinderRequestParams.outputFormat, "rapidJSON") + parameters.append(StopFinderRequestParams.version, "10.2.1.42") + parameters.append(StopFinderRequestParams.tfNSWSF, "true") + } + }.body() +} diff --git a/feature/trip-planner/network/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/network/api/service/stop_finder/StopFinderRequestParams.kt b/feature/trip-planner/network/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/network/api/service/stop_finder/StopFinderRequestParams.kt new file mode 100644 index 00000000..9405efe3 --- /dev/null +++ b/feature/trip-planner/network/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/network/api/service/stop_finder/StopFinderRequestParams.kt @@ -0,0 +1,60 @@ +package xyz.ksharma.krail.trip.planner.network.api.service.stop_finder + +internal object StopFinderRequestParams { + + /** + * Used to set the response data type. This documentation only covers responses that use the JSON format. + * Setting the outputFormat value to rapidJSON is required to enable JSON output. + * + * Available values : rapidJSON + */ + const val outputFormat: String = "outputFormat" + + /** + * This specifies the type of results expected in the list of returned stops. + * By specifying `any`, locations of all types can be returned. + * If you specifically know that you're searching using a coord, specify `coord`. + * Likewise, if you're using a stop ID or global stop ID as an input, use `stop` + * for more accurate results. + * + * Available values : any, coord, poi, stop + */ + const val typeSf: String = "type_sf" + + /** + * This is the search term that will be used to find locations. + * To lookup a coordinate, set type_sf to coord, and use the following format: + * LONGITUDE:LATITUDE:EPSG:4326 (Note that longitude is first). For example, 151.206290:-33.884080:EPSG:4326. + * To lookup a stop set type_sf to stop and enter the stop id or global stop ID. For example, 10101100 + * + * Default value : Circular Quay + */ + const val nameSf: String = "name_sf" + + /** + * This specifies the format the coordinates are returned in. + * While other variations are available, the EPSG:4326 format will return the widely-used format. + * + * Available values : EPSG:4326 + */ + const val coordOutputFormat = "coordOutputFormat" // "EPSG:4326", + + /** + * Including this parameter enables a number of options that result in the stop finder + * operating in the same way as the Transport for NSW Trip Planner web site. + * + * Available values : true + * + * Default value : true + */ + const val tfNSWSF = "TfNSWSF" + + /** + * Indicates which version of the API the caller is expecting for both request and response + * data. Note that if this version differs + * from the version listed above then the returned data may not be as expected. + * + * Default value : 10.2.1.42 + */ + const val version = "version" +} diff --git a/feature/trip-planner/network/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/network/api/service/trip/TripRequest.kt b/feature/trip-planner/network/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/network/api/service/trip/TripRequest.kt new file mode 100644 index 00000000..8941c76d --- /dev/null +++ b/feature/trip-planner/network/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/network/api/service/trip/TripRequest.kt @@ -0,0 +1,56 @@ +package xyz.ksharma.krail.trip.planner.network.api.service.trip + +import io.ktor.client.HttpClient +import io.ktor.client.call.body +import io.ktor.client.request.get +import io.ktor.http.HttpHeaders +import io.ktor.http.headers +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.IO +import kotlinx.coroutines.withContext +import kotlinx.datetime.Instant +import kotlinx.datetime.TimeZone +import kotlinx.datetime.toLocalDateTime +import xyz.ksharma.krail.trip.planner.network.api.model.TripResponse +import xyz.ksharma.krail.trip.planner.network.api.service.NSW_TRANSPORT_BASE_URL + +suspend fun fetchTrip( + httpClient: HttpClient, apiKey: String, + originStopId: String, + destinationStopId: String, +): TripResponse = withContext(Dispatchers.IO) { + httpClient.get("$NSW_TRANSPORT_BASE_URL/v1/tp/trip") { + headers { + append("Authorization", "apikey $apiKey") // Example of Authorization header + append(HttpHeaders.Accept, "application/json") // Accept JSON response + } + + url { + parameters.append(TripRequestParams.nameOrigin, originStopId) + parameters.append(TripRequestParams.nameDestination, destinationStopId) + + parameters.append(TripRequestParams.depArrMacro, "dep") + parameters.append(TripRequestParams.typeDestination, "any") + parameters.append(TripRequestParams.calcNumberOfTrips, "6") + parameters.append(TripRequestParams.typeOrigin, "any") + parameters.append(TripRequestParams.tfNSWTR, "true") + parameters.append(TripRequestParams.version, "10.2.1.42") + parameters.append(TripRequestParams.coordOutputFormat, "EPSG:4326") + parameters.append(TripRequestParams.itOptionsActive, "1") + parameters.append(TripRequestParams.computeMonomodalTripBicycle, "false") + parameters.append(TripRequestParams.cycleSpeed, "16") + parameters.append(TripRequestParams.useElevationData, "1") + parameters.append(TripRequestParams.outputFormat, "rapidJSON") + } + }.body() +} + +/** + * Converts Instant to ITD Time in HHMM 24-hour format. + * // todo - move to another module for time related functions + */ +fun Instant.toItdTime(): String { + val localDateTime = this.toLocalDateTime(TimeZone.of("Australia/Sydney")) + return localDateTime.hour.toString().padStart(2, '0') + localDateTime.minute.toString() + .padStart(2, '0') +} diff --git a/feature/trip-planner/network/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/network/api/service/trip/TripRequestParams.kt b/feature/trip-planner/network/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/network/api/service/trip/TripRequestParams.kt new file mode 100644 index 00000000..d8d102bd --- /dev/null +++ b/feature/trip-planner/network/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/network/api/service/trip/TripRequestParams.kt @@ -0,0 +1,237 @@ +package xyz.ksharma.krail.trip.planner.network.api.service.trip + +internal object TripRequestParams { + + /** + * Used to set the response data type. This documentation only covers responses that use + * the JSON format. Setting the outputFormat value to rapidJSON is required to enable JSON output. + */ + const val outputFormat: String = "outputFormat" + + /** + * This specifies the format the coordinates are returned in. While other variations are + * available, the EPSG:4326 format will return the widely-used format. + */ + const val coordOutputFormat: String = "coordOutputFormat" + + /** + * This value anchors the requested date time. If set to dep, then trips departing after + * the specified date/time at the specified location are included. + * + * If set to arr, then trips arriving before the specified time at its destination stop are included. + * Works in conjunctions with the [itdDate] and [itdTime] values. + * + * "dep" or "arr" + */ + const val depArrMacro: String = "depArrMacro" + + /** + * The reference date used when searching trips, in YYYYMMDD format. + * For instance, 20160901 refers to 1 September 2016. Works in conjunction with the + * [itdTime] and [depArrMacro] values. If not specified, the current server date is used. + * + * Optional, default to null if not provided + */ + const val itdDate = "itdDate" + + /** + * The reference time used when searching trips, in HHMM 24-hour format. + * For instance, 2215 refers to 10:15 PM. + * Works in conjunction with the [itdDate] and [depArrMacro] values. + * + * If not specified, the current server time is used. + * + * Optional, default to null if not provided + */ + const val itdTime = "itdTime" + + /** + * This is the type of data specified in the name_origin field. The origin indicates the + * starting point when searching for journeys. + * The best way to use the trip planner is to use use any for this field then specify a + * valid location ID in type_origin, or to use coord + * in this field and a correctly formatted coordinate in type_origin. + * + * Available values : any, coord + * + * Default value : any + */ + const val typeOrigin: String = "type_origin" + + /** + * This value is used to indicate the starting point when searching for journeys. + * This value can be one of three things: + * A valid location/stop ID (for example, 10101100 indicates Central Station - this can be + * determined using stop_finder). + * + * A valid global stop ID (for example, 200060 indicates Central Station - this can be + * determined using stop_finder) Coordinates in the format LONGITUDE:LATITUDE:EPSG:4326 + * (Note that longitude is first). + * + * Default value : 10101331 + */ + const val nameOrigin = "name_origin" + + /** + * This is the type of data specified in the name_destination field. The origin indicates + * the finishing point when searching for journeys. The best way to use the trip planner + * is to use use any for this field then specify a valid location ID in type_destination, + * or to use coord in this field and a correctly formatted coordinate in type_destination. + * + * Available values : any, coord + * + * Default value : any + */ + const val typeDestination: String = "type_destination" + + /** + * his value is used to indicate the finishing point when searching for journeys. + * This value can be one of three things: + * A valid location/stop ID (for example, 10101100 indicates Central Station - this can be + * determined using [stopFinder]). + * + * A valid global stop ID (for example, 200060 indicates Central Station - this can be + * determined using [stopFinder]) + * Coordinates in the format LONGITUDE:LATITUDE:EPSG:4326 (Note that longitude is first). + * + * Default value : 10102027 + */ + const val nameDestination = "name_destination" + + /** + * This parameter indicates the maximum number of trips to returned. Fewer trips may be returned anyway, + * depending on the available public transport services. + * + * Default value : 6 + */ + const val calcNumberOfTrips = "calcNumberOfTrips" + + /** + * Including this parameter (regardless of its value) ensures that only wheelchair-accessible + * options are returned. + * + * Available values : on + */ + const val wheelchair: String = "wheelchair"// Optional, used for wheelchair accessible trips + + /** + * This parameter which means of transport to exclude from the trip plan. + * To exclude one means, + * select one of the following: 1 = train, 2 = metro, 4 = light rail, 5 = bus, 7 = coach, + * 9 = ferry, 11 = school bus. + * `checkbox` allows you to exclude more than one means of transport when used in conjunction + * with the exclMOT_ parameters. + * + * Available values : checkbox, 1, 2, 4, 5, 7, 9, 11 + */ + const val excludedMeans: String = + "excludedMeans" // Optional, to exclude specific transport modes + + /** + * Excludes train services from the trip plan. Must be used in conjunction with + * [excludedMeans]=checkbox + * + * Available values : 1 + */ + const val exclMOT1 = "exclMOT_1" // Optional, to exclude trains + + /** + * Excludes metro services from the trip plan. Must be used in conjunction with + * [excludedMeans]=checkbox + * + * Available values : 1 + */ + const val exclMOT2 = "exclMOT_2" // Optional, to exclude metro + + /** + * Excludes light rail services from the trip plan. Must be used in conjunction with + * [excludedMeans]=checkbox + * + * Available values : 1 + */ + const val exclMOT4 = "exclMOT_4" // Optional, to exclude light rail + + /** + * Excludes bus services from the trip plan. Must be used in conjunction with + * [excludedMeans]=checkbox + * + * Available values : 1 + */ + const val exclMOT5 = "exclMOT_5" // Optional, to exclude bus + + + /** + * Excludes coach services from the trip plan. Must be used in conjunction with + * [excludedMeans]=checkbox + * + * Available values : 1 + */ + const val exclMOT7: String = "exclMOT_7" // Optional, to exclude bus + + /** + * Excludes ferry services from the trip plan. Must be used in conjunction with + * [excludedMeans]=checkbox + * + * Available values : 1 + */ + const val exclMOT9 = "exclMOT_9"// Optional, to exclude bus + + /** + * Excludes school bus services from the trip plan. Must be used in conjunction with + * [excludedMeans]=checkbox + * + * Available values : 1 + */ + const val exclMOT11 = "exclMOT_11" + + /** + * Including this parameter enables a number of options that result in this API call + * operating in the same way as the Transport for NSW Trip Planner web site, + * including enabling real-time data. + * + * Available values : true + * + * Default value : true + */ + const val tfNSWTR = "TfNSWTR" + + /** + * Indicates which version of the API the caller is expecting for both request and response + * data. Note that if this version differs from the version listed above then the returned + * data may not be as expected. + * + * Default value : 10.2.1.42 + */ + const val version = "version" + + /** + * This parameter activates the options for individual transport. If the parameter is + * disabled, the parameters concerning individual transport will not be taken into account. + * possible values are 0 and 1 + * + * Default value : 1 + */ + const val itOptionsActive = "itOptionsActive" // Activates individual transport options + + /** + * Activates the calculation of a monomodal trip, i.e., a trip that takes place exclusively + * with the means of transport , e.g., with bicycle. + * + * Note 1: In order to use this parameter, the options for individual transport must be + * activated with itOptionsActive=1. + * + * Note 2: If no monomodal trip with the means of transport is calculated despite the + * parameter, the maximum time is often set too low. The parameter MaxITTime applies to + * all means of transport, the parameter MaxITTimeto the means of transport (e.g., MaxITTime107). + * These parameters are located in the [Parameters] section or are added to it. + * The configuration can be alternatively overridden by the [maxTime] parameter. + * + */ + const val computeMonomodalTripBicycle = "computeMonomodalTripBicycle" + + const val cycleSpeed = "cycleSpeed" + + const val useElevationData = "useElevationData" + + const val elevFac = "elevFac" +} diff --git a/feature/trip-planner/network/api/src/commonTest/assets/hige_multiple_legs_trip.json b/feature/trip-planner/network/src/commonTest/assets/hige_multiple_legs_trip.json similarity index 100% rename from feature/trip-planner/network/api/src/commonTest/assets/hige_multiple_legs_trip.json rename to feature/trip-planner/network/src/commonTest/assets/hige_multiple_legs_trip.json diff --git a/feature/trip-planner/network/api/src/commonTest/assets/multiple_legs b/feature/trip-planner/network/src/commonTest/assets/multiple_legs similarity index 100% rename from feature/trip-planner/network/api/src/commonTest/assets/multiple_legs rename to feature/trip-planner/network/src/commonTest/assets/multiple_legs diff --git a/feature/trip-planner/network/api/src/commonTest/assets/stop_finder_any.json b/feature/trip-planner/network/src/commonTest/assets/stop_finder_any.json similarity index 100% rename from feature/trip-planner/network/api/src/commonTest/assets/stop_finder_any.json rename to feature/trip-planner/network/src/commonTest/assets/stop_finder_any.json diff --git a/feature/trip-planner/network/api/src/commonTest/assets/stop_finder_stop.json b/feature/trip-planner/network/src/commonTest/assets/stop_finder_stop.json similarity index 100% rename from feature/trip-planner/network/api/src/commonTest/assets/stop_finder_stop.json rename to feature/trip-planner/network/src/commonTest/assets/stop_finder_stop.json diff --git a/feature/trip-planner/network/api/src/commonTest/assets/trip.json b/feature/trip-planner/network/src/commonTest/assets/trip.json similarity index 100% rename from feature/trip-planner/network/api/src/commonTest/assets/trip.json rename to feature/trip-planner/network/src/commonTest/assets/trip.json diff --git a/feature/trip-planner/network/api/src/commonTest/assets/trip_metro_bus.json b/feature/trip-planner/network/src/commonTest/assets/trip_metro_bus.json similarity index 100% rename from feature/trip-planner/network/api/src/commonTest/assets/trip_metro_bus.json rename to feature/trip-planner/network/src/commonTest/assets/trip_metro_bus.json diff --git a/feature/trip-planner/network/api/src/commonTest/assets/trip_occupancy_platform.json b/feature/trip-planner/network/src/commonTest/assets/trip_occupancy_platform.json similarity index 100% rename from feature/trip-planner/network/api/src/commonTest/assets/trip_occupancy_platform.json rename to feature/trip-planner/network/src/commonTest/assets/trip_occupancy_platform.json diff --git a/feature/trip-planner/network/api/src/commonTest/assets/trip_sevenhills_townhall.json b/feature/trip-planner/network/src/commonTest/assets/trip_sevenhills_townhall.json similarity index 100% rename from feature/trip-planner/network/api/src/commonTest/assets/trip_sevenhills_townhall.json rename to feature/trip-planner/network/src/commonTest/assets/trip_sevenhills_townhall.json diff --git a/feature/trip-planner/network/real/src/commonTest/kotlin/xyz/ksharma/krail/trip/planner/network/real/ratelimit/APIRateLimiterTest.kt b/feature/trip-planner/network/src/commonTest/kotlin/ratelimit/APIRateLimiterTest.kt similarity index 96% rename from feature/trip-planner/network/real/src/commonTest/kotlin/xyz/ksharma/krail/trip/planner/network/real/ratelimit/APIRateLimiterTest.kt rename to feature/trip-planner/network/src/commonTest/kotlin/ratelimit/APIRateLimiterTest.kt index 64292b24..901590af 100644 --- a/feature/trip-planner/network/real/src/commonTest/kotlin/xyz/ksharma/krail/trip/planner/network/real/ratelimit/APIRateLimiterTest.kt +++ b/feature/trip-planner/network/src/commonTest/kotlin/ratelimit/APIRateLimiterTest.kt @@ -1,4 +1,4 @@ -package xyz.ksharma.krail.trip.planner.network.real.ratelimit +package ratelimit import app.cash.turbine.test import kotlinx.coroutines.delay diff --git a/settings.gradle.kts b/settings.gradle.kts index f4bf466d..8d22fd57 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -36,9 +36,7 @@ include(":taj") // Design System include(":core:di") include(":core:date-time") include(":core:coroutines-ext") -include(":core:network") include(":feature:trip-planner:ui") include(":feature:trip-planner:state") -include(":feature:trip-planner:network:api") -include(":feature:trip-planner:network:real") +include(":feature:trip-planner:network") include(":sandook") From 540e4564bb917d9b2b74af1574add234df760222 Mon Sep 17 00:00:00 2001 From: Karan Sharma <55722391+ksharma-xyz@users.noreply.github.com> Date: Tue, 19 Nov 2024 15:51:09 +1100 Subject: [PATCH 38/67] Ktor works --- composeApp/build.gradle.kts | 8 ++++ .../xyz/ksharma/krail/common/KrailApp.kt | 25 ++++++++++ feature/trip-planner/network/build.gradle.kts | 46 +++++++++++-------- .../planner/network/api/service/HttpClient.kt | 7 +++ .../service/stop_finder/StopFinderRequest.kt | 12 ++--- .../network/api/service/trip/TripRequest.kt | 7 --- feature/trip-planner/ui/build.gradle.kts | 1 - .../planner/ui/alerts/ServiceAlertScreen.kt | 4 +- gradle/libs.versions.toml | 7 +++ settings.gradle.kts | 1 - 10 files changed, 78 insertions(+), 40 deletions(-) diff --git a/composeApp/build.gradle.kts b/composeApp/build.gradle.kts index 29be056a..c4140a2b 100644 --- a/composeApp/build.gradle.kts +++ b/composeApp/build.gradle.kts @@ -57,6 +57,7 @@ kotlin { commonMain.dependencies { implementation(projects.taj) implementation(projects.sandook) + implementation(projects.feature.tripPlanner.network) implementation(compose.runtime) implementation(compose.foundation) @@ -67,6 +68,13 @@ kotlin { implementation(libs.androidx.lifecycle.runtime.compose) implementation(libs.multiplatform.settings) + + + implementation(libs.ktor.client.core) + implementation(libs.ktor.client.cio) + implementation(libs.ktor.client.content.negotiation) + implementation(libs.ktor.client.logging) + implementation(libs.ktor.serialization.kotlinx.json) } } } diff --git a/composeApp/src/commonMain/kotlin/xyz/ksharma/krail/common/KrailApp.kt b/composeApp/src/commonMain/kotlin/xyz/ksharma/krail/common/KrailApp.kt index 65d8bdc6..3ac8f7a7 100644 --- a/composeApp/src/commonMain/kotlin/xyz/ksharma/krail/common/KrailApp.kt +++ b/composeApp/src/commonMain/kotlin/xyz/ksharma/krail/common/KrailApp.kt @@ -12,11 +12,16 @@ import kotlinx.coroutines.delay import xyz.ksharma.krail.sandook.RealSandook import xyz.ksharma.krail.taj.components.Text import xyz.ksharma.krail.taj.theme.KrailTheme +import xyz.ksharma.krail.trip.planner.network.api.model.StopType +import xyz.ksharma.krail.trip.planner.network.api.service.rememberHttpClient +import xyz.ksharma.krail.trip.planner.network.api.service.stop_finder.fetchStop +import xyz.ksharma.krail.trip.planner.network.api.service.trip.fetchTrip @Composable fun KrailApp() { KrailTheme { +/* LaunchedEffect(Unit) { val sandook = RealSandook(Settings()) println("sandook: " + sandook) @@ -30,7 +35,27 @@ fun KrailApp() { x = sandook.getString("key") println("Sandook value: x: $x") } +*/ + val httpClient = rememberHttpClient() + LaunchedEffect(Unit) { + try { + + val stopResponse = fetchStop( + httpClient = httpClient, + stopType = StopType.STOP, + stopSearchQuery = "central" + ) + println("stopResponse total: ${stopResponse.locations?.size}") + + stopResponse.locations?.forEach { + println("Stop: ${it.name}, ${it.id}") + } + } catch (e: Exception) { + println("Stop Exception: $e") + throw e + } + } Column(modifier = Modifier.fillMaxSize().background(color = KrailTheme.colors.surface)) { diff --git a/feature/trip-planner/network/build.gradle.kts b/feature/trip-planner/network/build.gradle.kts index 679a3e7a..8219ae72 100644 --- a/feature/trip-planner/network/build.gradle.kts +++ b/feature/trip-planner/network/build.gradle.kts @@ -1,4 +1,5 @@ -import java.util.Properties +import com.android.build.gradle.internal.cxx.configure.gradleLocalProperties +import com.codingfeline.buildkonfig.compiler.FieldSpec android { namespace = "xyz.ksharma.krail.trip.planner.network" @@ -21,18 +22,9 @@ plugins { alias(libs.plugins.compose.compiler) alias(libs.plugins.kotlin.serialization) alias(libs.plugins.ksp) + alias(libs.plugins.buildkonfig) } - -// Get local.properties values -val localProperties = Properties() -val localPropertiesFile = rootProject.file("local.properties") -if (localPropertiesFile.exists()) { - localProperties.load(localPropertiesFile.inputStream()) -} -val nswTransportApiKey: String = localProperties.getProperty("NSW_TRANSPORT_API_KEY", "") - - kotlin { applyDefaultHierarchyTemplate() @@ -41,33 +33,31 @@ kotlin { iosSimulatorArm64() sourceSets { - androidMain.dependencies { + /*androidMain.dependencies { implementation(libs.ktor.client.okhttp) - } + }*/ commonMain { dependencies { - implementation(projects.core.coroutinesExt) - api(projects.core.di) - implementation(libs.di.kotlinInjectRuntime) implementation(libs.kotlinx.serialization.json) implementation(libs.ktor.client.core) implementation(libs.ktor.client.cio) + implementation(libs.ktor.client.auth) implementation(libs.ktor.client.content.negotiation) implementation(libs.ktor.client.logging) implementation(libs.ktor.serialization.kotlinx.json) implementation(libs.kotlinx.datetime) - implementation(compose.runtime) + implementation(libs.slf4j.simple) // Logging } } - iosMain { + /*iosMain { dependencies { implementation(libs.ktor.client.darwin) } - } + }*/ } } @@ -81,3 +71,21 @@ dependencies { // kspIosArm64("me.tatarka.inject:kotlin-inject-compiler-ksp:0.7.2") // kspIosSimulatorArm64("me.tatarka.inject:kotlin-inject-compiler-ksp:0.7.2") } + +// READ API KEY +val localProperties = gradleLocalProperties(rootProject.rootDir, providers) +val nswTransportApiKey: String = localProperties.getProperty("NSW_TRANSPORT_API_KEY", "") +require(nswTransportApiKey.isNotEmpty()) { + "Register your API key from the developer and place it in local.properties as `API_KEY`" +} +buildkonfig { + packageName = "xyz.ksharma.krail.trip.planner.network" + + require(nswTransportApiKey.isNotEmpty()) { + "Register your api key from developer and place it in local.properties as `API_KEY`" + } + + defaultConfigs { + buildConfigField(FieldSpec.Type.STRING, "NSW_TRANSPORT_API_KEY", nswTransportApiKey) + } +} diff --git a/feature/trip-planner/network/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/network/api/service/HttpClient.kt b/feature/trip-planner/network/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/network/api/service/HttpClient.kt index 93da9f6b..89264b47 100644 --- a/feature/trip-planner/network/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/network/api/service/HttpClient.kt +++ b/feature/trip-planner/network/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/network/api/service/HttpClient.kt @@ -4,10 +4,13 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.remember import io.ktor.client.HttpClient import io.ktor.client.plugins.contentnegotiation.ContentNegotiation +import io.ktor.client.plugins.defaultRequest import io.ktor.client.plugins.logging.LogLevel import io.ktor.client.plugins.logging.Logging +import io.ktor.http.HttpHeaders import io.ktor.serialization.kotlinx.json.json import kotlinx.serialization.json.Json +import xyz.ksharma.krail.trip.planner.network.BuildKonfig fun getHttpClient(): HttpClient { return HttpClient { @@ -22,6 +25,10 @@ fun getHttpClient(): HttpClient { // if(debug) - TODO level = LogLevel.BODY } + + defaultRequest { + headers.append(HttpHeaders.Authorization, "apikey ${BuildKonfig.NSW_TRANSPORT_API_KEY}") + } } } diff --git a/feature/trip-planner/network/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/network/api/service/stop_finder/StopFinderRequest.kt b/feature/trip-planner/network/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/network/api/service/stop_finder/StopFinderRequest.kt index 9cf62c97..812820d6 100644 --- a/feature/trip-planner/network/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/network/api/service/stop_finder/StopFinderRequest.kt +++ b/feature/trip-planner/network/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/network/api/service/stop_finder/StopFinderRequest.kt @@ -8,22 +8,16 @@ import io.ktor.http.headers import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.IO import kotlinx.coroutines.withContext +import xyz.ksharma.krail.trip.planner.network.api.model.StopFinderResponse import xyz.ksharma.krail.trip.planner.network.api.model.StopType -import xyz.ksharma.krail.trip.planner.network.api.model.TripResponse import xyz.ksharma.krail.trip.planner.network.api.service.NSW_TRANSPORT_BASE_URL suspend fun fetchStop( httpClient: HttpClient, - apiKey: String, stopType: StopType, stopSearchQuery: String, -): TripResponse = withContext(Dispatchers.IO) { - httpClient.get("${NSW_TRANSPORT_BASE_URL}/v1/tp/stop_finder") { - headers { - append("Authorization", "apikey $apiKey") // Example of Authorization header - append(HttpHeaders.Accept, "application/json") // Accept JSON response - } - +): StopFinderResponse { + return httpClient.get("${NSW_TRANSPORT_BASE_URL}/v1/tp/stop_finder") { url { parameters.append(StopFinderRequestParams.nameSf, stopSearchQuery) diff --git a/feature/trip-planner/network/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/network/api/service/trip/TripRequest.kt b/feature/trip-planner/network/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/network/api/service/trip/TripRequest.kt index 8941c76d..095eff81 100644 --- a/feature/trip-planner/network/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/network/api/service/trip/TripRequest.kt +++ b/feature/trip-planner/network/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/network/api/service/trip/TripRequest.kt @@ -3,8 +3,6 @@ package xyz.ksharma.krail.trip.planner.network.api.service.trip import io.ktor.client.HttpClient import io.ktor.client.call.body import io.ktor.client.request.get -import io.ktor.http.HttpHeaders -import io.ktor.http.headers import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.IO import kotlinx.coroutines.withContext @@ -20,11 +18,6 @@ suspend fun fetchTrip( destinationStopId: String, ): TripResponse = withContext(Dispatchers.IO) { httpClient.get("$NSW_TRANSPORT_BASE_URL/v1/tp/trip") { - headers { - append("Authorization", "apikey $apiKey") // Example of Authorization header - append(HttpHeaders.Accept, "application/json") // Accept JSON response - } - url { parameters.append(TripRequestParams.nameOrigin, originStopId) parameters.append(TripRequestParams.nameDestination, destinationStopId) diff --git a/feature/trip-planner/ui/build.gradle.kts b/feature/trip-planner/ui/build.gradle.kts index 27b1550e..4b2f782e 100644 --- a/feature/trip-planner/ui/build.gradle.kts +++ b/feature/trip-planner/ui/build.gradle.kts @@ -25,7 +25,6 @@ kotlin { commonMain { dependencies { implementation(projects.taj) - implementation(projects.core.di) implementation(projects.feature.tripPlanner.state) implementation(projects.core.dateTime) diff --git a/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/alerts/ServiceAlertScreen.kt b/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/alerts/ServiceAlertScreen.kt index aff03486..5127b987 100644 --- a/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/alerts/ServiceAlertScreen.kt +++ b/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/alerts/ServiceAlertScreen.kt @@ -20,7 +20,6 @@ import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.ColorFilter -import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import kotlinx.collections.immutable.ImmutableSet import kotlinx.collections.immutable.persistentSetOf @@ -28,7 +27,6 @@ import kotlinx.collections.immutable.toImmutableList import xyz.ksharma.krail.taj.components.Text import xyz.ksharma.krail.taj.components.TitleBar import xyz.ksharma.krail.taj.theme.KrailTheme -import xyz.ksharma.krail.trip.planner.ui.DefaultSystemBarColors import xyz.ksharma.krail.trip.planner.ui.state.alerts.ServiceAlert import xyz.ksharma.krail.trip.planner.ui.state.alerts.ServiceAlertPriority import xyz.ksharma.krail.trip.planner.ui.timetable.ActionButton @@ -39,7 +37,7 @@ fun ServiceAlertScreen( modifier: Modifier = Modifier, onBackClick: () -> Unit = {}, ) { - DefaultSystemBarColors() +// DefaultSystemBarColors() Column( modifier = modifier diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 9ec3aa33..af301372 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -12,6 +12,7 @@ activity-compose = "1.9.3" kotlinInject = "0.7.2" kotlinxCollectionsImmutable = "0.3.8" kotlinxDatetime = "0.6.1" +ktorClientAuth = "2.3.12" lifecycleViewmodelCompose = "2.8.2" multiplatformSettings = "1.2.0" navigationCompose = "2.8.0-alpha10" @@ -22,17 +23,21 @@ compose-multiplatform = "1.7.0" ktor = "3.0.1" androidx-lifecycle = "2.8.3" kotlinxCoroutines = "1.9.0" +buildkonfigGradlePlugin = "0.15.2" #SDK minSdk = "26" compileSdk = "35" +slf4jSimple = "2.0.16" targetSdk = "35" [libraries] +buildkonfig-gradle-plugin = { module = "com.codingfeline.buildkonfig:buildkonfig-gradle-plugin", version.ref = "buildkonfigGradlePlugin" } core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "core-ktx" } activity-compose = { group = "androidx.activity", name = "activity-compose", version.ref = "activity-compose" } kotlinx-collections-immutable = { module = "org.jetbrains.kotlinx:kotlinx-collections-immutable", version.ref = "kotlinxCollectionsImmutable" } kotlinx-datetime = { module = "org.jetbrains.kotlinx:kotlinx-datetime", version.ref = "kotlinxDatetime" } +ktor-client-auth = { module = "io.ktor:ktor-client-auth", version.ref = "ktorClientAuth" } lifecycle-runtime-ktx = { group = "androidx.lifecycle", name = "lifecycle-runtime-ktx", version.ref = "android-lifecycle" } navigation-compose = { module = "org.jetbrains.androidx.navigation:navigation-compose", version.ref = "navigationCompose" } kotlinx-coroutines-core = { group = "org.jetbrains.kotlinx", name = "kotlinx-coroutines-core", version.ref = "kotlinxCoroutines" } @@ -56,6 +61,7 @@ ktor-serialization-kotlinx-json = { module = "io.ktor:ktor-serialization-kotlinx ktor-client-logging = { module = "io.ktor:ktor-client-logging", version.ref = "ktor" } #Test +slf4j-simple = { module = "org.slf4j:slf4j-simple", version.ref = "slf4jSimple" } test-composeUiTestManifest = { group = "androidx.compose.ui", name = "ui-test-manifest" } test-composeUiTestJunit4 = { group = "androidx.compose.ui", name = "ui-test-junit4" } test-junit = { group = "junit", name = "junit", version.ref = "junit" } @@ -82,6 +88,7 @@ kotlinAndroid = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" } kotlin-serialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlin" } cash-paparazzi = { id = "app.cash.paparazzi", version.ref = "paparazzi" } ksp = { id = "com.google.devtools.ksp", version.ref = "ksp" } +buildkonfig = { id = "com.codingfeline.buildkonfig", version.ref = "buildkonfigGradlePlugin" } compose-compiler = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" } google-services = { id = "com.google.gms.google-services", version = "4.4.2" } composeMultiplatform = { id = "org.jetbrains.compose", version.ref = "compose-multiplatform" } diff --git a/settings.gradle.kts b/settings.gradle.kts index 8d22fd57..7964de8b 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -35,7 +35,6 @@ include(":composeApp") include(":taj") // Design System include(":core:di") include(":core:date-time") -include(":core:coroutines-ext") include(":feature:trip-planner:ui") include(":feature:trip-planner:state") include(":feature:trip-planner:network") From ac5e4898340d62bc016d59442021bb9ff104aad0 Mon Sep 17 00:00:00 2001 From: Karan Sharma <55722391+ksharma-xyz@users.noreply.github.com> Date: Tue, 19 Nov 2024 23:37:55 +1100 Subject: [PATCH 39/67] update SavedTripsViewModel --- feature/trip-planner/ui/build.gradle.kts | 2 ++ .../ui/savedtrips/SavedTripsViewModel.kt | 27 +++++++++---------- .../ui/searchstop/SearchStopViewModel.kt | 7 ++--- .../xyz/ksharma/krail/sandook/RealSandook.kt | 2 +- 4 files changed, 18 insertions(+), 20 deletions(-) diff --git a/feature/trip-planner/ui/build.gradle.kts b/feature/trip-planner/ui/build.gradle.kts index 4b2f782e..d2920d41 100644 --- a/feature/trip-planner/ui/build.gradle.kts +++ b/feature/trip-planner/ui/build.gradle.kts @@ -27,6 +27,8 @@ kotlin { implementation(projects.taj) implementation(projects.feature.tripPlanner.state) implementation(projects.core.dateTime) + implementation(projects.sandook) + implementation(projects.feature.tripPlanner.network) implementation(compose.foundation) implementation(compose.animation) diff --git a/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/savedtrips/SavedTripsViewModel.kt b/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/savedtrips/SavedTripsViewModel.kt index 411f0794..cabcc4b7 100644 --- a/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/savedtrips/SavedTripsViewModel.kt +++ b/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/savedtrips/SavedTripsViewModel.kt @@ -3,35 +3,34 @@ package xyz.ksharma.krail.trip.planner.ui.savedtrips import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import kotlinx.collections.immutable.persistentListOf -import kotlinx.coroutines.CoroutineDispatcher +import kotlinx.collections.immutable.toImmutableList +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.IO import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch -import xyz.ksharma.krail.di.AppDispatchers -import xyz.ksharma.krail.di.Dispatcher +import xyz.ksharma.krail.sandook.RealSandook +import xyz.ksharma.krail.sandook.Sandook import xyz.ksharma.krail.trip.planner.ui.state.savedtrip.SavedTripUiEvent import xyz.ksharma.krail.trip.planner.ui.state.savedtrip.SavedTripsState import xyz.ksharma.krail.trip.planner.ui.state.timetable.Trip -class SavedTripsViewModel ( -// sandookFactory: SandookFactory, - @Dispatcher(AppDispatchers.IO) private val ioDispatcher: CoroutineDispatcher, -) : ViewModel() { - -// private val sandook: Sandook = sandookFactory.create(SandookFactory.SandookKey.SAVED_TRIP) +class SavedTripsViewModel : ViewModel() { + private val sandook: Sandook = RealSandook() private val _uiState: MutableStateFlow = MutableStateFlow(SavedTripsState()) val uiState: StateFlow = _uiState private fun loadSavedTrips() { - viewModelScope.launch(context = ioDispatcher) { - val trips = persistentListOf() /*sandook.keys().mapNotNull { key -> + viewModelScope.launch(context = Dispatchers.IO) { + val trips = persistentListOf() + sandook.keys().mapNotNull { key -> val tripString = sandook.getString(key, null) tripString?.let { tripJsonString -> Trip.fromJsonString(tripJsonString) } - }.toImmutableList()*/ + }.toImmutableList() trips.forEachIndexed { index, trip -> //Timber.d("\t SavedTrip: #$index ${trip.fromStopName} -> ${trip.toStopName}") @@ -50,8 +49,8 @@ class SavedTripsViewModel ( private fun onDeleteSavedTrip(savedTrip: Trip) { // Timber.d("onDeleteSavedTrip: $savedTrip") - viewModelScope.launch(context = ioDispatcher) { -// sandook.remove(key = savedTrip.tripId) + viewModelScope.launch(context = Dispatchers.IO) { + sandook.remove(key = savedTrip.tripId) loadSavedTrips() } } diff --git a/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/searchstop/SearchStopViewModel.kt b/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/searchstop/SearchStopViewModel.kt index 6fc3cf30..27003d8b 100644 --- a/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/searchstop/SearchStopViewModel.kt +++ b/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/searchstop/SearchStopViewModel.kt @@ -8,15 +8,12 @@ import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch -import xyz.ksharma.krail.trip.planner.network.api.model.StopFinderResponse -import xyz.ksharma.krail.trip.planner.network.api.repository.TripPlanningRepository +import me.tatarka.inject.annotations.Inject import xyz.ksharma.krail.trip.planner.ui.searchstop.StopResultMapper.toStopResults import xyz.ksharma.krail.trip.planner.ui.state.searchstop.SearchStopState import xyz.ksharma.krail.trip.planner.ui.state.searchstop.SearchStopUiEvent -class SearchStopViewModel @Inject constructor( - private val tripPlanningRepository: TripPlanningRepository, -) : ViewModel() { +class SearchStopViewModel : ViewModel() { private val _uiState: MutableStateFlow = MutableStateFlow(SearchStopState()) val uiState: StateFlow = _uiState diff --git a/sandook/src/commonMain/kotlin/xyz/ksharma/krail/sandook/RealSandook.kt b/sandook/src/commonMain/kotlin/xyz/ksharma/krail/sandook/RealSandook.kt index 39912730..25997b99 100644 --- a/sandook/src/commonMain/kotlin/xyz/ksharma/krail/sandook/RealSandook.kt +++ b/sandook/src/commonMain/kotlin/xyz/ksharma/krail/sandook/RealSandook.kt @@ -2,7 +2,7 @@ package xyz.ksharma.krail.sandook import com.russhwolf.settings.Settings -class RealSandook (private val settings: Settings) : Sandook { +class RealSandook (private val settings: Settings = Settings()) : Sandook { override fun keys(): Set = settings.keys From a1e1bae44574548362fbaff54c4d6c25880a4654 Mon Sep 17 00:00:00 2001 From: Karan Sharma <55722391+ksharma-xyz@users.noreply.github.com> Date: Tue, 19 Nov 2024 23:54:12 +1100 Subject: [PATCH 40/67] Update TimeTableViewModel --- .../api/ratelimit/NetworkRateLimiter.kt | 4 +- .../service/stop_finder/StopFinderRequest.kt | 7 +- .../network/api/service/trip/TripRequest.kt | 2 +- .../ui/searchstop/SearchStopViewModel.kt | 24 ++++--- .../ui/timetable/TimeTableDestination.kt | 3 +- .../planner/ui/timetable/TimeTableScreen.kt | 1 - .../ui/timetable/TimeTableViewModel.kt | 68 ++++++++++--------- .../ui/timetable/TimeTableViewModelFactory.kt | 18 ----- .../timetable/business/TripResponseMapper.kt | 24 +++---- 9 files changed, 68 insertions(+), 83 deletions(-) delete mode 100644 feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/timetable/TimeTableViewModelFactory.kt diff --git a/feature/trip-planner/network/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/network/api/ratelimit/NetworkRateLimiter.kt b/feature/trip-planner/network/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/network/api/ratelimit/NetworkRateLimiter.kt index cc615ad0..1c677f83 100644 --- a/feature/trip-planner/network/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/network/api/ratelimit/NetworkRateLimiter.kt +++ b/feature/trip-planner/network/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/network/api/ratelimit/NetworkRateLimiter.kt @@ -9,7 +9,6 @@ import kotlinx.coroutines.flow.debounce import kotlinx.coroutines.flow.flatMapLatest import kotlinx.coroutines.flow.flow import kotlinx.coroutines.flow.update -import me.tatarka.inject.annotations.Inject import kotlin.time.Duration.Companion.milliseconds import kotlin.time.Duration.Companion.seconds @@ -19,8 +18,7 @@ import kotlin.time.Duration.Companion.seconds * * Note: This class should not be Singleton. It should be created per use-case. */ -@Inject -internal class NetworkRateLimiter : RateLimiter { +class NetworkRateLimiter : RateLimiter { private val triggerFlow = MutableSharedFlow(replay = 1) private val isFirstTime = MutableStateFlow(false) diff --git a/feature/trip-planner/network/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/network/api/service/stop_finder/StopFinderRequest.kt b/feature/trip-planner/network/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/network/api/service/stop_finder/StopFinderRequest.kt index 812820d6..978dea7c 100644 --- a/feature/trip-planner/network/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/network/api/service/stop_finder/StopFinderRequest.kt +++ b/feature/trip-planner/network/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/network/api/service/stop_finder/StopFinderRequest.kt @@ -3,18 +3,13 @@ package xyz.ksharma.krail.trip.planner.network.api.service.stop_finder import io.ktor.client.HttpClient import io.ktor.client.call.body import io.ktor.client.request.get -import io.ktor.http.HttpHeaders -import io.ktor.http.headers -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.IO -import kotlinx.coroutines.withContext import xyz.ksharma.krail.trip.planner.network.api.model.StopFinderResponse import xyz.ksharma.krail.trip.planner.network.api.model.StopType import xyz.ksharma.krail.trip.planner.network.api.service.NSW_TRANSPORT_BASE_URL suspend fun fetchStop( httpClient: HttpClient, - stopType: StopType, + stopType: StopType = StopType.STOP, stopSearchQuery: String, ): StopFinderResponse { return httpClient.get("${NSW_TRANSPORT_BASE_URL}/v1/tp/stop_finder") { diff --git a/feature/trip-planner/network/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/network/api/service/trip/TripRequest.kt b/feature/trip-planner/network/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/network/api/service/trip/TripRequest.kt index 095eff81..8776dff5 100644 --- a/feature/trip-planner/network/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/network/api/service/trip/TripRequest.kt +++ b/feature/trip-planner/network/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/network/api/service/trip/TripRequest.kt @@ -13,7 +13,7 @@ import xyz.ksharma.krail.trip.planner.network.api.model.TripResponse import xyz.ksharma.krail.trip.planner.network.api.service.NSW_TRANSPORT_BASE_URL suspend fun fetchTrip( - httpClient: HttpClient, apiKey: String, + httpClient: HttpClient, originStopId: String, destinationStopId: String, ): TripResponse = withContext(Dispatchers.IO) { diff --git a/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/searchstop/SearchStopViewModel.kt b/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/searchstop/SearchStopViewModel.kt index 27003d8b..b379d09c 100644 --- a/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/searchstop/SearchStopViewModel.kt +++ b/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/searchstop/SearchStopViewModel.kt @@ -4,11 +4,17 @@ import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import kotlinx.collections.immutable.persistentListOf import kotlinx.collections.immutable.toImmutableList +import kotlinx.coroutines.CoroutineDispatcher +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.IO +import kotlinx.coroutines.ensureActive import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch -import me.tatarka.inject.annotations.Inject +import kotlinx.coroutines.withContext +import xyz.ksharma.krail.trip.planner.network.api.service.getHttpClient +import xyz.ksharma.krail.trip.planner.network.api.service.stop_finder.fetchStop import xyz.ksharma.krail.trip.planner.ui.searchstop.StopResultMapper.toStopResults import xyz.ksharma.krail.trip.planner.ui.state.searchstop.SearchStopState import xyz.ksharma.krail.trip.planner.ui.state.searchstop.SearchStopUiEvent @@ -18,6 +24,8 @@ class SearchStopViewModel : ViewModel() { private val _uiState: MutableStateFlow = MutableStateFlow(SearchStopState()) val uiState: StateFlow = _uiState + private val httpClient = getHttpClient() + fun onEvent(event: SearchStopUiEvent) { when (event) { is SearchStopUiEvent.SearchTextChanged -> onSearchTextChanged(event.query) @@ -29,13 +37,13 @@ class SearchStopViewModel : ViewModel() { updateUiState { displayLoading() } viewModelScope.launch { - tripPlanningRepository.stopFinder(stopSearchQuery = query) - .onSuccess { response: StopFinderResponse -> - val results = response.toStopResults() - updateUiState { displayData(results) } - }.onFailure { - updateUiState { displayError() } - } + runCatching { + val results = + fetchStop(httpClient = httpClient, stopSearchQuery = query).toStopResults() + updateUiState { displayData(results) } + }.getOrElse { + updateUiState { displayError() } + } } } diff --git a/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/timetable/TimeTableDestination.kt b/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/timetable/TimeTableDestination.kt index 9e248847..5824d55f 100644 --- a/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/timetable/TimeTableDestination.kt +++ b/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/timetable/TimeTableDestination.kt @@ -8,7 +8,6 @@ import androidx.navigation.NavHostController import androidx.navigation.NavOptions import androidx.navigation.compose.composable import androidx.navigation.toRoute - import xyz.ksharma.krail.trip.planner.ui.navigation.ServiceAlertRoute import xyz.ksharma.krail.trip.planner.ui.navigation.TimeTableRoute import xyz.ksharma.krail.trip.planner.ui.state.timetable.TimeTableUiEvent @@ -33,7 +32,7 @@ internal fun NavGraphBuilder.timeTableDestination(navController: NavHostControll onEvent = { viewModel.onEvent(it) }, onBackClick = { navController.popBackStack() }, onAlertClick = { journeyId -> - Timber.d("journeyId: $journeyId") + println("journeyId: $journeyId") viewModel.fetchAlertsForJourney(journeyId) { alerts -> if (alerts.isNotEmpty()) { navController.navigate( diff --git a/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/timetable/TimeTableScreen.kt b/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/timetable/TimeTableScreen.kt index 1f5642c8..aa39a0f0 100644 --- a/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/timetable/TimeTableScreen.kt +++ b/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/timetable/TimeTableScreen.kt @@ -21,7 +21,6 @@ import androidx.compose.material.icons.automirrored.filled.ArrowBack import androidx.compose.material.icons.filled.Edit import androidx.compose.material.icons.filled.Star import androidx.compose.material.icons.outlined.Star ->>>>>>> 142d9d4b (Make timetable multiplatform) import androidx.compose.runtime.Composable import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.runtime.getValue diff --git a/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/timetable/TimeTableViewModel.kt b/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/timetable/TimeTableViewModel.kt index 66114b0f..dc056cd3 100644 --- a/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/timetable/TimeTableViewModel.kt +++ b/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/timetable/TimeTableViewModel.kt @@ -4,7 +4,8 @@ import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import kotlinx.collections.immutable.persistentListOf import kotlinx.collections.immutable.toImmutableList -import kotlinx.coroutines.CoroutineDispatcher +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.IO import kotlinx.coroutines.delay import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.SharingStarted @@ -18,13 +19,12 @@ import kotlinx.coroutines.launch import kotlinx.coroutines.withContext import xyz.ksharma.krail.core.datetime.DateTimeHelper.calculateTimeDifferenceFromNow import xyz.ksharma.krail.core.datetime.DateTimeHelper.toGenericFormattedTimeString -import xyz.ksharma.krail.di.AppDispatchers -import xyz.ksharma.krail.di.Dispatcher +import xyz.ksharma.krail.sandook.RealSandook import xyz.ksharma.krail.sandook.Sandook -import xyz.ksharma.krail.sandook.di.SandookFactory -import xyz.ksharma.krail.trip.planner.network.api.RateLimiter import xyz.ksharma.krail.trip.planner.network.api.model.TripResponse -import xyz.ksharma.krail.trip.planner.network.api.repository.TripPlanningRepository +import xyz.ksharma.krail.trip.planner.network.api.ratelimit.NetworkRateLimiter +import xyz.ksharma.krail.trip.planner.network.api.ratelimit.RateLimiter +import xyz.ksharma.krail.trip.planner.network.api.service.getHttpClient import xyz.ksharma.krail.trip.planner.ui.state.alerts.ServiceAlert import xyz.ksharma.krail.trip.planner.ui.state.timetable.TimeTableState import xyz.ksharma.krail.trip.planner.ui.state.timetable.TimeTableUiEvent @@ -33,25 +33,23 @@ import xyz.ksharma.krail.trip.planner.ui.timetable.business.buildJourneyList import xyz.ksharma.krail.trip.planner.ui.timetable.business.logForUnderstandingData import kotlin.time.Duration.Companion.seconds -class TimeTableViewModel ( - private val tripRepository: TripPlanningRepository, - sandookFactory: SandookFactory, - @Dispatcher(AppDispatchers.IO) private val ioDispatcher: CoroutineDispatcher, - private val rateLimiter: RateLimiter, -) : ViewModel() { +class TimeTableViewModel : ViewModel() { - private val sandook: Sandook = sandookFactory.create(SandookFactory.SandookKey.SAVED_TRIP) + private val sandook: Sandook = RealSandook() + private val rateLimiter: RateLimiter = NetworkRateLimiter() private val _uiState: MutableStateFlow = MutableStateFlow(TimeTableState()) val uiState: StateFlow = _uiState + private val httpClient = getHttpClient() + private val _isLoading: MutableStateFlow = MutableStateFlow(false) val isLoading: StateFlow = _isLoading // Will start fetching the trip as soon as the screen is visible, which means if android-app goes // to background and come back up again, the API call will be made. // Probably good to have data up to date. .onStart { - // Timber.d("onStart: Fetching Trip") + // Timber.d("onStart: Fetching Trip") fetchTrip() }.stateIn(viewModelScope, SharingStarted.WhileSubscribed(ANR_TIMEOUT), true) @@ -85,17 +83,17 @@ class TimeTableViewModel ( } private fun fetchTrip() { - // Timber.d("fetchTrip API Call") - viewModelScope.launch(ioDispatcher) { + // Timber.d("fetchTrip API Call") + viewModelScope.launch(Dispatchers.IO) { // TODO - silent refresh here, UI to display loading but silent one. rateLimiter.rateLimitFlow { - // Timber.d("rateLimitFlow block") + // Timber.d("rateLimitFlow block") loadTrip() }.catch { e -> println("Error while fetching trip: $e") }.collectLatest { result -> result.onSuccess { response -> - // Timber.d("Success API response") + // Timber.d("Success API response") updateUiState { copy( isLoading = false, @@ -105,40 +103,46 @@ class TimeTableViewModel ( } response.logForUnderstandingData() }.onFailure { - // Timber.e("Error while fetching trip: $it") + // Timber.e("Error while fetching trip: $it") updateUiState { copy(isLoading = false, isError = true) } } } } } - private suspend fun loadTrip(): Result = withContext(ioDispatcher) { - // Timber.d("loadTrip API Call") + private suspend fun loadTrip(): Result = withContext(Dispatchers.IO) { + // Timber.d("loadTrip API Call") require( tripInfo != null && tripInfo!!.fromStopId.isNotEmpty() && tripInfo!!.toStopId.isNotEmpty(), ) { "Trip Info is null or empty" } - tripRepository.trip( - originStopId = tripInfo!!.fromStopId, - destinationStopId = tripInfo!!.toStopId, - ) + runCatching { + val tripResponse = xyz.ksharma.krail.trip.planner.network.api.service.trip.fetchTrip( + httpClient = httpClient, + originStopId = tripInfo!!.fromStopId, + destinationStopId = tripInfo!!.toStopId, + ) + Result.success(tripResponse) + }.getOrElse { error -> + Result.failure(error) + } } private fun onSaveTripButtonClicked() { //Timber.d("Save Trip Button Clicked") - viewModelScope.launch(ioDispatcher) { + viewModelScope.launch(Dispatchers.IO) { tripInfo?.let { trip -> - // Timber.d("Toggle Save Trip: $trip") + // Timber.d("Toggle Save Trip: $trip") val savedTrip = sandook.getString(key = trip.tripId) if (savedTrip != null) { // Trip is already saved, so delete it sandook.remove(key = trip.tripId) - // Timber.d("Deleted Trip (Pref): ${Trip.fromJsonString(savedTrip)}") + // Timber.d("Deleted Trip (Pref): ${Trip.fromJsonString(savedTrip)}") updateUiState { copy(isTripSaved = false) } } else { // Trip is not saved, so save it sandook.putString(key = trip.tripId, value = trip.toJsonString()) - // Timber.d("Saved Trip (Pref): $trip") + // Timber.d("Saved Trip (Pref): $trip") updateUiState { copy(isTripSaved = true) } } } @@ -166,7 +170,7 @@ class TimeTableViewModel ( } private fun onReverseTripButtonClicked() { - // Timber.d("Reverse Trip Button Clicked -- Trigger") + // Timber.d("Reverse Trip Button Clicked -- Trigger") require(tripInfo != null) { "Trip Info is null" } val reverseTrip = Trip( fromStopId = tripInfo!!.toStopId, @@ -192,7 +196,7 @@ class TimeTableViewModel ( * journey card should be updated. */ private fun updateTimeText() = viewModelScope.launch { - val updatedJourneyList = withContext(ioDispatcher) { + val updatedJourneyList = withContext(Dispatchers.IO) { _uiState.value.journeyList.map { journeyCardInfo -> journeyCardInfo.copy( timeText = calculateTimeDifferenceFromNow( @@ -215,7 +219,7 @@ class TimeTableViewModel ( fun fetchAlertsForJourney(journeyId: String, onResult: (Set) -> Unit) { viewModelScope.launch { - val alerts = withContext(ioDispatcher) { + val alerts = withContext(Dispatchers.IO) { runCatching { _uiState.value.journeyList.find { it.journeyId == journeyId }?.let { journey -> getAlertsFromJourney(journey) diff --git a/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/timetable/TimeTableViewModelFactory.kt b/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/timetable/TimeTableViewModelFactory.kt deleted file mode 100644 index 1e9d8dae..00000000 --- a/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/timetable/TimeTableViewModelFactory.kt +++ /dev/null @@ -1,18 +0,0 @@ -package xyz.ksharma.krail.trip.planner.ui.timetable - -import kotlinx.coroutines.CoroutineDispatcher -import me.tatarka.inject.annotations.Inject -import xyz.ksharma.krail.di.AppDispatchers -import xyz.ksharma.krail.di.Dispatcher - -@Inject -class TimeTableViewModelFactory( - private val tripRepository: TripPlanningRepository, - private val sandookFactory: SandookFactory, - @Dispatcher(AppDispatchers.IO) private val ioDispatcher: CoroutineDispatcher, - private val rateLimiter: RateLimiter -) { - fun create(): TimeTableViewModel { - return TimeTableViewModel(tripRepository, sandookFactory, ioDispatcher, rateLimiter) - } -} diff --git a/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/timetable/business/TripResponseMapper.kt b/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/timetable/business/TripResponseMapper.kt index 548d2dc6..01691be2 100644 --- a/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/timetable/business/TripResponseMapper.kt +++ b/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/timetable/business/TripResponseMapper.kt @@ -68,7 +68,7 @@ internal fun TripResponse.buildJourneyList(): ImmutableList leg.infos.orEmpty() }.toSet().size, ).also { - //Timber.d("\tJourneyId: ${it.journeyId}") + //println("\tJourneyId: ${it.journeyId}") } } else { null @@ -113,8 +113,8 @@ fun TripResponse.Leg?.getPlatformNumber(): String? { private fun List.logTransportModes() = forEachIndexed { index, leg -> // log origin's disassembledName - Timber.d("Origin #$index: ${leg.origin?.disassembledName}") - Timber.d( + println("Origin #$index: ${leg.origin?.disassembledName}") + println( "TransportMode #$index: ${leg.transportation?.product?.productClass}, " + "name: ${leg.transportation?.product?.name}, " + "stops: ${leg.stopSequence?.size}, " + @@ -137,13 +137,13 @@ private fun List.getTransportModeLines() = mapNotNull { leg -> private fun List.getLegsList() = mapNotNull { it.toUiModel() }.toImmutableList() private fun String.getTimeText() = let { - // Timber.d("originTime: $it :- ${calculateTimeDifferenceFromNow(utcDateString = it)}") + // println("originTime: $it :- ${calculateTimeDifferenceFromNow(utcDateString = it)}") calculateTimeDifferenceFromNow(utcDateString = it).toGenericFormattedTimeString() } @Suppress("ComplexCondition") private fun TripResponse.Leg.toUiModel(): TimeTableState.JourneyCardInfo.Leg? { - Timber.d( + println( "\tFFF Leg: ${this.duration}, " + "leg: ${this.origin?.name} TO ${this.destination?.name}" + " - isWalk:${this.isWalkingLeg()}, " + @@ -161,9 +161,9 @@ private fun TripResponse.Leg.toUiModel(): TimeTableState.JourneyCardInfo.Leg? { val stops = stopSequence?.mapNotNull { it.toUiModel() }?.toImmutableList() val alerts = infos?.mapNotNull { it.toAlert() }?.toImmutableList() alerts?.forEach { - //Timber.d("\t Alert: ${it.heading.take(5)}, ${it.message.take(5)}, ${it.priority}") + //println("\t Alert: ${it.heading.take(5)}, ${it.message.take(5)}, ${it.priority}") } - // Timber.d("Alert---") + // println("Alert---") return when { // Walking Leg - Always check before public transport leg @@ -174,7 +174,7 @@ private fun TripResponse.Leg.toUiModel(): TimeTableState.JourneyCardInfo.Leg? { } else -> { // Public Transport Leg -// Timber.d("FFF PTLeg: $displayDuration, leg: ${this.destination?.name} ") +// println("FFF PTLeg: $displayDuration, leg: ${this.destination?.name} ") if (transportMode != null && lineName != null && displayText != null && numberOfStops != null && stops != null && displayDuration != null ) { @@ -249,19 +249,19 @@ private fun TripResponse.StopSequence.toUiModel(): TimeTableState.JourneyCardInf } internal fun TripResponse.logForUnderstandingData() { - Timber.d("Journeys: ${journeys?.size}") + println("Journeys: ${journeys?.size}") journeys?.mapIndexed { jindex, j -> - Timber.d("JOURNEY #${jindex + 1}") + println("JOURNEY #${jindex + 1}") j.legs?.forEachIndexed { index, leg -> val transportationProductClass = leg.transportation?.product?.productClass - Timber.d( + println( " LEG#${index + 1} -- Duration: ${leg.duration} -- " + "productClass:${transportationProductClass?.toInt()}", ) - Timber.d( + println( "\t\t ORG: ${ leg.origin?.departureTimeEstimated?.utcToAEST() ?.formatTo12HourTime() From 3846d637ed2f91a6b0216590fc7789efe184ffee Mon Sep 17 00:00:00 2001 From: Karan Sharma <55722391+ksharma-xyz@users.noreply.github.com> Date: Tue, 19 Nov 2024 23:57:45 +1100 Subject: [PATCH 41/67] update UsualRideViewModel --- .../planner/ui/usualride/UsualRideViewModel.kt | 16 ++++++---------- .../planner/ui/viewmodel/ViewModelFactory.kt | 17 ----------------- 2 files changed, 6 insertions(+), 27 deletions(-) delete mode 100644 feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/viewmodel/ViewModelFactory.kt diff --git a/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/usualride/UsualRideViewModel.kt b/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/usualride/UsualRideViewModel.kt index 0ac9fa73..99ebba82 100644 --- a/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/usualride/UsualRideViewModel.kt +++ b/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/usualride/UsualRideViewModel.kt @@ -2,24 +2,20 @@ package xyz.ksharma.krail.trip.planner.ui.usualride import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope -import kotlinx.coroutines.CoroutineDispatcher +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.IO import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.launch -import xyz.ksharma.krail.di.AppDispatchers -import xyz.ksharma.krail.di.Dispatcher +import xyz.ksharma.krail.sandook.RealSandook import xyz.ksharma.krail.sandook.Sandook -import xyz.ksharma.krail.sandook.di.SandookFactory import xyz.ksharma.krail.trip.planner.ui.state.TransportMode import xyz.ksharma.krail.trip.planner.ui.state.usualride.UsualRideEvent import xyz.ksharma.krail.trip.planner.ui.state.usualride.UsualRideState -class UsualRideViewModel( - sandookFactory: SandookFactory, - @Dispatcher(AppDispatchers.IO) private val ioDispatcher: CoroutineDispatcher, -) : ViewModel() { +class UsualRideViewModel : ViewModel() { - private val sandook: Sandook = sandookFactory.create(SandookFactory.SandookKey.THEME) + private val sandook: Sandook = RealSandook() private val _uiState: MutableStateFlow = MutableStateFlow(UsualRideState()) val uiState: StateFlow = _uiState @@ -31,7 +27,7 @@ class UsualRideViewModel( } private fun onTransportModeSelected(productClass: Int) { - viewModelScope.launch(ioDispatcher) { + viewModelScope.launch(Dispatchers.IO) { TransportMode.toTransportModeType(productClass)?.let { mode -> //Timber.d("onTransportModeSelected: $mode") sandook.putInt("selectedMode", mode.productClass) diff --git a/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/viewmodel/ViewModelFactory.kt b/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/viewmodel/ViewModelFactory.kt deleted file mode 100644 index 5302054f..00000000 --- a/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/viewmodel/ViewModelFactory.kt +++ /dev/null @@ -1,17 +0,0 @@ -package xyz.ksharma.krail.trip.planner.ui.viewmodel - -import kotlinx.coroutines.CoroutineDispatcher -import xyz.ksharma.krail.trip.planner.ui.savedtrips.SavedTripsViewModel - -interface ViewModelFactory { - fun create(): T -} - -// Factory for SavedTripsViewModel -class SavedTripsViewModelFactory( - private val ioDispatcher: CoroutineDispatcher, -) : ViewModelFactory { - override fun create(): SavedTripsViewModel { - return SavedTripsViewModel(ioDispatcher) - } -} From e9fdca2373c9005232cb72255237708676252110 Mon Sep 17 00:00:00 2001 From: Karan Sharma <55722391+ksharma-xyz@users.noreply.github.com> Date: Wed, 20 Nov 2024 00:07:42 +1100 Subject: [PATCH 42/67] Add KrailNavHost / Splash Screen --- composeApp/build.gradle.kts | 5 +- .../xyz/ksharma/krail/common/KrailApp.kt | 60 +----- .../xyz/ksharma/krail/common/KrailNavHost.kt | 99 +++++++++ .../krail/common/splash/SplashScreen.kt | 199 ++++++++++++++++++ .../krail/common/splash/SplashViewModel.kt | 37 ++++ 5 files changed, 340 insertions(+), 60 deletions(-) create mode 100644 composeApp/src/commonMain/kotlin/xyz/ksharma/krail/common/KrailNavHost.kt create mode 100644 composeApp/src/commonMain/kotlin/xyz/ksharma/krail/common/splash/SplashScreen.kt create mode 100644 composeApp/src/commonMain/kotlin/xyz/ksharma/krail/common/splash/SplashViewModel.kt diff --git a/composeApp/build.gradle.kts b/composeApp/build.gradle.kts index c4140a2b..dadf033d 100644 --- a/composeApp/build.gradle.kts +++ b/composeApp/build.gradle.kts @@ -58,6 +58,10 @@ kotlin { implementation(projects.taj) implementation(projects.sandook) implementation(projects.feature.tripPlanner.network) + implementation(projects.feature.tripPlanner.ui) + implementation(projects.feature.tripPlanner.state) + + implementation(libs.navigation.compose) implementation(compose.runtime) implementation(compose.foundation) @@ -69,7 +73,6 @@ kotlin { implementation(libs.multiplatform.settings) - implementation(libs.ktor.client.core) implementation(libs.ktor.client.cio) implementation(libs.ktor.client.content.negotiation) diff --git a/composeApp/src/commonMain/kotlin/xyz/ksharma/krail/common/KrailApp.kt b/composeApp/src/commonMain/kotlin/xyz/ksharma/krail/common/KrailApp.kt index 3ac8f7a7..46c3bb97 100644 --- a/composeApp/src/commonMain/kotlin/xyz/ksharma/krail/common/KrailApp.kt +++ b/composeApp/src/commonMain/kotlin/xyz/ksharma/krail/common/KrailApp.kt @@ -1,69 +1,11 @@ package xyz.ksharma.krail.common -import androidx.compose.foundation.background -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.layout.statusBarsPadding import androidx.compose.runtime.Composable -import androidx.compose.runtime.LaunchedEffect -import androidx.compose.ui.Modifier -import com.russhwolf.settings.Settings -import kotlinx.coroutines.delay -import xyz.ksharma.krail.sandook.RealSandook -import xyz.ksharma.krail.taj.components.Text import xyz.ksharma.krail.taj.theme.KrailTheme -import xyz.ksharma.krail.trip.planner.network.api.model.StopType -import xyz.ksharma.krail.trip.planner.network.api.service.rememberHttpClient -import xyz.ksharma.krail.trip.planner.network.api.service.stop_finder.fetchStop -import xyz.ksharma.krail.trip.planner.network.api.service.trip.fetchTrip @Composable fun KrailApp() { KrailTheme { - -/* - LaunchedEffect(Unit) { - val sandook = RealSandook(Settings()) - println("sandook: " + sandook) - sandook.putString("key", "value") - - delay(2000) - val sandook1 = RealSandook(Settings()) - println("sandook1: " + sandook1) - var x = sandook1.getString("key") - println("Sandook1 value: x: $x") - x = sandook.getString("key") - println("Sandook value: x: $x") - } -*/ - - val httpClient = rememberHttpClient() - LaunchedEffect(Unit) { - try { - - val stopResponse = fetchStop( - httpClient = httpClient, - stopType = StopType.STOP, - stopSearchQuery = "central" - ) - println("stopResponse total: ${stopResponse.locations?.size}") - - stopResponse.locations?.forEach { - println("Stop: ${it.name}, ${it.id}") - } - } catch (e: Exception) { - println("Stop Exception: $e") - throw e - } - } - - - Column(modifier = Modifier.fillMaxSize().background(color = KrailTheme.colors.surface)) { - Text( - "Hello, Krail!", - style = KrailTheme.typography.titleLarge, - modifier = Modifier.statusBarsPadding() - ) - } + KrailNavHost() } } diff --git a/composeApp/src/commonMain/kotlin/xyz/ksharma/krail/common/KrailNavHost.kt b/composeApp/src/commonMain/kotlin/xyz/ksharma/krail/common/KrailNavHost.kt new file mode 100644 index 00000000..43279b8c --- /dev/null +++ b/composeApp/src/commonMain/kotlin/xyz/ksharma/krail/common/KrailNavHost.kt @@ -0,0 +1,99 @@ +package xyz.ksharma.krail.common + +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.runtime.Composable +import androidx.compose.runtime.CompositionLocalProvider +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.saveable.rememberSaveable +import androidx.compose.runtime.setValue +import androidx.compose.ui.Modifier +import androidx.lifecycle.compose.collectAsStateWithLifecycle +import androidx.lifecycle.viewmodel.compose.viewModel +import androidx.navigation.NavOptions +import androidx.navigation.compose.NavHost +import androidx.navigation.compose.composable +import androidx.navigation.compose.rememberNavController +import kotlinx.serialization.Serializable +import xyz.ksharma.krail.common.splash.SplashScreen +import xyz.ksharma.krail.common.splash.SplashViewModel +import xyz.ksharma.krail.taj.LocalThemeColor +import xyz.ksharma.krail.taj.LocalThemeContentColor +import xyz.ksharma.krail.taj.theme.KrailTheme +import xyz.ksharma.krail.taj.theme.getForegroundColor +import xyz.ksharma.krail.taj.unspecifiedColor +import xyz.ksharma.krail.trip.planner.ui.components.hexToComposeColor +import xyz.ksharma.krail.trip.planner.ui.components.toHex +import xyz.ksharma.krail.trip.planner.ui.navigation.SavedTripsRoute +import xyz.ksharma.krail.trip.planner.ui.navigation.UsualRideRoute +import xyz.ksharma.krail.trip.planner.ui.navigation.tripPlannerDestinations + +/** + * TODO - I don't like [NavHost] defined in app module, I would love to refactor it to :core:navigation module + * but that results in a cyclic dependency. Feature module needs to depend on :core:navigation for navigation logic and + * then core:navigation needs to depend on feature module for the destinations / nested navigation graphs. + * This results in putting all nav logic in the app module, which will have negative impacts on the build time. + * Why is navigation so hard in Compose? + * + * Navigation logic is currently taken from [NowInAndroid](https://github.com/android/nowinandroid] app, + * so fine for now. But I will want to refactor it to something nicer e.g. using Circuit library + * from Slack, but that would also mean refactoring to use MVP instead of MVVM. + */ +@Composable +fun KrailNavHost(modifier: Modifier = Modifier) { + val navController = rememberNavController() + val themeColorHexCode = rememberSaveable { mutableStateOf(unspecifiedColor) } + var productClass: Int? by rememberSaveable { mutableStateOf(null) } + val themeContentColorHexCode = rememberSaveable { mutableStateOf(unspecifiedColor) } + themeContentColorHexCode.value = + getForegroundColor( + backgroundColor = themeColorHexCode.value.hexToComposeColor(), + ).toHex() + + CompositionLocalProvider( + LocalThemeColor provides themeColorHexCode, + LocalThemeContentColor provides themeContentColorHexCode, + ) { + NavHost( + navController = navController, + startDestination = SplashScreen, + modifier = modifier.fillMaxSize(), + ) { + tripPlannerDestinations(navController = navController) + + composable { + val viewModel: SplashViewModel = viewModel() + val isLoading by viewModel.isLoading.collectAsStateWithLifecycle() + val mode by viewModel.uiState.collectAsStateWithLifecycle() + + productClass = mode?.productClass + themeColorHexCode.value = mode?.colorCode ?: unspecifiedColor + + SplashScreen( + logoColor = if (productClass != null && themeColorHexCode.value != unspecifiedColor) { + themeColorHexCode.value.hexToComposeColor() + } else { + KrailTheme.colors.onSurface + }, + backgroundColor = KrailTheme.colors.surface, + onSplashComplete = { + navController.navigate( + route = if (productClass != null) { + SavedTripsRoute + } else { + UsualRideRoute + }, + navOptions = NavOptions.Builder() + .setLaunchSingleTop(true) + .setPopUpTo(inclusive = true) + .build(), + ) + }, + ) + } + } + } +} + +@Serializable +private data object SplashScreen diff --git a/composeApp/src/commonMain/kotlin/xyz/ksharma/krail/common/splash/SplashScreen.kt b/composeApp/src/commonMain/kotlin/xyz/ksharma/krail/common/splash/SplashScreen.kt new file mode 100644 index 00000000..6cbce151 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/xyz/ksharma/krail/common/splash/SplashScreen.kt @@ -0,0 +1,199 @@ +package xyz.ksharma.krail.common.splash + +import androidx.compose.animation.core.FastOutSlowInEasing +import androidx.compose.animation.core.LinearEasing +import androidx.compose.animation.core.RepeatMode +import androidx.compose.animation.core.StartOffset +import androidx.compose.animation.core.animateFloat +import androidx.compose.animation.core.infiniteRepeatable +import androidx.compose.animation.core.keyframes +import androidx.compose.animation.core.rememberInfiniteTransition +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableFloatStateOf +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberUpdatedState +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.graphicsLayer +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.unit.TextUnit +import androidx.compose.ui.unit.TextUnitType +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import kotlinx.coroutines.delay +import org.jetbrains.compose.ui.tooling.preview.Preview +import xyz.ksharma.krail.taj.components.Text +import xyz.ksharma.krail.taj.theme.KrailTheme + +@Composable +fun SplashScreen( + logoColor: Color?, + backgroundColor: Color?, + onSplashComplete: () -> Unit, + modifier: Modifier = Modifier, +) { + Box( + modifier = modifier + .fillMaxSize() + .background(color = backgroundColor ?: KrailTheme.colors.surface), + contentAlignment = Alignment.Center, + ) { + AnimatedKrailLogo(logoColor = logoColor ?: KrailTheme.colors.onSurface) + + val splashComplete by rememberUpdatedState(onSplashComplete) + LaunchedEffect(key1 = Unit) { + delay(1200) + splashComplete() + } + } +} + +@Composable +private fun AnimatedKrailLogo( + logoColor: Color, + modifier: Modifier = Modifier, +) { + var animationStarted by remember { mutableStateOf(false) } + + LaunchedEffect(key1 = Unit) { + animationStarted = true + } + Column(modifier = modifier, horizontalAlignment = Alignment.CenterHorizontally) { + Row { + AnimatedLetter( + letter = "K", + animationStarted = animationStarted, + fontSize = 80.sp, + delayMillis = 0, + logoColor = logoColor, + modifier = Modifier.alignByBaseline(), + ) + AnimatedLetter( + letter = "R", + animationStarted = animationStarted, + logoColor = logoColor, + delayMillis = 50, + modifier = Modifier.alignByBaseline(), + ) + AnimatedLetter( + letter = "A", + animationStarted = animationStarted, + logoColor = logoColor, + delayMillis = 100, + modifier = Modifier.alignByBaseline(), + ) + AnimatedLetter( + letter = "I", + animationStarted = animationStarted, + logoColor = logoColor, + delayMillis = 150, + modifier = Modifier.alignByBaseline(), + ) + AnimatedLetter( + letter = "L", + animationStarted = animationStarted, + logoColor = logoColor, + delayMillis = 200, + modifier = Modifier.alignByBaseline(), + ) + } + + Text( + text = "Ride the rail, without fail.", + style = KrailTheme.typography.titleMedium.copy( + fontWeight = FontWeight.Normal, + ), + textAlign = TextAlign.Center, + color = logoColor, + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 16.dp, vertical = 0.dp), + ) + } +} + +@Composable +private fun AnimatedLetter( + letter: String, + animationStarted: Boolean, + logoColor: Color, + modifier: Modifier = Modifier, + fontSize: TextUnit = TextUnit(65F, TextUnitType.Sp), + delayMillis: Int = 100, +) { + val infiniteTransition = rememberInfiniteTransition(label = "animeAnimation") + + // Scale animation with anticipation and squash/stretch + val scale by infiniteTransition.animateFloat( + initialValue = 1f, + targetValue = 1.2f, + animationSpec = infiniteRepeatable( + animation = keyframes { + durationMillis = 1100 + 0.0f at 0 using LinearEasing // Hold initial scale + 0.7f at 200 using FastOutSlowInEasing // Anticipation (quick shrink) + 1.2f at 500 using FastOutSlowInEasing // Squash/stretch (overshoot) + 1.0f at 1000 using FastOutSlowInEasing // Settle back to normal scale + 1.0f at 1100 using LinearEasing // Keep at normal scale + }, + repeatMode = RepeatMode.Reverse, + initialStartOffset = StartOffset(offsetMillis = delayMillis), + ), + label = "animeAnimation", + ) + + val letterScale by remember(scale) { + mutableFloatStateOf(if (animationStarted) scale else 1f) + } + + Text( + text = letter, + color = logoColor, + style = KrailTheme.typography.displayLarge.copy( + fontSize = fontSize, + letterSpacing = 4.sp, + fontWeight = FontWeight.ExtraBold, + ), + modifier = modifier + .graphicsLayer { + scaleX = letterScale + scaleY = letterScale + } + .padding(4.dp), + ) +} + +@Preview +@Composable +private fun PreviewLogo() { + KrailTheme { + Column(modifier = Modifier.background(color = KrailTheme.colors.surface)) { + AnimatedKrailLogo(logoColor = Color(0xFFF6891F)) + } + } +} + +@Preview +@Composable +private fun PreviewSplashScreen() { + KrailTheme { + SplashScreen( + onSplashComplete = {}, + logoColor = KrailTheme.colors.onSurface, + backgroundColor = Color(0xFF009B77), + ) + } +} diff --git a/composeApp/src/commonMain/kotlin/xyz/ksharma/krail/common/splash/SplashViewModel.kt b/composeApp/src/commonMain/kotlin/xyz/ksharma/krail/common/splash/SplashViewModel.kt new file mode 100644 index 00000000..8e728140 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/xyz/ksharma/krail/common/splash/SplashViewModel.kt @@ -0,0 +1,37 @@ +package xyz.ksharma.krail.common.splash + +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.IO +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.SharingStarted +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.onStart +import kotlinx.coroutines.flow.stateIn +import kotlinx.coroutines.launch +import xyz.ksharma.krail.sandook.RealSandook +import xyz.ksharma.krail.sandook.Sandook +import xyz.ksharma.krail.trip.planner.ui.state.TransportMode + +class SplashViewModel : ViewModel() { + + private val sandook: Sandook = RealSandook() + + private val _uiState: MutableStateFlow = MutableStateFlow(null) + val uiState: MutableStateFlow = _uiState + + private val _isLoading: MutableStateFlow = MutableStateFlow(false) + val isLoading: StateFlow = _isLoading + .onStart { + getThemeTransportMode() + }.stateIn(viewModelScope, SharingStarted.WhileSubscribed(5000), true) + + private fun getThemeTransportMode() { + viewModelScope.launch(Dispatchers.IO) { + val productClass = sandook.getInt("selectedMode") + val mode = TransportMode.toTransportModeType(productClass) + _uiState.value = mode + } + } +} From 6048b3a88b1bb43df9c12cd1fb76aae6a54c4961 Mon Sep 17 00:00:00 2001 From: Karan Sharma <55722391+ksharma-xyz@users.noreply.github.com> Date: Wed, 20 Nov 2024 00:09:09 +1100 Subject: [PATCH 43/67] Remove duplicate DateTimeHelper --- .../krail/core/datetime/DateTimeHelper.kt | 113 ------------------ 1 file changed, 113 deletions(-) delete mode 100644 core/date-time/src/main/kotlin/xyz/ksharma/krail/core/datetime/DateTimeHelper.kt diff --git a/core/date-time/src/main/kotlin/xyz/ksharma/krail/core/datetime/DateTimeHelper.kt b/core/date-time/src/main/kotlin/xyz/ksharma/krail/core/datetime/DateTimeHelper.kt deleted file mode 100644 index b917be9a..00000000 --- a/core/date-time/src/main/kotlin/xyz/ksharma/krail/core/datetime/DateTimeHelper.kt +++ /dev/null @@ -1,113 +0,0 @@ -package xyz.ksharma.krail.core.datetime - -import kotlinx.datetime.Instant -import timber.log.Timber -import java.time.Duration -import java.time.ZoneId -import java.time.ZonedDateTime -import java.time.format.DateTimeFormatter -import kotlin.math.absoluteValue -import kotlin.time.Duration.Companion.minutes -import kotlin.time.DurationUnit -import kotlin.time.toKotlinDuration - -object DateTimeHelper { - - fun String.formatTo12HourTime(): String { - // Parse the string as ZonedDateTime - val zonedDateTime = ZonedDateTime.parse(this) - - // Define the formatter for 12-hour time with AM/PM - val timeFormatter = DateTimeFormatter.ofPattern("h:mm a") - - // Format the ZonedDateTime to 12-hour format - return zonedDateTime.format(timeFormatter) - } - - /** - * Convert a date-time string in UTC to AEST time zone. - * E.g. "2021-08-01T12:00:00Z" -> "2021-08-01T22:00:00+10:00[Australia/Sydney]" - */ - fun String.utcToAEST(): String { - // Parse the string as a ZonedDateTime in UTC - val utcDateTime = ZonedDateTime.parse(this, DateTimeFormatter.ISO_ZONED_DATE_TIME) - - // Convert to AEST time zone (UTC+10) - val aestZoneId = ZoneId.of("Australia/Sydney") - val aestDateTime = utcDateTime.withZoneSameInstant(aestZoneId) - return aestDateTime.format(DateTimeFormatter.ISO_DATE_TIME) - } - - /** - * Converts a date-time string in Australian Eastern Standard Time (AEST) - * to a string representing the time in hh:mm a format (12-hour format). - * - * The input string is expected to be in ISO 8601 format (e.g., "2024-09-24T19:00:00+10:00"). - * - * @return The time in hh:mm a format (e.g., "07:00 am"). - */ - fun String.aestToHHMM(): String { - val aestDateTime = ZonedDateTime.parse(this, DateTimeFormatter.ISO_DATE_TIME) - val timeFormatter = DateTimeFormatter.ofPattern("hh:mm a") - return aestDateTime.format(timeFormatter) - } - - /** - * Calculates the time difference between two dates in the UTC format - * ("2024-09-24T09:00:00Z") - * - * @return The time difference between the two dates as a kotlin.time.Duration object. - */ - fun calculateTimeDifferenceFromNow( - utcDateString: String, - now: Instant = kotlinx.datetime.Clock.System.now(), // Get current Instant in UTC - ): kotlin.time.Duration { - val instant = Instant.parse(utcDateString) // Parse UTC string to Instant - return instant - now - } - - fun kotlin.time.Duration.toGenericFormattedTimeString(): String { - val totalMinutes = this.toLong(DurationUnit.MINUTES) - val hours = this.toLong(DurationUnit.HOURS) - val partialMinutes = totalMinutes - (hours * 60.minutes.inWholeMinutes) - - val formattedDifference = when { - totalMinutes < 0 -> - "${totalMinutes.absoluteValue} " + - "${if (totalMinutes.absoluteValue == 1L) "min" else "mins"} ago" - - totalMinutes == 0L -> "Now" - hours == 1L -> "in ${hours.absoluteValue}h ${partialMinutes.absoluteValue}m" - hours >= 2 -> "in ${hours.absoluteValue}h" - else -> "in ${totalMinutes.absoluteValue} ${if (totalMinutes.absoluteValue == 1L) "min" else "mins"}" - } - Timber.d("\t minutes: $partialMinutes, hours: $hours, formattedDifference: $formattedDifference -> originTime") - return formattedDifference - } - - fun kotlin.time.Duration.toFormattedDurationTimeString(): String { - val totalMinutes = this.toLong(DurationUnit.MINUTES) - val hours = this.toLong(DurationUnit.HOURS) - val partialMinutes = totalMinutes - (hours * 60.minutes.inWholeMinutes) - - val formattedDifference = when { - hours >= 1 -> "${hours.absoluteValue}h ${partialMinutes.absoluteValue}m" - else -> "${totalMinutes.absoluteValue} ${if (totalMinutes.absoluteValue == 1L) "min" else "mins"}" - } - return formattedDifference - } - - fun calculateTimeDifference( - utcDateString1: String, - utcDateString2: String, - ): kotlin.time.Duration { - // Parse the first UTC date string to a ZonedDateTime - val dateTime1 = ZonedDateTime.parse(utcDateString1, DateTimeFormatter.ISO_ZONED_DATE_TIME) - - // Parse the second UTC date string to a ZonedDateTime - val dateTime2 = ZonedDateTime.parse(utcDateString2, DateTimeFormatter.ISO_ZONED_DATE_TIME) - - // Calculate the duration between the two ZonedDateTime instances - return Duration.between(dateTime1, dateTime2).toKotlinDuration() - } -} From 4255ec9a3df6c83d36701abc944abff1679489c1 Mon Sep 17 00:00:00 2001 From: Karan Sharma <55722391+ksharma-xyz@users.noreply.github.com> Date: Wed, 20 Nov 2024 00:27:20 +1100 Subject: [PATCH 44/67] Fixing compilation errors --- .../ksharma/krail/common/splash/SplashViewModel.kt | 2 +- feature/trip-planner/state/build.gradle.kts | 4 ++++ feature/trip-planner/ui/build.gradle.kts | 12 ++++++++++++ .../krail/trip/planner/ui/alerts/CollapsibleAlert.kt | 3 ++- .../ksharma/krail/trip/planner/ui/alerts/HtmlText.kt | 6 +++++- .../planner/ui/savedtrips/SavedTripsDestination.kt | 12 ------------ .../trip/planner/ui/usualride/UsualRideViewModel.kt | 2 +- 7 files changed, 25 insertions(+), 16 deletions(-) diff --git a/composeApp/src/commonMain/kotlin/xyz/ksharma/krail/common/splash/SplashViewModel.kt b/composeApp/src/commonMain/kotlin/xyz/ksharma/krail/common/splash/SplashViewModel.kt index 8e728140..252c3c9f 100644 --- a/composeApp/src/commonMain/kotlin/xyz/ksharma/krail/common/splash/SplashViewModel.kt +++ b/composeApp/src/commonMain/kotlin/xyz/ksharma/krail/common/splash/SplashViewModel.kt @@ -29,7 +29,7 @@ class SplashViewModel : ViewModel() { private fun getThemeTransportMode() { viewModelScope.launch(Dispatchers.IO) { - val productClass = sandook.getInt("selectedMode") + val productClass = sandook.getString("selectedMode")?.toIntOrNull() ?: 0 val mode = TransportMode.toTransportModeType(productClass) _uiState.value = mode } diff --git a/feature/trip-planner/state/build.gradle.kts b/feature/trip-planner/state/build.gradle.kts index 488cc809..6ea023c0 100644 --- a/feature/trip-planner/state/build.gradle.kts +++ b/feature/trip-planner/state/build.gradle.kts @@ -2,6 +2,8 @@ plugins { alias(libs.plugins.krail.android.library) alias(libs.plugins.kotlin.serialization) alias(libs.plugins.krail.kotlin.multiplatform) + alias(libs.plugins.krail.compose.multiplatform) + alias(libs.plugins.compose.compiler) } android { @@ -27,6 +29,8 @@ kotlin { dependencies { implementation(libs.kotlinx.collections.immutable) implementation(libs.kotlinx.serialization.json) + + implementation(compose.runtime) } } } diff --git a/feature/trip-planner/ui/build.gradle.kts b/feature/trip-planner/ui/build.gradle.kts index d2920d41..17febf3a 100644 --- a/feature/trip-planner/ui/build.gradle.kts +++ b/feature/trip-planner/ui/build.gradle.kts @@ -42,6 +42,18 @@ kotlin { implementation(libs.lifecycle.viewmodel.compose) implementation(libs.di.kotlinInjectRuntime) + + // TODO - remove once DI added - start + implementation(libs.multiplatform.settings) + implementation(libs.ktor.client.core) + implementation(libs.ktor.client.cio) + implementation(libs.ktor.client.auth) + implementation(libs.ktor.client.content.negotiation) + implementation(libs.ktor.client.logging) + implementation(libs.ktor.serialization.kotlinx.json) + implementation(libs.kotlinx.datetime) + implementation(libs.slf4j.simple) // Logging + // TODO - remove once DI added - end } } commonTest { diff --git a/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/alerts/CollapsibleAlert.kt b/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/alerts/CollapsibleAlert.kt index 716dcc74..145c1ccd 100644 --- a/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/alerts/CollapsibleAlert.kt +++ b/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/alerts/CollapsibleAlert.kt @@ -89,7 +89,8 @@ fun CollapsibleAlert( ) } if (isHtml) { - HtmlText(serviceAlert.message, onClick = onClick) + // TODO - Html Text Component + Text(text = serviceAlert.message) // , onClick = onClick } else { Text( text = serviceAlert.message, diff --git a/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/alerts/HtmlText.kt b/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/alerts/HtmlText.kt index 805ddfff..dbdcf5d2 100644 --- a/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/alerts/HtmlText.kt +++ b/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/alerts/HtmlText.kt @@ -1,4 +1,5 @@ package xyz.ksharma.krail.trip.planner.ui.alerts +/* import android.graphics.Typeface import android.text.method.LinkMovementMethod @@ -16,9 +17,11 @@ import androidx.compose.ui.viewinterop.AndroidView import androidx.core.text.HtmlCompat import xyz.ksharma.krail.taj.theme.KrailTheme +*/ /** * Reference - https://developer.android.com/codelabs/jetpack-compose-migration#8 - */ + *//* + @Composable fun HtmlText(html: String, modifier: Modifier = Modifier, onClick: () -> Unit = {}) { // Remembers the HTML formatted description. Re-executes on a new description @@ -61,3 +64,4 @@ fun HtmlText(html: String, modifier: Modifier = Modifier, onClick: () -> Unit = modifier = modifier, ) } +*/ diff --git a/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/savedtrips/SavedTripsDestination.kt b/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/savedtrips/SavedTripsDestination.kt index 9aaea775..dd4591a5 100644 --- a/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/savedtrips/SavedTripsDestination.kt +++ b/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/savedtrips/SavedTripsDestination.kt @@ -11,9 +11,6 @@ import androidx.navigation.NavGraphBuilder import androidx.navigation.NavHostController import androidx.navigation.NavOptions import androidx.navigation.compose.composable -import kotlinx.coroutines.CoroutineDispatcher -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.IO import xyz.ksharma.krail.trip.planner.ui.navigation.SavedTripsRoute import xyz.ksharma.krail.trip.planner.ui.navigation.SearchStopFieldType import xyz.ksharma.krail.trip.planner.ui.navigation.SearchStopRoute @@ -21,7 +18,6 @@ import xyz.ksharma.krail.trip.planner.ui.navigation.TimeTableRoute import xyz.ksharma.krail.trip.planner.ui.state.savedtrip.SavedTripUiEvent import xyz.ksharma.krail.trip.planner.ui.state.searchstop.model.StopItem import xyz.ksharma.krail.trip.planner.ui.state.searchstop.model.StopItem.Companion.fromJsonString -import xyz.ksharma.krail.trip.planner.ui.viewmodel.SavedTripsViewModelFactory @Suppress("LongMethod") internal fun NavGraphBuilder.savedTripsDestination(navController: NavHostController) { @@ -112,11 +108,3 @@ internal fun NavGraphBuilder.savedTripsDestination(navController: NavHostControl ) } } - -// Create an instance of ViewModelFactory with injected dependencies -fun createSavedTripsViewModel( - ioDispatcher: CoroutineDispatcher = Dispatchers.IO -): SavedTripsViewModel { - val factory = SavedTripsViewModelFactory(ioDispatcher) - return factory.create() // Instantiate the ViewModel -} diff --git a/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/usualride/UsualRideViewModel.kt b/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/usualride/UsualRideViewModel.kt index 99ebba82..a2069af3 100644 --- a/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/usualride/UsualRideViewModel.kt +++ b/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/usualride/UsualRideViewModel.kt @@ -30,7 +30,7 @@ class UsualRideViewModel : ViewModel() { viewModelScope.launch(Dispatchers.IO) { TransportMode.toTransportModeType(productClass)?.let { mode -> //Timber.d("onTransportModeSelected: $mode") - sandook.putInt("selectedMode", mode.productClass) + sandook.putString("selectedMode", mode.productClass.toString()) } } } From fa990314ba1e97772b1fa22ccd0639905282072f Mon Sep 17 00:00:00 2001 From: Karan Sharma <55722391+ksharma-xyz@users.noreply.github.com> Date: Wed, 20 Nov 2024 00:59:50 +1100 Subject: [PATCH 45/67] DateTime issues --- .../krail/core/datetime/DateTimeHelper.kt | 18 +++++++++++++-- .../service/stop_finder/StopFinderRequest.kt | 23 +++++++++++-------- .../ui/searchstop/SearchStopViewModel.kt | 9 ++++++-- .../planner/ui/searchstop/StopResultMapper.kt | 4 ++++ .../ui/timetable/TimeTableViewModel.kt | 3 +-- .../timetable/business/TripResponseMapper.kt | 16 ++++++------- 6 files changed, 48 insertions(+), 25 deletions(-) diff --git a/core/date-time/src/commonMain/kotlin/xyz/ksharma/krail/core/datetime/DateTimeHelper.kt b/core/date-time/src/commonMain/kotlin/xyz/ksharma/krail/core/datetime/DateTimeHelper.kt index 42289436..748d4e7c 100644 --- a/core/date-time/src/commonMain/kotlin/xyz/ksharma/krail/core/datetime/DateTimeHelper.kt +++ b/core/date-time/src/commonMain/kotlin/xyz/ksharma/krail/core/datetime/DateTimeHelper.kt @@ -2,6 +2,7 @@ package xyz.ksharma.krail.core.datetime import kotlinx.datetime.Clock import kotlinx.datetime.Instant +import kotlinx.datetime.LocalDateTime import kotlinx.datetime.TimeZone import kotlinx.datetime.toLocalDateTime import kotlin.math.absoluteValue @@ -26,16 +27,29 @@ object DateTimeHelper { return localDateTime.toString() } + fun String.utcToAEST_LocalDateTime(): LocalDateTime { + val instant = Instant.parse(this) + val aestZone = TimeZone.of("Australia/Sydney") + val localDateTime = instant.toLocalDateTime(aestZone) + return localDateTime + } + + fun LocalDateTime.toHHMM(): String { + val hour = if (this.hour % 12 == 0) 12 else this.hour % 12 // Ensure 12-hour format + val minute = this.minute.toString().padStart(2, '0') + val amPm = if (this.hour < 12) "AM" else "PM" + return "$hour:$minute $amPm" + } fun String.aestToHHMM(): String { - val localDateTime = Instant.parse(this).toLocalDateTime(TimeZone.of("Australia/Sydney")) + val dateTimeString = if (this.length == 16) "$this:00" else this + val localDateTime = Instant.parse(dateTimeString).toLocalDateTime(TimeZone.of("Australia/Sydney")) val hour = if (localDateTime.hour % 12 == 0) 12 else localDateTime.hour % 12 // Ensure 12-hour format val minute = localDateTime.minute.toString().padStart(2, '0') val amPm = if (localDateTime.hour < 12) "AM" else "PM" return "$hour:$minute $amPm" } - fun calculateTimeDifferenceFromNow( utcDateString: String, now: Instant = Clock.System.now(), diff --git a/feature/trip-planner/network/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/network/api/service/stop_finder/StopFinderRequest.kt b/feature/trip-planner/network/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/network/api/service/stop_finder/StopFinderRequest.kt index 978dea7c..177b7a9b 100644 --- a/feature/trip-planner/network/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/network/api/service/stop_finder/StopFinderRequest.kt +++ b/feature/trip-planner/network/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/network/api/service/stop_finder/StopFinderRequest.kt @@ -12,15 +12,18 @@ suspend fun fetchStop( stopType: StopType = StopType.STOP, stopSearchQuery: String, ): StopFinderResponse { - return httpClient.get("${NSW_TRANSPORT_BASE_URL}/v1/tp/stop_finder") { - url { - parameters.append(StopFinderRequestParams.nameSf, stopSearchQuery) + val response: StopFinderResponse = + httpClient.get("${NSW_TRANSPORT_BASE_URL}/v1/tp/stop_finder") { + url { + parameters.append(StopFinderRequestParams.nameSf, stopSearchQuery) - parameters.append(StopFinderRequestParams.typeSf, stopType.type) - parameters.append(StopFinderRequestParams.coordOutputFormat, "EPSG:4326") - parameters.append(StopFinderRequestParams.outputFormat, "rapidJSON") - parameters.append(StopFinderRequestParams.version, "10.2.1.42") - parameters.append(StopFinderRequestParams.tfNSWSF, "true") - } - }.body() + parameters.append(StopFinderRequestParams.typeSf, stopType.type) + parameters.append(StopFinderRequestParams.coordOutputFormat, "EPSG:4326") + parameters.append(StopFinderRequestParams.outputFormat, "rapidJSON") +// parameters.append(StopFinderRequestParams.version, "10.2.1.42") + parameters.append(StopFinderRequestParams.tfNSWSF, "true") + } + }.body() + println("response: $response") + return response } diff --git a/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/searchstop/SearchStopViewModel.kt b/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/searchstop/SearchStopViewModel.kt index b379d09c..8a35ecab 100644 --- a/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/searchstop/SearchStopViewModel.kt +++ b/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/searchstop/SearchStopViewModel.kt @@ -38,8 +38,13 @@ class SearchStopViewModel : ViewModel() { viewModelScope.launch { runCatching { - val results = - fetchStop(httpClient = httpClient, stopSearchQuery = query).toStopResults() + val response = + fetchStop(httpClient = httpClient, stopSearchQuery = query) + println("response VM: $response") + + val results = response.toStopResults() + println("results: $results") + updateUiState { displayData(results) } }.getOrElse { updateUiState { displayError() } diff --git a/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/searchstop/StopResultMapper.kt b/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/searchstop/StopResultMapper.kt index a4257826..acc4aeb2 100644 --- a/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/searchstop/StopResultMapper.kt +++ b/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/searchstop/StopResultMapper.kt @@ -19,12 +19,16 @@ object StopResultMapper { fun StopFinderResponse.toStopResults( selectedModes: Set = TransportMode.values(), ): List { + println( "selectedModes: " + selectedModes) + return locations.orEmpty().mapNotNull { location -> val stopName = location.disassembledName ?: return@mapNotNull null // Skip if stop name is null val stopId = location.id ?: return@mapNotNull null // Skip if stop ID is null val modes = location.productClasses.orEmpty() .mapNotNull { productClass -> TransportMode.toTransportModeType(productClass) } + println("productClasses [${location.name}]: ${location.productClasses}") + // Filter based on selected mode types if (selectedModes.isNotEmpty() && !modes.any { it in selectedModes }) { return@mapNotNull null diff --git a/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/timetable/TimeTableViewModel.kt b/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/timetable/TimeTableViewModel.kt index dc056cd3..f7fe48fb 100644 --- a/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/timetable/TimeTableViewModel.kt +++ b/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/timetable/TimeTableViewModel.kt @@ -30,7 +30,6 @@ import xyz.ksharma.krail.trip.planner.ui.state.timetable.TimeTableState import xyz.ksharma.krail.trip.planner.ui.state.timetable.TimeTableUiEvent import xyz.ksharma.krail.trip.planner.ui.state.timetable.Trip import xyz.ksharma.krail.trip.planner.ui.timetable.business.buildJourneyList -import xyz.ksharma.krail.trip.planner.ui.timetable.business.logForUnderstandingData import kotlin.time.Duration.Companion.seconds class TimeTableViewModel : ViewModel() { @@ -101,7 +100,7 @@ class TimeTableViewModel : ViewModel() { isError = false, ) } - response.logForUnderstandingData() + //response.logForUnderstandingData() }.onFailure { // Timber.e("Error while fetching trip: $it") updateUiState { copy(isLoading = false, isError = true) } diff --git a/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/timetable/business/TripResponseMapper.kt b/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/timetable/business/TripResponseMapper.kt index 01691be2..5ec67b50 100644 --- a/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/timetable/business/TripResponseMapper.kt +++ b/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/timetable/business/TripResponseMapper.kt @@ -2,14 +2,12 @@ package xyz.ksharma.krail.trip.planner.ui.timetable.business import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.toImmutableList - -import xyz.ksharma.krail.core.datetime.DateTimeHelper.aestToHHMM import xyz.ksharma.krail.core.datetime.DateTimeHelper.calculateTimeDifference import xyz.ksharma.krail.core.datetime.DateTimeHelper.calculateTimeDifferenceFromNow -import xyz.ksharma.krail.core.datetime.DateTimeHelper.formatTo12HourTime import xyz.ksharma.krail.core.datetime.DateTimeHelper.toFormattedDurationTimeString import xyz.ksharma.krail.core.datetime.DateTimeHelper.toGenericFormattedTimeString -import xyz.ksharma.krail.core.datetime.DateTimeHelper.utcToAEST +import xyz.ksharma.krail.core.datetime.DateTimeHelper.toHHMM +import xyz.ksharma.krail.core.datetime.DateTimeHelper.utcToAEST_LocalDateTime import xyz.ksharma.krail.trip.planner.network.api.model.TripResponse import xyz.ksharma.krail.trip.planner.ui.state.TransportMode import xyz.ksharma.krail.trip.planner.ui.state.TransportModeLine @@ -248,7 +246,7 @@ private fun TripResponse.StopSequence.toUiModel(): TimeTableState.JourneyCardInf } } -internal fun TripResponse.logForUnderstandingData() { +/*internal fun TripResponse.logForUnderstandingData() { println("Journeys: ${journeys?.size}") journeys?.mapIndexed { jindex, j -> println("JOURNEY #${jindex + 1}") @@ -279,12 +277,12 @@ internal fun TripResponse.logForUnderstandingData() { ) } } -} +}*/ /** * Prints the stops for legs when interchange required. */ -private fun List.interchangeStopsList() = this.mapNotNull { +/*private fun List.interchangeStopsList() = this.mapNotNull { // TODO - figure role of ARR vs DEP time val timeArr = it.arrivalTimeEstimated?.utcToAEST() ?.formatTo12HourTime() ?: it.arrivalTimePlanned?.utcToAEST()?.formatTo12HourTime() @@ -298,9 +296,9 @@ private fun List.interchangeStopsList() = this.mapNot "\n\t\t\t\t Stop: ${it.name}," + " depTime: ${timeArr ?: depTime}" } -} +}*/ -private fun String.fromUTCToDisplayTimeString() = this.utcToAEST().aestToHHMM() +private fun String.fromUTCToDisplayTimeString() = this.utcToAEST_LocalDateTime().toHHMM() private fun TripResponse.Leg.isWalkingLeg(): Boolean = transportation?.product?.productClass == 99L || transportation?.product?.productClass == 100L From 8a640884c5f71867a3fd26f9cee2d4dca4707c6f Mon Sep 17 00:00:00 2001 From: Karan Sharma <55722391+ksharma-xyz@users.noreply.github.com> Date: Wed, 20 Nov 2024 01:17:09 +1100 Subject: [PATCH 46/67] Fix: KMP does not support enum nav type --- .../ui/navigation/TripPlannerDestinations.kt | 22 +++++++++++++++---- .../ui/savedtrips/SavedTripsDestination.kt | 4 ++-- 2 files changed, 20 insertions(+), 6 deletions(-) diff --git a/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/navigation/TripPlannerDestinations.kt b/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/navigation/TripPlannerDestinations.kt index 6e7b888c..7c64272b 100644 --- a/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/navigation/TripPlannerDestinations.kt +++ b/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/navigation/TripPlannerDestinations.kt @@ -31,9 +31,20 @@ fun NavGraphBuilder.tripPlannerDestinations( } } -internal enum class SearchStopFieldType(val key: String) { - FROM(key = "FromSearchStopResult"), - TO(key = "ToSearchStopResult"), +@Serializable +internal sealed class SearchStopFieldType(val key: String) { + data object FROM : SearchStopFieldType("FromSearchStopResult") + data object TO : SearchStopFieldType("ToSearchStopResult") + + companion object { + fun fromKey(key: String): SearchStopFieldType { + return when (key) { + FROM.key -> FROM + TO.key -> TO + else -> throw IllegalArgumentException("Unknown key: $key") + } + } + } } @Serializable @@ -51,7 +62,10 @@ internal data class TimeTableRoute( ) @Serializable -internal data class SearchStopRoute(val fieldType: SearchStopFieldType) +internal data class SearchStopRoute(val fieldTypeKey: String) { + val fieldType: SearchStopFieldType + get() = SearchStopFieldType.fromKey(fieldTypeKey) +} @Serializable data object UsualRideRoute diff --git a/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/savedtrips/SavedTripsDestination.kt b/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/savedtrips/SavedTripsDestination.kt index dd4591a5..8a54d009 100644 --- a/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/savedtrips/SavedTripsDestination.kt +++ b/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/savedtrips/SavedTripsDestination.kt @@ -58,12 +58,12 @@ internal fun NavGraphBuilder.savedTripsDestination(navController: NavHostControl toStopItem = toStopItem, fromButtonClick = { // Timber.d("fromButtonClick - nav: ${SearchStopRoute(fieldType = SearchStopFieldType.FROM)}") - navController.navigate(SearchStopRoute(fieldType = SearchStopFieldType.FROM)) + navController.navigate(SearchStopRoute(fieldTypeKey = SearchStopFieldType.FROM.key)) }, toButtonClick = { // Timber.d("toButtonClick - nav: ${SearchStopRoute(fieldType = SearchStopFieldType.TO)}") navController.navigate( - route = SearchStopRoute(fieldType = SearchStopFieldType.TO), + route = SearchStopRoute(fieldTypeKey = SearchStopFieldType.TO.key), navOptions = NavOptions.Builder().setLaunchSingleTop(true).build(), ) }, From c4c3531c9bb30b23fd51ccb76ba6115ad06df852 Mon Sep 17 00:00:00 2001 From: Karan Sharma <55722391+ksharma-xyz@users.noreply.github.com> Date: Wed, 20 Nov 2024 01:45:20 +1100 Subject: [PATCH 47/67] ViewModel - https://www.jetbrains.com/help/kotlin-multiplatform-dev/compose-viewmodel.html#adding-the-common-viewmodel-to-your-project Ktor Engine - https://ktor.io/docs/client-engines.html#default --- .../xyz/ksharma/krail/common/KrailNavHost.kt | 2 +- feature/trip-planner/network/build.gradle.kts | 8 +++--- .../ui/savedtrips/SavedTripsDestination.kt | 2 +- .../ui/searchstop/SearchStopDestination.kt | 2 +- .../ui/timetable/TimeTableDestination.kt | 2 +- .../ui/usualride/UsualRideDestination.kt | 2 +- .../planner/ui/viewmodel/ViewModelFactory.kt | 27 +++++++++++++++++++ gradle/libs.versions.toml | 2 +- 8 files changed, 37 insertions(+), 10 deletions(-) create mode 100644 feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/viewmodel/ViewModelFactory.kt diff --git a/composeApp/src/commonMain/kotlin/xyz/ksharma/krail/common/KrailNavHost.kt b/composeApp/src/commonMain/kotlin/xyz/ksharma/krail/common/KrailNavHost.kt index 43279b8c..1fc52b8c 100644 --- a/composeApp/src/commonMain/kotlin/xyz/ksharma/krail/common/KrailNavHost.kt +++ b/composeApp/src/commonMain/kotlin/xyz/ksharma/krail/common/KrailNavHost.kt @@ -62,7 +62,7 @@ fun KrailNavHost(modifier: Modifier = Modifier) { tripPlannerDestinations(navController = navController) composable { - val viewModel: SplashViewModel = viewModel() + val viewModel: SplashViewModel = viewModel { SplashViewModel() } val isLoading by viewModel.isLoading.collectAsStateWithLifecycle() val mode by viewModel.uiState.collectAsStateWithLifecycle() diff --git a/feature/trip-planner/network/build.gradle.kts b/feature/trip-planner/network/build.gradle.kts index 8219ae72..799db543 100644 --- a/feature/trip-planner/network/build.gradle.kts +++ b/feature/trip-planner/network/build.gradle.kts @@ -33,9 +33,9 @@ kotlin { iosSimulatorArm64() sourceSets { - /*androidMain.dependencies { + androidMain.dependencies { implementation(libs.ktor.client.okhttp) - }*/ + } commonMain { dependencies { @@ -53,11 +53,11 @@ kotlin { } } - /*iosMain { + iosMain { dependencies { implementation(libs.ktor.client.darwin) } - }*/ + } } } diff --git a/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/savedtrips/SavedTripsDestination.kt b/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/savedtrips/SavedTripsDestination.kt index 8a54d009..dd3e9bad 100644 --- a/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/savedtrips/SavedTripsDestination.kt +++ b/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/savedtrips/SavedTripsDestination.kt @@ -22,7 +22,7 @@ import xyz.ksharma.krail.trip.planner.ui.state.searchstop.model.StopItem.Compani @Suppress("LongMethod") internal fun NavGraphBuilder.savedTripsDestination(navController: NavHostController) { composable { backStackEntry -> - val viewModel: SavedTripsViewModel = viewModel() + val viewModel: SavedTripsViewModel = viewModel { SavedTripsViewModel() } val savedTripState by viewModel.uiState.collectAsStateWithLifecycle() val fromArg: String? = diff --git a/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/searchstop/SearchStopDestination.kt b/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/searchstop/SearchStopDestination.kt index 944081b7..2b6effd3 100644 --- a/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/searchstop/SearchStopDestination.kt +++ b/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/searchstop/SearchStopDestination.kt @@ -11,7 +11,7 @@ import xyz.ksharma.krail.trip.planner.ui.navigation.SearchStopRoute fun NavGraphBuilder.searchStopDestination(navController: NavHostController) { composable { backStackEntry -> - val viewModel: SearchStopViewModel = viewModel() + val viewModel: SearchStopViewModel = viewModel { SearchStopViewModel() } val searchStopState by viewModel.uiState.collectAsStateWithLifecycle() val route: SearchStopRoute = backStackEntry.toRoute() diff --git a/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/timetable/TimeTableDestination.kt b/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/timetable/TimeTableDestination.kt index 5824d55f..ab65a02b 100644 --- a/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/timetable/TimeTableDestination.kt +++ b/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/timetable/TimeTableDestination.kt @@ -15,7 +15,7 @@ import xyz.ksharma.krail.trip.planner.ui.state.timetable.Trip internal fun NavGraphBuilder.timeTableDestination(navController: NavHostController) { composable { backStackEntry -> - val viewModel: TimeTableViewModel = viewModel() + val viewModel: TimeTableViewModel = viewModel { TimeTableViewModel() } val timeTableState by viewModel.uiState.collectAsStateWithLifecycle() val route: TimeTableRoute = backStackEntry.toRoute() val isLoading by viewModel.isLoading.collectAsStateWithLifecycle() diff --git a/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/usualride/UsualRideDestination.kt b/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/usualride/UsualRideDestination.kt index ba62997e..2193e58b 100644 --- a/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/usualride/UsualRideDestination.kt +++ b/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/usualride/UsualRideDestination.kt @@ -23,7 +23,7 @@ import xyz.ksharma.krail.trip.planner.ui.state.usualride.UsualRideEvent internal fun NavGraphBuilder.usualRideDestination(navController: NavHostController) { composable { - val viewModel:UsualRideViewModel = viewModel() + val viewModel:UsualRideViewModel = viewModel { UsualRideViewModel() } var themeColor by LocalThemeColor.current var themeContentColor by LocalThemeContentColor.current var mode: TransportMode? by remember { mutableStateOf(null) } diff --git a/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/viewmodel/ViewModelFactory.kt b/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/viewmodel/ViewModelFactory.kt new file mode 100644 index 00000000..37ffc766 --- /dev/null +++ b/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/viewmodel/ViewModelFactory.kt @@ -0,0 +1,27 @@ +/* +package xyz.ksharma.krail.trip.planner.ui.viewmodel +import androidx.compose.runtime.Composable +import androidx.lifecycle.ViewModel +import androidx.lifecycle.ViewModelProvider +import androidx.lifecycle.viewmodel.compose.viewModel +import kotlin.reflect.KClass + +class CommonViewModelFactory( + private val viewModelClass: KClass, + private val creator: () -> VM +) : ViewModelProvider.Factory { + override fun create(modelClass: Class): T { + if (modelClass.isAssignableFrom(viewModelClass.java)) { + @Suppress("UNCHECKED_CAST") + return creator() as T + } + throw IllegalArgumentException("Unknown ViewModel class") + } +} + +@Composable +inline fun composeViewModel(noinline creator: () -> VM): VM { + val factory = CommonViewModelFactory(VM::class, creator) + return viewModel(factory = factory) +} +*/ diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index af301372..1091095e 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -13,7 +13,7 @@ kotlinInject = "0.7.2" kotlinxCollectionsImmutable = "0.3.8" kotlinxDatetime = "0.6.1" ktorClientAuth = "2.3.12" -lifecycleViewmodelCompose = "2.8.2" +lifecycleViewmodelCompose = "2.8.3" multiplatformSettings = "1.2.0" navigationCompose = "2.8.0-alpha10" kotlinxSerializationJson = "1.7.3" From c2ca76d19b505ca5657636da6c7dbddc342be9a4 Mon Sep 17 00:00:00 2001 From: Karan Sharma <55722391+ksharma-xyz@users.noreply.github.com> Date: Wed, 20 Nov 2024 01:50:09 +1100 Subject: [PATCH 48/67] Copy text change --- .../ksharma/krail/core/datetime/DateTimeHelper.kt | 12 ++++++------ .../ui/timetable/business/TripResponseMapper.kt | 4 ++-- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/core/date-time/src/commonMain/kotlin/xyz/ksharma/krail/core/datetime/DateTimeHelper.kt b/core/date-time/src/commonMain/kotlin/xyz/ksharma/krail/core/datetime/DateTimeHelper.kt index 748d4e7c..3af36886 100644 --- a/core/date-time/src/commonMain/kotlin/xyz/ksharma/krail/core/datetime/DateTimeHelper.kt +++ b/core/date-time/src/commonMain/kotlin/xyz/ksharma/krail/core/datetime/DateTimeHelper.kt @@ -27,7 +27,7 @@ object DateTimeHelper { return localDateTime.toString() } - fun String.utcToAEST_LocalDateTime(): LocalDateTime { + fun String.utcToLocalDateTimeAEST(): LocalDateTime { val instant = Instant.parse(this) val aestZone = TimeZone.of("Australia/Sydney") val localDateTime = instant.toLocalDateTime(aestZone) @@ -41,14 +41,14 @@ object DateTimeHelper { return "$hour:$minute $amPm" } - fun String.aestToHHMM(): String { + /* fun String.aestToHHMM(): String { val dateTimeString = if (this.length == 16) "$this:00" else this val localDateTime = Instant.parse(dateTimeString).toLocalDateTime(TimeZone.of("Australia/Sydney")) val hour = if (localDateTime.hour % 12 == 0) 12 else localDateTime.hour % 12 // Ensure 12-hour format val minute = localDateTime.minute.toString().padStart(2, '0') val amPm = if (localDateTime.hour < 12) "AM" else "PM" return "$hour:$minute $amPm" - } + }*/ fun calculateTimeDifferenceFromNow( utcDateString: String, @@ -66,9 +66,9 @@ object DateTimeHelper { return when { totalMinutes < 0 -> "${totalMinutes.absoluteValue} ${if (totalMinutes.absoluteValue == 1L) "min" else "mins"} ago" totalMinutes == 0L -> "Now" - hours == 1L -> "In ${hours.absoluteValue}h ${partialMinutes.absoluteValue}m" - hours >= 2 -> "In ${hours.absoluteValue}h" - else -> "In ${totalMinutes.absoluteValue} ${if (totalMinutes.absoluteValue == 1L) "min" else "mins"}" + hours == 1L -> "in ${hours.absoluteValue}h ${partialMinutes.absoluteValue}m" + hours >= 2 -> "in ${hours.absoluteValue}h" + else -> "in ${totalMinutes.absoluteValue} ${if (totalMinutes.absoluteValue == 1L) "min" else "mins"}" } } diff --git a/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/timetable/business/TripResponseMapper.kt b/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/timetable/business/TripResponseMapper.kt index 5ec67b50..2fc502f6 100644 --- a/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/timetable/business/TripResponseMapper.kt +++ b/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/timetable/business/TripResponseMapper.kt @@ -7,7 +7,7 @@ import xyz.ksharma.krail.core.datetime.DateTimeHelper.calculateTimeDifferenceFro import xyz.ksharma.krail.core.datetime.DateTimeHelper.toFormattedDurationTimeString import xyz.ksharma.krail.core.datetime.DateTimeHelper.toGenericFormattedTimeString import xyz.ksharma.krail.core.datetime.DateTimeHelper.toHHMM -import xyz.ksharma.krail.core.datetime.DateTimeHelper.utcToAEST_LocalDateTime +import xyz.ksharma.krail.core.datetime.DateTimeHelper.utcToLocalDateTimeAEST import xyz.ksharma.krail.trip.planner.network.api.model.TripResponse import xyz.ksharma.krail.trip.planner.ui.state.TransportMode import xyz.ksharma.krail.trip.planner.ui.state.TransportModeLine @@ -298,7 +298,7 @@ private fun TripResponse.StopSequence.toUiModel(): TimeTableState.JourneyCardInf } }*/ -private fun String.fromUTCToDisplayTimeString() = this.utcToAEST_LocalDateTime().toHHMM() +private fun String.fromUTCToDisplayTimeString() = this.utcToLocalDateTimeAEST().toHHMM() private fun TripResponse.Leg.isWalkingLeg(): Boolean = transportation?.product?.productClass == 99L || transportation?.product?.productClass == 100L From 8115b342717341e90cbdd8f86ff8df1ec029a5f2 Mon Sep 17 00:00:00 2001 From: Karan Sharma <55722391+ksharma-xyz@users.noreply.github.com> Date: Wed, 20 Nov 2024 20:34:32 +1100 Subject: [PATCH 49/67] Fix icons --- feature/trip-planner/ui/build.gradle.kts | 2 ++ .../drawable/ic_a11y.xml | 0 .../drawable/ic_alert.xml | 0 .../drawable/ic_arrow_down.xml | 0 .../drawable/ic_arrow_right.xml | 0 .../drawable/ic_clock.xml | 0 .../drawable/ic_loc.xml | 0 .../drawable/ic_location.xml | 0 .../drawable/ic_mode_ferry.xml | 0 .../drawable/ic_reverse.xml | 0 .../drawable/ic_search.xml | 0 .../drawable/ic_star.xml | 0 .../drawable/ic_star_filled.xml | 0 .../drawable/ic_walk.xml | 0 .../values/strings.xml | 0 .../trip/planner/ui/components/JourneyCard.kt | 36 ++++++++++--------- .../trip/planner/ui/components/LegView.kt | 10 +++--- .../planner/ui/components/SavedTripCard.kt | 5 ++- .../planner/ui/components/SearchStopRow.kt | 8 +++-- .../trip/planner/ui/components/WalkingLeg.kt | 9 +++-- .../planner/ui/savedtrips/SavedTripsScreen.kt | 4 +++ .../planner/ui/timetable/TimeTableScreen.kt | 14 +++++--- .../ui/timetable/TimeTableViewModel.kt | 31 ++++++++-------- 23 files changed, 73 insertions(+), 46 deletions(-) rename feature/trip-planner/ui/src/commonMain/{resources => composeResources}/drawable/ic_a11y.xml (100%) rename feature/trip-planner/ui/src/commonMain/{resources => composeResources}/drawable/ic_alert.xml (100%) rename feature/trip-planner/ui/src/commonMain/{resources => composeResources}/drawable/ic_arrow_down.xml (100%) rename feature/trip-planner/ui/src/commonMain/{resources => composeResources}/drawable/ic_arrow_right.xml (100%) rename feature/trip-planner/ui/src/commonMain/{resources => composeResources}/drawable/ic_clock.xml (100%) rename feature/trip-planner/ui/src/commonMain/{resources => composeResources}/drawable/ic_loc.xml (100%) rename feature/trip-planner/ui/src/commonMain/{resources => composeResources}/drawable/ic_location.xml (100%) rename feature/trip-planner/ui/src/commonMain/{resources => composeResources}/drawable/ic_mode_ferry.xml (100%) rename feature/trip-planner/ui/src/commonMain/{resources => composeResources}/drawable/ic_reverse.xml (100%) rename feature/trip-planner/ui/src/commonMain/{resources => composeResources}/drawable/ic_search.xml (100%) rename feature/trip-planner/ui/src/commonMain/{resources => composeResources}/drawable/ic_star.xml (100%) rename feature/trip-planner/ui/src/commonMain/{resources => composeResources}/drawable/ic_star_filled.xml (100%) rename feature/trip-planner/ui/src/commonMain/{resources => composeResources}/drawable/ic_walk.xml (100%) rename feature/trip-planner/ui/src/commonMain/{resources => composeResources}/values/strings.xml (100%) diff --git a/feature/trip-planner/ui/build.gradle.kts b/feature/trip-planner/ui/build.gradle.kts index 17febf3a..2c90de63 100644 --- a/feature/trip-planner/ui/build.gradle.kts +++ b/feature/trip-planner/ui/build.gradle.kts @@ -34,6 +34,8 @@ kotlin { implementation(compose.animation) implementation(compose.ui) implementation(compose.material3) + implementation(compose.components.resources) + implementation(compose.components.uiToolingPreview) implementation(libs.kotlinx.datetime) implementation(libs.kotlinx.collections.immutable) diff --git a/feature/trip-planner/ui/src/commonMain/resources/drawable/ic_a11y.xml b/feature/trip-planner/ui/src/commonMain/composeResources/drawable/ic_a11y.xml similarity index 100% rename from feature/trip-planner/ui/src/commonMain/resources/drawable/ic_a11y.xml rename to feature/trip-planner/ui/src/commonMain/composeResources/drawable/ic_a11y.xml diff --git a/feature/trip-planner/ui/src/commonMain/resources/drawable/ic_alert.xml b/feature/trip-planner/ui/src/commonMain/composeResources/drawable/ic_alert.xml similarity index 100% rename from feature/trip-planner/ui/src/commonMain/resources/drawable/ic_alert.xml rename to feature/trip-planner/ui/src/commonMain/composeResources/drawable/ic_alert.xml diff --git a/feature/trip-planner/ui/src/commonMain/resources/drawable/ic_arrow_down.xml b/feature/trip-planner/ui/src/commonMain/composeResources/drawable/ic_arrow_down.xml similarity index 100% rename from feature/trip-planner/ui/src/commonMain/resources/drawable/ic_arrow_down.xml rename to feature/trip-planner/ui/src/commonMain/composeResources/drawable/ic_arrow_down.xml diff --git a/feature/trip-planner/ui/src/commonMain/resources/drawable/ic_arrow_right.xml b/feature/trip-planner/ui/src/commonMain/composeResources/drawable/ic_arrow_right.xml similarity index 100% rename from feature/trip-planner/ui/src/commonMain/resources/drawable/ic_arrow_right.xml rename to feature/trip-planner/ui/src/commonMain/composeResources/drawable/ic_arrow_right.xml diff --git a/feature/trip-planner/ui/src/commonMain/resources/drawable/ic_clock.xml b/feature/trip-planner/ui/src/commonMain/composeResources/drawable/ic_clock.xml similarity index 100% rename from feature/trip-planner/ui/src/commonMain/resources/drawable/ic_clock.xml rename to feature/trip-planner/ui/src/commonMain/composeResources/drawable/ic_clock.xml diff --git a/feature/trip-planner/ui/src/commonMain/resources/drawable/ic_loc.xml b/feature/trip-planner/ui/src/commonMain/composeResources/drawable/ic_loc.xml similarity index 100% rename from feature/trip-planner/ui/src/commonMain/resources/drawable/ic_loc.xml rename to feature/trip-planner/ui/src/commonMain/composeResources/drawable/ic_loc.xml diff --git a/feature/trip-planner/ui/src/commonMain/resources/drawable/ic_location.xml b/feature/trip-planner/ui/src/commonMain/composeResources/drawable/ic_location.xml similarity index 100% rename from feature/trip-planner/ui/src/commonMain/resources/drawable/ic_location.xml rename to feature/trip-planner/ui/src/commonMain/composeResources/drawable/ic_location.xml diff --git a/feature/trip-planner/ui/src/commonMain/resources/drawable/ic_mode_ferry.xml b/feature/trip-planner/ui/src/commonMain/composeResources/drawable/ic_mode_ferry.xml similarity index 100% rename from feature/trip-planner/ui/src/commonMain/resources/drawable/ic_mode_ferry.xml rename to feature/trip-planner/ui/src/commonMain/composeResources/drawable/ic_mode_ferry.xml diff --git a/feature/trip-planner/ui/src/commonMain/resources/drawable/ic_reverse.xml b/feature/trip-planner/ui/src/commonMain/composeResources/drawable/ic_reverse.xml similarity index 100% rename from feature/trip-planner/ui/src/commonMain/resources/drawable/ic_reverse.xml rename to feature/trip-planner/ui/src/commonMain/composeResources/drawable/ic_reverse.xml diff --git a/feature/trip-planner/ui/src/commonMain/resources/drawable/ic_search.xml b/feature/trip-planner/ui/src/commonMain/composeResources/drawable/ic_search.xml similarity index 100% rename from feature/trip-planner/ui/src/commonMain/resources/drawable/ic_search.xml rename to feature/trip-planner/ui/src/commonMain/composeResources/drawable/ic_search.xml diff --git a/feature/trip-planner/ui/src/commonMain/resources/drawable/ic_star.xml b/feature/trip-planner/ui/src/commonMain/composeResources/drawable/ic_star.xml similarity index 100% rename from feature/trip-planner/ui/src/commonMain/resources/drawable/ic_star.xml rename to feature/trip-planner/ui/src/commonMain/composeResources/drawable/ic_star.xml diff --git a/feature/trip-planner/ui/src/commonMain/resources/drawable/ic_star_filled.xml b/feature/trip-planner/ui/src/commonMain/composeResources/drawable/ic_star_filled.xml similarity index 100% rename from feature/trip-planner/ui/src/commonMain/resources/drawable/ic_star_filled.xml rename to feature/trip-planner/ui/src/commonMain/composeResources/drawable/ic_star_filled.xml diff --git a/feature/trip-planner/ui/src/commonMain/resources/drawable/ic_walk.xml b/feature/trip-planner/ui/src/commonMain/composeResources/drawable/ic_walk.xml similarity index 100% rename from feature/trip-planner/ui/src/commonMain/resources/drawable/ic_walk.xml rename to feature/trip-planner/ui/src/commonMain/composeResources/drawable/ic_walk.xml diff --git a/feature/trip-planner/ui/src/commonMain/resources/values/strings.xml b/feature/trip-planner/ui/src/commonMain/composeResources/values/strings.xml similarity index 100% rename from feature/trip-planner/ui/src/commonMain/resources/values/strings.xml rename to feature/trip-planner/ui/src/commonMain/composeResources/values/strings.xml diff --git a/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/components/JourneyCard.kt b/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/components/JourneyCard.kt index ffbd2472..b8b812b7 100644 --- a/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/components/JourneyCard.kt +++ b/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/components/JourneyCard.kt @@ -21,8 +21,6 @@ import androidx.compose.foundation.layout.size import androidx.compose.foundation.shape.CircleShape import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.filled.Call -import androidx.compose.material.icons.filled.ShoppingCart import androidx.compose.material.icons.outlined.Add import androidx.compose.runtime.Composable import androidx.compose.runtime.CompositionLocalProvider @@ -35,7 +33,7 @@ import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.Brush import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.ColorFilter -import androidx.compose.ui.graphics.vector.ImageVector +import androidx.compose.ui.graphics.painter.Painter import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.semantics.Role import androidx.compose.ui.semantics.semantics @@ -47,6 +45,12 @@ import androidx.compose.ui.unit.sp import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.persistentListOf import kotlinx.collections.immutable.toImmutableList +import krail.feature.trip_planner.ui.generated.resources.Res +import krail.feature.trip_planner.ui.generated.resources.ic_a11y +import krail.feature.trip_planner.ui.generated.resources.ic_alert +import krail.feature.trip_planner.ui.generated.resources.ic_clock +import krail.feature.trip_planner.ui.generated.resources.ic_walk +import org.jetbrains.compose.resources.painterResource import xyz.ksharma.krail.taj.LocalContentAlpha import xyz.ksharma.krail.taj.components.SeparatorIcon import xyz.ksharma.krail.taj.components.Text @@ -230,8 +234,8 @@ fun ExpandedJourneyCardContent( ) { if (totalUniqueServiceAlerts > 0) { SmallButton( - imageVector = Icons.Filled.Call, // TODO - R.drawable.ic_alert, - text = if(totalUniqueServiceAlerts > 1) { + painter = painterResource(Res.drawable.ic_alert), + text = if (totalUniqueServiceAlerts > 1) { "$totalUniqueServiceAlerts Alerts" } else { "$totalUniqueServiceAlerts Alert" @@ -244,7 +248,7 @@ fun ExpandedJourneyCardContent( } TextWithIcon( - imageVector = Icons.Filled.ShoppingCart,//TODO - R.drawable.ic_clock, + painter = painterResource(Res.drawable.ic_clock), text = totalTravelTime, textStyle = KrailTheme.typography.bodyLarge, iconSize = iconSize, @@ -364,7 +368,7 @@ fun DefaultJourneyCardContent( } } - platformNumber?.let { platform -> // todo - extract + platformNumber?.let { platform -> Box( modifier = Modifier .padding(start = 8.dp) @@ -378,7 +382,7 @@ fun DefaultJourneyCardContent( contentAlignment = Alignment.Center, ) { Text( - text = platform.toString(), + text = platform, textAlign = TextAlign.Center, style = KrailTheme.typography.labelLarge, ) @@ -407,7 +411,7 @@ fun DefaultJourneyCardContent( modifier = Modifier.padding(end = 10.dp), ) TextWithIcon( - imageVector = Icons.Filled.ShoppingCart, //TODO - R.drawable.ic_clock, + painter = painterResource(Res.drawable.ic_clock), text = totalTravelTime, modifier = Modifier .align(Alignment.CenterVertically) @@ -415,7 +419,7 @@ fun DefaultJourneyCardContent( ) totalWalkTime?.let { TextWithIcon( - imageVector = Icons.Filled.ShoppingCart,// TODO - R.drawable.ic_walk, + painter = painterResource(Res.drawable.ic_walk), text = totalWalkTime, modifier = Modifier .align(Alignment.CenterVertically) @@ -425,7 +429,7 @@ fun DefaultJourneyCardContent( Spacer(modifier = Modifier.weight(1f)) if (isWheelchairAccessible) { Image( - imageVector = Icons.Outlined.Add, + painter = painterResource(Res.drawable.ic_a11y), contentDescription = null, colorFilter = ColorFilter.tint(color = KrailTheme.colors.onSurface), modifier = Modifier @@ -439,7 +443,7 @@ fun DefaultJourneyCardContent( @Composable private fun TextWithIcon( - imageVector: ImageVector, + painter: Painter, text: String, modifier: Modifier = Modifier, textStyle: TextStyle = KrailTheme.typography.bodyMedium, @@ -454,7 +458,7 @@ private fun TextWithIcon( horizontalArrangement = Arrangement.spacedBy(4.dp), ) { Image( - imageVector = imageVector, + painter = painter, contentDescription = null, colorFilter = ColorFilter.tint(color = color.copy(alpha = contentAlpha)), modifier = Modifier @@ -471,7 +475,7 @@ private fun TextWithIcon( @Composable private fun SmallButton( - imageVector: ImageVector, + painter: Painter, text: String, modifier: Modifier = Modifier, textStyle: TextStyle = KrailTheme.typography.bodyMedium, @@ -501,7 +505,7 @@ private fun SmallButton( horizontalArrangement = Arrangement.spacedBy(4.dp), ) { Image( - imageVector = imageVector, + painter = painter, contentDescription = null, colorFilter = ColorFilter.tint(color = color.copy(alpha = contentAlpha)), modifier = Modifier @@ -594,7 +598,7 @@ private fun PreviewJourneyCardCollapsed() { ), ), - ), + ), cardState = JourneyCardState.EXPANDED, totalWalkTime = "10 mins", totalUniqueServiceAlerts = 1, diff --git a/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/components/LegView.kt b/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/components/LegView.kt index 995a122b..a9e98e6a 100644 --- a/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/components/LegView.kt +++ b/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/components/LegView.kt @@ -17,8 +17,6 @@ import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.shape.RoundedCornerShape -import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.filled.Add import androidx.compose.runtime.Composable import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.runtime.getValue @@ -39,6 +37,10 @@ import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.toImmutableList +import krail.feature.trip_planner.ui.generated.resources.Res +import krail.feature.trip_planner.ui.generated.resources.ic_a11y +import krail.feature.trip_planner.ui.generated.resources.ic_clock +import org.jetbrains.compose.resources.painterResource import xyz.ksharma.krail.taj.LocalContentAlpha import xyz.ksharma.krail.taj.components.Text import xyz.ksharma.krail.taj.theme.KrailTheme @@ -199,7 +201,7 @@ private fun RouteSummary( if (displayDuration) { Row(horizontalArrangement = Arrangement.End) { Image( - imageVector = Icons.Filled.Add, //painterResource(R.drawable.ic_clock), // TODO replace with clock icon + painter = painterResource(Res.drawable.ic_clock), contentDescription = null, colorFilter = ColorFilter.tint(color = KrailTheme.colors.onSurface), modifier = Modifier @@ -244,7 +246,7 @@ private fun StopInfo( ) if (isWheelchairAccessible) { Image( - imageVector = Icons.Filled.Add, // TODO - //painterResource(R.drawable.ic_a11y), + painter = painterResource(Res.drawable.ic_a11y), contentDescription = null, colorFilter = ColorFilter.tint( color = if (isProminent) { diff --git a/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/components/SavedTripCard.kt b/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/components/SavedTripCard.kt index 5d85e61a..683498c4 100644 --- a/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/components/SavedTripCard.kt +++ b/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/components/SavedTripCard.kt @@ -26,6 +26,9 @@ import androidx.compose.ui.graphics.ColorFilter import androidx.compose.ui.semantics.Role import androidx.compose.ui.semantics.semantics import androidx.compose.ui.unit.dp +import krail.feature.trip_planner.ui.generated.resources.Res +import krail.feature.trip_planner.ui.generated.resources.ic_star_filled +import org.jetbrains.compose.resources.painterResource import xyz.ksharma.krail.taj.LocalThemeColor import xyz.ksharma.krail.taj.components.Text import xyz.ksharma.krail.taj.theme.KrailTheme @@ -89,7 +92,7 @@ fun SavedTripCard( contentAlignment = Alignment.Center, ) { Image( - imageVector = Icons.Filled.Star, + painter = painterResource(Res.drawable.ic_star_filled), contentDescription = "Save Trip", colorFilter = ColorFilter.tint( primaryTransportMode?.colorCode diff --git a/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/components/SearchStopRow.kt b/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/components/SearchStopRow.kt index 88282382..da6f6441 100644 --- a/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/components/SearchStopRow.kt +++ b/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/components/SearchStopRow.kt @@ -22,6 +22,10 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.ColorFilter import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.unit.dp +import krail.feature.trip_planner.ui.generated.resources.Res +import krail.feature.trip_planner.ui.generated.resources.ic_reverse +import krail.feature.trip_planner.ui.generated.resources.ic_search +import org.jetbrains.compose.resources.painterResource import xyz.ksharma.krail.taj.LocalOnContentColor import xyz.ksharma.krail.taj.LocalThemeColor import xyz.ksharma.krail.taj.components.RoundIconButton @@ -88,7 +92,7 @@ fun SearchStopRow( RoundIconButton( content = { Image( - imageVector = Icons.Filled.Edit, //todo 0 //.vectorResource(TripPlannerUiR.drawable.ic_reverse), + painter = painterResource(Res.drawable.ic_reverse), contentDescription = "Reverse", colorFilter = ColorFilter.tint(LocalOnContentColor.current), ) @@ -99,7 +103,7 @@ fun SearchStopRow( RoundIconButton( content = { Image( - imageVector = Icons.Filled.Search, // TODO - ImageVector.vectorResource(TripPlannerUiR.drawable.ic_search), + painter = painterResource(Res.drawable.ic_search), contentDescription = "Search", colorFilter = ColorFilter.tint(LocalOnContentColor.current), ) diff --git a/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/components/WalkingLeg.kt b/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/components/WalkingLeg.kt index 241c4a55..aab41145 100644 --- a/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/components/WalkingLeg.kt +++ b/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/components/WalkingLeg.kt @@ -6,14 +6,16 @@ import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.size -import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.filled.Add import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.ColorFilter import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp +import krail.feature.trip_planner.ui.generated.resources.Res +import krail.feature.trip_planner.ui.generated.resources.ic_walk +import org.jetbrains.compose.resources.painterResource +import org.jetbrains.compose.ui.tooling.preview.Preview import xyz.ksharma.krail.taj.LocalContentAlpha import xyz.ksharma.krail.taj.components.Text import xyz.ksharma.krail.taj.theme.KrailTheme @@ -35,7 +37,7 @@ fun WalkingLeg( horizontalArrangement = Arrangement.spacedBy(8.dp), ) { Image( - imageVector = Icons.Filled.Add,// TODO - painterResource(id = R.drawable.ic_walk), + painter = painterResource(Res.drawable.ic_walk), contentDescription = null, colorFilter = ColorFilter.tint( color = KrailTheme.colors.onSurface.copy(alpha = contentAlpha), @@ -46,6 +48,7 @@ fun WalkingLeg( } } +@Preview @Composable private fun PreviewWalkingLeg() { KrailTheme { diff --git a/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/savedtrips/SavedTripsScreen.kt b/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/savedtrips/SavedTripsScreen.kt index b245136c..a4c1961a 100644 --- a/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/savedtrips/SavedTripsScreen.kt +++ b/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/savedtrips/SavedTripsScreen.kt @@ -26,6 +26,10 @@ import xyz.ksharma.krail.trip.planner.ui.components.SearchStopRow import xyz.ksharma.krail.trip.planner.ui.state.savedtrip.SavedTripUiEvent import xyz.ksharma.krail.trip.planner.ui.state.savedtrip.SavedTripsState import xyz.ksharma.krail.trip.planner.ui.state.searchstop.model.StopItem +import krail.feature.trip_planner.ui.generated.resources.Res +import krail.feature.trip_planner.ui.generated.resources.ic_reverse +import org.jetbrains.compose.resources.painterResource + @Composable fun SavedTripsScreen( diff --git a/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/timetable/TimeTableScreen.kt b/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/timetable/TimeTableScreen.kt index aa39a0f0..38b66d44 100644 --- a/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/timetable/TimeTableScreen.kt +++ b/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/timetable/TimeTableScreen.kt @@ -18,7 +18,6 @@ import androidx.compose.foundation.lazy.items import androidx.compose.foundation.shape.CircleShape import androidx.compose.material.icons.Icons import androidx.compose.material.icons.automirrored.filled.ArrowBack -import androidx.compose.material.icons.filled.Edit import androidx.compose.material.icons.filled.Star import androidx.compose.material.icons.outlined.Star import androidx.compose.runtime.Composable @@ -38,6 +37,11 @@ import androidx.compose.ui.unit.dp import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.persistentListOf import kotlinx.collections.immutable.toImmutableList +import krail.feature.trip_planner.ui.generated.resources.Res +import krail.feature.trip_planner.ui.generated.resources.ic_reverse +import krail.feature.trip_planner.ui.generated.resources.ic_star +import krail.feature.trip_planner.ui.generated.resources.ic_star_filled +import org.jetbrains.compose.resources.painterResource import xyz.ksharma.krail.taj.LocalThemeColor import xyz.ksharma.krail.taj.LocalThemeContentColor import xyz.ksharma.krail.taj.components.Text @@ -125,7 +129,7 @@ fun TimeTableScreen( contentDescription = "Reverse Trip Search", ) { Image( - imageVector = Icons.Filled.Edit,// TODO - icons ImageVector.vectorResource(R.drawable.ic_reverse), + painter = painterResource(Res.drawable.ic_reverse), contentDescription = null, colorFilter = ColorFilter.tint(themeContentColor), modifier = Modifier.size(24.dp), @@ -140,10 +144,10 @@ fun TimeTableScreen( }, ) { Image( - imageVector = if (timeTableState.isTripSaved) { - Icons.Filled.Star + painter = if (timeTableState.isTripSaved) { + painterResource(Res.drawable.ic_star_filled) } else { - Icons.Outlined.Star + painterResource(Res.drawable.ic_star) }, contentDescription = null, colorFilter = ColorFilter.tint(themeContentColor), diff --git a/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/timetable/TimeTableViewModel.kt b/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/timetable/TimeTableViewModel.kt index f7fe48fb..ec4d2085 100644 --- a/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/timetable/TimeTableViewModel.kt +++ b/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/timetable/TimeTableViewModel.kt @@ -48,7 +48,7 @@ class TimeTableViewModel : ViewModel() { // to background and come back up again, the API call will be made. // Probably good to have data up to date. .onStart { - // Timber.d("onStart: Fetching Trip") + println("onStart: Fetching Trip") fetchTrip() }.stateIn(viewModelScope, SharingStarted.WhileSubscribed(ANR_TIMEOUT), true) @@ -82,17 +82,17 @@ class TimeTableViewModel : ViewModel() { } private fun fetchTrip() { - // Timber.d("fetchTrip API Call") + println("fetchTrip API Call") viewModelScope.launch(Dispatchers.IO) { // TODO - silent refresh here, UI to display loading but silent one. rateLimiter.rateLimitFlow { - // Timber.d("rateLimitFlow block") + // println("rateLimitFlow block") loadTrip() }.catch { e -> println("Error while fetching trip: $e") }.collectLatest { result -> result.onSuccess { response -> - // Timber.d("Success API response") + // println("Success API response") updateUiState { copy( isLoading = false, @@ -110,7 +110,7 @@ class TimeTableViewModel : ViewModel() { } private suspend fun loadTrip(): Result = withContext(Dispatchers.IO) { - // Timber.d("loadTrip API Call") + println("loadTrip API Call") require( tripInfo != null && tripInfo!!.fromStopId.isNotEmpty() && tripInfo!!.toStopId.isNotEmpty(), ) { "Trip Info is null or empty" } @@ -128,20 +128,20 @@ class TimeTableViewModel : ViewModel() { } private fun onSaveTripButtonClicked() { - //Timber.d("Save Trip Button Clicked") + println("Save Trip Button Clicked") viewModelScope.launch(Dispatchers.IO) { tripInfo?.let { trip -> - // Timber.d("Toggle Save Trip: $trip") + println("Toggle Save Trip: $trip") val savedTrip = sandook.getString(key = trip.tripId) - if (savedTrip != null) { + if (savedTrip != null && savedTrip != "null") { // Trip is already saved, so delete it sandook.remove(key = trip.tripId) - // Timber.d("Deleted Trip (Pref): ${Trip.fromJsonString(savedTrip)}") + println("Deleted Trip (Pref): ${Trip.fromJsonString(savedTrip)}") updateUiState { copy(isTripSaved = false) } } else { // Trip is not saved, so save it sandook.putString(key = trip.tripId, value = trip.toJsonString()) - // Timber.d("Saved Trip (Pref): $trip") + println("Saved Trip (Pref): $trip") updateUiState { copy(isTripSaved = true) } } } @@ -149,14 +149,15 @@ class TimeTableViewModel : ViewModel() { } private fun onJourneyCardClicked(journeyId: String) { - //Timber.d("Journey Card Clicked(JourneyId): $journeyId") + println("Journey Card Clicked(JourneyId): $journeyId") _expandedJourneyId.update { if (it == journeyId) null else journeyId } } private fun onLoadTimeTable(trip: Trip) { - //Timber.d("onLoadTimeTable -- Trigger fromStopItem: ${trip.fromStopId}, toStopItem: ${trip.toStopId}") + println("onLoadTimeTable -- Trigger fromStopItem: ${trip.fromStopId}, toStopItem: ${trip.toStopId}") tripInfo = trip val savedTrip = sandook.getString(key = trip.tripId) + println("Saved Trip[${trip.tripId}]: $savedTrip") updateUiState { copy( isLoading = true, @@ -169,7 +170,7 @@ class TimeTableViewModel : ViewModel() { } private fun onReverseTripButtonClicked() { - // Timber.d("Reverse Trip Button Clicked -- Trigger") + println("Reverse Trip Button Clicked -- Trigger") require(tripInfo != null) { "Trip Info is null" } val reverseTrip = Trip( fromStopId = tripInfo!!.toStopId, @@ -182,7 +183,7 @@ class TimeTableViewModel : ViewModel() { updateUiState { copy( trip = reverseTrip, - isTripSaved = savedTrip != null, + isTripSaved = savedTrip != null && savedTrip != "null", isLoading = true, isError = false, ) @@ -209,7 +210,7 @@ class TimeTableViewModel : ViewModel() { copy(journeyList = updatedJourneyList) } - // Timber.d("New Time: ${uiState.value.journeyList.joinToString(", ") { it.timeText }}") + // println("New Time: ${uiState.value.journeyList.joinToString(", ") { it.timeText }}") } private inline fun updateUiState(block: TimeTableState.() -> TimeTableState) { From c6edcba895c32631263edbf2530377c8f4116400 Mon Sep 17 00:00:00 2001 From: Karan Sharma <55722391+ksharma-xyz@users.noreply.github.com> Date: Wed, 20 Nov 2024 20:59:18 +1100 Subject: [PATCH 50/67] Debug saved trips error --- .../krail/trip/planner/ui/savedtrips/SavedTripsViewModel.kt | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/savedtrips/SavedTripsViewModel.kt b/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/savedtrips/SavedTripsViewModel.kt index cabcc4b7..895a49f6 100644 --- a/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/savedtrips/SavedTripsViewModel.kt +++ b/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/savedtrips/SavedTripsViewModel.kt @@ -23,17 +23,19 @@ class SavedTripsViewModel : ViewModel() { val uiState: StateFlow = _uiState private fun loadSavedTrips() { + println("loadSavedTrips, keys: ${sandook.keys()}") viewModelScope.launch(context = Dispatchers.IO) { val trips = persistentListOf() - sandook.keys().mapNotNull { key -> + sandook.keys().mapNotNull { key -> /// ERROR HERE TODO - keys mismatch val tripString = sandook.getString(key, null) tripString?.let { tripJsonString -> Trip.fromJsonString(tripJsonString) } }.toImmutableList() + println( "SavedTrips: ${trips.size} number") trips.forEachIndexed { index, trip -> - //Timber.d("\t SavedTrip: #$index ${trip.fromStopName} -> ${trip.toStopName}") + println("\t SavedTrip: #$index ${trip.fromStopName} -> ${trip.toStopName}") } updateUiState { copy(savedTrips = trips) } From e11883893f82d2d2315ae3e063b9a5c8653e3a14 Mon Sep 17 00:00:00 2001 From: Karan Sharma <55722391+ksharma-xyz@users.noreply.github.com> Date: Wed, 20 Nov 2024 22:54:02 +1100 Subject: [PATCH 51/67] DI : Failed attempt - kotlin-inject --- composeApp/build.gradle.kts | 15 ++++++++ .../xyz/ksharma/krail/common/KrailApp.kt | 2 ++ .../krail/common/splash/SplashViewModel.kt | 2 ++ .../krail/core/datetime/DateTimeHelperTest.kt | 3 +- core/di/build.gradle.kts | 14 ++++++-- .../xyz/ksharma/krail/di/AppComponent.kt | 10 ++++++ .../xyz/ksharma/krail/di/AppDispatchers.kt | 12 ------- .../xyz/ksharma/krail/di/CoroutinesModule.kt | 24 ------------- .../xyz/ksharma/krail/di/DispatchersModule.kt | 26 -------------- .../kotlin/xyz/ksharma/krail/di/Singleton.kt | 7 ++++ feature/trip-planner/network/build.gradle.kts | 11 ++++-- .../network/api/di/NetworkComponent.kt | 35 +++++++++++++++++++ .../api/ratelimit/NetworkRateLimiter.kt | 2 ++ .../planner/network/api/service/HttpClient.kt | 1 + feature/trip-planner/ui/build.gradle.kts | 19 +--------- .../trip/planner/ui/di/ViewModelComponent.kt | 33 +++++++++++++++++ .../ui/savedtrips/SavedTripsViewModel.kt | 4 ++- .../ui/timetable/TimeTableViewModel.kt | 4 ++- .../ui/usualride/UsualRideViewModel.kt | 2 ++ .../planner/ui/viewmodel/ViewModelFactory.kt | 27 -------------- gradle/libs.versions.toml | 3 ++ sandook/build.gradle.kts | 29 ++++++++++++--- .../xyz/ksharma/krail/sandook/RealSandook.kt | 5 ++- .../krail/sandook/di/SandookComponent.kt | 19 +++++----- .../xyz/ksharma/krail/sandook/di/Singleton.kt | 8 +++++ 25 files changed, 188 insertions(+), 129 deletions(-) create mode 100644 core/di/src/commonMain/kotlin/xyz/ksharma/krail/di/AppComponent.kt delete mode 100644 core/di/src/commonMain/kotlin/xyz/ksharma/krail/di/AppDispatchers.kt delete mode 100644 core/di/src/commonMain/kotlin/xyz/ksharma/krail/di/CoroutinesModule.kt delete mode 100644 core/di/src/commonMain/kotlin/xyz/ksharma/krail/di/DispatchersModule.kt create mode 100644 core/di/src/commonMain/kotlin/xyz/ksharma/krail/di/Singleton.kt create mode 100644 feature/trip-planner/network/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/network/api/di/NetworkComponent.kt create mode 100644 feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/di/ViewModelComponent.kt delete mode 100644 feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/viewmodel/ViewModelFactory.kt create mode 100644 sandook/src/commonMain/kotlin/xyz/ksharma/krail/sandook/di/Singleton.kt diff --git a/composeApp/build.gradle.kts b/composeApp/build.gradle.kts index dadf033d..f880e403 100644 --- a/composeApp/build.gradle.kts +++ b/composeApp/build.gradle.kts @@ -11,6 +11,7 @@ plugins { alias(libs.plugins.krail.android.library) alias(libs.plugins.compose.compiler) alias(libs.plugins.kotlin.serialization) + alias(libs.plugins.ksp) } kotlin { @@ -78,6 +79,20 @@ kotlin { implementation(libs.ktor.client.content.negotiation) implementation(libs.ktor.client.logging) implementation(libs.ktor.serialization.kotlinx.json) + + + implementation(libs.di.kotlinInjectRuntime) } } } + +dependencies { + // 1. Configure code generation into the common source set + kspCommonMainMetadata(libs.di.kotlinInjectRuntime) + + // 2. Configure code generation into each KMP target source set + //kspAndroid(libs.di.kotlinInjectCompilerKsp) + // kspIosX64("me.tatarka.inject:kotlin-inject-compiler-ksp:0.7.2") + // kspIosArm64("me.tatarka.inject:kotlin-inject-compiler-ksp:0.7.2") + // kspIosSimulatorArm64("me.tatarka.inject:kotlin-inject-compiler-ksp:0.7.2") +} diff --git a/composeApp/src/commonMain/kotlin/xyz/ksharma/krail/common/KrailApp.kt b/composeApp/src/commonMain/kotlin/xyz/ksharma/krail/common/KrailApp.kt index 46c3bb97..c8b013c1 100644 --- a/composeApp/src/commonMain/kotlin/xyz/ksharma/krail/common/KrailApp.kt +++ b/composeApp/src/commonMain/kotlin/xyz/ksharma/krail/common/KrailApp.kt @@ -1,10 +1,12 @@ package xyz.ksharma.krail.common import androidx.compose.runtime.Composable + import xyz.ksharma.krail.taj.theme.KrailTheme @Composable fun KrailApp() { + KrailTheme { KrailNavHost() } diff --git a/composeApp/src/commonMain/kotlin/xyz/ksharma/krail/common/splash/SplashViewModel.kt b/composeApp/src/commonMain/kotlin/xyz/ksharma/krail/common/splash/SplashViewModel.kt index 252c3c9f..96882e54 100644 --- a/composeApp/src/commonMain/kotlin/xyz/ksharma/krail/common/splash/SplashViewModel.kt +++ b/composeApp/src/commonMain/kotlin/xyz/ksharma/krail/common/splash/SplashViewModel.kt @@ -10,10 +10,12 @@ import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.onStart import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.launch +import me.tatarka.inject.annotations.Inject import xyz.ksharma.krail.sandook.RealSandook import xyz.ksharma.krail.sandook.Sandook import xyz.ksharma.krail.trip.planner.ui.state.TransportMode +@Inject class SplashViewModel : ViewModel() { private val sandook: Sandook = RealSandook() diff --git a/core/date-time/src/commonTest/kotlin/xyz/ksharma/krail/core/datetime/DateTimeHelperTest.kt b/core/date-time/src/commonTest/kotlin/xyz/ksharma/krail/core/datetime/DateTimeHelperTest.kt index fcfd477d..e08495c4 100644 --- a/core/date-time/src/commonTest/kotlin/xyz/ksharma/krail/core/datetime/DateTimeHelperTest.kt +++ b/core/date-time/src/commonTest/kotlin/xyz/ksharma/krail/core/datetime/DateTimeHelperTest.kt @@ -1,7 +1,6 @@ package xyz.ksharma.krail.core.datetime import kotlinx.datetime.Instant -import xyz.ksharma.krail.core.datetime.DateTimeHelper.aestToHHMM import xyz.ksharma.krail.core.datetime.DateTimeHelper.formatTo12HourTime import xyz.ksharma.krail.core.datetime.DateTimeHelper.toFormattedDurationTimeString import xyz.ksharma.krail.core.datetime.DateTimeHelper.toGenericFormattedTimeString @@ -54,11 +53,13 @@ class DateTimeHelperTest { assertEquals("2024-10-07T12:00:23", "2024-10-07T01:00:23Z".utcToAEST()) } +/* @Test fun testAestToHHMM() { assertEquals("11:00 AM", "2024-10-07T00:00:00Z".aestToHHMM()) assertEquals("12:00 PM", "2024-10-07T01:00:00Z".aestToHHMM()) } +*/ @Test fun testToGenericFormattedTimeString() { diff --git a/core/di/build.gradle.kts b/core/di/build.gradle.kts index d13deb47..1b925529 100644 --- a/core/di/build.gradle.kts +++ b/core/di/build.gradle.kts @@ -1,6 +1,8 @@ plugins { alias(libs.plugins.krail.android.library) alias(libs.plugins.krail.kotlin.multiplatform) + alias(libs.plugins.krail.compose.multiplatform) + alias(libs.plugins.compose.compiler) alias(libs.plugins.ksp) } @@ -20,6 +22,8 @@ kotlin { dependencies { implementation(libs.di.kotlinInjectRuntime) implementation(libs.kotlinx.coroutines.core) + + implementation(compose.runtime) } } } @@ -31,7 +35,11 @@ dependencies { // 2. Configure code generation into each KMP target source set kspAndroid(libs.di.kotlinInjectCompilerKsp) - // kspIosX64("me.tatarka.inject:kotlin-inject-compiler-ksp:0.7.2") - // kspIosArm64("me.tatarka.inject:kotlin-inject-compiler-ksp:0.7.2") - // kspIosSimulatorArm64("me.tatarka.inject:kotlin-inject-compiler-ksp:0.7.2") + // kspIosX64("me.tatarka.inject:kotlin-inject-compiler-ksp:0.7.2") + // kspIosArm64("me.tatarka.inject:kotlin-inject-compiler-ksp:0.7.2") + // kspIosSimulatorArm64("me.tatarka.inject:kotlin-inject-compiler-ksp:0.7.2") +} + +ksp { + arg("me.tatarka.inject.generateCompanionExtensions", "true") } diff --git a/core/di/src/commonMain/kotlin/xyz/ksharma/krail/di/AppComponent.kt b/core/di/src/commonMain/kotlin/xyz/ksharma/krail/di/AppComponent.kt new file mode 100644 index 00000000..0c1c4c9b --- /dev/null +++ b/core/di/src/commonMain/kotlin/xyz/ksharma/krail/di/AppComponent.kt @@ -0,0 +1,10 @@ +package xyz.ksharma.krail.di + +import me.tatarka.inject.annotations.Component + +@Singleton +@Component +abstract class AppComponent { + + companion object +} diff --git a/core/di/src/commonMain/kotlin/xyz/ksharma/krail/di/AppDispatchers.kt b/core/di/src/commonMain/kotlin/xyz/ksharma/krail/di/AppDispatchers.kt deleted file mode 100644 index 1fad3849..00000000 --- a/core/di/src/commonMain/kotlin/xyz/ksharma/krail/di/AppDispatchers.kt +++ /dev/null @@ -1,12 +0,0 @@ -package xyz.ksharma.krail.di - -import me.tatarka.inject.annotations.Qualifier - -enum class AppDispatchers { - Default, - IO, -} - -@Qualifier -@Retention(AnnotationRetention.RUNTIME) -annotation class Dispatcher(val dispatch: AppDispatchers) diff --git a/core/di/src/commonMain/kotlin/xyz/ksharma/krail/di/CoroutinesModule.kt b/core/di/src/commonMain/kotlin/xyz/ksharma/krail/di/CoroutinesModule.kt deleted file mode 100644 index 983e69aa..00000000 --- a/core/di/src/commonMain/kotlin/xyz/ksharma/krail/di/CoroutinesModule.kt +++ /dev/null @@ -1,24 +0,0 @@ -package xyz.ksharma.krail.di - -import me.tatarka.inject.annotations.Component -import me.tatarka.inject.annotations.Inject -import me.tatarka.inject.annotations.Provides -import kotlinx.coroutines.CoroutineDispatcher -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.SupervisorJob - -@Component -abstract class CoroutinesComponent { - - abstract val coroutinesModule: CoroutinesModule - - @Provides - fun provideCoroutineScope( - @Dispatcher(AppDispatchers.IO) ioDispatcher: CoroutineDispatcher, - ): CoroutineScope = CoroutineScope(context = ioDispatcher + SupervisorJob()) -} - -@Inject -class CoroutinesModule( - @Dispatcher(AppDispatchers.IO) val ioDispatcher: CoroutineDispatcher, -) diff --git a/core/di/src/commonMain/kotlin/xyz/ksharma/krail/di/DispatchersModule.kt b/core/di/src/commonMain/kotlin/xyz/ksharma/krail/di/DispatchersModule.kt deleted file mode 100644 index f1fe619c..00000000 --- a/core/di/src/commonMain/kotlin/xyz/ksharma/krail/di/DispatchersModule.kt +++ /dev/null @@ -1,26 +0,0 @@ -package xyz.ksharma.krail.di - -import me.tatarka.inject.annotations.Component -import me.tatarka.inject.annotations.Inject -import me.tatarka.inject.annotations.Provides -import kotlinx.coroutines.CoroutineDispatcher -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.IO - -@Component -abstract class DispatchersComponent { - - abstract val dispatchersModule: DispatchersModule - - @Provides - fun defaultDispatcher(): CoroutineDispatcher = Dispatchers.Default - - @Provides - fun ioDispatcher(): CoroutineDispatcher = Dispatchers.IO -} - -@Inject -class DispatchersModule( - @Dispatcher(AppDispatchers.Default) val defaultDispatcher: CoroutineDispatcher, - @Dispatcher(AppDispatchers.IO) val ioDispatcher: CoroutineDispatcher -) diff --git a/core/di/src/commonMain/kotlin/xyz/ksharma/krail/di/Singleton.kt b/core/di/src/commonMain/kotlin/xyz/ksharma/krail/di/Singleton.kt new file mode 100644 index 00000000..d327b40d --- /dev/null +++ b/core/di/src/commonMain/kotlin/xyz/ksharma/krail/di/Singleton.kt @@ -0,0 +1,7 @@ +package xyz.ksharma.krail.di + +import me.tatarka.inject.annotations.Scope + +@Scope +@Target(AnnotationTarget.CLASS, AnnotationTarget.FUNCTION, AnnotationTarget.PROPERTY_GETTER) +annotation class Singleton diff --git a/feature/trip-planner/network/build.gradle.kts b/feature/trip-planner/network/build.gradle.kts index 799db543..940a06c6 100644 --- a/feature/trip-planner/network/build.gradle.kts +++ b/feature/trip-planner/network/build.gradle.kts @@ -29,8 +29,15 @@ kotlin { applyDefaultHierarchyTemplate() androidTarget() - iosArm64() - iosSimulatorArm64() + listOf( + iosX64(), + iosArm64(), + iosSimulatorArm64() + ).forEach { + it.binaries.framework { + baseName = "network" + } + } sourceSets { androidMain.dependencies { diff --git a/feature/trip-planner/network/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/network/api/di/NetworkComponent.kt b/feature/trip-planner/network/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/network/api/di/NetworkComponent.kt new file mode 100644 index 00000000..ec74efc8 --- /dev/null +++ b/feature/trip-planner/network/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/network/api/di/NetworkComponent.kt @@ -0,0 +1,35 @@ +/* +package xyz.ksharma.krail.trip.planner.network.api.di + +import io.ktor.client.HttpClient +import io.ktor.client.plugins.contentnegotiation.ContentNegotiation +import io.ktor.serialization.kotlinx.json.json +import kotlinx.serialization.json.Json +import me.tatarka.inject.annotations.Component +import me.tatarka.inject.annotations.Provides +import xyz.ksharma.krail.trip.planner.network.api.ratelimit.NetworkRateLimiter +import xyz.ksharma.krail.trip.planner.network.api.ratelimit.RateLimiter + +@Component +abstract class NetworkComponent { + + val NetworkRateLimiter.bind: RateLimiter + @Provides get() = this + + @Provides + fun provideHttpClient(): HttpClient = getHttpClient() +} + +fun getHttpClient(): HttpClient { + return HttpClient { + expectSuccess = true + install(ContentNegotiation) { + json(Json { + ignoreUnknownKeys = true + isLenient = true + prettyPrint = true + }) + } + } +} +*/ diff --git a/feature/trip-planner/network/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/network/api/ratelimit/NetworkRateLimiter.kt b/feature/trip-planner/network/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/network/api/ratelimit/NetworkRateLimiter.kt index 1c677f83..4937eb25 100644 --- a/feature/trip-planner/network/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/network/api/ratelimit/NetworkRateLimiter.kt +++ b/feature/trip-planner/network/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/network/api/ratelimit/NetworkRateLimiter.kt @@ -9,6 +9,7 @@ import kotlinx.coroutines.flow.debounce import kotlinx.coroutines.flow.flatMapLatest import kotlinx.coroutines.flow.flow import kotlinx.coroutines.flow.update +import me.tatarka.inject.annotations.Inject import kotlin.time.Duration.Companion.milliseconds import kotlin.time.Duration.Companion.seconds @@ -18,6 +19,7 @@ import kotlin.time.Duration.Companion.seconds * * Note: This class should not be Singleton. It should be created per use-case. */ +@Inject class NetworkRateLimiter : RateLimiter { private val triggerFlow = MutableSharedFlow(replay = 1) diff --git a/feature/trip-planner/network/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/network/api/service/HttpClient.kt b/feature/trip-planner/network/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/network/api/service/HttpClient.kt index 89264b47..018c461f 100644 --- a/feature/trip-planner/network/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/network/api/service/HttpClient.kt +++ b/feature/trip-planner/network/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/network/api/service/HttpClient.kt @@ -14,6 +14,7 @@ import xyz.ksharma.krail.trip.planner.network.BuildKonfig fun getHttpClient(): HttpClient { return HttpClient { + expectSuccess = true install(ContentNegotiation) { json(Json { ignoreUnknownKeys = true diff --git a/feature/trip-planner/ui/build.gradle.kts b/feature/trip-planner/ui/build.gradle.kts index 2c90de63..f7288e29 100644 --- a/feature/trip-planner/ui/build.gradle.kts +++ b/feature/trip-planner/ui/build.gradle.kts @@ -76,25 +76,8 @@ dependencies { kspCommonMainMetadata(libs.di.kotlinInjectRuntime) // 2. Configure code generation into each KMP target source set - //kspAndroid(libs.di.kotlinInjectCompilerKsp) + ksp(libs.di.kotlinInjectCompilerKsp) // kspIosX64("me.tatarka.inject:kotlin-inject-compiler-ksp:0.7.2") // kspIosArm64("me.tatarka.inject:kotlin-inject-compiler-ksp:0.7.2") // kspIosSimulatorArm64("me.tatarka.inject:kotlin-inject-compiler-ksp:0.7.2") } - - -/* -dependencies { - implementation(projects.core.dateTime) - implementation(projects.core.designSystem) - implementation(projects.feature.tripPlanner.network.api) - implementation(projects.feature.tripPlanner.state) - implementation(projects.sandook.api) - - implementation(projects.sandook.real) - implementation(libs.compose.material3) - - testImplementation(libs.test.composeUiTestJunit4) - testImplementation(libs.test.kotlin) -} -*/ diff --git a/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/di/ViewModelComponent.kt b/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/di/ViewModelComponent.kt new file mode 100644 index 00000000..220800bc --- /dev/null +++ b/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/di/ViewModelComponent.kt @@ -0,0 +1,33 @@ +/* +package xyz.ksharma.krail.trip.planner.ui.di + +import me.tatarka.inject.annotations.Component +import me.tatarka.inject.annotations.KmpComponentCreate +import me.tatarka.inject.annotations.Provides +import xyz.ksharma.krail.sandook.Sandook +import xyz.ksharma.krail.trip.planner.ui.savedtrips.SavedTripsViewModel +import xyz.ksharma.krail.trip.planner.ui.timetable.TimeTableViewModel +import xyz.ksharma.krail.trip.planner.ui.usualride.UsualRideViewModel + +@Component +abstract class ViewModelComponent { + + @Provides + fun provideUsualRideViewModel( + sandook: Sandook, + ): UsualRideViewModel = UsualRideViewModel(sandook) + + @Provides + fun provideTimeTableViewModel( + sandook: Sandook, + ): TimeTableViewModel = TimeTableViewModel(sandook) + + + @Provides + fun provideSavedTripsViewModel( + sandook: Sandook, + ): SavedTripsViewModel = SavedTripsViewModel(sandook) + + companion object +} +*/ diff --git a/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/savedtrips/SavedTripsViewModel.kt b/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/savedtrips/SavedTripsViewModel.kt index 895a49f6..65dd7079 100644 --- a/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/savedtrips/SavedTripsViewModel.kt +++ b/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/savedtrips/SavedTripsViewModel.kt @@ -10,15 +10,17 @@ import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch +import me.tatarka.inject.annotations.Inject import xyz.ksharma.krail.sandook.RealSandook import xyz.ksharma.krail.sandook.Sandook import xyz.ksharma.krail.trip.planner.ui.state.savedtrip.SavedTripUiEvent import xyz.ksharma.krail.trip.planner.ui.state.savedtrip.SavedTripsState import xyz.ksharma.krail.trip.planner.ui.state.timetable.Trip +@Inject class SavedTripsViewModel : ViewModel() { - private val sandook: Sandook = RealSandook() + private val sandook: Sandook = RealSandook() private val _uiState: MutableStateFlow = MutableStateFlow(SavedTripsState()) val uiState: StateFlow = _uiState diff --git a/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/timetable/TimeTableViewModel.kt b/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/timetable/TimeTableViewModel.kt index ec4d2085..dd571e1f 100644 --- a/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/timetable/TimeTableViewModel.kt +++ b/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/timetable/TimeTableViewModel.kt @@ -17,6 +17,7 @@ import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch import kotlinx.coroutines.withContext +import me.tatarka.inject.annotations.Inject import xyz.ksharma.krail.core.datetime.DateTimeHelper.calculateTimeDifferenceFromNow import xyz.ksharma.krail.core.datetime.DateTimeHelper.toGenericFormattedTimeString import xyz.ksharma.krail.sandook.RealSandook @@ -32,10 +33,11 @@ import xyz.ksharma.krail.trip.planner.ui.state.timetable.Trip import xyz.ksharma.krail.trip.planner.ui.timetable.business.buildJourneyList import kotlin.time.Duration.Companion.seconds +@Inject class TimeTableViewModel : ViewModel() { - private val sandook: Sandook = RealSandook() private val rateLimiter: RateLimiter = NetworkRateLimiter() + private val sandook: Sandook = RealSandook() private val _uiState: MutableStateFlow = MutableStateFlow(TimeTableState()) val uiState: StateFlow = _uiState diff --git a/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/usualride/UsualRideViewModel.kt b/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/usualride/UsualRideViewModel.kt index a2069af3..1c8bff9b 100644 --- a/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/usualride/UsualRideViewModel.kt +++ b/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/usualride/UsualRideViewModel.kt @@ -7,12 +7,14 @@ import kotlinx.coroutines.IO import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.launch +import me.tatarka.inject.annotations.Inject import xyz.ksharma.krail.sandook.RealSandook import xyz.ksharma.krail.sandook.Sandook import xyz.ksharma.krail.trip.planner.ui.state.TransportMode import xyz.ksharma.krail.trip.planner.ui.state.usualride.UsualRideEvent import xyz.ksharma.krail.trip.planner.ui.state.usualride.UsualRideState +@Inject class UsualRideViewModel : ViewModel() { private val sandook: Sandook = RealSandook() diff --git a/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/viewmodel/ViewModelFactory.kt b/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/viewmodel/ViewModelFactory.kt deleted file mode 100644 index 37ffc766..00000000 --- a/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/viewmodel/ViewModelFactory.kt +++ /dev/null @@ -1,27 +0,0 @@ -/* -package xyz.ksharma.krail.trip.planner.ui.viewmodel -import androidx.compose.runtime.Composable -import androidx.lifecycle.ViewModel -import androidx.lifecycle.ViewModelProvider -import androidx.lifecycle.viewmodel.compose.viewModel -import kotlin.reflect.KClass - -class CommonViewModelFactory( - private val viewModelClass: KClass, - private val creator: () -> VM -) : ViewModelProvider.Factory { - override fun create(modelClass: Class): T { - if (modelClass.isAssignableFrom(viewModelClass.java)) { - @Suppress("UNCHECKED_CAST") - return creator() as T - } - throw IllegalArgumentException("Unknown ViewModel class") - } -} - -@Composable -inline fun composeViewModel(noinline creator: () -> VM): VM { - val factory = CommonViewModelFactory(VM::class, creator) - return viewModel(factory = factory) -} -*/ diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 1091095e..c9fb2296 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -24,6 +24,7 @@ ktor = "3.0.1" androidx-lifecycle = "2.8.3" kotlinxCoroutines = "1.9.0" buildkonfigGradlePlugin = "0.15.2" +kermit = "2.0.4" #SDK minSdk = "26" @@ -42,6 +43,8 @@ lifecycle-runtime-ktx = { group = "androidx.lifecycle", name = "lifecycle-runtim navigation-compose = { module = "org.jetbrains.androidx.navigation:navigation-compose", version.ref = "navigationCompose" } kotlinx-coroutines-core = { group = "org.jetbrains.kotlinx", name = "kotlinx-coroutines-core", version.ref = "kotlinxCoroutines" } +log-kermit = { module = "co.touchlab:kermit", version.ref = "kermit" } + lifecycle-viewmodel-compose = { module = "org.jetbrains.androidx.lifecycle:lifecycle-viewmodel-compose", version.ref = "lifecycleViewmodelCompose" } androidx-lifecycle-runtime-compose = { group = "org.jetbrains.androidx.lifecycle", name = "lifecycle-runtime-compose", version.ref = "androidx-lifecycle" } multiplatform-settings = { module = "com.russhwolf:multiplatform-settings-no-arg", version.ref = "multiplatformSettings" } diff --git a/sandook/build.gradle.kts b/sandook/build.gradle.kts index 6ad2030b..dccc0393 100644 --- a/sandook/build.gradle.kts +++ b/sandook/build.gradle.kts @@ -1,3 +1,6 @@ +import org.jetbrains.kotlin.gradle.dsl.KotlinMultiplatformExtension +import org.jetbrains.kotlin.gradle.tasks.KotlinCompilationTask + plugins { alias(libs.plugins.krail.android.library) alias(libs.plugins.krail.kotlin.multiplatform) @@ -28,27 +31,43 @@ kotlin { sourceSets { commonMain { dependencies { +// implementation(projects.core.di) + implementation(libs.kotlinx.serialization.json) implementation(libs.di.kotlinInjectRuntime) implementation(libs.multiplatform.settings) implementation(compose.runtime) + implementation(libs.log.kermit) } } } + + //configureCommonMainKsp() } dependencies { // 1. Configure code generation into the common source set kspCommonMainMetadata(libs.di.kotlinInjectRuntime) - + ksp(libs.di.kotlinInjectCompilerKsp) // 2. Configure code generation into each KMP target source set - //kspAndroid(libs.di.kotlinInjectCompilerKsp) - // kspIosX64("me.tatarka.inject:kotlin-inject-compiler-ksp:0.7.2") - // kspIosArm64("me.tatarka.inject:kotlin-inject-compiler-ksp:0.7.2") - // kspIosSimulatorArm64("me.tatarka.inject:kotlin-inject-compiler-ksp:0.7.2") + kspAndroid(libs.di.kotlinInjectCompilerKsp) + //kspIosX64("me.tatarka.inject:kotlin-inject-compiler-ksp:0.7.2") + kspIosArm64(libs.di.kotlinInjectCompilerKsp) + kspIosSimulatorArm64(libs.di.kotlinInjectCompilerKsp) } ksp { arg("me.tatarka.inject.generateCompanionExtensions", "true") } +/* +fun KotlinMultiplatformExtension.configureCommonMainKsp() { + sourceSets.named("commonMain").configure { + kotlin.srcDir("build/generated/ksp/metadata/commonMain/kotlin") + } + + project.tasks.withType(KotlinCompilationTask::class.java).configureEach { + if(name != "kspCommonMainKotlinMetadata") { + dependsOn("kspCommonMainKotlinMetadata") + } + }*/ diff --git a/sandook/src/commonMain/kotlin/xyz/ksharma/krail/sandook/RealSandook.kt b/sandook/src/commonMain/kotlin/xyz/ksharma/krail/sandook/RealSandook.kt index 25997b99..04d14e28 100644 --- a/sandook/src/commonMain/kotlin/xyz/ksharma/krail/sandook/RealSandook.kt +++ b/sandook/src/commonMain/kotlin/xyz/ksharma/krail/sandook/RealSandook.kt @@ -1,8 +1,11 @@ package xyz.ksharma.krail.sandook import com.russhwolf.settings.Settings +import me.tatarka.inject.annotations.Inject -class RealSandook (private val settings: Settings = Settings()) : Sandook { +@Inject +class RealSandook : Sandook { + private val settings: Settings = Settings() override fun keys(): Set = settings.keys diff --git a/sandook/src/commonMain/kotlin/xyz/ksharma/krail/sandook/di/SandookComponent.kt b/sandook/src/commonMain/kotlin/xyz/ksharma/krail/sandook/di/SandookComponent.kt index 7f770585..70bc0d15 100644 --- a/sandook/src/commonMain/kotlin/xyz/ksharma/krail/sandook/di/SandookComponent.kt +++ b/sandook/src/commonMain/kotlin/xyz/ksharma/krail/sandook/di/SandookComponent.kt @@ -1,21 +1,24 @@ +/* package xyz.ksharma.krail.sandook.di -import com.russhwolf.settings.Settings import me.tatarka.inject.annotations.Component import me.tatarka.inject.annotations.Provides import xyz.ksharma.krail.sandook.RealSandook import xyz.ksharma.krail.sandook.Sandook +@Singleton @Component abstract class SandookComponent { - abstract val sandook: Sandook - // Settings() does not provide encryption use dependency without no-arg if encryption is required. - @Provides - fun provideSettings(): Settings = Settings() - - @Provides - fun provideSandook(settings: Settings): Sandook = RealSandook(settings) + internal val RealSandook.bind: Sandook + @Singleton + @Provides get() = this companion object } + +*/ +/* +@KmpComponentCreate +expect fun createSandookComponent(): SandookComponent +*/ diff --git a/sandook/src/commonMain/kotlin/xyz/ksharma/krail/sandook/di/Singleton.kt b/sandook/src/commonMain/kotlin/xyz/ksharma/krail/sandook/di/Singleton.kt new file mode 100644 index 00000000..839ded8c --- /dev/null +++ b/sandook/src/commonMain/kotlin/xyz/ksharma/krail/sandook/di/Singleton.kt @@ -0,0 +1,8 @@ +/* +package xyz.ksharma.krail.sandook.di +import me.tatarka.inject.annotations.Scope + +@Scope +@Target(AnnotationTarget.CLASS, AnnotationTarget.FUNCTION, AnnotationTarget.PROPERTY_GETTER) +annotation class Singleton +*/ From 8afe9cbaa7d5cbdc2ed629ee8b19e92d1bdef9ff Mon Sep 17 00:00:00 2001 From: Karan Sharma <55722391+ksharma-xyz@users.noreply.github.com> Date: Thu, 21 Nov 2024 23:00:48 +1100 Subject: [PATCH 52/67] DI: Koin to rescue --- composeApp/build.gradle.kts | 3 +- .../xyz/ksharma/krail/common/KrailApp.kt | 9 ++- .../xyz/ksharma/krail/common/KrailNavHost.kt | 6 +- .../ksharma/krail/common/di/KoinAppModule.kt | 17 ++++++ .../krail/common/splash/SplashViewModel.kt | 9 +-- feature/trip-planner/network/build.gradle.kts | 2 + .../network/api/di/NetworkComponent.kt | 35 ----------- .../planner/network/api/di/NetworkModule.kt | 17 ++++++ .../api/ratelimit/NetworkRateLimiter.kt | 4 +- .../planner/network/api/service/HttpClient.kt | 9 +-- .../api/service/RealTripPlanningService.kt | 61 +++++++++++++++++++ .../api/service/TripPlanningService.kt | 16 +++++ .../service/stop_finder/StopFinderRequest.kt | 29 --------- .../network/api/service/trip/TripRequest.kt | 49 --------------- feature/trip-planner/ui/build.gradle.kts | 2 + .../trip/planner/ui/di/ViewModelModule.kt | 15 +++++ .../ui/savedtrips/SavedTripsDestination.kt | 7 ++- .../ui/savedtrips/SavedTripsViewModel.kt | 10 ++- .../ui/searchstop/SearchStopDestination.kt | 7 ++- .../ui/searchstop/SearchStopViewModel.kt | 15 +---- .../ui/timetable/TimeTableDestination.kt | 6 +- .../ui/timetable/TimeTableViewModel.kt | 26 +++----- .../ui/usualride/UsualRideDestination.kt | 5 +- .../ui/usualride/UsualRideViewModel.kt | 5 +- gradle/libs.versions.toml | 11 ++++ sandook/build.gradle.kts | 17 +++++- .../xyz/ksharma/krail/sandook/RealSandook.kt | 2 - .../xyz/ksharma/krail/sandook/di/DbModule.kt | 11 ++++ 28 files changed, 221 insertions(+), 184 deletions(-) create mode 100644 composeApp/src/commonMain/kotlin/xyz/ksharma/krail/common/di/KoinAppModule.kt delete mode 100644 feature/trip-planner/network/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/network/api/di/NetworkComponent.kt create mode 100644 feature/trip-planner/network/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/network/api/di/NetworkModule.kt create mode 100644 feature/trip-planner/network/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/network/api/service/RealTripPlanningService.kt delete mode 100644 feature/trip-planner/network/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/network/api/service/stop_finder/StopFinderRequest.kt delete mode 100644 feature/trip-planner/network/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/network/api/service/trip/TripRequest.kt create mode 100644 feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/di/ViewModelModule.kt create mode 100644 sandook/src/commonMain/kotlin/xyz/ksharma/krail/sandook/di/DbModule.kt diff --git a/composeApp/build.gradle.kts b/composeApp/build.gradle.kts index f880e403..267f75da 100644 --- a/composeApp/build.gradle.kts +++ b/composeApp/build.gradle.kts @@ -80,8 +80,7 @@ kotlin { implementation(libs.ktor.client.logging) implementation(libs.ktor.serialization.kotlinx.json) - - implementation(libs.di.kotlinInjectRuntime) + implementation(libs.di.koinComposeViewmodelNav) } } } diff --git a/composeApp/src/commonMain/kotlin/xyz/ksharma/krail/common/KrailApp.kt b/composeApp/src/commonMain/kotlin/xyz/ksharma/krail/common/KrailApp.kt index c8b013c1..9745e42f 100644 --- a/composeApp/src/commonMain/kotlin/xyz/ksharma/krail/common/KrailApp.kt +++ b/composeApp/src/commonMain/kotlin/xyz/ksharma/krail/common/KrailApp.kt @@ -1,13 +1,16 @@ package xyz.ksharma.krail.common import androidx.compose.runtime.Composable +import org.koin.compose.KoinApplication +import xyz.ksharma.krail.common.di.koinConfig import xyz.ksharma.krail.taj.theme.KrailTheme @Composable fun KrailApp() { - - KrailTheme { - KrailNavHost() + KoinApplication(application = koinConfig) { + KrailTheme { + KrailNavHost() + } } } diff --git a/composeApp/src/commonMain/kotlin/xyz/ksharma/krail/common/KrailNavHost.kt b/composeApp/src/commonMain/kotlin/xyz/ksharma/krail/common/KrailNavHost.kt index 1fc52b8c..f4343d7b 100644 --- a/composeApp/src/commonMain/kotlin/xyz/ksharma/krail/common/KrailNavHost.kt +++ b/composeApp/src/commonMain/kotlin/xyz/ksharma/krail/common/KrailNavHost.kt @@ -15,6 +15,9 @@ import androidx.navigation.compose.NavHost import androidx.navigation.compose.composable import androidx.navigation.compose.rememberNavController import kotlinx.serialization.Serializable +import org.koin.compose.viewmodel.koinNavViewModel +import org.koin.compose.viewmodel.koinViewModel +import org.koin.core.annotation.KoinExperimentalAPI import xyz.ksharma.krail.common.splash.SplashScreen import xyz.ksharma.krail.common.splash.SplashViewModel import xyz.ksharma.krail.taj.LocalThemeColor @@ -39,6 +42,7 @@ import xyz.ksharma.krail.trip.planner.ui.navigation.tripPlannerDestinations * so fine for now. But I will want to refactor it to something nicer e.g. using Circuit library * from Slack, but that would also mean refactoring to use MVP instead of MVVM. */ +@OptIn(KoinExperimentalAPI::class) @Composable fun KrailNavHost(modifier: Modifier = Modifier) { val navController = rememberNavController() @@ -62,7 +66,7 @@ fun KrailNavHost(modifier: Modifier = Modifier) { tripPlannerDestinations(navController = navController) composable { - val viewModel: SplashViewModel = viewModel { SplashViewModel() } + val viewModel: SplashViewModel = koinNavViewModel() val isLoading by viewModel.isLoading.collectAsStateWithLifecycle() val mode by viewModel.uiState.collectAsStateWithLifecycle() diff --git a/composeApp/src/commonMain/kotlin/xyz/ksharma/krail/common/di/KoinAppModule.kt b/composeApp/src/commonMain/kotlin/xyz/ksharma/krail/common/di/KoinAppModule.kt new file mode 100644 index 00000000..40145aac --- /dev/null +++ b/composeApp/src/commonMain/kotlin/xyz/ksharma/krail/common/di/KoinAppModule.kt @@ -0,0 +1,17 @@ +package xyz.ksharma.krail.common.di + +import org.koin.core.module.dsl.viewModelOf +import org.koin.dsl.koinConfiguration +import org.koin.dsl.module +import xyz.ksharma.krail.sandook.di.dbModule +import xyz.ksharma.krail.trip.planner.network.api.di.networkModule +import xyz.ksharma.krail.trip.planner.ui.di.viewModelsModule +import xyz.ksharma.krail.common.splash.SplashViewModel + +val koinConfig = koinConfiguration { + modules(networkModule + dbModule + viewModelsModule + splashModule) +} + +val splashModule = module { + viewModelOf(::SplashViewModel) +} diff --git a/composeApp/src/commonMain/kotlin/xyz/ksharma/krail/common/splash/SplashViewModel.kt b/composeApp/src/commonMain/kotlin/xyz/ksharma/krail/common/splash/SplashViewModel.kt index 96882e54..fc6b0b55 100644 --- a/composeApp/src/commonMain/kotlin/xyz/ksharma/krail/common/splash/SplashViewModel.kt +++ b/composeApp/src/commonMain/kotlin/xyz/ksharma/krail/common/splash/SplashViewModel.kt @@ -10,15 +10,12 @@ import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.onStart import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.launch -import me.tatarka.inject.annotations.Inject -import xyz.ksharma.krail.sandook.RealSandook import xyz.ksharma.krail.sandook.Sandook import xyz.ksharma.krail.trip.planner.ui.state.TransportMode -@Inject -class SplashViewModel : ViewModel() { - - private val sandook: Sandook = RealSandook() +class SplashViewModel( + private val sandook: Sandook, +) : ViewModel() { private val _uiState: MutableStateFlow = MutableStateFlow(null) val uiState: MutableStateFlow = _uiState diff --git a/feature/trip-planner/network/build.gradle.kts b/feature/trip-planner/network/build.gradle.kts index 940a06c6..e64c6caf 100644 --- a/feature/trip-planner/network/build.gradle.kts +++ b/feature/trip-planner/network/build.gradle.kts @@ -57,6 +57,8 @@ kotlin { implementation(libs.kotlinx.datetime) implementation(compose.runtime) implementation(libs.slf4j.simple) // Logging + + api(libs.di.koinComposeViewmodelNav) } } diff --git a/feature/trip-planner/network/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/network/api/di/NetworkComponent.kt b/feature/trip-planner/network/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/network/api/di/NetworkComponent.kt deleted file mode 100644 index ec74efc8..00000000 --- a/feature/trip-planner/network/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/network/api/di/NetworkComponent.kt +++ /dev/null @@ -1,35 +0,0 @@ -/* -package xyz.ksharma.krail.trip.planner.network.api.di - -import io.ktor.client.HttpClient -import io.ktor.client.plugins.contentnegotiation.ContentNegotiation -import io.ktor.serialization.kotlinx.json.json -import kotlinx.serialization.json.Json -import me.tatarka.inject.annotations.Component -import me.tatarka.inject.annotations.Provides -import xyz.ksharma.krail.trip.planner.network.api.ratelimit.NetworkRateLimiter -import xyz.ksharma.krail.trip.planner.network.api.ratelimit.RateLimiter - -@Component -abstract class NetworkComponent { - - val NetworkRateLimiter.bind: RateLimiter - @Provides get() = this - - @Provides - fun provideHttpClient(): HttpClient = getHttpClient() -} - -fun getHttpClient(): HttpClient { - return HttpClient { - expectSuccess = true - install(ContentNegotiation) { - json(Json { - ignoreUnknownKeys = true - isLenient = true - prettyPrint = true - }) - } - } -} -*/ diff --git a/feature/trip-planner/network/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/network/api/di/NetworkModule.kt b/feature/trip-planner/network/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/network/api/di/NetworkModule.kt new file mode 100644 index 00000000..0b5d1481 --- /dev/null +++ b/feature/trip-planner/network/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/network/api/di/NetworkModule.kt @@ -0,0 +1,17 @@ +package xyz.ksharma.krail.trip.planner.network.api.di + +import io.ktor.client.HttpClient +import org.koin.core.module.dsl.bind +import org.koin.core.module.dsl.singleOf +import org.koin.dsl.module +import xyz.ksharma.krail.trip.planner.network.api.ratelimit.RateLimiter +import xyz.ksharma.krail.trip.planner.network.api.ratelimit.NetworkRateLimiter +import xyz.ksharma.krail.trip.planner.network.api.service.TripPlanningService +import xyz.ksharma.krail.trip.planner.network.api.service.RealTripPlanningService +import xyz.ksharma.krail.trip.planner.network.api.service.getHttpClient + +val networkModule = module { + singleOf(::NetworkRateLimiter) { bind() } + single { getHttpClient() } + singleOf(::RealTripPlanningService) { bind() } +} diff --git a/feature/trip-planner/network/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/network/api/ratelimit/NetworkRateLimiter.kt b/feature/trip-planner/network/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/network/api/ratelimit/NetworkRateLimiter.kt index 4937eb25..a01d9129 100644 --- a/feature/trip-planner/network/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/network/api/ratelimit/NetworkRateLimiter.kt +++ b/feature/trip-planner/network/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/network/api/ratelimit/NetworkRateLimiter.kt @@ -9,7 +9,6 @@ import kotlinx.coroutines.flow.debounce import kotlinx.coroutines.flow.flatMapLatest import kotlinx.coroutines.flow.flow import kotlinx.coroutines.flow.update -import me.tatarka.inject.annotations.Inject import kotlin.time.Duration.Companion.milliseconds import kotlin.time.Duration.Companion.seconds @@ -19,8 +18,7 @@ import kotlin.time.Duration.Companion.seconds * * Note: This class should not be Singleton. It should be created per use-case. */ -@Inject -class NetworkRateLimiter : RateLimiter { +internal class NetworkRateLimiter : RateLimiter { private val triggerFlow = MutableSharedFlow(replay = 1) private val isFirstTime = MutableStateFlow(false) diff --git a/feature/trip-planner/network/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/network/api/service/HttpClient.kt b/feature/trip-planner/network/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/network/api/service/HttpClient.kt index 018c461f..5328df4f 100644 --- a/feature/trip-planner/network/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/network/api/service/HttpClient.kt +++ b/feature/trip-planner/network/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/network/api/service/HttpClient.kt @@ -1,7 +1,5 @@ package xyz.ksharma.krail.trip.planner.network.api.service -import androidx.compose.runtime.Composable -import androidx.compose.runtime.remember import io.ktor.client.HttpClient import io.ktor.client.plugins.contentnegotiation.ContentNegotiation import io.ktor.client.plugins.defaultRequest @@ -12,7 +10,7 @@ import io.ktor.serialization.kotlinx.json.json import kotlinx.serialization.json.Json import xyz.ksharma.krail.trip.planner.network.BuildKonfig -fun getHttpClient(): HttpClient { +internal fun getHttpClient(): HttpClient { return HttpClient { expectSuccess = true install(ContentNegotiation) { @@ -32,8 +30,3 @@ fun getHttpClient(): HttpClient { } } } - -@Composable -fun rememberHttpClient() = remember { getHttpClient() } - -internal const val NSW_TRANSPORT_BASE_URL = "https://api.transport.nsw.gov.au" diff --git a/feature/trip-planner/network/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/network/api/service/RealTripPlanningService.kt b/feature/trip-planner/network/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/network/api/service/RealTripPlanningService.kt new file mode 100644 index 00000000..6ce54c1e --- /dev/null +++ b/feature/trip-planner/network/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/network/api/service/RealTripPlanningService.kt @@ -0,0 +1,61 @@ +package xyz.ksharma.krail.trip.planner.network.api.service + +import io.ktor.client.HttpClient +import io.ktor.client.call.body +import io.ktor.client.request.get +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.IO +import kotlinx.coroutines.withContext +import xyz.ksharma.krail.trip.planner.network.api.model.StopFinderResponse +import xyz.ksharma.krail.trip.planner.network.api.model.StopType +import xyz.ksharma.krail.trip.planner.network.api.model.TripResponse +import xyz.ksharma.krail.trip.planner.network.api.service.stop_finder.StopFinderRequestParams +import xyz.ksharma.krail.trip.planner.network.api.service.trip.TripRequestParams + +internal class RealTripPlanningService(private val httpClient: HttpClient) : TripPlanningService { + + override suspend fun trip( + originStopId: String, + destinationStopId: String + ): TripResponse = withContext(Dispatchers.IO) { + + httpClient.get("$NSW_TRANSPORT_BASE_URL/v1/tp/trip") { + url { + parameters.append(TripRequestParams.nameOrigin, originStopId) + parameters.append(TripRequestParams.nameDestination, destinationStopId) + + parameters.append(TripRequestParams.depArrMacro, "dep") + parameters.append(TripRequestParams.typeDestination, "any") + parameters.append(TripRequestParams.calcNumberOfTrips, "6") + parameters.append(TripRequestParams.typeOrigin, "any") + parameters.append(TripRequestParams.tfNSWTR, "true") + parameters.append(TripRequestParams.version, "10.2.1.42") + parameters.append(TripRequestParams.coordOutputFormat, "EPSG:4326") + parameters.append(TripRequestParams.itOptionsActive, "1") + parameters.append(TripRequestParams.computeMonomodalTripBicycle, "false") + parameters.append(TripRequestParams.cycleSpeed, "16") + parameters.append(TripRequestParams.useElevationData, "1") + parameters.append(TripRequestParams.outputFormat, "rapidJSON") + } + }.body() + } + + override suspend fun stopFinder( + stopSearchQuery: String, + stopType: StopType, + ): StopFinderResponse = withContext(Dispatchers.IO) { + + httpClient.get("${NSW_TRANSPORT_BASE_URL}/v1/tp/stop_finder") { + url { + parameters.append(StopFinderRequestParams.nameSf, stopSearchQuery) + + parameters.append(StopFinderRequestParams.typeSf, stopType.type) + parameters.append(StopFinderRequestParams.coordOutputFormat, "EPSG:4326") + parameters.append(StopFinderRequestParams.outputFormat, "rapidJSON") +// parameters.append(StopFinderRequestParams.version, "10.2.1.42") + parameters.append(StopFinderRequestParams.tfNSWSF, "true") + } + }.body() + } + +} diff --git a/feature/trip-planner/network/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/network/api/service/TripPlanningService.kt b/feature/trip-planner/network/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/network/api/service/TripPlanningService.kt index ea47eb60..ef0e037a 100644 --- a/feature/trip-planner/network/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/network/api/service/TripPlanningService.kt +++ b/feature/trip-planner/network/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/network/api/service/TripPlanningService.kt @@ -1,8 +1,24 @@ package xyz.ksharma.krail.trip.planner.network.api.service +import xyz.ksharma.krail.trip.planner.network.api.model.StopFinderResponse +import xyz.ksharma.krail.trip.planner.network.api.model.StopType +import xyz.ksharma.krail.trip.planner.network.api.model.TripResponse + /** * Swagger: https://opendata.transport.nsw.gov.au/dataset/trip-planner-apis/resource/917c66c3-8123-4a0f-b1b1-b4220f32585d */ +internal const val NSW_TRANSPORT_BASE_URL = "https://api.transport.nsw.gov.au" + +interface TripPlanningService { + + suspend fun trip(originStopId: String, destinationStopId: String): TripResponse + + suspend fun stopFinder( + stopSearchQuery: String, + stopType: StopType = StopType.STOP, + ): StopFinderResponse +} + /** * This endpoint returns a list of departures for a given location based on the date and time diff --git a/feature/trip-planner/network/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/network/api/service/stop_finder/StopFinderRequest.kt b/feature/trip-planner/network/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/network/api/service/stop_finder/StopFinderRequest.kt deleted file mode 100644 index 177b7a9b..00000000 --- a/feature/trip-planner/network/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/network/api/service/stop_finder/StopFinderRequest.kt +++ /dev/null @@ -1,29 +0,0 @@ -package xyz.ksharma.krail.trip.planner.network.api.service.stop_finder - -import io.ktor.client.HttpClient -import io.ktor.client.call.body -import io.ktor.client.request.get -import xyz.ksharma.krail.trip.planner.network.api.model.StopFinderResponse -import xyz.ksharma.krail.trip.planner.network.api.model.StopType -import xyz.ksharma.krail.trip.planner.network.api.service.NSW_TRANSPORT_BASE_URL - -suspend fun fetchStop( - httpClient: HttpClient, - stopType: StopType = StopType.STOP, - stopSearchQuery: String, -): StopFinderResponse { - val response: StopFinderResponse = - httpClient.get("${NSW_TRANSPORT_BASE_URL}/v1/tp/stop_finder") { - url { - parameters.append(StopFinderRequestParams.nameSf, stopSearchQuery) - - parameters.append(StopFinderRequestParams.typeSf, stopType.type) - parameters.append(StopFinderRequestParams.coordOutputFormat, "EPSG:4326") - parameters.append(StopFinderRequestParams.outputFormat, "rapidJSON") -// parameters.append(StopFinderRequestParams.version, "10.2.1.42") - parameters.append(StopFinderRequestParams.tfNSWSF, "true") - } - }.body() - println("response: $response") - return response -} diff --git a/feature/trip-planner/network/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/network/api/service/trip/TripRequest.kt b/feature/trip-planner/network/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/network/api/service/trip/TripRequest.kt deleted file mode 100644 index 8776dff5..00000000 --- a/feature/trip-planner/network/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/network/api/service/trip/TripRequest.kt +++ /dev/null @@ -1,49 +0,0 @@ -package xyz.ksharma.krail.trip.planner.network.api.service.trip - -import io.ktor.client.HttpClient -import io.ktor.client.call.body -import io.ktor.client.request.get -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.IO -import kotlinx.coroutines.withContext -import kotlinx.datetime.Instant -import kotlinx.datetime.TimeZone -import kotlinx.datetime.toLocalDateTime -import xyz.ksharma.krail.trip.planner.network.api.model.TripResponse -import xyz.ksharma.krail.trip.planner.network.api.service.NSW_TRANSPORT_BASE_URL - -suspend fun fetchTrip( - httpClient: HttpClient, - originStopId: String, - destinationStopId: String, -): TripResponse = withContext(Dispatchers.IO) { - httpClient.get("$NSW_TRANSPORT_BASE_URL/v1/tp/trip") { - url { - parameters.append(TripRequestParams.nameOrigin, originStopId) - parameters.append(TripRequestParams.nameDestination, destinationStopId) - - parameters.append(TripRequestParams.depArrMacro, "dep") - parameters.append(TripRequestParams.typeDestination, "any") - parameters.append(TripRequestParams.calcNumberOfTrips, "6") - parameters.append(TripRequestParams.typeOrigin, "any") - parameters.append(TripRequestParams.tfNSWTR, "true") - parameters.append(TripRequestParams.version, "10.2.1.42") - parameters.append(TripRequestParams.coordOutputFormat, "EPSG:4326") - parameters.append(TripRequestParams.itOptionsActive, "1") - parameters.append(TripRequestParams.computeMonomodalTripBicycle, "false") - parameters.append(TripRequestParams.cycleSpeed, "16") - parameters.append(TripRequestParams.useElevationData, "1") - parameters.append(TripRequestParams.outputFormat, "rapidJSON") - } - }.body() -} - -/** - * Converts Instant to ITD Time in HHMM 24-hour format. - * // todo - move to another module for time related functions - */ -fun Instant.toItdTime(): String { - val localDateTime = this.toLocalDateTime(TimeZone.of("Australia/Sydney")) - return localDateTime.hour.toString().padStart(2, '0') + localDateTime.minute.toString() - .padStart(2, '0') -} diff --git a/feature/trip-planner/ui/build.gradle.kts b/feature/trip-planner/ui/build.gradle.kts index f7288e29..3a2d3470 100644 --- a/feature/trip-planner/ui/build.gradle.kts +++ b/feature/trip-planner/ui/build.gradle.kts @@ -56,6 +56,8 @@ kotlin { implementation(libs.kotlinx.datetime) implementation(libs.slf4j.simple) // Logging // TODO - remove once DI added - end + + implementation(libs.di.koinComposeViewmodelNav) } } commonTest { diff --git a/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/di/ViewModelModule.kt b/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/di/ViewModelModule.kt new file mode 100644 index 00000000..40ada16f --- /dev/null +++ b/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/di/ViewModelModule.kt @@ -0,0 +1,15 @@ +package xyz.ksharma.krail.trip.planner.ui.di + +import org.koin.core.module.dsl.viewModelOf +import org.koin.dsl.module +import xyz.ksharma.krail.trip.planner.ui.savedtrips.SavedTripsViewModel +import xyz.ksharma.krail.trip.planner.ui.searchstop.SearchStopViewModel +import xyz.ksharma.krail.trip.planner.ui.timetable.TimeTableViewModel +import xyz.ksharma.krail.trip.planner.ui.usualride.UsualRideViewModel + +val viewModelsModule = module { + viewModelOf(::SavedTripsViewModel) + viewModelOf(::SearchStopViewModel) + viewModelOf(::TimeTableViewModel) + viewModelOf(::UsualRideViewModel) +} diff --git a/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/savedtrips/SavedTripsDestination.kt b/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/savedtrips/SavedTripsDestination.kt index dd3e9bad..17142bc2 100644 --- a/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/savedtrips/SavedTripsDestination.kt +++ b/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/savedtrips/SavedTripsDestination.kt @@ -6,11 +6,13 @@ import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.lifecycle.compose.collectAsStateWithLifecycle -import androidx.lifecycle.viewmodel.compose.viewModel import androidx.navigation.NavGraphBuilder import androidx.navigation.NavHostController import androidx.navigation.NavOptions import androidx.navigation.compose.composable +import org.koin.compose.viewmodel.koinNavViewModel +import org.koin.compose.viewmodel.koinViewModel +import org.koin.core.annotation.KoinExperimentalAPI import xyz.ksharma.krail.trip.planner.ui.navigation.SavedTripsRoute import xyz.ksharma.krail.trip.planner.ui.navigation.SearchStopFieldType import xyz.ksharma.krail.trip.planner.ui.navigation.SearchStopRoute @@ -20,9 +22,10 @@ import xyz.ksharma.krail.trip.planner.ui.state.searchstop.model.StopItem import xyz.ksharma.krail.trip.planner.ui.state.searchstop.model.StopItem.Companion.fromJsonString @Suppress("LongMethod") +@OptIn(KoinExperimentalAPI::class) internal fun NavGraphBuilder.savedTripsDestination(navController: NavHostController) { composable { backStackEntry -> - val viewModel: SavedTripsViewModel = viewModel { SavedTripsViewModel() } + val viewModel: SavedTripsViewModel = koinNavViewModel() val savedTripState by viewModel.uiState.collectAsStateWithLifecycle() val fromArg: String? = diff --git a/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/savedtrips/SavedTripsViewModel.kt b/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/savedtrips/SavedTripsViewModel.kt index 65dd7079..8df5dd07 100644 --- a/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/savedtrips/SavedTripsViewModel.kt +++ b/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/savedtrips/SavedTripsViewModel.kt @@ -10,17 +10,15 @@ import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch -import me.tatarka.inject.annotations.Inject -import xyz.ksharma.krail.sandook.RealSandook import xyz.ksharma.krail.sandook.Sandook import xyz.ksharma.krail.trip.planner.ui.state.savedtrip.SavedTripUiEvent import xyz.ksharma.krail.trip.planner.ui.state.savedtrip.SavedTripsState import xyz.ksharma.krail.trip.planner.ui.state.timetable.Trip -@Inject -class SavedTripsViewModel : ViewModel() { +class SavedTripsViewModel( + private val sandook: Sandook, +) : ViewModel() { - private val sandook: Sandook = RealSandook() private val _uiState: MutableStateFlow = MutableStateFlow(SavedTripsState()) val uiState: StateFlow = _uiState @@ -35,7 +33,7 @@ class SavedTripsViewModel : ViewModel() { } }.toImmutableList() - println( "SavedTrips: ${trips.size} number") + println("SavedTrips: ${trips.size} number") trips.forEachIndexed { index, trip -> println("\t SavedTrip: #$index ${trip.fromStopName} -> ${trip.toStopName}") } diff --git a/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/searchstop/SearchStopDestination.kt b/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/searchstop/SearchStopDestination.kt index 2b6effd3..a69ed1d8 100644 --- a/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/searchstop/SearchStopDestination.kt +++ b/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/searchstop/SearchStopDestination.kt @@ -2,16 +2,19 @@ package xyz.ksharma.krail.trip.planner.ui.searchstop import androidx.compose.runtime.getValue import androidx.lifecycle.compose.collectAsStateWithLifecycle -import androidx.lifecycle.viewmodel.compose.viewModel import androidx.navigation.NavGraphBuilder import androidx.navigation.NavHostController import androidx.navigation.compose.composable import androidx.navigation.toRoute +import org.koin.compose.viewmodel.koinNavViewModel +import org.koin.compose.viewmodel.koinViewModel +import org.koin.core.annotation.KoinExperimentalAPI import xyz.ksharma.krail.trip.planner.ui.navigation.SearchStopRoute +@OptIn(KoinExperimentalAPI::class) fun NavGraphBuilder.searchStopDestination(navController: NavHostController) { composable { backStackEntry -> - val viewModel: SearchStopViewModel = viewModel { SearchStopViewModel() } + val viewModel: SearchStopViewModel = koinNavViewModel() val searchStopState by viewModel.uiState.collectAsStateWithLifecycle() val route: SearchStopRoute = backStackEntry.toRoute() diff --git a/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/searchstop/SearchStopViewModel.kt b/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/searchstop/SearchStopViewModel.kt index 8a35ecab..e48affbc 100644 --- a/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/searchstop/SearchStopViewModel.kt +++ b/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/searchstop/SearchStopViewModel.kt @@ -4,28 +4,20 @@ import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import kotlinx.collections.immutable.persistentListOf import kotlinx.collections.immutable.toImmutableList -import kotlinx.coroutines.CoroutineDispatcher -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.IO -import kotlinx.coroutines.ensureActive import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch -import kotlinx.coroutines.withContext -import xyz.ksharma.krail.trip.planner.network.api.service.getHttpClient -import xyz.ksharma.krail.trip.planner.network.api.service.stop_finder.fetchStop +import xyz.ksharma.krail.trip.planner.network.api.service.TripPlanningService import xyz.ksharma.krail.trip.planner.ui.searchstop.StopResultMapper.toStopResults import xyz.ksharma.krail.trip.planner.ui.state.searchstop.SearchStopState import xyz.ksharma.krail.trip.planner.ui.state.searchstop.SearchStopUiEvent -class SearchStopViewModel : ViewModel() { +class SearchStopViewModel(private val tripPlanningService: TripPlanningService) : ViewModel() { private val _uiState: MutableStateFlow = MutableStateFlow(SearchStopState()) val uiState: StateFlow = _uiState - private val httpClient = getHttpClient() - fun onEvent(event: SearchStopUiEvent) { when (event) { is SearchStopUiEvent.SearchTextChanged -> onSearchTextChanged(event.query) @@ -38,8 +30,7 @@ class SearchStopViewModel : ViewModel() { viewModelScope.launch { runCatching { - val response = - fetchStop(httpClient = httpClient, stopSearchQuery = query) + val response = tripPlanningService.stopFinder(stopSearchQuery = query) println("response VM: $response") val results = response.toStopResults() diff --git a/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/timetable/TimeTableDestination.kt b/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/timetable/TimeTableDestination.kt index ab65a02b..5ffa73d9 100644 --- a/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/timetable/TimeTableDestination.kt +++ b/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/timetable/TimeTableDestination.kt @@ -2,20 +2,22 @@ package xyz.ksharma.krail.trip.planner.ui.timetable import androidx.compose.runtime.getValue import androidx.lifecycle.compose.collectAsStateWithLifecycle -import androidx.lifecycle.viewmodel.compose.viewModel import androidx.navigation.NavGraphBuilder import androidx.navigation.NavHostController import androidx.navigation.NavOptions import androidx.navigation.compose.composable import androidx.navigation.toRoute +import org.koin.compose.viewmodel.koinNavViewModel +import org.koin.core.annotation.KoinExperimentalAPI import xyz.ksharma.krail.trip.planner.ui.navigation.ServiceAlertRoute import xyz.ksharma.krail.trip.planner.ui.navigation.TimeTableRoute import xyz.ksharma.krail.trip.planner.ui.state.timetable.TimeTableUiEvent import xyz.ksharma.krail.trip.planner.ui.state.timetable.Trip +@OptIn(KoinExperimentalAPI::class) internal fun NavGraphBuilder.timeTableDestination(navController: NavHostController) { composable { backStackEntry -> - val viewModel: TimeTableViewModel = viewModel { TimeTableViewModel() } + val viewModel: TimeTableViewModel = koinNavViewModel() val timeTableState by viewModel.uiState.collectAsStateWithLifecycle() val route: TimeTableRoute = backStackEntry.toRoute() val isLoading by viewModel.isLoading.collectAsStateWithLifecycle() diff --git a/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/timetable/TimeTableViewModel.kt b/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/timetable/TimeTableViewModel.kt index dd571e1f..dcf2505c 100644 --- a/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/timetable/TimeTableViewModel.kt +++ b/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/timetable/TimeTableViewModel.kt @@ -17,15 +17,12 @@ import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch import kotlinx.coroutines.withContext -import me.tatarka.inject.annotations.Inject import xyz.ksharma.krail.core.datetime.DateTimeHelper.calculateTimeDifferenceFromNow import xyz.ksharma.krail.core.datetime.DateTimeHelper.toGenericFormattedTimeString -import xyz.ksharma.krail.sandook.RealSandook import xyz.ksharma.krail.sandook.Sandook import xyz.ksharma.krail.trip.planner.network.api.model.TripResponse -import xyz.ksharma.krail.trip.planner.network.api.ratelimit.NetworkRateLimiter import xyz.ksharma.krail.trip.planner.network.api.ratelimit.RateLimiter -import xyz.ksharma.krail.trip.planner.network.api.service.getHttpClient +import xyz.ksharma.krail.trip.planner.network.api.service.TripPlanningService import xyz.ksharma.krail.trip.planner.ui.state.alerts.ServiceAlert import xyz.ksharma.krail.trip.planner.ui.state.timetable.TimeTableState import xyz.ksharma.krail.trip.planner.ui.state.timetable.TimeTableUiEvent @@ -33,24 +30,22 @@ import xyz.ksharma.krail.trip.planner.ui.state.timetable.Trip import xyz.ksharma.krail.trip.planner.ui.timetable.business.buildJourneyList import kotlin.time.Duration.Companion.seconds -@Inject -class TimeTableViewModel : ViewModel() { - - private val rateLimiter: RateLimiter = NetworkRateLimiter() - private val sandook: Sandook = RealSandook() +class TimeTableViewModel( + private val tripPlanningService: TripPlanningService, + private val rateLimiter: RateLimiter, + private val sandook: Sandook, +) : ViewModel() { private val _uiState: MutableStateFlow = MutableStateFlow(TimeTableState()) val uiState: StateFlow = _uiState - private val httpClient = getHttpClient() - private val _isLoading: MutableStateFlow = MutableStateFlow(false) val isLoading: StateFlow = _isLoading // Will start fetching the trip as soon as the screen is visible, which means if android-app goes // to background and come back up again, the API call will be made. // Probably good to have data up to date. .onStart { - println("onStart: Fetching Trip") + println("onStart: Fetching Trip") fetchTrip() }.stateIn(viewModelScope, SharingStarted.WhileSubscribed(ANR_TIMEOUT), true) @@ -112,14 +107,13 @@ class TimeTableViewModel : ViewModel() { } private suspend fun loadTrip(): Result = withContext(Dispatchers.IO) { - println("loadTrip API Call") + println("loadTrip API Call") require( tripInfo != null && tripInfo!!.fromStopId.isNotEmpty() && tripInfo!!.toStopId.isNotEmpty(), ) { "Trip Info is null or empty" } runCatching { - val tripResponse = xyz.ksharma.krail.trip.planner.network.api.service.trip.fetchTrip( - httpClient = httpClient, + val tripResponse = tripPlanningService.trip( originStopId = tripInfo!!.fromStopId, destinationStopId = tripInfo!!.toStopId, ) @@ -151,7 +145,7 @@ class TimeTableViewModel : ViewModel() { } private fun onJourneyCardClicked(journeyId: String) { - println("Journey Card Clicked(JourneyId): $journeyId") + println("Journey Card Clicked(JourneyId): $journeyId") _expandedJourneyId.update { if (it == journeyId) null else journeyId } } diff --git a/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/usualride/UsualRideDestination.kt b/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/usualride/UsualRideDestination.kt index 2193e58b..4079a8eb 100644 --- a/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/usualride/UsualRideDestination.kt +++ b/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/usualride/UsualRideDestination.kt @@ -10,6 +10,8 @@ import androidx.navigation.NavHostController import androidx.navigation.NavOptions import androidx.navigation.compose.composable import kotlinx.collections.immutable.toImmutableSet +import org.koin.compose.viewmodel.koinNavViewModel +import org.koin.core.annotation.KoinExperimentalAPI import xyz.ksharma.krail.taj.LocalThemeColor import xyz.ksharma.krail.taj.LocalThemeContentColor import xyz.ksharma.krail.taj.theme.getForegroundColor @@ -21,9 +23,10 @@ import xyz.ksharma.krail.trip.planner.ui.state.TransportMode import xyz.ksharma.krail.trip.planner.ui.state.TransportModeSortOrder import xyz.ksharma.krail.trip.planner.ui.state.usualride.UsualRideEvent +@OptIn(KoinExperimentalAPI::class) internal fun NavGraphBuilder.usualRideDestination(navController: NavHostController) { composable { - val viewModel:UsualRideViewModel = viewModel { UsualRideViewModel() } + val viewModel:UsualRideViewModel = koinNavViewModel() var themeColor by LocalThemeColor.current var themeContentColor by LocalThemeContentColor.current var mode: TransportMode? by remember { mutableStateOf(null) } diff --git a/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/usualride/UsualRideViewModel.kt b/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/usualride/UsualRideViewModel.kt index 1c8bff9b..bad69555 100644 --- a/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/usualride/UsualRideViewModel.kt +++ b/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/usualride/UsualRideViewModel.kt @@ -14,10 +14,7 @@ import xyz.ksharma.krail.trip.planner.ui.state.TransportMode import xyz.ksharma.krail.trip.planner.ui.state.usualride.UsualRideEvent import xyz.ksharma.krail.trip.planner.ui.state.usualride.UsualRideState -@Inject -class UsualRideViewModel : ViewModel() { - - private val sandook: Sandook = RealSandook() +class UsualRideViewModel(private val sandook: Sandook) : ViewModel() { private val _uiState: MutableStateFlow = MutableStateFlow(UsualRideState()) val uiState: StateFlow = _uiState diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index c9fb2296..ad423dff 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -25,6 +25,8 @@ androidx-lifecycle = "2.8.3" kotlinxCoroutines = "1.9.0" buildkonfigGradlePlugin = "0.15.2" kermit = "2.0.4" +sqlDelight = "2.0.2" +koin = "4.0.1-Beta1" #SDK minSdk = "26" @@ -53,6 +55,10 @@ multiplatform-settings = { module = "com.russhwolf:multiplatform-settings-no-arg di-kotlinInjectRuntime = { module = "me.tatarka.inject:kotlin-inject-runtime-kmp", version.ref = "kotlinInject" } di-kotlinInjectCompilerKsp = { module = "me.tatarka.inject:kotlin-inject-compiler-ksp", version.ref = "kotlinInject" } +di-koinAndroid = {module = "io.insert-koin:koin-android", version.ref = "koin"} +di-koinComposeViewmodel = {module = "io.insert-koin:koin-compose-viewmodel", version.ref = "koin"} +di-koinComposeViewmodelNav = {module = "io.insert-koin:koin-compose-viewmodel-navigation", version.ref = "koin"} + #Network kotlinx-serialization-json = { group = "org.jetbrains.kotlinx", name = "kotlinx-serialization-json", version.ref = "kotlinxSerializationJson" } ktor-client-content-negotiation = { module = "io.ktor:ktor-client-content-negotiation", version.ref = "ktor" } @@ -84,6 +90,10 @@ composeCompiler-gradlePlugin = { module = "org.jetbrains.kotlin:compose-compiler #CodeStyle Use spotless plugin for multiplatform support +# Database +db-android-driver = { module = "app.cash.sqldelight:android-driver", version.ref = "sqlDelight" } +db-native-driver = { module = "app.cash.sqldelight:native-driver", version.ref = "sqlDelight" } +db-runtime = { module = "app.cash.sqldelight:runtime", version.ref = "sqlDelight" } [plugins] androidApplication = { id = "com.android.application", version.ref = "agp" } @@ -96,6 +106,7 @@ compose-compiler = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = " google-services = { id = "com.google.gms.google-services", version = "4.4.2" } composeMultiplatform = { id = "org.jetbrains.compose", version.ref = "compose-multiplatform" } kotlinMultiplatform = { id = "org.jetbrains.kotlin.multiplatform", version.ref = "kotlin" } +sqldelight = { id = "app.cash.sqldelight", version.ref = "sqlDelight" } #Convention Plugins krail-android-application = { id = "krail.android.application", version = "unspecified" } diff --git a/sandook/build.gradle.kts b/sandook/build.gradle.kts index dccc0393..24eba89f 100644 --- a/sandook/build.gradle.kts +++ b/sandook/build.gradle.kts @@ -8,6 +8,7 @@ plugins { alias(libs.plugins.kotlin.serialization) alias(libs.plugins.ksp) alias(libs.plugins.compose.compiler) + alias(libs.plugins.sqldelight) } android { @@ -29,6 +30,12 @@ kotlin { } sourceSets { + androidMain { + dependencies { + implementation(libs.db.android.driver) + } + } + commonMain { dependencies { // implementation(projects.core.di) @@ -36,11 +43,19 @@ kotlin { implementation(libs.kotlinx.serialization.json) implementation(libs.di.kotlinInjectRuntime) implementation(libs.multiplatform.settings) - implementation(compose.runtime) implementation(libs.log.kermit) + implementation(libs.kotlinx.datetime) + implementation(libs.db.runtime) + + api(libs.di.koinComposeViewmodelNav) } } + + iosMain.dependencies { + implementation(libs.ktor.client.darwin) + implementation(libs.db.native.driver) + } } //configureCommonMainKsp() diff --git a/sandook/src/commonMain/kotlin/xyz/ksharma/krail/sandook/RealSandook.kt b/sandook/src/commonMain/kotlin/xyz/ksharma/krail/sandook/RealSandook.kt index 04d14e28..8f3b183e 100644 --- a/sandook/src/commonMain/kotlin/xyz/ksharma/krail/sandook/RealSandook.kt +++ b/sandook/src/commonMain/kotlin/xyz/ksharma/krail/sandook/RealSandook.kt @@ -1,9 +1,7 @@ package xyz.ksharma.krail.sandook import com.russhwolf.settings.Settings -import me.tatarka.inject.annotations.Inject -@Inject class RealSandook : Sandook { private val settings: Settings = Settings() diff --git a/sandook/src/commonMain/kotlin/xyz/ksharma/krail/sandook/di/DbModule.kt b/sandook/src/commonMain/kotlin/xyz/ksharma/krail/sandook/di/DbModule.kt new file mode 100644 index 00000000..c2df4623 --- /dev/null +++ b/sandook/src/commonMain/kotlin/xyz/ksharma/krail/sandook/di/DbModule.kt @@ -0,0 +1,11 @@ +package xyz.ksharma.krail.sandook.di + +import org.koin.core.module.dsl.bind +import org.koin.core.module.dsl.singleOf +import org.koin.dsl.module +import xyz.ksharma.krail.sandook.RealSandook +import xyz.ksharma.krail.sandook.Sandook + +val dbModule = module { + singleOf(::RealSandook) { bind() } +} From 34cf3cabec1ec2da57c72f6e32958bb90b5e2850 Mon Sep 17 00:00:00 2001 From: Karan Sharma <55722391+ksharma-xyz@users.noreply.github.com> Date: Thu, 21 Nov 2024 23:35:32 +1100 Subject: [PATCH 53/67] DI - Providing separate HttpEngine for android / iOS as when using koin for providing object, it does not use the default for the platform --- feature/trip-planner/network/build.gradle.kts | 1 - .../planner/network/api/service/HttpClient.kt | 33 +++++++++++++++++++ .../planner/network/api/di/NetworkModule.kt | 8 ++--- .../planner/network/api/service/HttpClient.kt | 29 +--------------- .../planner/network/api/service/HttpClient.kt | 33 +++++++++++++++++++ 5 files changed, 71 insertions(+), 33 deletions(-) create mode 100644 feature/trip-planner/network/src/androidMain/kotlin/xyz/ksharma/krail/trip/planner/network/api/service/HttpClient.kt create mode 100644 feature/trip-planner/network/src/iosMain/kotlin/xyz/ksharma/krail/trip/planner/network/api/service/HttpClient.kt diff --git a/feature/trip-planner/network/build.gradle.kts b/feature/trip-planner/network/build.gradle.kts index e64c6caf..b2a4b09c 100644 --- a/feature/trip-planner/network/build.gradle.kts +++ b/feature/trip-planner/network/build.gradle.kts @@ -49,7 +49,6 @@ kotlin { implementation(libs.di.kotlinInjectRuntime) implementation(libs.kotlinx.serialization.json) implementation(libs.ktor.client.core) - implementation(libs.ktor.client.cio) implementation(libs.ktor.client.auth) implementation(libs.ktor.client.content.negotiation) implementation(libs.ktor.client.logging) diff --git a/feature/trip-planner/network/src/androidMain/kotlin/xyz/ksharma/krail/trip/planner/network/api/service/HttpClient.kt b/feature/trip-planner/network/src/androidMain/kotlin/xyz/ksharma/krail/trip/planner/network/api/service/HttpClient.kt new file mode 100644 index 00000000..2d2bedb3 --- /dev/null +++ b/feature/trip-planner/network/src/androidMain/kotlin/xyz/ksharma/krail/trip/planner/network/api/service/HttpClient.kt @@ -0,0 +1,33 @@ +package xyz.ksharma.krail.trip.planner.network.api.service + +import io.ktor.client.HttpClient +import io.ktor.client.engine.okhttp.OkHttp +import io.ktor.client.plugins.contentnegotiation.ContentNegotiation +import io.ktor.client.plugins.defaultRequest +import io.ktor.client.plugins.logging.LogLevel +import io.ktor.client.plugins.logging.Logging +import io.ktor.http.HttpHeaders +import io.ktor.serialization.kotlinx.json.json +import kotlinx.serialization.json.Json +import xyz.ksharma.krail.trip.planner.network.BuildKonfig + +actual fun httpClient(): HttpClient { + return HttpClient(OkHttp) { + expectSuccess = true + install(ContentNegotiation) { + json(Json { + ignoreUnknownKeys = true + isLenient = true + prettyPrint = true + }) + } + install(Logging) { +// if(debug) - TODO + level = LogLevel.BODY + } + + defaultRequest { + headers.append(HttpHeaders.Authorization, "apikey ${BuildKonfig.NSW_TRANSPORT_API_KEY}") + } + } +} diff --git a/feature/trip-planner/network/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/network/api/di/NetworkModule.kt b/feature/trip-planner/network/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/network/api/di/NetworkModule.kt index 0b5d1481..3e6ae788 100644 --- a/feature/trip-planner/network/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/network/api/di/NetworkModule.kt +++ b/feature/trip-planner/network/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/network/api/di/NetworkModule.kt @@ -4,14 +4,14 @@ import io.ktor.client.HttpClient import org.koin.core.module.dsl.bind import org.koin.core.module.dsl.singleOf import org.koin.dsl.module -import xyz.ksharma.krail.trip.planner.network.api.ratelimit.RateLimiter import xyz.ksharma.krail.trip.planner.network.api.ratelimit.NetworkRateLimiter -import xyz.ksharma.krail.trip.planner.network.api.service.TripPlanningService +import xyz.ksharma.krail.trip.planner.network.api.ratelimit.RateLimiter import xyz.ksharma.krail.trip.planner.network.api.service.RealTripPlanningService -import xyz.ksharma.krail.trip.planner.network.api.service.getHttpClient +import xyz.ksharma.krail.trip.planner.network.api.service.TripPlanningService +import xyz.ksharma.krail.trip.planner.network.api.service.httpClient val networkModule = module { singleOf(::NetworkRateLimiter) { bind() } - single { getHttpClient() } + single { httpClient() } singleOf(::RealTripPlanningService) { bind() } } diff --git a/feature/trip-planner/network/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/network/api/service/HttpClient.kt b/feature/trip-planner/network/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/network/api/service/HttpClient.kt index 5328df4f..8a2c4a0f 100644 --- a/feature/trip-planner/network/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/network/api/service/HttpClient.kt +++ b/feature/trip-planner/network/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/network/api/service/HttpClient.kt @@ -1,32 +1,5 @@ package xyz.ksharma.krail.trip.planner.network.api.service import io.ktor.client.HttpClient -import io.ktor.client.plugins.contentnegotiation.ContentNegotiation -import io.ktor.client.plugins.defaultRequest -import io.ktor.client.plugins.logging.LogLevel -import io.ktor.client.plugins.logging.Logging -import io.ktor.http.HttpHeaders -import io.ktor.serialization.kotlinx.json.json -import kotlinx.serialization.json.Json -import xyz.ksharma.krail.trip.planner.network.BuildKonfig -internal fun getHttpClient(): HttpClient { - return HttpClient { - expectSuccess = true - install(ContentNegotiation) { - json(Json { - ignoreUnknownKeys = true - isLenient = true - prettyPrint = true - }) - } - install(Logging) { -// if(debug) - TODO - level = LogLevel.BODY - } - - defaultRequest { - headers.append(HttpHeaders.Authorization, "apikey ${BuildKonfig.NSW_TRANSPORT_API_KEY}") - } - } -} +expect fun httpClient(): HttpClient diff --git a/feature/trip-planner/network/src/iosMain/kotlin/xyz/ksharma/krail/trip/planner/network/api/service/HttpClient.kt b/feature/trip-planner/network/src/iosMain/kotlin/xyz/ksharma/krail/trip/planner/network/api/service/HttpClient.kt new file mode 100644 index 00000000..eaa44398 --- /dev/null +++ b/feature/trip-planner/network/src/iosMain/kotlin/xyz/ksharma/krail/trip/planner/network/api/service/HttpClient.kt @@ -0,0 +1,33 @@ +package xyz.ksharma.krail.trip.planner.network.api.service + +import io.ktor.client.HttpClient +import io.ktor.client.engine.darwin.Darwin +import io.ktor.client.plugins.contentnegotiation.ContentNegotiation +import io.ktor.client.plugins.defaultRequest +import io.ktor.client.plugins.logging.LogLevel +import io.ktor.client.plugins.logging.Logging +import io.ktor.http.HttpHeaders +import io.ktor.serialization.kotlinx.json.json +import kotlinx.serialization.json.Json +import xyz.ksharma.krail.trip.planner.network.BuildKonfig + +actual fun httpClient(): HttpClient { + return HttpClient(Darwin) { + expectSuccess = true + install(ContentNegotiation) { + json(Json { + ignoreUnknownKeys = true + isLenient = true + prettyPrint = true + }) + } + install(Logging) { +// if(debug) - TODO + level = LogLevel.BODY + } + + defaultRequest { + headers.append(HttpHeaders.Authorization, "apikey ${BuildKonfig.NSW_TRANSPORT_API_KEY}") + } + } +} From 23c5b7f04d05dbc23be14275e97374e988b4c156 Mon Sep 17 00:00:00 2001 From: Karan Sharma <55722391+ksharma-xyz@users.noreply.github.com> Date: Fri, 22 Nov 2024 10:22:30 +1100 Subject: [PATCH 54/67] DB: Integrate SQLDelight --- gradle/libs.versions.toml | 6 ++-- sandook/build.gradle.kts | 23 +++++------- .../xyz/ksharma/krail/sandook/KrailSandook.sq | 36 +++++++++++++++++++ 3 files changed, 48 insertions(+), 17 deletions(-) create mode 100644 sandook/src/commonMain/sqldelight/xyz/ksharma/krail/sandook/KrailSandook.sq diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index ad423dff..15a713a1 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -91,9 +91,9 @@ composeCompiler-gradlePlugin = { module = "org.jetbrains.kotlin:compose-compiler #CodeStyle Use spotless plugin for multiplatform support # Database -db-android-driver = { module = "app.cash.sqldelight:android-driver", version.ref = "sqlDelight" } -db-native-driver = { module = "app.cash.sqldelight:native-driver", version.ref = "sqlDelight" } -db-runtime = { module = "app.cash.sqldelight:runtime", version.ref = "sqlDelight" } +db-sqlAndroidDriver = { module = "app.cash.sqldelight:android-driver", version.ref = "sqlDelight" } +db-sqlNativeDriver = { module = "app.cash.sqldelight:native-driver", version.ref = "sqlDelight" } +db-sqlRuntime = { module = "app.cash.sqldelight:runtime", version.ref = "sqlDelight" } [plugins] androidApplication = { id = "com.android.application", version.ref = "agp" } diff --git a/sandook/build.gradle.kts b/sandook/build.gradle.kts index 24eba89f..48d11926 100644 --- a/sandook/build.gradle.kts +++ b/sandook/build.gradle.kts @@ -32,7 +32,7 @@ kotlin { sourceSets { androidMain { dependencies { - implementation(libs.db.android.driver) + implementation(libs.db.sqlAndroidDriver) } } @@ -46,7 +46,7 @@ kotlin { implementation(compose.runtime) implementation(libs.log.kermit) implementation(libs.kotlinx.datetime) - implementation(libs.db.runtime) + implementation(libs.db.sqlRuntime) api(libs.di.koinComposeViewmodelNav) } @@ -54,11 +54,9 @@ kotlin { iosMain.dependencies { implementation(libs.ktor.client.darwin) - implementation(libs.db.native.driver) + implementation(libs.db.sqlNativeDriver) } } - - //configureCommonMainKsp() } dependencies { @@ -75,14 +73,11 @@ dependencies { ksp { arg("me.tatarka.inject.generateCompanionExtensions", "true") } -/* -fun KotlinMultiplatformExtension.configureCommonMainKsp() { - sourceSets.named("commonMain").configure { - kotlin.srcDir("build/generated/ksp/metadata/commonMain/kotlin") - } - project.tasks.withType(KotlinCompilationTask::class.java).configureEach { - if(name != "kspCommonMainKotlinMetadata") { - dependsOn("kspCommonMainKotlinMetadata") +sqldelight { + databases { + create("KrailSandook") { + packageName.set("xyz.ksharma.krail.sandook") } - }*/ + } +} diff --git a/sandook/src/commonMain/sqldelight/xyz/ksharma/krail/sandook/KrailSandook.sq b/sandook/src/commonMain/sqldelight/xyz/ksharma/krail/sandook/KrailSandook.sq new file mode 100644 index 00000000..10cee7d9 --- /dev/null +++ b/sandook/src/commonMain/sqldelight/xyz/ksharma/krail/sandook/KrailSandook.sq @@ -0,0 +1,36 @@ +-- Theme Table -- +CREATE TABLE theme ( + productClass INTEGER NOT NULL +); + +insertOrReplaceTheme: +INSERT OR REPLACE INTO theme(productClass) +VALUES (?); + +getTheme: +SELECT productClass FROM theme LIMIT 1; + +-- Saved Trip Table -- +CREATE TABLE savedtrip ( + tripId TEXT PRIMARY KEY, + fromStopId TEXT NOT NULL, + fromStopName TEXT NOT NULL, + toStopId TEXT NOT NULL, + toStopName TEXT NOT NULL, + timestamp TEXT DEFAULT (datetime('now')) +); + +insertOrReplaceTrip: +INSERT OR REPLACE INTO savedtrip(tripId, fromStopId, fromStopName, toStopId, toStopName, timestamp) +VALUES (?, ?, ?, ?, ?, datetime('now')); + +deleteTrip: +DELETE FROM savedtrip WHERE tripId = ?; + +selectAllTrips: +SELECT * FROM savedtrip +ORDER BY timestamp DESC; + +findTripById: +SELECT * FROM savedtrip +WHERE tripId = ?; From d77838c8a4176e236ec732d106032cbb05e6940d Mon Sep 17 00:00:00 2001 From: Karan Sharma <55722391+ksharma-xyz@users.noreply.github.com> Date: Fri, 22 Nov 2024 11:54:37 +1100 Subject: [PATCH 55/67] Creating Sandook using SqlDelight --- .../sandook/AndroidSandookDriverFactory.kt | 16 ++++++ .../ksharma/krail/sandook/RealSandookDb.kt | 56 +++++++++++++++++++ .../xyz/ksharma/krail/sandook/SandookDb.kt | 24 ++++++++ .../krail/sandook/SandookDriverFactory.kt | 7 +++ .../xyz/ksharma/krail/sandook/KrailSandook.sq | 28 ++++++---- .../krail/sandook/IosSandookDriverFactory.kt | 11 ++++ 6 files changed, 131 insertions(+), 11 deletions(-) create mode 100644 sandook/src/androidMain/kotlin/xyz/ksharma/krail/sandook/AndroidSandookDriverFactory.kt create mode 100644 sandook/src/commonMain/kotlin/xyz/ksharma/krail/sandook/RealSandookDb.kt create mode 100644 sandook/src/commonMain/kotlin/xyz/ksharma/krail/sandook/SandookDb.kt create mode 100644 sandook/src/commonMain/kotlin/xyz/ksharma/krail/sandook/SandookDriverFactory.kt create mode 100644 sandook/src/iosMain/kotlin/xyz/ksharma/krail/sandook/IosSandookDriverFactory.kt diff --git a/sandook/src/androidMain/kotlin/xyz/ksharma/krail/sandook/AndroidSandookDriverFactory.kt b/sandook/src/androidMain/kotlin/xyz/ksharma/krail/sandook/AndroidSandookDriverFactory.kt new file mode 100644 index 00000000..45110105 --- /dev/null +++ b/sandook/src/androidMain/kotlin/xyz/ksharma/krail/sandook/AndroidSandookDriverFactory.kt @@ -0,0 +1,16 @@ +package xyz.ksharma.krail.sandook + +import android.content.Context +import app.cash.sqldelight.db.SqlDriver +import app.cash.sqldelight.driver.android.AndroidSqliteDriver + +class AndroidDatabaseDriverFactory(private val context: Context) : SandookDriverFactory { + + override fun createDriver(): SqlDriver { + return AndroidSqliteDriver( + schema = KrailSandook.Schema, + context = context, + name = "krailSandook.db" + ) + } +} diff --git a/sandook/src/commonMain/kotlin/xyz/ksharma/krail/sandook/RealSandookDb.kt b/sandook/src/commonMain/kotlin/xyz/ksharma/krail/sandook/RealSandookDb.kt new file mode 100644 index 00000000..e7c1a7fe --- /dev/null +++ b/sandook/src/commonMain/kotlin/xyz/ksharma/krail/sandook/RealSandookDb.kt @@ -0,0 +1,56 @@ +package xyz.ksharma.krail.sandook + +internal class RealSandookDb(factory: SandookDriverFactory) : SandookDb { + + private val database = KrailSandook(factory.createDriver()) + private val query = database.krailSandookQueries + + // region Theme + override fun insertOrReplaceTheme(productClass: Long) { + query.insertOrReplaceProductClass(productClass) + } + + override fun getProductClass(): Long? { + return query.selectProductClass().executeAsOneOrNull() + } + + override fun clearTheme() { + query.clearTheme() + } + + // endregion + + // region SavedTrip + override fun insertOrReplaceTrip( + tripId: String, + fromStopId: String, + fromStopName: String, + toStopId: String, + toStopName: String, + ) { + query.insertOrReplaceTrip( + tripId, + fromStopId, + fromStopName, + toStopId, + toStopName, + ) + } + + override fun deleteTrip(tripId: String) { + query.deleteTrip(tripId) + } + + override fun selectAllTrips(): List { + return query.selectAllTrips().executeAsList() + } + + override fun selectTripById(tripId: String): SavedTrip? { + return query.selectTripById(tripId).executeAsOneOrNull() + } + + override fun clearSavedTrips() { + query.clearSavedTrips() + } + // endregion +} diff --git a/sandook/src/commonMain/kotlin/xyz/ksharma/krail/sandook/SandookDb.kt b/sandook/src/commonMain/kotlin/xyz/ksharma/krail/sandook/SandookDb.kt new file mode 100644 index 00000000..4225ca5c --- /dev/null +++ b/sandook/src/commonMain/kotlin/xyz/ksharma/krail/sandook/SandookDb.kt @@ -0,0 +1,24 @@ +package xyz.ksharma.krail.sandook + +interface SandookDb { + + // region Theme + fun insertOrReplaceTheme(productClass: Long) + fun getProductClass(): Long? + fun clearTheme() + // endregion + + // region SavedTrip + fun insertOrReplaceTrip( + tripId: String, + fromStopId: String, + fromStopName: String, + toStopId: String, + toStopName: String + ) + fun deleteTrip(tripId: String) + fun selectAllTrips(): List + fun selectTripById(tripId: String): SavedTrip? + fun clearSavedTrips() + // endregion +} diff --git a/sandook/src/commonMain/kotlin/xyz/ksharma/krail/sandook/SandookDriverFactory.kt b/sandook/src/commonMain/kotlin/xyz/ksharma/krail/sandook/SandookDriverFactory.kt new file mode 100644 index 00000000..678a5186 --- /dev/null +++ b/sandook/src/commonMain/kotlin/xyz/ksharma/krail/sandook/SandookDriverFactory.kt @@ -0,0 +1,7 @@ +package xyz.ksharma.krail.sandook + +import app.cash.sqldelight.db.SqlDriver + +interface SandookDriverFactory { + fun createDriver(): SqlDriver +} diff --git a/sandook/src/commonMain/sqldelight/xyz/ksharma/krail/sandook/KrailSandook.sq b/sandook/src/commonMain/sqldelight/xyz/ksharma/krail/sandook/KrailSandook.sq index 10cee7d9..183515fe 100644 --- a/sandook/src/commonMain/sqldelight/xyz/ksharma/krail/sandook/KrailSandook.sq +++ b/sandook/src/commonMain/sqldelight/xyz/ksharma/krail/sandook/KrailSandook.sq @@ -1,17 +1,20 @@ -- Theme Table -- -CREATE TABLE theme ( +CREATE TABLE Theme ( productClass INTEGER NOT NULL ); -insertOrReplaceTheme: -INSERT OR REPLACE INTO theme(productClass) +insertOrReplaceProductClass: +INSERT OR REPLACE INTO Theme(productClass) VALUES (?); -getTheme: -SELECT productClass FROM theme LIMIT 1; +selectProductClass: +SELECT productClass FROM Theme LIMIT 1; + +clearTheme: +DELETE FROM Theme; -- Saved Trip Table -- -CREATE TABLE savedtrip ( +CREATE TABLE SavedTrip ( tripId TEXT PRIMARY KEY, fromStopId TEXT NOT NULL, fromStopName TEXT NOT NULL, @@ -21,16 +24,19 @@ CREATE TABLE savedtrip ( ); insertOrReplaceTrip: -INSERT OR REPLACE INTO savedtrip(tripId, fromStopId, fromStopName, toStopId, toStopName, timestamp) +INSERT OR REPLACE INTO SavedTrip(tripId, fromStopId, fromStopName, toStopId, toStopName, timestamp) VALUES (?, ?, ?, ?, ?, datetime('now')); deleteTrip: -DELETE FROM savedtrip WHERE tripId = ?; +DELETE FROM SavedTrip WHERE tripId = ?; selectAllTrips: -SELECT * FROM savedtrip +SELECT * FROM SavedTrip ORDER BY timestamp DESC; -findTripById: -SELECT * FROM savedtrip +selectTripById: +SELECT * FROM SavedTrip WHERE tripId = ?; + +clearSavedTrips: +DELETE FROM SavedTrip; diff --git a/sandook/src/iosMain/kotlin/xyz/ksharma/krail/sandook/IosSandookDriverFactory.kt b/sandook/src/iosMain/kotlin/xyz/ksharma/krail/sandook/IosSandookDriverFactory.kt new file mode 100644 index 00000000..f63d1fdf --- /dev/null +++ b/sandook/src/iosMain/kotlin/xyz/ksharma/krail/sandook/IosSandookDriverFactory.kt @@ -0,0 +1,11 @@ +package xyz.ksharma.krail.sandook + +import app.cash.sqldelight.db.SqlDriver +import app.cash.sqldelight.driver.native.NativeSqliteDriver + +class IosSandookDriverFactory : SandookDriverFactory { + + override fun createDriver(): SqlDriver { + return NativeSqliteDriver(schema = KrailSandook.Schema, name = "krailSandook.db") + } +} From 12eb226b1ee7ccd13776157e2dcb671ad78b8438 Mon Sep 17 00:00:00 2001 From: Karan Sharma <55722391+ksharma-xyz@users.noreply.github.com> Date: Fri, 22 Nov 2024 15:53:37 +1100 Subject: [PATCH 56/67] Koin- android context can be provided once android-app is inside compsoe-app --- android-app/build.gradle.kts | 4 ++ .../xyz/ksharma/krail/KrailApplication.kt | 6 +- .../kotlin/xyz/ksharma/krail/MainActivity.kt | 14 +++++ composeApp/build.gradle.kts | 55 ++++++++++--------- .../xyz/ksharma/krail/common/KrailApp.kt | 9 +-- .../xyz/ksharma/krail/common/KrailNavHost.kt | 4 +- .../ksharma/krail/common/di/KoinAppModule.kt | 5 +- .../krail/common/splash/SplashViewModel.kt | 6 +- .../planner/network/api/service/HttpClient.kt | 2 +- .../api/service/RealTripPlanningService.kt | 3 +- sandook/build.gradle.kts | 1 + .../sandook/AndroidSandookDriverFactory.kt | 2 +- .../krail/sandook/AndroidSandookModule.kt | 18 ++++++ .../krail/sandook/{di => }/DbModule.kt | 5 +- 14 files changed, 87 insertions(+), 47 deletions(-) create mode 100644 sandook/src/androidMain/kotlin/xyz/ksharma/krail/sandook/AndroidSandookModule.kt rename sandook/src/commonMain/kotlin/xyz/ksharma/krail/sandook/{di => }/DbModule.kt (59%) diff --git a/android-app/build.gradle.kts b/android-app/build.gradle.kts index 9e971b55..d0f10e97 100644 --- a/android-app/build.gradle.kts +++ b/android-app/build.gradle.kts @@ -47,6 +47,8 @@ dependencies { // Projects implementation(projects.composeApp) + implementation(projects.sandook) + /* implementation(projects.core.network) implementation(projects.feature.tripPlanner.network.api) implementation(projects.feature.tripPlanner.network.real) @@ -60,4 +62,6 @@ dependencies { implementation(libs.core.ktx) implementation(libs.kotlinx.serialization.json) implementation(libs.lifecycle.runtime.ktx) + + implementation(libs.di.koinAndroid) } diff --git a/android-app/src/main/kotlin/xyz/ksharma/krail/KrailApplication.kt b/android-app/src/main/kotlin/xyz/ksharma/krail/KrailApplication.kt index e66c82fb..01cf04f3 100644 --- a/android-app/src/main/kotlin/xyz/ksharma/krail/KrailApplication.kt +++ b/android-app/src/main/kotlin/xyz/ksharma/krail/KrailApplication.kt @@ -2,4 +2,8 @@ package xyz.ksharma.krail import android.app.Application -class KrailApplication : Application() +class KrailApplication : Application() { + override fun onCreate() { + super.onCreate() + } +} diff --git a/android-app/src/main/kotlin/xyz/ksharma/krail/MainActivity.kt b/android-app/src/main/kotlin/xyz/ksharma/krail/MainActivity.kt index 9041f704..a7f340f4 100644 --- a/android-app/src/main/kotlin/xyz/ksharma/krail/MainActivity.kt +++ b/android-app/src/main/kotlin/xyz/ksharma/krail/MainActivity.kt @@ -4,7 +4,13 @@ import android.os.Bundle import androidx.activity.ComponentActivity import androidx.activity.compose.setContent import androidx.activity.enableEdgeToEdge +import org.koin.android.ext.koin.androidContext +import org.koin.compose.KoinApplication +import org.koin.dsl.includes +import org.koin.dsl.koinConfiguration import xyz.ksharma.krail.common.KrailApp +import xyz.ksharma.krail.common.di.koinConfig +import xyz.ksharma.krail.sandook.androidDbModule class MainActivity : ComponentActivity() { @@ -15,4 +21,12 @@ class MainActivity : ComponentActivity() { KrailApp() } } + + /* + private val koinConfig1 = koinConfiguration { + androidContext(androidContext = this@MainActivity.applicationContext) + modules(androidDbModule) + includes(koinConfig) + } + */ } diff --git a/composeApp/build.gradle.kts b/composeApp/build.gradle.kts index 267f75da..f3266a09 100644 --- a/composeApp/build.gradle.kts +++ b/composeApp/build.gradle.kts @@ -34,25 +34,30 @@ kotlin { } sourceSets { - androidMain.dependencies { - implementation(compose.preview) - implementation(libs.activity.compose) - - // Projects - /* - implementation(projects.core.network) - implementation(projects.core.utils) - implementation(projects.feature.tripPlanner.network.api) - implementation(projects.feature.tripPlanner.network.real) - implementation(projects.feature.tripPlanner.state) - implementation(projects.feature.tripPlanner.ui) - implementation(projects.sandook.api) - implementation(projects.sandook.real) - */ - implementation(compose.foundation) - implementation(libs.core.ktx) - implementation(libs.kotlinx.serialization.json) - implementation(libs.lifecycle.runtime.ktx) + androidMain { + dependencies { + implementation(compose.preview) + implementation(libs.activity.compose) + + // Projects + /* + implementation(projects.core.network) + implementation(projects.core.utils) + implementation(projects.feature.tripPlanner.network.api) + implementation(projects.feature.tripPlanner.network.real) + implementation(projects.feature.tripPlanner.state) + implementation(projects.feature.tripPlanner.ui) + implementation(projects.sandook.api) + implementation(projects.sandook.real) + */ + implementation(compose.foundation) + implementation(libs.core.ktx) + implementation(libs.kotlinx.serialization.json) + implementation(libs.lifecycle.runtime.ktx) + + implementation(libs.di.koinAndroid) + } + } commonMain.dependencies { @@ -86,12 +91,12 @@ kotlin { } dependencies { - // 1. Configure code generation into the common source set +// 1. Configure code generation into the common source set kspCommonMainMetadata(libs.di.kotlinInjectRuntime) - // 2. Configure code generation into each KMP target source set - //kspAndroid(libs.di.kotlinInjectCompilerKsp) - // kspIosX64("me.tatarka.inject:kotlin-inject-compiler-ksp:0.7.2") - // kspIosArm64("me.tatarka.inject:kotlin-inject-compiler-ksp:0.7.2") - // kspIosSimulatorArm64("me.tatarka.inject:kotlin-inject-compiler-ksp:0.7.2") +// 2. Configure code generation into each KMP target source set +//kspAndroid(libs.di.kotlinInjectCompilerKsp) +// kspIosX64("me.tatarka.inject:kotlin-inject-compiler-ksp:0.7.2") +// kspIosArm64("me.tatarka.inject:kotlin-inject-compiler-ksp:0.7.2") +// kspIosSimulatorArm64("me.tatarka.inject:kotlin-inject-compiler-ksp:0.7.2") } diff --git a/composeApp/src/commonMain/kotlin/xyz/ksharma/krail/common/KrailApp.kt b/composeApp/src/commonMain/kotlin/xyz/ksharma/krail/common/KrailApp.kt index 9745e42f..46c3bb97 100644 --- a/composeApp/src/commonMain/kotlin/xyz/ksharma/krail/common/KrailApp.kt +++ b/composeApp/src/commonMain/kotlin/xyz/ksharma/krail/common/KrailApp.kt @@ -1,16 +1,11 @@ package xyz.ksharma.krail.common import androidx.compose.runtime.Composable -import org.koin.compose.KoinApplication -import xyz.ksharma.krail.common.di.koinConfig - import xyz.ksharma.krail.taj.theme.KrailTheme @Composable fun KrailApp() { - KoinApplication(application = koinConfig) { - KrailTheme { - KrailNavHost() - } + KrailTheme { + KrailNavHost() } } diff --git a/composeApp/src/commonMain/kotlin/xyz/ksharma/krail/common/KrailNavHost.kt b/composeApp/src/commonMain/kotlin/xyz/ksharma/krail/common/KrailNavHost.kt index f4343d7b..3d938afd 100644 --- a/composeApp/src/commonMain/kotlin/xyz/ksharma/krail/common/KrailNavHost.kt +++ b/composeApp/src/commonMain/kotlin/xyz/ksharma/krail/common/KrailNavHost.kt @@ -9,13 +9,11 @@ import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.lifecycle.compose.collectAsStateWithLifecycle -import androidx.lifecycle.viewmodel.compose.viewModel import androidx.navigation.NavOptions import androidx.navigation.compose.NavHost import androidx.navigation.compose.composable import androidx.navigation.compose.rememberNavController import kotlinx.serialization.Serializable -import org.koin.compose.viewmodel.koinNavViewModel import org.koin.compose.viewmodel.koinViewModel import org.koin.core.annotation.KoinExperimentalAPI import xyz.ksharma.krail.common.splash.SplashScreen @@ -66,7 +64,7 @@ fun KrailNavHost(modifier: Modifier = Modifier) { tripPlannerDestinations(navController = navController) composable { - val viewModel: SplashViewModel = koinNavViewModel() + val viewModel: SplashViewModel = koinViewModel() val isLoading by viewModel.isLoading.collectAsStateWithLifecycle() val mode by viewModel.uiState.collectAsStateWithLifecycle() diff --git a/composeApp/src/commonMain/kotlin/xyz/ksharma/krail/common/di/KoinAppModule.kt b/composeApp/src/commonMain/kotlin/xyz/ksharma/krail/common/di/KoinAppModule.kt index 40145aac..8bc74401 100644 --- a/composeApp/src/commonMain/kotlin/xyz/ksharma/krail/common/di/KoinAppModule.kt +++ b/composeApp/src/commonMain/kotlin/xyz/ksharma/krail/common/di/KoinAppModule.kt @@ -1,9 +1,12 @@ package xyz.ksharma.krail.common.di +import io.ktor.client.plugins.logging.DEFAULT +import io.ktor.client.plugins.logging.Logger import org.koin.core.module.dsl.viewModelOf +import org.koin.dsl.includes import org.koin.dsl.koinConfiguration import org.koin.dsl.module -import xyz.ksharma.krail.sandook.di.dbModule +import xyz.ksharma.krail.sandook.dbModule import xyz.ksharma.krail.trip.planner.network.api.di.networkModule import xyz.ksharma.krail.trip.planner.ui.di.viewModelsModule import xyz.ksharma.krail.common.splash.SplashViewModel diff --git a/composeApp/src/commonMain/kotlin/xyz/ksharma/krail/common/splash/SplashViewModel.kt b/composeApp/src/commonMain/kotlin/xyz/ksharma/krail/common/splash/SplashViewModel.kt index fc6b0b55..3ccebd4f 100644 --- a/composeApp/src/commonMain/kotlin/xyz/ksharma/krail/common/splash/SplashViewModel.kt +++ b/composeApp/src/commonMain/kotlin/xyz/ksharma/krail/common/splash/SplashViewModel.kt @@ -10,11 +10,11 @@ import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.onStart import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.launch -import xyz.ksharma.krail.sandook.Sandook +import xyz.ksharma.krail.sandook.SandookDb import xyz.ksharma.krail.trip.planner.ui.state.TransportMode class SplashViewModel( - private val sandook: Sandook, + private val sandookDb: SandookDb, ) : ViewModel() { private val _uiState: MutableStateFlow = MutableStateFlow(null) @@ -28,7 +28,7 @@ class SplashViewModel( private fun getThemeTransportMode() { viewModelScope.launch(Dispatchers.IO) { - val productClass = sandook.getString("selectedMode")?.toIntOrNull() ?: 0 + val productClass = sandookDb.getProductClass()?.toInt() ?: 0 val mode = TransportMode.toTransportModeType(productClass) _uiState.value = mode } diff --git a/feature/trip-planner/network/src/androidMain/kotlin/xyz/ksharma/krail/trip/planner/network/api/service/HttpClient.kt b/feature/trip-planner/network/src/androidMain/kotlin/xyz/ksharma/krail/trip/planner/network/api/service/HttpClient.kt index 2d2bedb3..ab51d313 100644 --- a/feature/trip-planner/network/src/androidMain/kotlin/xyz/ksharma/krail/trip/planner/network/api/service/HttpClient.kt +++ b/feature/trip-planner/network/src/androidMain/kotlin/xyz/ksharma/krail/trip/planner/network/api/service/HttpClient.kt @@ -23,7 +23,7 @@ actual fun httpClient(): HttpClient { } install(Logging) { // if(debug) - TODO - level = LogLevel.BODY + level = LogLevel.ALL } defaultRequest { diff --git a/feature/trip-planner/network/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/network/api/service/RealTripPlanningService.kt b/feature/trip-planner/network/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/network/api/service/RealTripPlanningService.kt index 6ce54c1e..cd890d56 100644 --- a/feature/trip-planner/network/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/network/api/service/RealTripPlanningService.kt +++ b/feature/trip-planner/network/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/network/api/service/RealTripPlanningService.kt @@ -16,7 +16,7 @@ internal class RealTripPlanningService(private val httpClient: HttpClient) : Tri override suspend fun trip( originStopId: String, - destinationStopId: String + destinationStopId: String, ): TripResponse = withContext(Dispatchers.IO) { httpClient.get("$NSW_TRANSPORT_BASE_URL/v1/tp/trip") { @@ -44,7 +44,6 @@ internal class RealTripPlanningService(private val httpClient: HttpClient) : Tri stopSearchQuery: String, stopType: StopType, ): StopFinderResponse = withContext(Dispatchers.IO) { - httpClient.get("${NSW_TRANSPORT_BASE_URL}/v1/tp/stop_finder") { url { parameters.append(StopFinderRequestParams.nameSf, stopSearchQuery) diff --git a/sandook/build.gradle.kts b/sandook/build.gradle.kts index 48d11926..b3135b0c 100644 --- a/sandook/build.gradle.kts +++ b/sandook/build.gradle.kts @@ -33,6 +33,7 @@ kotlin { androidMain { dependencies { implementation(libs.db.sqlAndroidDriver) + implementation(libs.di.koinAndroid) } } diff --git a/sandook/src/androidMain/kotlin/xyz/ksharma/krail/sandook/AndroidSandookDriverFactory.kt b/sandook/src/androidMain/kotlin/xyz/ksharma/krail/sandook/AndroidSandookDriverFactory.kt index 45110105..90a5eb1d 100644 --- a/sandook/src/androidMain/kotlin/xyz/ksharma/krail/sandook/AndroidSandookDriverFactory.kt +++ b/sandook/src/androidMain/kotlin/xyz/ksharma/krail/sandook/AndroidSandookDriverFactory.kt @@ -4,7 +4,7 @@ import android.content.Context import app.cash.sqldelight.db.SqlDriver import app.cash.sqldelight.driver.android.AndroidSqliteDriver -class AndroidDatabaseDriverFactory(private val context: Context) : SandookDriverFactory { +class AndroidSandookDriverFactory(private val context: Context) : SandookDriverFactory { override fun createDriver(): SqlDriver { return AndroidSqliteDriver( diff --git a/sandook/src/androidMain/kotlin/xyz/ksharma/krail/sandook/AndroidSandookModule.kt b/sandook/src/androidMain/kotlin/xyz/ksharma/krail/sandook/AndroidSandookModule.kt new file mode 100644 index 00000000..d91b2099 --- /dev/null +++ b/sandook/src/androidMain/kotlin/xyz/ksharma/krail/sandook/AndroidSandookModule.kt @@ -0,0 +1,18 @@ +package xyz.ksharma.krail.sandook + +import org.koin.android.ext.koin.androidContext +import org.koin.core.module.dsl.bind +import org.koin.core.module.dsl.singleOf +import org.koin.dsl.module + +val androidDbModule = module { + singleOf(::AndroidSandookDriverFactory) { bind() } + + single { + RealSandookDb( + factory = AndroidSandookDriverFactory( + context = androidContext() + ) + ) + } +} diff --git a/sandook/src/commonMain/kotlin/xyz/ksharma/krail/sandook/di/DbModule.kt b/sandook/src/commonMain/kotlin/xyz/ksharma/krail/sandook/DbModule.kt similarity index 59% rename from sandook/src/commonMain/kotlin/xyz/ksharma/krail/sandook/di/DbModule.kt rename to sandook/src/commonMain/kotlin/xyz/ksharma/krail/sandook/DbModule.kt index c2df4623..f650d415 100644 --- a/sandook/src/commonMain/kotlin/xyz/ksharma/krail/sandook/di/DbModule.kt +++ b/sandook/src/commonMain/kotlin/xyz/ksharma/krail/sandook/DbModule.kt @@ -1,11 +1,10 @@ -package xyz.ksharma.krail.sandook.di +package xyz.ksharma.krail.sandook import org.koin.core.module.dsl.bind import org.koin.core.module.dsl.singleOf import org.koin.dsl.module -import xyz.ksharma.krail.sandook.RealSandook -import xyz.ksharma.krail.sandook.Sandook val dbModule = module { singleOf(::RealSandook) { bind() } + singleOf(::RealSandookDb) { bind() } } From 9185f4fd0cc6e8701a0f90ce3c1709e52343f645 Mon Sep 17 00:00:00 2001 From: Karan Sharma <55722391+ksharma-xyz@users.noreply.github.com> Date: Fri, 22 Nov 2024 20:34:02 +1100 Subject: [PATCH 57/67] kotlin-inject take 2 --- core/di/build.gradle.kts | 6 ++---- feature/trip-planner/network/build.gradle.kts | 16 +++++++++++----- .../network/api/AndroidNetworkComponent.kt | 3 +++ .../trip/planner/network/api/NetworkComponent.kt | 10 ++++++++++ .../network/api}/ratelimit/APIRateLimiterTest.kt | 4 ++-- .../planner/network/api/IosNetworkComponent.kt | 3 +++ feature/trip-planner/ui/build.gradle.kts | 9 +++------ .../trip/planner/ui/components/ColorsTest.kt | 6 ++++-- sandook/build.gradle.kts | 2 -- 9 files changed, 38 insertions(+), 21 deletions(-) create mode 100644 feature/trip-planner/network/src/androidMain/kotlin/xyz/ksharma/krail/trip/planner/network/api/AndroidNetworkComponent.kt create mode 100644 feature/trip-planner/network/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/network/api/NetworkComponent.kt rename feature/trip-planner/network/src/commonTest/kotlin/{ => xyz/ksharma/krail/trip/planner/network/api}/ratelimit/APIRateLimiterTest.kt (94%) create mode 100644 feature/trip-planner/network/src/iosMain/kotlin/xyz/ksharma/krail/trip/planner/network/api/IosNetworkComponent.kt diff --git a/core/di/build.gradle.kts b/core/di/build.gradle.kts index 1b925529..d3003c27 100644 --- a/core/di/build.gradle.kts +++ b/core/di/build.gradle.kts @@ -32,12 +32,10 @@ kotlin { dependencies { // 1. Configure code generation into the common source set kspCommonMainMetadata(libs.di.kotlinInjectRuntime) - // 2. Configure code generation into each KMP target source set kspAndroid(libs.di.kotlinInjectCompilerKsp) - // kspIosX64("me.tatarka.inject:kotlin-inject-compiler-ksp:0.7.2") - // kspIosArm64("me.tatarka.inject:kotlin-inject-compiler-ksp:0.7.2") - // kspIosSimulatorArm64("me.tatarka.inject:kotlin-inject-compiler-ksp:0.7.2") + kspIosArm64(libs.di.kotlinInjectCompilerKsp) + kspIosSimulatorArm64(libs.di.kotlinInjectCompilerKsp) } ksp { diff --git a/feature/trip-planner/network/build.gradle.kts b/feature/trip-planner/network/build.gradle.kts index b2a4b09c..6c5e9f45 100644 --- a/feature/trip-planner/network/build.gradle.kts +++ b/feature/trip-planner/network/build.gradle.kts @@ -66,18 +66,24 @@ kotlin { implementation(libs.ktor.client.darwin) } } + + commonTest { + dependencies { + implementation(libs.test.kotlin) + implementation(libs.test.turbine) + implementation(libs.test.kotlinxCoroutineTest) + } + } } } dependencies { // 1. Configure code generation into the common source set kspCommonMainMetadata(libs.di.kotlinInjectRuntime) - // 2. Configure code generation into each KMP target source set - //kspAndroid(libs.di.kotlinInjectCompilerKsp) - // kspIosX64("me.tatarka.inject:kotlin-inject-compiler-ksp:0.7.2") - // kspIosArm64("me.tatarka.inject:kotlin-inject-compiler-ksp:0.7.2") - // kspIosSimulatorArm64("me.tatarka.inject:kotlin-inject-compiler-ksp:0.7.2") + kspAndroid(libs.di.kotlinInjectCompilerKsp) + kspIosArm64(libs.di.kotlinInjectCompilerKsp) + kspIosSimulatorArm64(libs.di.kotlinInjectCompilerKsp) } // READ API KEY diff --git a/feature/trip-planner/network/src/androidMain/kotlin/xyz/ksharma/krail/trip/planner/network/api/AndroidNetworkComponent.kt b/feature/trip-planner/network/src/androidMain/kotlin/xyz/ksharma/krail/trip/planner/network/api/AndroidNetworkComponent.kt new file mode 100644 index 00000000..22ace500 --- /dev/null +++ b/feature/trip-planner/network/src/androidMain/kotlin/xyz/ksharma/krail/trip/planner/network/api/AndroidNetworkComponent.kt @@ -0,0 +1,3 @@ +package xyz.ksharma.krail.trip.planner.network.api + +//actual fun createNetworkComponent(): NetworkComponent = NetworkComponent::class.create() diff --git a/feature/trip-planner/network/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/network/api/NetworkComponent.kt b/feature/trip-planner/network/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/network/api/NetworkComponent.kt new file mode 100644 index 00000000..88a2a344 --- /dev/null +++ b/feature/trip-planner/network/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/network/api/NetworkComponent.kt @@ -0,0 +1,10 @@ +package xyz.ksharma.krail.trip.planner.network.api + +import me.tatarka.inject.annotations.Component +import me.tatarka.inject.annotations.KmpComponentCreate + +@Component +abstract class NetworkComponent + +@KmpComponentCreate +expect fun createNetworkComponent(): NetworkComponent diff --git a/feature/trip-planner/network/src/commonTest/kotlin/ratelimit/APIRateLimiterTest.kt b/feature/trip-planner/network/src/commonTest/kotlin/xyz/ksharma/krail/trip/planner/network/api/ratelimit/APIRateLimiterTest.kt similarity index 94% rename from feature/trip-planner/network/src/commonTest/kotlin/ratelimit/APIRateLimiterTest.kt rename to feature/trip-planner/network/src/commonTest/kotlin/xyz/ksharma/krail/trip/planner/network/api/ratelimit/APIRateLimiterTest.kt index 901590af..1e0215ea 100644 --- a/feature/trip-planner/network/src/commonTest/kotlin/ratelimit/APIRateLimiterTest.kt +++ b/feature/trip-planner/network/src/commonTest/kotlin/xyz/ksharma/krail/trip/planner/network/api/ratelimit/APIRateLimiterTest.kt @@ -1,4 +1,4 @@ -package ratelimit +package xyz.ksharma.krail.trip.planner.network.api.ratelimit import app.cash.turbine.test import kotlinx.coroutines.delay @@ -9,7 +9,7 @@ import kotlin.time.Duration.Companion.seconds class APIRateLimiterTest { - private val rateLimiter = APIRateLimiter() + private val rateLimiter = NetworkRateLimiter() @Test fun `Given rate limiter When triggered once Then should emit only once within the time interval`() = runTest { diff --git a/feature/trip-planner/network/src/iosMain/kotlin/xyz/ksharma/krail/trip/planner/network/api/IosNetworkComponent.kt b/feature/trip-planner/network/src/iosMain/kotlin/xyz/ksharma/krail/trip/planner/network/api/IosNetworkComponent.kt new file mode 100644 index 00000000..22ace500 --- /dev/null +++ b/feature/trip-planner/network/src/iosMain/kotlin/xyz/ksharma/krail/trip/planner/network/api/IosNetworkComponent.kt @@ -0,0 +1,3 @@ +package xyz.ksharma.krail.trip.planner.network.api + +//actual fun createNetworkComponent(): NetworkComponent = NetworkComponent::class.create() diff --git a/feature/trip-planner/ui/build.gradle.kts b/feature/trip-planner/ui/build.gradle.kts index 3a2d3470..e2c3343d 100644 --- a/feature/trip-planner/ui/build.gradle.kts +++ b/feature/trip-planner/ui/build.gradle.kts @@ -72,14 +72,11 @@ android { namespace = "xyz.ksharma.krail.trip.planner.ui" } - dependencies { // 1. Configure code generation into the common source set kspCommonMainMetadata(libs.di.kotlinInjectRuntime) - // 2. Configure code generation into each KMP target source set - ksp(libs.di.kotlinInjectCompilerKsp) - // kspIosX64("me.tatarka.inject:kotlin-inject-compiler-ksp:0.7.2") - // kspIosArm64("me.tatarka.inject:kotlin-inject-compiler-ksp:0.7.2") - // kspIosSimulatorArm64("me.tatarka.inject:kotlin-inject-compiler-ksp:0.7.2") +// kspAndroid(libs.di.kotlinInjectCompilerKsp) + kspIosArm64(libs.di.kotlinInjectCompilerKsp) + kspIosSimulatorArm64(libs.di.kotlinInjectCompilerKsp) } diff --git a/feature/trip-planner/ui/src/commonTest/kotlin/xyz/ksharma/krail/trip/planner/ui/components/ColorsTest.kt b/feature/trip-planner/ui/src/commonTest/kotlin/xyz/ksharma/krail/trip/planner/ui/components/ColorsTest.kt index 2951a99b..a926cd66 100644 --- a/feature/trip-planner/ui/src/commonTest/kotlin/xyz/ksharma/krail/trip/planner/ui/components/ColorsTest.kt +++ b/feature/trip-planner/ui/src/commonTest/kotlin/xyz/ksharma/krail/trip/planner/ui/components/ColorsTest.kt @@ -2,6 +2,8 @@ package xyz.ksharma.krail.trip.planner.ui.components import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.luminance +import kotlin.test.Test +import kotlin.test.assertEquals // Define the colors @@ -34,13 +36,13 @@ fun getForegroundColor(backgroundColor: Color, isDarkMode: Boolean): Color { return if (whiteContrast >= 4.0f) whiteColor else blackColor } -fun Color.toHex(): String { +/*fun Color.toHex(): String { val red = (this.red * 255).toInt() val green = (this.green * 255).toInt() val blue = (this.blue * 255).toInt() val alpha = (this.alpha * 255).toInt() return String.format("#%02X%02X%02X%02X", alpha, red, green, blue) -} +}*/ class ColorUtilsTest { diff --git a/sandook/build.gradle.kts b/sandook/build.gradle.kts index b3135b0c..542f710c 100644 --- a/sandook/build.gradle.kts +++ b/sandook/build.gradle.kts @@ -63,10 +63,8 @@ kotlin { dependencies { // 1. Configure code generation into the common source set kspCommonMainMetadata(libs.di.kotlinInjectRuntime) - ksp(libs.di.kotlinInjectCompilerKsp) // 2. Configure code generation into each KMP target source set kspAndroid(libs.di.kotlinInjectCompilerKsp) - //kspIosX64("me.tatarka.inject:kotlin-inject-compiler-ksp:0.7.2") kspIosArm64(libs.di.kotlinInjectCompilerKsp) kspIosSimulatorArm64(libs.di.kotlinInjectCompilerKsp) } From c999af5caac94420b15bf9a771dde0efba7e6637 Mon Sep 17 00:00:00 2001 From: Karan Sharma <55722391+ksharma-xyz@users.noreply.github.com> Date: Fri, 22 Nov 2024 21:27:15 +1100 Subject: [PATCH 58/67] HttpClient Injection works --- android-app/build.gradle.kts | 1 + .../kotlin/xyz/ksharma/krail/MainActivity.kt | 6 ------ composeApp/build.gradle.kts | 6 +++--- .../xyz/ksharma/krail/common/KrailApp.kt | 12 +++++++++++- .../xyz/ksharma/krail/common/KrailNavHost.kt | 6 +++++- .../ksharma/krail/common/di/KoinAppModule.kt | 2 ++ .../krail/common/splash/SplashViewModel.kt | 2 ++ .../planner/network/api/NetworkComponent.kt | 19 ++++++++++++++++++- .../api/ratelimit/NetworkRateLimiter.kt | 4 +++- .../api/service/RealTripPlanningService.kt | 4 +++- 10 files changed, 48 insertions(+), 14 deletions(-) diff --git a/android-app/build.gradle.kts b/android-app/build.gradle.kts index d0f10e97..7853002a 100644 --- a/android-app/build.gradle.kts +++ b/android-app/build.gradle.kts @@ -3,6 +3,7 @@ plugins { alias(libs.plugins.krail.kotlin.android) alias(libs.plugins.krail.compose.multiplatform) alias(libs.plugins.kotlin.serialization) + alias(libs.plugins.compose.compiler) } android { diff --git a/android-app/src/main/kotlin/xyz/ksharma/krail/MainActivity.kt b/android-app/src/main/kotlin/xyz/ksharma/krail/MainActivity.kt index a7f340f4..94abf4e7 100644 --- a/android-app/src/main/kotlin/xyz/ksharma/krail/MainActivity.kt +++ b/android-app/src/main/kotlin/xyz/ksharma/krail/MainActivity.kt @@ -4,13 +4,7 @@ import android.os.Bundle import androidx.activity.ComponentActivity import androidx.activity.compose.setContent import androidx.activity.enableEdgeToEdge -import org.koin.android.ext.koin.androidContext -import org.koin.compose.KoinApplication -import org.koin.dsl.includes -import org.koin.dsl.koinConfiguration import xyz.ksharma.krail.common.KrailApp -import xyz.ksharma.krail.common.di.koinConfig -import xyz.ksharma.krail.sandook.androidDbModule class MainActivity : ComponentActivity() { diff --git a/composeApp/build.gradle.kts b/composeApp/build.gradle.kts index f3266a09..421962c1 100644 --- a/composeApp/build.gradle.kts +++ b/composeApp/build.gradle.kts @@ -62,10 +62,10 @@ kotlin { commonMain.dependencies { implementation(projects.taj) - implementation(projects.sandook) +// implementation(projects.sandook) implementation(projects.feature.tripPlanner.network) - implementation(projects.feature.tripPlanner.ui) - implementation(projects.feature.tripPlanner.state) +// implementation(projects.feature.tripPlanner.ui) +// implementation(projects.feature.tripPlanner.state) implementation(libs.navigation.compose) diff --git a/composeApp/src/commonMain/kotlin/xyz/ksharma/krail/common/KrailApp.kt b/composeApp/src/commonMain/kotlin/xyz/ksharma/krail/common/KrailApp.kt index 46c3bb97..478038fa 100644 --- a/composeApp/src/commonMain/kotlin/xyz/ksharma/krail/common/KrailApp.kt +++ b/composeApp/src/commonMain/kotlin/xyz/ksharma/krail/common/KrailApp.kt @@ -1,11 +1,21 @@ package xyz.ksharma.krail.common +import androidx.compose.material.Text import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect import xyz.ksharma.krail.taj.theme.KrailTheme +import xyz.ksharma.krail.trip.planner.network.api.createNetworkComponent @Composable fun KrailApp() { + + LaunchedEffect(Unit) { + val httpClient = createNetworkComponent().provideHttpClient() + println("HTTP Client: ${httpClient.engine.config}") + } + KrailTheme { - KrailNavHost() + Text("Hello, Krail!") +// KrailNavHost() } } diff --git a/composeApp/src/commonMain/kotlin/xyz/ksharma/krail/common/KrailNavHost.kt b/composeApp/src/commonMain/kotlin/xyz/ksharma/krail/common/KrailNavHost.kt index 3d938afd..d844c7e0 100644 --- a/composeApp/src/commonMain/kotlin/xyz/ksharma/krail/common/KrailNavHost.kt +++ b/composeApp/src/commonMain/kotlin/xyz/ksharma/krail/common/KrailNavHost.kt @@ -1,3 +1,4 @@ +/* package xyz.ksharma.krail.common import androidx.compose.foundation.layout.fillMaxSize @@ -29,6 +30,7 @@ import xyz.ksharma.krail.trip.planner.ui.navigation.SavedTripsRoute import xyz.ksharma.krail.trip.planner.ui.navigation.UsualRideRoute import xyz.ksharma.krail.trip.planner.ui.navigation.tripPlannerDestinations +*/ /** * TODO - I don't like [NavHost] defined in app module, I would love to refactor it to :core:navigation module * but that results in a cyclic dependency. Feature module needs to depend on :core:navigation for navigation logic and @@ -39,7 +41,8 @@ import xyz.ksharma.krail.trip.planner.ui.navigation.tripPlannerDestinations * Navigation logic is currently taken from [NowInAndroid](https://github.com/android/nowinandroid] app, * so fine for now. But I will want to refactor it to something nicer e.g. using Circuit library * from Slack, but that would also mean refactoring to use MVP instead of MVVM. - */ + *//* + @OptIn(KoinExperimentalAPI::class) @Composable fun KrailNavHost(modifier: Modifier = Modifier) { @@ -99,3 +102,4 @@ fun KrailNavHost(modifier: Modifier = Modifier) { @Serializable private data object SplashScreen +*/ diff --git a/composeApp/src/commonMain/kotlin/xyz/ksharma/krail/common/di/KoinAppModule.kt b/composeApp/src/commonMain/kotlin/xyz/ksharma/krail/common/di/KoinAppModule.kt index 8bc74401..dce9b240 100644 --- a/composeApp/src/commonMain/kotlin/xyz/ksharma/krail/common/di/KoinAppModule.kt +++ b/composeApp/src/commonMain/kotlin/xyz/ksharma/krail/common/di/KoinAppModule.kt @@ -1,3 +1,4 @@ +/* package xyz.ksharma.krail.common.di import io.ktor.client.plugins.logging.DEFAULT @@ -18,3 +19,4 @@ val koinConfig = koinConfiguration { val splashModule = module { viewModelOf(::SplashViewModel) } +*/ diff --git a/composeApp/src/commonMain/kotlin/xyz/ksharma/krail/common/splash/SplashViewModel.kt b/composeApp/src/commonMain/kotlin/xyz/ksharma/krail/common/splash/SplashViewModel.kt index 3ccebd4f..8845c9a9 100644 --- a/composeApp/src/commonMain/kotlin/xyz/ksharma/krail/common/splash/SplashViewModel.kt +++ b/composeApp/src/commonMain/kotlin/xyz/ksharma/krail/common/splash/SplashViewModel.kt @@ -1,3 +1,4 @@ +/* package xyz.ksharma.krail.common.splash import androidx.lifecycle.ViewModel @@ -34,3 +35,4 @@ class SplashViewModel( } } } +*/ diff --git a/feature/trip-planner/network/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/network/api/NetworkComponent.kt b/feature/trip-planner/network/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/network/api/NetworkComponent.kt index 88a2a344..243870a8 100644 --- a/feature/trip-planner/network/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/network/api/NetworkComponent.kt +++ b/feature/trip-planner/network/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/network/api/NetworkComponent.kt @@ -1,10 +1,27 @@ package xyz.ksharma.krail.trip.planner.network.api +import io.ktor.client.HttpClient import me.tatarka.inject.annotations.Component import me.tatarka.inject.annotations.KmpComponentCreate +import me.tatarka.inject.annotations.Provides +import xyz.ksharma.krail.trip.planner.network.api.ratelimit.NetworkRateLimiter +import xyz.ksharma.krail.trip.planner.network.api.ratelimit.RateLimiter +import xyz.ksharma.krail.trip.planner.network.api.service.RealTripPlanningService +import xyz.ksharma.krail.trip.planner.network.api.service.TripPlanningService +import xyz.ksharma.krail.trip.planner.network.api.service.httpClient @Component -abstract class NetworkComponent +abstract class NetworkComponent { + + @Provides + fun provideHttpClient(): HttpClient = httpClient() + + protected val RealTripPlanningService.bind: TripPlanningService + @Provides get() = this + + protected val NetworkRateLimiter.bind: RateLimiter + @Provides get() = this +} @KmpComponentCreate expect fun createNetworkComponent(): NetworkComponent diff --git a/feature/trip-planner/network/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/network/api/ratelimit/NetworkRateLimiter.kt b/feature/trip-planner/network/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/network/api/ratelimit/NetworkRateLimiter.kt index a01d9129..4937eb25 100644 --- a/feature/trip-planner/network/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/network/api/ratelimit/NetworkRateLimiter.kt +++ b/feature/trip-planner/network/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/network/api/ratelimit/NetworkRateLimiter.kt @@ -9,6 +9,7 @@ import kotlinx.coroutines.flow.debounce import kotlinx.coroutines.flow.flatMapLatest import kotlinx.coroutines.flow.flow import kotlinx.coroutines.flow.update +import me.tatarka.inject.annotations.Inject import kotlin.time.Duration.Companion.milliseconds import kotlin.time.Duration.Companion.seconds @@ -18,7 +19,8 @@ import kotlin.time.Duration.Companion.seconds * * Note: This class should not be Singleton. It should be created per use-case. */ -internal class NetworkRateLimiter : RateLimiter { +@Inject +class NetworkRateLimiter : RateLimiter { private val triggerFlow = MutableSharedFlow(replay = 1) private val isFirstTime = MutableStateFlow(false) diff --git a/feature/trip-planner/network/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/network/api/service/RealTripPlanningService.kt b/feature/trip-planner/network/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/network/api/service/RealTripPlanningService.kt index cd890d56..a8f32123 100644 --- a/feature/trip-planner/network/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/network/api/service/RealTripPlanningService.kt +++ b/feature/trip-planner/network/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/network/api/service/RealTripPlanningService.kt @@ -6,13 +6,15 @@ import io.ktor.client.request.get import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.IO import kotlinx.coroutines.withContext +import me.tatarka.inject.annotations.Inject import xyz.ksharma.krail.trip.planner.network.api.model.StopFinderResponse import xyz.ksharma.krail.trip.planner.network.api.model.StopType import xyz.ksharma.krail.trip.planner.network.api.model.TripResponse import xyz.ksharma.krail.trip.planner.network.api.service.stop_finder.StopFinderRequestParams import xyz.ksharma.krail.trip.planner.network.api.service.trip.TripRequestParams -internal class RealTripPlanningService(private val httpClient: HttpClient) : TripPlanningService { +@Inject +class RealTripPlanningService(private val httpClient: HttpClient) : TripPlanningService { override suspend fun trip( originStopId: String, From 207999ee01bd23e37c2cc099d4bfc337c70292f0 Mon Sep 17 00:00:00 2001 From: Karan Sharma <55722391+ksharma-xyz@users.noreply.github.com> Date: Fri, 22 Nov 2024 22:13:59 +1100 Subject: [PATCH 59/67] FAIL: kotlin-inject DI --- .../xyz/ksharma/krail/KrailApplication.kt | 8 ++++++ .../kotlin/xyz/ksharma/krail/MainActivity.kt | 14 +++++++++- composeApp/build.gradle.kts | 18 +++++++----- .../krail/common/AndroidActivityComponent.kt | 14 ++++++++++ .../common/AndroidApplicationComponent.kt | 26 +++++++++++++++++ .../krail/common/AppCoroutineDispatchers.kt | 11 ++++++++ .../ksharma/krail/common/AppDispatchers.kt | 28 +++++++++++++++++++ .../xyz/ksharma/krail/common/KrailApp.kt | 5 ++++ .../xyz/ksharma/krail/common/LazyExt.kt | 4 +++ .../kotlin/xyz/ksharma/krail/common/Scope.kt | 12 ++++++++ feature/trip-planner/network/build.gradle.kts | 5 ++++ .../planner/network/api/NetworkComponent.kt | 2 ++ sandook/build.gradle.kts | 1 + .../sandook/AndroidSandookDriverFactory.kt | 16 ----------- .../krail/sandook/AndroidSandookModule.kt | 2 ++ .../krail/sandook/SQLPlatformComponent.kt | 25 +++++++++++++++++ .../ksharma/krail/sandook/RealSandookDb.kt | 7 +++-- .../xyz/ksharma/krail/sandook/SQLComponent.kt | 20 +++++++++++++ .../ksharma/krail/sandook/SandookComponent.kt | 19 +++++++++++++ .../krail/sandook/SandookDriverFactory.kt | 7 ----- .../ksharma/krail/sandook/SandookFactory.kt | 11 ++++++++ .../krail/sandook/di/SandookComponent.kt | 24 ---------------- .../krail/sandook/IosSandookDriverFactory.kt | 11 -------- .../krail/sandook/SQLPlatformComponent.kt | 18 ++++++++++++ 24 files changed, 240 insertions(+), 68 deletions(-) create mode 100644 composeApp/src/androidMain/kotlin/xyz/ksharma/krail/common/AndroidActivityComponent.kt create mode 100644 composeApp/src/androidMain/kotlin/xyz/ksharma/krail/common/AndroidApplicationComponent.kt create mode 100644 composeApp/src/commonMain/kotlin/xyz/ksharma/krail/common/AppCoroutineDispatchers.kt create mode 100644 composeApp/src/commonMain/kotlin/xyz/ksharma/krail/common/AppDispatchers.kt create mode 100644 composeApp/src/commonMain/kotlin/xyz/ksharma/krail/common/LazyExt.kt create mode 100644 composeApp/src/commonMain/kotlin/xyz/ksharma/krail/common/Scope.kt delete mode 100644 sandook/src/androidMain/kotlin/xyz/ksharma/krail/sandook/AndroidSandookDriverFactory.kt create mode 100644 sandook/src/androidMain/kotlin/xyz/ksharma/krail/sandook/SQLPlatformComponent.kt create mode 100644 sandook/src/commonMain/kotlin/xyz/ksharma/krail/sandook/SQLComponent.kt create mode 100644 sandook/src/commonMain/kotlin/xyz/ksharma/krail/sandook/SandookComponent.kt delete mode 100644 sandook/src/commonMain/kotlin/xyz/ksharma/krail/sandook/SandookDriverFactory.kt create mode 100644 sandook/src/commonMain/kotlin/xyz/ksharma/krail/sandook/SandookFactory.kt delete mode 100644 sandook/src/commonMain/kotlin/xyz/ksharma/krail/sandook/di/SandookComponent.kt delete mode 100644 sandook/src/iosMain/kotlin/xyz/ksharma/krail/sandook/IosSandookDriverFactory.kt create mode 100644 sandook/src/iosMain/kotlin/xyz/ksharma/krail/sandook/SQLPlatformComponent.kt diff --git a/android-app/src/main/kotlin/xyz/ksharma/krail/KrailApplication.kt b/android-app/src/main/kotlin/xyz/ksharma/krail/KrailApplication.kt index 01cf04f3..ce65dbc5 100644 --- a/android-app/src/main/kotlin/xyz/ksharma/krail/KrailApplication.kt +++ b/android-app/src/main/kotlin/xyz/ksharma/krail/KrailApplication.kt @@ -1,8 +1,16 @@ package xyz.ksharma.krail import android.app.Application +import xyz.ksharma.krail.common.AndroidApplicationComponent class KrailApplication : Application() { + +/* + val component: AndroidApplicationComponent by lazy { + AndroidApplicationComponent::class.create(this) + } +*/ + override fun onCreate() { super.onCreate() } diff --git a/android-app/src/main/kotlin/xyz/ksharma/krail/MainActivity.kt b/android-app/src/main/kotlin/xyz/ksharma/krail/MainActivity.kt index 94abf4e7..887df763 100644 --- a/android-app/src/main/kotlin/xyz/ksharma/krail/MainActivity.kt +++ b/android-app/src/main/kotlin/xyz/ksharma/krail/MainActivity.kt @@ -9,8 +9,14 @@ import xyz.ksharma.krail.common.KrailApp class MainActivity : ComponentActivity() { override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) + enableEdgeToEdge() + super.onCreate(savedInstanceState) + +/* + val applicationComponent = AndroidApplicationComponent.from(this) +*/ + setContent { KrailApp() } @@ -24,3 +30,9 @@ class MainActivity : ComponentActivity() { } */ } + +/* +private fun AndroidApplicationComponent.Companion.from(context: Context): AndroidApplicationComponent { + return (context.applicationContext as KrailApplication).component +} +*/ diff --git a/composeApp/build.gradle.kts b/composeApp/build.gradle.kts index 421962c1..e0cca089 100644 --- a/composeApp/build.gradle.kts +++ b/composeApp/build.gradle.kts @@ -62,7 +62,7 @@ kotlin { commonMain.dependencies { implementation(projects.taj) -// implementation(projects.sandook) + implementation(projects.sandook) implementation(projects.feature.tripPlanner.network) // implementation(projects.feature.tripPlanner.ui) // implementation(projects.feature.tripPlanner.state) @@ -86,17 +86,21 @@ kotlin { implementation(libs.ktor.serialization.kotlinx.json) implementation(libs.di.koinComposeViewmodelNav) + implementation(libs.di.kotlinInjectRuntime) } } } dependencies { -// 1. Configure code generation into the common source set + // 1. Configure code generation into the common source set kspCommonMainMetadata(libs.di.kotlinInjectRuntime) + // 2. Configure code generation into each KMP target source set + //kspAndroid(libs.di.kotlinInjectCompilerKsp) + kspIosArm64(libs.di.kotlinInjectCompilerKsp) + kspIosSimulatorArm64(libs.di.kotlinInjectCompilerKsp) +} -// 2. Configure code generation into each KMP target source set -//kspAndroid(libs.di.kotlinInjectCompilerKsp) -// kspIosX64("me.tatarka.inject:kotlin-inject-compiler-ksp:0.7.2") -// kspIosArm64("me.tatarka.inject:kotlin-inject-compiler-ksp:0.7.2") -// kspIosSimulatorArm64("me.tatarka.inject:kotlin-inject-compiler-ksp:0.7.2") +ksp { + arg("me.tatarka.inject.dumpGraph", "true") + arg("me.tatarka.inject.generateCompanionExtensions", "true") } diff --git a/composeApp/src/androidMain/kotlin/xyz/ksharma/krail/common/AndroidActivityComponent.kt b/composeApp/src/androidMain/kotlin/xyz/ksharma/krail/common/AndroidActivityComponent.kt new file mode 100644 index 00000000..0ed88902 --- /dev/null +++ b/composeApp/src/androidMain/kotlin/xyz/ksharma/krail/common/AndroidActivityComponent.kt @@ -0,0 +1,14 @@ +package xyz.ksharma.krail.common + +import android.app.Activity +import me.tatarka.inject.annotations.Component +import me.tatarka.inject.annotations.Provides + +//@ActivityScope +@Component +abstract class AndroidActivityComponent( + @get:Provides val activity: Activity, + @Component val applicationComponent: AndroidApplicationComponent, +) { + companion object +} diff --git a/composeApp/src/androidMain/kotlin/xyz/ksharma/krail/common/AndroidApplicationComponent.kt b/composeApp/src/androidMain/kotlin/xyz/ksharma/krail/common/AndroidApplicationComponent.kt new file mode 100644 index 00000000..9da5657b --- /dev/null +++ b/composeApp/src/androidMain/kotlin/xyz/ksharma/krail/common/AndroidApplicationComponent.kt @@ -0,0 +1,26 @@ +package xyz.ksharma.krail.common + +import android.app.Application +import me.tatarka.inject.annotations.Component +import me.tatarka.inject.annotations.Provides + +@Component +abstract class AndroidApplicationComponent( + @get:Provides val application: Application, +) { + protected val RealX.bind: X + @Provides get() = this + + companion object +} + +class RealX() : X{ + override fun a() { + println("RealX") + } + +} + +interface X { + fun a() +} diff --git a/composeApp/src/commonMain/kotlin/xyz/ksharma/krail/common/AppCoroutineDispatchers.kt b/composeApp/src/commonMain/kotlin/xyz/ksharma/krail/common/AppCoroutineDispatchers.kt new file mode 100644 index 00000000..92482cc4 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/xyz/ksharma/krail/common/AppCoroutineDispatchers.kt @@ -0,0 +1,11 @@ +package xyz.ksharma.krail.common + +import kotlinx.coroutines.CoroutineDispatcher + +data class AppCoroutineDispatchers( + val io: CoroutineDispatcher, + val databaseWrite: CoroutineDispatcher, + val databaseRead: CoroutineDispatcher, + val computation: CoroutineDispatcher, + val main: CoroutineDispatcher, +) diff --git a/composeApp/src/commonMain/kotlin/xyz/ksharma/krail/common/AppDispatchers.kt b/composeApp/src/commonMain/kotlin/xyz/ksharma/krail/common/AppDispatchers.kt new file mode 100644 index 00000000..d4e5f5a4 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/xyz/ksharma/krail/common/AppDispatchers.kt @@ -0,0 +1,28 @@ +package xyz.ksharma.krail.common + +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.IO +import kotlinx.coroutines.SupervisorJob +import me.tatarka.inject.annotations.Provides + +interface SharedApplicationComponent { + + val dispatchers: AppCoroutineDispatchers + + @ApplicationScope + @Provides + fun provideCoroutineDispatchers(): AppCoroutineDispatchers = AppCoroutineDispatchers( + io = Dispatchers.IO, + databaseWrite = Dispatchers.IO.limitedParallelism(1), + databaseRead = Dispatchers.IO.limitedParallelism(4), + computation = Dispatchers.Default, + main = Dispatchers.Main, + ) + + @ApplicationScope + @Provides + fun provideApplicationCoroutineScope( + dispatchers: AppCoroutineDispatchers, + ): ApplicationCoroutineScope = CoroutineScope(dispatchers.main + SupervisorJob()) +} diff --git a/composeApp/src/commonMain/kotlin/xyz/ksharma/krail/common/KrailApp.kt b/composeApp/src/commonMain/kotlin/xyz/ksharma/krail/common/KrailApp.kt index 478038fa..3869162b 100644 --- a/composeApp/src/commonMain/kotlin/xyz/ksharma/krail/common/KrailApp.kt +++ b/composeApp/src/commonMain/kotlin/xyz/ksharma/krail/common/KrailApp.kt @@ -3,6 +3,7 @@ package xyz.ksharma.krail.common import androidx.compose.material.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect +import xyz.ksharma.krail.sandook.createSandookComponent import xyz.ksharma.krail.taj.theme.KrailTheme import xyz.ksharma.krail.trip.planner.network.api.createNetworkComponent @@ -12,6 +13,10 @@ fun KrailApp() { LaunchedEffect(Unit) { val httpClient = createNetworkComponent().provideHttpClient() println("HTTP Client: ${httpClient.engine.config}") + + val sandookComponent = createSandookComponent() + sandookComponent.sandookDb.insertOrReplaceTheme(1) + println("Sandook component: ${sandookComponent.sandookDb}") } KrailTheme { diff --git a/composeApp/src/commonMain/kotlin/xyz/ksharma/krail/common/LazyExt.kt b/composeApp/src/commonMain/kotlin/xyz/ksharma/krail/common/LazyExt.kt new file mode 100644 index 00000000..4360da33 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/xyz/ksharma/krail/common/LazyExt.kt @@ -0,0 +1,4 @@ +package xyz.ksharma.krail.common + +@Suppress("NOTHING_TO_INLINE") +inline fun unsafeLazy(noinline initializer: () -> T): Lazy = lazy(LazyThreadSafetyMode.NONE, initializer) diff --git a/composeApp/src/commonMain/kotlin/xyz/ksharma/krail/common/Scope.kt b/composeApp/src/commonMain/kotlin/xyz/ksharma/krail/common/Scope.kt new file mode 100644 index 00000000..0793d174 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/xyz/ksharma/krail/common/Scope.kt @@ -0,0 +1,12 @@ +package xyz.ksharma.krail.common + +import kotlinx.coroutines.CoroutineScope +import me.tatarka.inject.annotations.Scope + +@Scope +annotation class ApplicationScope + +@Scope +annotation class ActivityScope + +typealias ApplicationCoroutineScope = CoroutineScope diff --git a/feature/trip-planner/network/build.gradle.kts b/feature/trip-planner/network/build.gradle.kts index 6c5e9f45..da38cd93 100644 --- a/feature/trip-planner/network/build.gradle.kts +++ b/feature/trip-planner/network/build.gradle.kts @@ -86,6 +86,11 @@ dependencies { kspIosSimulatorArm64(libs.di.kotlinInjectCompilerKsp) } +ksp { + arg("me.tatarka.inject.generateCompanionExtensions", "true") + arg("me.tatarka.inject.dumpGraph", "true") +} + // READ API KEY val localProperties = gradleLocalProperties(rootProject.rootDir, providers) val nswTransportApiKey: String = localProperties.getProperty("NSW_TRANSPORT_API_KEY", "") diff --git a/feature/trip-planner/network/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/network/api/NetworkComponent.kt b/feature/trip-planner/network/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/network/api/NetworkComponent.kt index 243870a8..9175e142 100644 --- a/feature/trip-planner/network/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/network/api/NetworkComponent.kt +++ b/feature/trip-planner/network/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/network/api/NetworkComponent.kt @@ -21,6 +21,8 @@ abstract class NetworkComponent { protected val NetworkRateLimiter.bind: RateLimiter @Provides get() = this + + companion object } @KmpComponentCreate diff --git a/sandook/build.gradle.kts b/sandook/build.gradle.kts index 542f710c..82019c45 100644 --- a/sandook/build.gradle.kts +++ b/sandook/build.gradle.kts @@ -71,6 +71,7 @@ dependencies { ksp { arg("me.tatarka.inject.generateCompanionExtensions", "true") + arg("me.tatarka.inject.dumpGraph", "true") } sqldelight { diff --git a/sandook/src/androidMain/kotlin/xyz/ksharma/krail/sandook/AndroidSandookDriverFactory.kt b/sandook/src/androidMain/kotlin/xyz/ksharma/krail/sandook/AndroidSandookDriverFactory.kt deleted file mode 100644 index 90a5eb1d..00000000 --- a/sandook/src/androidMain/kotlin/xyz/ksharma/krail/sandook/AndroidSandookDriverFactory.kt +++ /dev/null @@ -1,16 +0,0 @@ -package xyz.ksharma.krail.sandook - -import android.content.Context -import app.cash.sqldelight.db.SqlDriver -import app.cash.sqldelight.driver.android.AndroidSqliteDriver - -class AndroidSandookDriverFactory(private val context: Context) : SandookDriverFactory { - - override fun createDriver(): SqlDriver { - return AndroidSqliteDriver( - schema = KrailSandook.Schema, - context = context, - name = "krailSandook.db" - ) - } -} diff --git a/sandook/src/androidMain/kotlin/xyz/ksharma/krail/sandook/AndroidSandookModule.kt b/sandook/src/androidMain/kotlin/xyz/ksharma/krail/sandook/AndroidSandookModule.kt index d91b2099..d1a70edb 100644 --- a/sandook/src/androidMain/kotlin/xyz/ksharma/krail/sandook/AndroidSandookModule.kt +++ b/sandook/src/androidMain/kotlin/xyz/ksharma/krail/sandook/AndroidSandookModule.kt @@ -1,3 +1,4 @@ +/* package xyz.ksharma.krail.sandook import org.koin.android.ext.koin.androidContext @@ -16,3 +17,4 @@ val androidDbModule = module { ) } } +*/ diff --git a/sandook/src/androidMain/kotlin/xyz/ksharma/krail/sandook/SQLPlatformComponent.kt b/sandook/src/androidMain/kotlin/xyz/ksharma/krail/sandook/SQLPlatformComponent.kt new file mode 100644 index 00000000..f15a4ac3 --- /dev/null +++ b/sandook/src/androidMain/kotlin/xyz/ksharma/krail/sandook/SQLPlatformComponent.kt @@ -0,0 +1,25 @@ +@file:Suppress("EXPECT_ACTUAL_CLASSIFIERS_ARE_IN_BETA_WARNING") + +package xyz.ksharma.krail.sandook + +import android.app.Application +import app.cash.sqldelight.db.SqlDriver +import app.cash.sqldelight.driver.android.AndroidSqliteDriver +import me.tatarka.inject.annotations.Component +import me.tatarka.inject.annotations.Provides + +@Component +actual interface SQLPlatformComponent { + + @Provides + fun provideSQLDriver(application: Application): SqlDriver = + AndroidSqliteDriver( + schema = KrailSandook.Schema, + context = application, + name = "krailSandook.db", + ) + + companion object +} + +//actual fun sandookDriverFactory(): SandookDriverFactory = AndroidSandookDriverFactory(context = context) diff --git a/sandook/src/commonMain/kotlin/xyz/ksharma/krail/sandook/RealSandookDb.kt b/sandook/src/commonMain/kotlin/xyz/ksharma/krail/sandook/RealSandookDb.kt index e7c1a7fe..e5f1e052 100644 --- a/sandook/src/commonMain/kotlin/xyz/ksharma/krail/sandook/RealSandookDb.kt +++ b/sandook/src/commonMain/kotlin/xyz/ksharma/krail/sandook/RealSandookDb.kt @@ -1,8 +1,11 @@ package xyz.ksharma.krail.sandook -internal class RealSandookDb(factory: SandookDriverFactory) : SandookDb { +import me.tatarka.inject.annotations.Inject - private val database = KrailSandook(factory.createDriver()) +@Inject +internal class RealSandookDb(factory: SandookFactory) : SandookDb { + + private val database = factory.build() private val query = database.krailSandookQueries // region Theme diff --git a/sandook/src/commonMain/kotlin/xyz/ksharma/krail/sandook/SQLComponent.kt b/sandook/src/commonMain/kotlin/xyz/ksharma/krail/sandook/SQLComponent.kt new file mode 100644 index 00000000..da6529c9 --- /dev/null +++ b/sandook/src/commonMain/kotlin/xyz/ksharma/krail/sandook/SQLComponent.kt @@ -0,0 +1,20 @@ +@file:Suppress("EXPECT_ACTUAL_CLASSIFIERS_ARE_IN_BETA_WARNING") + +package xyz.ksharma.krail.sandook + +import me.tatarka.inject.annotations.Component +import me.tatarka.inject.annotations.Provides + +expect interface SQLPlatformComponent + +@Component +interface SQLComponent : SQLPlatformComponent { + + // @ApplicationScope + @Provides + fun provideSqlDelightDatabase( + factory: SandookFactory, + ): KrailSandook = factory.build() + + companion object +} diff --git a/sandook/src/commonMain/kotlin/xyz/ksharma/krail/sandook/SandookComponent.kt b/sandook/src/commonMain/kotlin/xyz/ksharma/krail/sandook/SandookComponent.kt new file mode 100644 index 00000000..d760b329 --- /dev/null +++ b/sandook/src/commonMain/kotlin/xyz/ksharma/krail/sandook/SandookComponent.kt @@ -0,0 +1,19 @@ +package xyz.ksharma.krail.sandook + +import me.tatarka.inject.annotations.Component +import me.tatarka.inject.annotations.KmpComponentCreate +import me.tatarka.inject.annotations.Provides + +@Component +abstract class SandookComponent : SQLPlatformComponent { + + internal val RealSandookDb.bind: SandookDb + @Provides get() = this + + abstract val sandookDb: SandookDb + + companion object +} + +@KmpComponentCreate +expect fun createSandookComponent(): SandookComponent diff --git a/sandook/src/commonMain/kotlin/xyz/ksharma/krail/sandook/SandookDriverFactory.kt b/sandook/src/commonMain/kotlin/xyz/ksharma/krail/sandook/SandookDriverFactory.kt deleted file mode 100644 index 678a5186..00000000 --- a/sandook/src/commonMain/kotlin/xyz/ksharma/krail/sandook/SandookDriverFactory.kt +++ /dev/null @@ -1,7 +0,0 @@ -package xyz.ksharma.krail.sandook - -import app.cash.sqldelight.db.SqlDriver - -interface SandookDriverFactory { - fun createDriver(): SqlDriver -} diff --git a/sandook/src/commonMain/kotlin/xyz/ksharma/krail/sandook/SandookFactory.kt b/sandook/src/commonMain/kotlin/xyz/ksharma/krail/sandook/SandookFactory.kt new file mode 100644 index 00000000..36f4ed12 --- /dev/null +++ b/sandook/src/commonMain/kotlin/xyz/ksharma/krail/sandook/SandookFactory.kt @@ -0,0 +1,11 @@ +package xyz.ksharma.krail.sandook + +import app.cash.sqldelight.db.SqlDriver +import me.tatarka.inject.annotations.Inject + +@Inject +class SandookFactory(private val driver: SqlDriver) { + fun build(): KrailSandook = KrailSandook(driver = driver) +} + +//expect fun sandookDriverFactory(): SandookDriverFactory diff --git a/sandook/src/commonMain/kotlin/xyz/ksharma/krail/sandook/di/SandookComponent.kt b/sandook/src/commonMain/kotlin/xyz/ksharma/krail/sandook/di/SandookComponent.kt deleted file mode 100644 index 70bc0d15..00000000 --- a/sandook/src/commonMain/kotlin/xyz/ksharma/krail/sandook/di/SandookComponent.kt +++ /dev/null @@ -1,24 +0,0 @@ -/* -package xyz.ksharma.krail.sandook.di - -import me.tatarka.inject.annotations.Component -import me.tatarka.inject.annotations.Provides -import xyz.ksharma.krail.sandook.RealSandook -import xyz.ksharma.krail.sandook.Sandook - -@Singleton -@Component -abstract class SandookComponent { - - internal val RealSandook.bind: Sandook - @Singleton - @Provides get() = this - - companion object -} - -*/ -/* -@KmpComponentCreate -expect fun createSandookComponent(): SandookComponent -*/ diff --git a/sandook/src/iosMain/kotlin/xyz/ksharma/krail/sandook/IosSandookDriverFactory.kt b/sandook/src/iosMain/kotlin/xyz/ksharma/krail/sandook/IosSandookDriverFactory.kt deleted file mode 100644 index f63d1fdf..00000000 --- a/sandook/src/iosMain/kotlin/xyz/ksharma/krail/sandook/IosSandookDriverFactory.kt +++ /dev/null @@ -1,11 +0,0 @@ -package xyz.ksharma.krail.sandook - -import app.cash.sqldelight.db.SqlDriver -import app.cash.sqldelight.driver.native.NativeSqliteDriver - -class IosSandookDriverFactory : SandookDriverFactory { - - override fun createDriver(): SqlDriver { - return NativeSqliteDriver(schema = KrailSandook.Schema, name = "krailSandook.db") - } -} diff --git a/sandook/src/iosMain/kotlin/xyz/ksharma/krail/sandook/SQLPlatformComponent.kt b/sandook/src/iosMain/kotlin/xyz/ksharma/krail/sandook/SQLPlatformComponent.kt new file mode 100644 index 00000000..f3a10f13 --- /dev/null +++ b/sandook/src/iosMain/kotlin/xyz/ksharma/krail/sandook/SQLPlatformComponent.kt @@ -0,0 +1,18 @@ +@file:Suppress("EXPECT_ACTUAL_CLASSIFIERS_ARE_IN_BETA_WARNING") + +package xyz.ksharma.krail.sandook + +import app.cash.sqldelight.db.SqlDriver +import app.cash.sqldelight.driver.native.NativeSqliteDriver +import me.tatarka.inject.annotations.Component +import me.tatarka.inject.annotations.Provides + +@Component +actual interface SQLPlatformComponent { + + @Provides + fun provideSQLDriver(): SqlDriver = + NativeSqliteDriver(schema = KrailSandook.Schema, name = "krailSandook.db") + + companion object +} From 7f7a154e4a0cba1605a3eb26322b0bed91e7bf08 Mon Sep 17 00:00:00 2001 From: Karan Sharma <55722391+ksharma-xyz@users.noreply.github.com> Date: Sat, 23 Nov 2024 11:04:14 +1100 Subject: [PATCH 60/67] DI - Koin Try again --- android-app/build.gradle.kts | 5 -- android-app/src/main/AndroidManifest.xml | 4 +- .../xyz/ksharma/krail/KrailApplication.kt | 17 ---- composeApp/build.gradle.kts | 73 ++++++++++-------- .../src/androidMain/AndroidManifest.xml | 31 ++++++++ .../xyz/ksharma/krail/KrailApplication.kt | 15 ++++ .../kotlin/xyz/ksharma/krail/MainActivity.kt | 2 +- .../krail/{common => }/Platform-android.kt | 2 +- .../krail/common/AndroidActivityComponent.kt | 14 ---- .../common/AndroidApplicationComponent.kt | 26 ------- .../xyz/ksharma/krail/di/AppModule.android.kt | 11 +++ composeApp/src/androidMain/proguard-rules.pro | 65 ++++++++++++++++ .../res/color/gradient_background.xml | 10 +++ .../res/drawable/ic_launcher_background.xml | 11 +++ .../res/drawable/ic_launcher_foreground.xml | 9 +++ .../res/mipmap-anydpi-v26/ic_launcher.xml | 6 ++ .../mipmap-anydpi-v26/ic_launcher_round.xml | 6 ++ .../androidMain/res/values-night/colors.xml | 5 ++ .../src/androidMain/res/values/colors.xml | 5 ++ .../src/androidMain/res/values/strings.xml | 3 + .../src/androidMain/res/values/themes.xml | 7 ++ .../src/androidMain/res/xml/backup_rules.xml | 13 ++++ .../res/xml/data_extraction_rules.xml | 19 +++++ .../{common => }/AppCoroutineDispatchers.kt | 2 +- .../kotlin/xyz/ksharma/krail/KrailApp.kt | 15 ++++ .../krail/{common => }/KrailNavHost.kt | 15 ++-- .../ksharma/krail/{common => }/Platform.kt | 2 +- .../ksharma/krail/common/AppDispatchers.kt | 28 ------- .../xyz/ksharma/krail/common/KrailApp.kt | 26 ------- .../xyz/ksharma/krail/common/LazyExt.kt | 4 - .../kotlin/xyz/ksharma/krail/common/Scope.kt | 12 --- .../di/KoinAppModule.kt => di/AppModule.kt} | 16 ++-- .../krail/{common => }/splash/SplashScreen.kt | 2 +- .../{common => }/splash/SplashViewModel.kt | 10 +-- .../krail/{common => }/MainViewController.kt | 2 +- .../krail/{common => }/Platform.ios.kt | 2 +- .../xyz/ksharma/krail/di/AppModule.ios.kt | 7 ++ feature/trip-planner/network/build.gradle.kts | 3 +- feature/trip-planner/ui/build.gradle.kts | 2 +- .../ui/savedtrips/SavedTripsDestination.kt | 5 +- .../ui/savedtrips/SavedTripsViewModel.kt | 33 +++++--- .../ui/searchstop/SearchStopDestination.kt | 5 +- .../ui/timetable/TimeTableDestination.kt | 6 +- .../ui/timetable/TimeTableViewModel.kt | 20 +++-- .../ui/usualride/UsualRideDestination.kt | 7 +- .../ui/usualride/UsualRideViewModel.kt | 8 +- .../KotlinMultiplatformConventionPlugin.kt | 10 +++ gradle/libs.versions.toml | 7 ++ .../UserInterfaceState.xcuserstate | Bin 0 -> 14462 bytes .../xcschemes/xcschememanagement.plist | 14 ++++ sandook/build.gradle.kts | 9 +-- .../sandook/AndroidSandookDriverFactory.kt | 15 ++++ .../krail/sandook/AndroidSandookModule.kt | 20 ----- .../krail/sandook/SQLPlatformComponent.kt | 25 ------ .../krail/sandook/di/AndroidSandookModule.kt | 21 +++++ .../xyz/ksharma/krail/sandook/DbModule.kt | 10 --- .../xyz/ksharma/krail/sandook/RealSandook.kt | 71 +++++++++-------- .../ksharma/krail/sandook/RealSandookDb.kt | 59 -------------- .../xyz/ksharma/krail/sandook/SQLComponent.kt | 20 ----- .../xyz/ksharma/krail/sandook/Sandook.kt | 47 +++++------ .../ksharma/krail/sandook/SandookComponent.kt | 19 ----- .../xyz/ksharma/krail/sandook/SandookDb.kt | 24 ------ .../ksharma/krail/sandook/SandookFactory.kt | 8 +- .../ksharma/krail/sandook/di/SandookModule.kt | 15 ++++ .../xyz/ksharma/krail/sandook/di/Singleton.kt | 8 -- .../krail/sandook/IosSandookDriverFactory.kt | 10 +++ .../krail/sandook/SQLPlatformComponent.kt | 18 ----- .../krail/sandook/di/IosSandookModule.kt | 16 ++++ settings.gradle.kts | 2 +- 69 files changed, 519 insertions(+), 520 deletions(-) delete mode 100644 android-app/src/main/kotlin/xyz/ksharma/krail/KrailApplication.kt create mode 100644 composeApp/src/androidMain/AndroidManifest.xml create mode 100644 composeApp/src/androidMain/kotlin/xyz/ksharma/krail/KrailApplication.kt rename {android-app/src/main => composeApp/src/androidMain}/kotlin/xyz/ksharma/krail/MainActivity.kt (95%) rename composeApp/src/androidMain/kotlin/xyz/ksharma/krail/{common => }/Platform-android.kt (84%) delete mode 100644 composeApp/src/androidMain/kotlin/xyz/ksharma/krail/common/AndroidActivityComponent.kt delete mode 100644 composeApp/src/androidMain/kotlin/xyz/ksharma/krail/common/AndroidApplicationComponent.kt create mode 100644 composeApp/src/androidMain/kotlin/xyz/ksharma/krail/di/AppModule.android.kt create mode 100644 composeApp/src/androidMain/proguard-rules.pro create mode 100644 composeApp/src/androidMain/res/color/gradient_background.xml create mode 100644 composeApp/src/androidMain/res/drawable/ic_launcher_background.xml create mode 100644 composeApp/src/androidMain/res/drawable/ic_launcher_foreground.xml create mode 100644 composeApp/src/androidMain/res/mipmap-anydpi-v26/ic_launcher.xml create mode 100644 composeApp/src/androidMain/res/mipmap-anydpi-v26/ic_launcher_round.xml create mode 100644 composeApp/src/androidMain/res/values-night/colors.xml create mode 100644 composeApp/src/androidMain/res/values/colors.xml create mode 100644 composeApp/src/androidMain/res/values/strings.xml create mode 100644 composeApp/src/androidMain/res/values/themes.xml create mode 100644 composeApp/src/androidMain/res/xml/backup_rules.xml create mode 100644 composeApp/src/androidMain/res/xml/data_extraction_rules.xml rename composeApp/src/commonMain/kotlin/xyz/ksharma/krail/{common => }/AppCoroutineDispatchers.kt (89%) create mode 100644 composeApp/src/commonMain/kotlin/xyz/ksharma/krail/KrailApp.kt rename composeApp/src/commonMain/kotlin/xyz/ksharma/krail/{common => }/KrailNavHost.kt (94%) rename composeApp/src/commonMain/kotlin/xyz/ksharma/krail/{common => }/Platform.kt (71%) delete mode 100644 composeApp/src/commonMain/kotlin/xyz/ksharma/krail/common/AppDispatchers.kt delete mode 100644 composeApp/src/commonMain/kotlin/xyz/ksharma/krail/common/KrailApp.kt delete mode 100644 composeApp/src/commonMain/kotlin/xyz/ksharma/krail/common/LazyExt.kt delete mode 100644 composeApp/src/commonMain/kotlin/xyz/ksharma/krail/common/Scope.kt rename composeApp/src/commonMain/kotlin/xyz/ksharma/krail/{common/di/KoinAppModule.kt => di/AppModule.kt} (53%) rename composeApp/src/commonMain/kotlin/xyz/ksharma/krail/{common => }/splash/SplashScreen.kt (99%) rename composeApp/src/commonMain/kotlin/xyz/ksharma/krail/{common => }/splash/SplashViewModel.kt (84%) rename composeApp/src/iosMain/kotlin/xyz/ksharma/krail/{common => }/MainViewController.kt (79%) rename composeApp/src/iosMain/kotlin/xyz/ksharma/krail/{common => }/Platform.ios.kt (87%) create mode 100644 composeApp/src/iosMain/kotlin/xyz/ksharma/krail/di/AppModule.ios.kt create mode 100644 iosApp/iosApp.xcodeproj/project.xcworkspace/xcuserdata/parora.xcuserdatad/UserInterfaceState.xcuserstate create mode 100644 iosApp/iosApp.xcodeproj/xcuserdata/parora.xcuserdatad/xcschemes/xcschememanagement.plist create mode 100644 sandook/src/androidMain/kotlin/xyz/ksharma/krail/sandook/AndroidSandookDriverFactory.kt delete mode 100644 sandook/src/androidMain/kotlin/xyz/ksharma/krail/sandook/AndroidSandookModule.kt delete mode 100644 sandook/src/androidMain/kotlin/xyz/ksharma/krail/sandook/SQLPlatformComponent.kt create mode 100644 sandook/src/androidMain/kotlin/xyz/ksharma/krail/sandook/di/AndroidSandookModule.kt delete mode 100644 sandook/src/commonMain/kotlin/xyz/ksharma/krail/sandook/DbModule.kt delete mode 100644 sandook/src/commonMain/kotlin/xyz/ksharma/krail/sandook/RealSandookDb.kt delete mode 100644 sandook/src/commonMain/kotlin/xyz/ksharma/krail/sandook/SQLComponent.kt delete mode 100644 sandook/src/commonMain/kotlin/xyz/ksharma/krail/sandook/SandookComponent.kt delete mode 100644 sandook/src/commonMain/kotlin/xyz/ksharma/krail/sandook/SandookDb.kt create mode 100644 sandook/src/commonMain/kotlin/xyz/ksharma/krail/sandook/di/SandookModule.kt delete mode 100644 sandook/src/commonMain/kotlin/xyz/ksharma/krail/sandook/di/Singleton.kt create mode 100644 sandook/src/iosMain/kotlin/xyz/ksharma/krail/sandook/IosSandookDriverFactory.kt delete mode 100644 sandook/src/iosMain/kotlin/xyz/ksharma/krail/sandook/SQLPlatformComponent.kt create mode 100644 sandook/src/iosMain/kotlin/xyz/ksharma/krail/sandook/di/IosSandookModule.kt diff --git a/android-app/build.gradle.kts b/android-app/build.gradle.kts index 7853002a..ea91a28c 100644 --- a/android-app/build.gradle.kts +++ b/android-app/build.gradle.kts @@ -1,9 +1,5 @@ plugins { - alias(libs.plugins.krail.android.application) alias(libs.plugins.krail.kotlin.android) - alias(libs.plugins.krail.compose.multiplatform) - alias(libs.plugins.kotlin.serialization) - alias(libs.plugins.compose.compiler) } android { @@ -64,5 +60,4 @@ dependencies { implementation(libs.kotlinx.serialization.json) implementation(libs.lifecycle.runtime.ktx) - implementation(libs.di.koinAndroid) } diff --git a/android-app/src/main/AndroidManifest.xml b/android-app/src/main/AndroidManifest.xml index c4400b34..8226f6db 100644 --- a/android-app/src/main/AndroidManifest.xml +++ b/android-app/src/main/AndroidManifest.xml @@ -5,7 +5,7 @@ diff --git a/android-app/src/main/kotlin/xyz/ksharma/krail/KrailApplication.kt b/android-app/src/main/kotlin/xyz/ksharma/krail/KrailApplication.kt deleted file mode 100644 index ce65dbc5..00000000 --- a/android-app/src/main/kotlin/xyz/ksharma/krail/KrailApplication.kt +++ /dev/null @@ -1,17 +0,0 @@ -package xyz.ksharma.krail - -import android.app.Application -import xyz.ksharma.krail.common.AndroidApplicationComponent - -class KrailApplication : Application() { - -/* - val component: AndroidApplicationComponent by lazy { - AndroidApplicationComponent::class.create(this) - } -*/ - - override fun onCreate() { - super.onCreate() - } -} diff --git a/composeApp/build.gradle.kts b/composeApp/build.gradle.kts index e0cca089..8b476c73 100644 --- a/composeApp/build.gradle.kts +++ b/composeApp/build.gradle.kts @@ -2,13 +2,47 @@ import org.jetbrains.kotlin.gradle.ExperimentalKotlinGradlePluginApi import org.jetbrains.kotlin.gradle.dsl.JvmTarget android { - namespace = "xyz.ksharma.krail.common" + namespace = "xyz.ksharma.krail" + + defaultConfig { + applicationId = "xyz.ksharma.krail" + versionCode = 12 + versionName = "1.0-alpha03" + + testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" + vectorDrawables { + useSupportLibrary = true + } + } + + buildTypes { + + debug { + applicationIdSuffix = ".debug" + isDebuggable = true + ndk { + isDebuggable = true + debugSymbolLevel = "FULL" + } + } + + release { + isMinifyEnabled = true + isDebuggable = false + isShrinkResources = true + ndk { + isDebuggable = false + debugSymbolLevel = "FULL" + } + proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro") + } + } } plugins { alias(libs.plugins.krail.kotlin.multiplatform) alias(libs.plugins.krail.compose.multiplatform) - alias(libs.plugins.krail.android.library) + alias(libs.plugins.krail.android.application) alias(libs.plugins.compose.compiler) alias(libs.plugins.kotlin.serialization) alias(libs.plugins.ksp) @@ -38,34 +72,20 @@ kotlin { dependencies { implementation(compose.preview) implementation(libs.activity.compose) - - // Projects - /* - implementation(projects.core.network) - implementation(projects.core.utils) - implementation(projects.feature.tripPlanner.network.api) - implementation(projects.feature.tripPlanner.network.real) - implementation(projects.feature.tripPlanner.state) - implementation(projects.feature.tripPlanner.ui) - implementation(projects.sandook.api) - implementation(projects.sandook.real) - */ implementation(compose.foundation) implementation(libs.core.ktx) implementation(libs.kotlinx.serialization.json) implementation(libs.lifecycle.runtime.ktx) - - implementation(libs.di.koinAndroid) + api(libs.di.koinAndroid) } - } commonMain.dependencies { implementation(projects.taj) implementation(projects.sandook) implementation(projects.feature.tripPlanner.network) -// implementation(projects.feature.tripPlanner.ui) -// implementation(projects.feature.tripPlanner.state) + implementation(projects.feature.tripPlanner.ui) + implementation(projects.feature.tripPlanner.state) implementation(libs.navigation.compose) @@ -85,22 +105,13 @@ kotlin { implementation(libs.ktor.client.logging) implementation(libs.ktor.serialization.kotlinx.json) - implementation(libs.di.koinComposeViewmodelNav) + api(libs.di.koinComposeViewmodel) + implementation(libs.di.kotlinInjectRuntime) } } } dependencies { - // 1. Configure code generation into the common source set - kspCommonMainMetadata(libs.di.kotlinInjectRuntime) - // 2. Configure code generation into each KMP target source set - //kspAndroid(libs.di.kotlinInjectCompilerKsp) - kspIosArm64(libs.di.kotlinInjectCompilerKsp) - kspIosSimulatorArm64(libs.di.kotlinInjectCompilerKsp) -} - -ksp { - arg("me.tatarka.inject.dumpGraph", "true") - arg("me.tatarka.inject.generateCompanionExtensions", "true") + implementation(projects.sandook) } diff --git a/composeApp/src/androidMain/AndroidManifest.xml b/composeApp/src/androidMain/AndroidManifest.xml new file mode 100644 index 00000000..c4400b34 --- /dev/null +++ b/composeApp/src/androidMain/AndroidManifest.xml @@ -0,0 +1,31 @@ + + + + + + + + + + + + + + + + diff --git a/composeApp/src/androidMain/kotlin/xyz/ksharma/krail/KrailApplication.kt b/composeApp/src/androidMain/kotlin/xyz/ksharma/krail/KrailApplication.kt new file mode 100644 index 00000000..20378d13 --- /dev/null +++ b/composeApp/src/androidMain/kotlin/xyz/ksharma/krail/KrailApplication.kt @@ -0,0 +1,15 @@ +package xyz.ksharma.krail + +import android.app.Application + +class KrailApplication : Application() { + + override fun onCreate() { + super.onCreate() + instance = this + } + + companion object { + var instance : Application? = null + } +} diff --git a/android-app/src/main/kotlin/xyz/ksharma/krail/MainActivity.kt b/composeApp/src/androidMain/kotlin/xyz/ksharma/krail/MainActivity.kt similarity index 95% rename from android-app/src/main/kotlin/xyz/ksharma/krail/MainActivity.kt rename to composeApp/src/androidMain/kotlin/xyz/ksharma/krail/MainActivity.kt index 887df763..6c3fafc2 100644 --- a/android-app/src/main/kotlin/xyz/ksharma/krail/MainActivity.kt +++ b/composeApp/src/androidMain/kotlin/xyz/ksharma/krail/MainActivity.kt @@ -4,7 +4,7 @@ import android.os.Bundle import androidx.activity.ComponentActivity import androidx.activity.compose.setContent import androidx.activity.enableEdgeToEdge -import xyz.ksharma.krail.common.KrailApp +import xyz.ksharma.krail.KrailApp class MainActivity : ComponentActivity() { diff --git a/composeApp/src/androidMain/kotlin/xyz/ksharma/krail/common/Platform-android.kt b/composeApp/src/androidMain/kotlin/xyz/ksharma/krail/Platform-android.kt similarity index 84% rename from composeApp/src/androidMain/kotlin/xyz/ksharma/krail/common/Platform-android.kt rename to composeApp/src/androidMain/kotlin/xyz/ksharma/krail/Platform-android.kt index e8d01c6e..0129e774 100644 --- a/composeApp/src/androidMain/kotlin/xyz/ksharma/krail/common/Platform-android.kt +++ b/composeApp/src/androidMain/kotlin/xyz/ksharma/krail/Platform-android.kt @@ -1,4 +1,4 @@ -package xyz.ksharma.krail.common +package xyz.ksharma.krail import android.os.Build diff --git a/composeApp/src/androidMain/kotlin/xyz/ksharma/krail/common/AndroidActivityComponent.kt b/composeApp/src/androidMain/kotlin/xyz/ksharma/krail/common/AndroidActivityComponent.kt deleted file mode 100644 index 0ed88902..00000000 --- a/composeApp/src/androidMain/kotlin/xyz/ksharma/krail/common/AndroidActivityComponent.kt +++ /dev/null @@ -1,14 +0,0 @@ -package xyz.ksharma.krail.common - -import android.app.Activity -import me.tatarka.inject.annotations.Component -import me.tatarka.inject.annotations.Provides - -//@ActivityScope -@Component -abstract class AndroidActivityComponent( - @get:Provides val activity: Activity, - @Component val applicationComponent: AndroidApplicationComponent, -) { - companion object -} diff --git a/composeApp/src/androidMain/kotlin/xyz/ksharma/krail/common/AndroidApplicationComponent.kt b/composeApp/src/androidMain/kotlin/xyz/ksharma/krail/common/AndroidApplicationComponent.kt deleted file mode 100644 index 9da5657b..00000000 --- a/composeApp/src/androidMain/kotlin/xyz/ksharma/krail/common/AndroidApplicationComponent.kt +++ /dev/null @@ -1,26 +0,0 @@ -package xyz.ksharma.krail.common - -import android.app.Application -import me.tatarka.inject.annotations.Component -import me.tatarka.inject.annotations.Provides - -@Component -abstract class AndroidApplicationComponent( - @get:Provides val application: Application, -) { - protected val RealX.bind: X - @Provides get() = this - - companion object -} - -class RealX() : X{ - override fun a() { - println("RealX") - } - -} - -interface X { - fun a() -} diff --git a/composeApp/src/androidMain/kotlin/xyz/ksharma/krail/di/AppModule.android.kt b/composeApp/src/androidMain/kotlin/xyz/ksharma/krail/di/AppModule.android.kt new file mode 100644 index 00000000..758abdee --- /dev/null +++ b/composeApp/src/androidMain/kotlin/xyz/ksharma/krail/di/AppModule.android.kt @@ -0,0 +1,11 @@ +package xyz.ksharma.krail.di + +import org.koin.android.ext.koin.androidContext +import org.koin.android.ext.koin.androidLogger +import org.koin.dsl.koinConfiguration +import xyz.ksharma.krail.KrailApplication + +actual fun nativeConfig() = koinConfiguration { + androidLogger() + androidContext(KrailApplication.instance ?: error("No Android application context set")) +} diff --git a/composeApp/src/androidMain/proguard-rules.pro b/composeApp/src/androidMain/proguard-rules.pro new file mode 100644 index 00000000..1b92f1c8 --- /dev/null +++ b/composeApp/src/androidMain/proguard-rules.pro @@ -0,0 +1,65 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile + +#Need this to keep serializable members as is +-keepclassmembers class * implements java.io.Serializable { + static final long serialVersionUID; + private static final java.io.ObjectStreamField[] serialPersistentFields; + private void writeObject(java.io.ObjectOutputStream); + private void readObject(java.io.ObjectInputStream); + java.lang.Object writeReplace(); + java.lang.Object readResolve(); +} + + +# For native methods, see http://proguard.sourceforge.net/manual/examples.html#native +-keepclasseswithmembernames class * { + native ; +} + +#This will print mappings - very useful for troubleshooting. +-printseeds ./build/seeds.txt +-printusage ./build/unused.txt +-printmapping ./build/mapping.txt + +#Some recommended settings for running with Android +-keep public class * extends android.app.Activity +-keep public class * extends android.app.Application + +-keep public enum * {} +-keep public interface * {} + +-keepattributes *Annotation* + +-keepclassmembers,allowobfuscation class * { + @javax.inject.* *; + @dagger.* *; + (); +} + + +-keepattributes *Annotation*,Signature +-dontwarn retrofit.** +-keep class retrofit.** { *; } +-keepclasseswithmembers class * { + @retrofit.* ; +} diff --git a/composeApp/src/androidMain/res/color/gradient_background.xml b/composeApp/src/androidMain/res/color/gradient_background.xml new file mode 100644 index 00000000..7ce4c3e7 --- /dev/null +++ b/composeApp/src/androidMain/res/color/gradient_background.xml @@ -0,0 +1,10 @@ + + diff --git a/composeApp/src/androidMain/res/drawable/ic_launcher_background.xml b/composeApp/src/androidMain/res/drawable/ic_launcher_background.xml new file mode 100644 index 00000000..532196b3 --- /dev/null +++ b/composeApp/src/androidMain/res/drawable/ic_launcher_background.xml @@ -0,0 +1,11 @@ + + + + diff --git a/composeApp/src/androidMain/res/drawable/ic_launcher_foreground.xml b/composeApp/src/androidMain/res/drawable/ic_launcher_foreground.xml new file mode 100644 index 00000000..e686afd5 --- /dev/null +++ b/composeApp/src/androidMain/res/drawable/ic_launcher_foreground.xml @@ -0,0 +1,9 @@ + + + diff --git a/composeApp/src/androidMain/res/mipmap-anydpi-v26/ic_launcher.xml b/composeApp/src/androidMain/res/mipmap-anydpi-v26/ic_launcher.xml new file mode 100644 index 00000000..b3e26b4c --- /dev/null +++ b/composeApp/src/androidMain/res/mipmap-anydpi-v26/ic_launcher.xml @@ -0,0 +1,6 @@ + + + + + + diff --git a/composeApp/src/androidMain/res/mipmap-anydpi-v26/ic_launcher_round.xml b/composeApp/src/androidMain/res/mipmap-anydpi-v26/ic_launcher_round.xml new file mode 100644 index 00000000..b3e26b4c --- /dev/null +++ b/composeApp/src/androidMain/res/mipmap-anydpi-v26/ic_launcher_round.xml @@ -0,0 +1,6 @@ + + + + + + diff --git a/composeApp/src/androidMain/res/values-night/colors.xml b/composeApp/src/androidMain/res/values-night/colors.xml new file mode 100644 index 00000000..fc622448 --- /dev/null +++ b/composeApp/src/androidMain/res/values-night/colors.xml @@ -0,0 +1,5 @@ + + + #1F1B16 + #EAE1D9 + diff --git a/composeApp/src/androidMain/res/values/colors.xml b/composeApp/src/androidMain/res/values/colors.xml new file mode 100644 index 00000000..18779681 --- /dev/null +++ b/composeApp/src/androidMain/res/values/colors.xml @@ -0,0 +1,5 @@ + + + #FFFBFF + #1F1B16 + diff --git a/composeApp/src/androidMain/res/values/strings.xml b/composeApp/src/androidMain/res/values/strings.xml new file mode 100644 index 00000000..53906e3d --- /dev/null +++ b/composeApp/src/androidMain/res/values/strings.xml @@ -0,0 +1,3 @@ + + Krail App + diff --git a/composeApp/src/androidMain/res/values/themes.xml b/composeApp/src/androidMain/res/values/themes.xml new file mode 100644 index 00000000..931e639d --- /dev/null +++ b/composeApp/src/androidMain/res/values/themes.xml @@ -0,0 +1,7 @@ + + + + diff --git a/composeApp/src/androidMain/res/xml/backup_rules.xml b/composeApp/src/androidMain/res/xml/backup_rules.xml new file mode 100644 index 00000000..fa0f996d --- /dev/null +++ b/composeApp/src/androidMain/res/xml/backup_rules.xml @@ -0,0 +1,13 @@ + + + + \ No newline at end of file diff --git a/composeApp/src/androidMain/res/xml/data_extraction_rules.xml b/composeApp/src/androidMain/res/xml/data_extraction_rules.xml new file mode 100644 index 00000000..9ee9997b --- /dev/null +++ b/composeApp/src/androidMain/res/xml/data_extraction_rules.xml @@ -0,0 +1,19 @@ + + + + + + + \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/xyz/ksharma/krail/common/AppCoroutineDispatchers.kt b/composeApp/src/commonMain/kotlin/xyz/ksharma/krail/AppCoroutineDispatchers.kt similarity index 89% rename from composeApp/src/commonMain/kotlin/xyz/ksharma/krail/common/AppCoroutineDispatchers.kt rename to composeApp/src/commonMain/kotlin/xyz/ksharma/krail/AppCoroutineDispatchers.kt index 92482cc4..1b30bfea 100644 --- a/composeApp/src/commonMain/kotlin/xyz/ksharma/krail/common/AppCoroutineDispatchers.kt +++ b/composeApp/src/commonMain/kotlin/xyz/ksharma/krail/AppCoroutineDispatchers.kt @@ -1,4 +1,4 @@ -package xyz.ksharma.krail.common +package xyz.ksharma.krail import kotlinx.coroutines.CoroutineDispatcher diff --git a/composeApp/src/commonMain/kotlin/xyz/ksharma/krail/KrailApp.kt b/composeApp/src/commonMain/kotlin/xyz/ksharma/krail/KrailApp.kt new file mode 100644 index 00000000..802d1692 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/xyz/ksharma/krail/KrailApp.kt @@ -0,0 +1,15 @@ +package xyz.ksharma.krail + +import androidx.compose.runtime.Composable +import org.koin.compose.KoinApplication +import xyz.ksharma.krail.di.koinConfig +import xyz.ksharma.krail.taj.theme.KrailTheme + +@Composable +fun KrailApp() { + KoinApplication(application = koinConfig) { + KrailTheme { + KrailNavHost() + } + } +} diff --git a/composeApp/src/commonMain/kotlin/xyz/ksharma/krail/common/KrailNavHost.kt b/composeApp/src/commonMain/kotlin/xyz/ksharma/krail/KrailNavHost.kt similarity index 94% rename from composeApp/src/commonMain/kotlin/xyz/ksharma/krail/common/KrailNavHost.kt rename to composeApp/src/commonMain/kotlin/xyz/ksharma/krail/KrailNavHost.kt index d844c7e0..c5e43cdc 100644 --- a/composeApp/src/commonMain/kotlin/xyz/ksharma/krail/common/KrailNavHost.kt +++ b/composeApp/src/commonMain/kotlin/xyz/ksharma/krail/KrailNavHost.kt @@ -1,5 +1,5 @@ -/* -package xyz.ksharma.krail.common + +package xyz.ksharma.krail import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.runtime.Composable @@ -16,9 +16,8 @@ import androidx.navigation.compose.composable import androidx.navigation.compose.rememberNavController import kotlinx.serialization.Serializable import org.koin.compose.viewmodel.koinViewModel -import org.koin.core.annotation.KoinExperimentalAPI -import xyz.ksharma.krail.common.splash.SplashScreen -import xyz.ksharma.krail.common.splash.SplashViewModel +import xyz.ksharma.krail.splash.SplashScreen +import xyz.ksharma.krail.splash.SplashViewModel import xyz.ksharma.krail.taj.LocalThemeColor import xyz.ksharma.krail.taj.LocalThemeContentColor import xyz.ksharma.krail.taj.theme.KrailTheme @@ -30,7 +29,6 @@ import xyz.ksharma.krail.trip.planner.ui.navigation.SavedTripsRoute import xyz.ksharma.krail.trip.planner.ui.navigation.UsualRideRoute import xyz.ksharma.krail.trip.planner.ui.navigation.tripPlannerDestinations -*/ /** * TODO - I don't like [NavHost] defined in app module, I would love to refactor it to :core:navigation module * but that results in a cyclic dependency. Feature module needs to depend on :core:navigation for navigation logic and @@ -41,9 +39,7 @@ import xyz.ksharma.krail.trip.planner.ui.navigation.tripPlannerDestinations * Navigation logic is currently taken from [NowInAndroid](https://github.com/android/nowinandroid] app, * so fine for now. But I will want to refactor it to something nicer e.g. using Circuit library * from Slack, but that would also mean refactoring to use MVP instead of MVVM. - *//* - -@OptIn(KoinExperimentalAPI::class) + */ @Composable fun KrailNavHost(modifier: Modifier = Modifier) { val navController = rememberNavController() @@ -102,4 +98,3 @@ fun KrailNavHost(modifier: Modifier = Modifier) { @Serializable private data object SplashScreen -*/ diff --git a/composeApp/src/commonMain/kotlin/xyz/ksharma/krail/common/Platform.kt b/composeApp/src/commonMain/kotlin/xyz/ksharma/krail/Platform.kt similarity index 71% rename from composeApp/src/commonMain/kotlin/xyz/ksharma/krail/common/Platform.kt rename to composeApp/src/commonMain/kotlin/xyz/ksharma/krail/Platform.kt index 61e8824d..17fe15b1 100644 --- a/composeApp/src/commonMain/kotlin/xyz/ksharma/krail/common/Platform.kt +++ b/composeApp/src/commonMain/kotlin/xyz/ksharma/krail/Platform.kt @@ -1,4 +1,4 @@ -package xyz.ksharma.krail.common +package xyz.ksharma.krail interface Platform { val name: String diff --git a/composeApp/src/commonMain/kotlin/xyz/ksharma/krail/common/AppDispatchers.kt b/composeApp/src/commonMain/kotlin/xyz/ksharma/krail/common/AppDispatchers.kt deleted file mode 100644 index d4e5f5a4..00000000 --- a/composeApp/src/commonMain/kotlin/xyz/ksharma/krail/common/AppDispatchers.kt +++ /dev/null @@ -1,28 +0,0 @@ -package xyz.ksharma.krail.common - -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.IO -import kotlinx.coroutines.SupervisorJob -import me.tatarka.inject.annotations.Provides - -interface SharedApplicationComponent { - - val dispatchers: AppCoroutineDispatchers - - @ApplicationScope - @Provides - fun provideCoroutineDispatchers(): AppCoroutineDispatchers = AppCoroutineDispatchers( - io = Dispatchers.IO, - databaseWrite = Dispatchers.IO.limitedParallelism(1), - databaseRead = Dispatchers.IO.limitedParallelism(4), - computation = Dispatchers.Default, - main = Dispatchers.Main, - ) - - @ApplicationScope - @Provides - fun provideApplicationCoroutineScope( - dispatchers: AppCoroutineDispatchers, - ): ApplicationCoroutineScope = CoroutineScope(dispatchers.main + SupervisorJob()) -} diff --git a/composeApp/src/commonMain/kotlin/xyz/ksharma/krail/common/KrailApp.kt b/composeApp/src/commonMain/kotlin/xyz/ksharma/krail/common/KrailApp.kt deleted file mode 100644 index 3869162b..00000000 --- a/composeApp/src/commonMain/kotlin/xyz/ksharma/krail/common/KrailApp.kt +++ /dev/null @@ -1,26 +0,0 @@ -package xyz.ksharma.krail.common - -import androidx.compose.material.Text -import androidx.compose.runtime.Composable -import androidx.compose.runtime.LaunchedEffect -import xyz.ksharma.krail.sandook.createSandookComponent -import xyz.ksharma.krail.taj.theme.KrailTheme -import xyz.ksharma.krail.trip.planner.network.api.createNetworkComponent - -@Composable -fun KrailApp() { - - LaunchedEffect(Unit) { - val httpClient = createNetworkComponent().provideHttpClient() - println("HTTP Client: ${httpClient.engine.config}") - - val sandookComponent = createSandookComponent() - sandookComponent.sandookDb.insertOrReplaceTheme(1) - println("Sandook component: ${sandookComponent.sandookDb}") - } - - KrailTheme { - Text("Hello, Krail!") -// KrailNavHost() - } -} diff --git a/composeApp/src/commonMain/kotlin/xyz/ksharma/krail/common/LazyExt.kt b/composeApp/src/commonMain/kotlin/xyz/ksharma/krail/common/LazyExt.kt deleted file mode 100644 index 4360da33..00000000 --- a/composeApp/src/commonMain/kotlin/xyz/ksharma/krail/common/LazyExt.kt +++ /dev/null @@ -1,4 +0,0 @@ -package xyz.ksharma.krail.common - -@Suppress("NOTHING_TO_INLINE") -inline fun unsafeLazy(noinline initializer: () -> T): Lazy = lazy(LazyThreadSafetyMode.NONE, initializer) diff --git a/composeApp/src/commonMain/kotlin/xyz/ksharma/krail/common/Scope.kt b/composeApp/src/commonMain/kotlin/xyz/ksharma/krail/common/Scope.kt deleted file mode 100644 index 0793d174..00000000 --- a/composeApp/src/commonMain/kotlin/xyz/ksharma/krail/common/Scope.kt +++ /dev/null @@ -1,12 +0,0 @@ -package xyz.ksharma.krail.common - -import kotlinx.coroutines.CoroutineScope -import me.tatarka.inject.annotations.Scope - -@Scope -annotation class ApplicationScope - -@Scope -annotation class ActivityScope - -typealias ApplicationCoroutineScope = CoroutineScope diff --git a/composeApp/src/commonMain/kotlin/xyz/ksharma/krail/common/di/KoinAppModule.kt b/composeApp/src/commonMain/kotlin/xyz/ksharma/krail/di/AppModule.kt similarity index 53% rename from composeApp/src/commonMain/kotlin/xyz/ksharma/krail/common/di/KoinAppModule.kt rename to composeApp/src/commonMain/kotlin/xyz/ksharma/krail/di/AppModule.kt index dce9b240..48d8d9bb 100644 --- a/composeApp/src/commonMain/kotlin/xyz/ksharma/krail/common/di/KoinAppModule.kt +++ b/composeApp/src/commonMain/kotlin/xyz/ksharma/krail/di/AppModule.kt @@ -1,22 +1,22 @@ -/* -package xyz.ksharma.krail.common.di +package xyz.ksharma.krail.di -import io.ktor.client.plugins.logging.DEFAULT -import io.ktor.client.plugins.logging.Logger import org.koin.core.module.dsl.viewModelOf +import org.koin.dsl.KoinAppDeclaration import org.koin.dsl.includes import org.koin.dsl.koinConfiguration import org.koin.dsl.module -import xyz.ksharma.krail.sandook.dbModule +import xyz.ksharma.krail.sandook.di.sandookModule +import xyz.ksharma.krail.splash.SplashViewModel import xyz.ksharma.krail.trip.planner.network.api.di.networkModule import xyz.ksharma.krail.trip.planner.ui.di.viewModelsModule -import xyz.ksharma.krail.common.splash.SplashViewModel val koinConfig = koinConfiguration { - modules(networkModule + dbModule + viewModelsModule + splashModule) + includes(nativeConfig()) + modules(networkModule + viewModelsModule + sandookModule + splashModule) } val splashModule = module { viewModelOf(::SplashViewModel) } -*/ + +expect fun nativeConfig() : KoinAppDeclaration diff --git a/composeApp/src/commonMain/kotlin/xyz/ksharma/krail/common/splash/SplashScreen.kt b/composeApp/src/commonMain/kotlin/xyz/ksharma/krail/splash/SplashScreen.kt similarity index 99% rename from composeApp/src/commonMain/kotlin/xyz/ksharma/krail/common/splash/SplashScreen.kt rename to composeApp/src/commonMain/kotlin/xyz/ksharma/krail/splash/SplashScreen.kt index 6cbce151..e10082d7 100644 --- a/composeApp/src/commonMain/kotlin/xyz/ksharma/krail/common/splash/SplashScreen.kt +++ b/composeApp/src/commonMain/kotlin/xyz/ksharma/krail/splash/SplashScreen.kt @@ -1,4 +1,4 @@ -package xyz.ksharma.krail.common.splash +package xyz.ksharma.krail.splash import androidx.compose.animation.core.FastOutSlowInEasing import androidx.compose.animation.core.LinearEasing diff --git a/composeApp/src/commonMain/kotlin/xyz/ksharma/krail/common/splash/SplashViewModel.kt b/composeApp/src/commonMain/kotlin/xyz/ksharma/krail/splash/SplashViewModel.kt similarity index 84% rename from composeApp/src/commonMain/kotlin/xyz/ksharma/krail/common/splash/SplashViewModel.kt rename to composeApp/src/commonMain/kotlin/xyz/ksharma/krail/splash/SplashViewModel.kt index 8845c9a9..b6621612 100644 --- a/composeApp/src/commonMain/kotlin/xyz/ksharma/krail/common/splash/SplashViewModel.kt +++ b/composeApp/src/commonMain/kotlin/xyz/ksharma/krail/splash/SplashViewModel.kt @@ -1,5 +1,4 @@ -/* -package xyz.ksharma.krail.common.splash +package xyz.ksharma.krail.splash import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope @@ -11,11 +10,11 @@ import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.onStart import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.launch -import xyz.ksharma.krail.sandook.SandookDb +import xyz.ksharma.krail.sandook.Sandook import xyz.ksharma.krail.trip.planner.ui.state.TransportMode class SplashViewModel( - private val sandookDb: SandookDb, + private val sandook: Sandook, ) : ViewModel() { private val _uiState: MutableStateFlow = MutableStateFlow(null) @@ -29,10 +28,9 @@ class SplashViewModel( private fun getThemeTransportMode() { viewModelScope.launch(Dispatchers.IO) { - val productClass = sandookDb.getProductClass()?.toInt() ?: 0 + val productClass = sandook.getProductClass()?.toInt() ?: 0 val mode = TransportMode.toTransportModeType(productClass) _uiState.value = mode } } } -*/ diff --git a/composeApp/src/iosMain/kotlin/xyz/ksharma/krail/common/MainViewController.kt b/composeApp/src/iosMain/kotlin/xyz/ksharma/krail/MainViewController.kt similarity index 79% rename from composeApp/src/iosMain/kotlin/xyz/ksharma/krail/common/MainViewController.kt rename to composeApp/src/iosMain/kotlin/xyz/ksharma/krail/MainViewController.kt index 2e00a5f6..607b0294 100644 --- a/composeApp/src/iosMain/kotlin/xyz/ksharma/krail/common/MainViewController.kt +++ b/composeApp/src/iosMain/kotlin/xyz/ksharma/krail/MainViewController.kt @@ -1,4 +1,4 @@ -package xyz.ksharma.krail.common +package xyz.ksharma.krail import androidx.compose.ui.window.ComposeUIViewController diff --git a/composeApp/src/iosMain/kotlin/xyz/ksharma/krail/common/Platform.ios.kt b/composeApp/src/iosMain/kotlin/xyz/ksharma/krail/Platform.ios.kt similarity index 87% rename from composeApp/src/iosMain/kotlin/xyz/ksharma/krail/common/Platform.ios.kt rename to composeApp/src/iosMain/kotlin/xyz/ksharma/krail/Platform.ios.kt index d790f524..77c96dc5 100644 --- a/composeApp/src/iosMain/kotlin/xyz/ksharma/krail/common/Platform.ios.kt +++ b/composeApp/src/iosMain/kotlin/xyz/ksharma/krail/Platform.ios.kt @@ -1,4 +1,4 @@ -package xyz.ksharma.krail.common +package xyz.ksharma.krail import platform.UIKit.UIDevice diff --git a/composeApp/src/iosMain/kotlin/xyz/ksharma/krail/di/AppModule.ios.kt b/composeApp/src/iosMain/kotlin/xyz/ksharma/krail/di/AppModule.ios.kt new file mode 100644 index 00000000..7d51e1d7 --- /dev/null +++ b/composeApp/src/iosMain/kotlin/xyz/ksharma/krail/di/AppModule.ios.kt @@ -0,0 +1,7 @@ +package xyz.ksharma.krail.di + +import org.koin.dsl.koinConfiguration + +actual fun nativeConfig() = koinConfiguration { + printLogger() +} diff --git a/feature/trip-planner/network/build.gradle.kts b/feature/trip-planner/network/build.gradle.kts index da38cd93..e8bb3e13 100644 --- a/feature/trip-planner/network/build.gradle.kts +++ b/feature/trip-planner/network/build.gradle.kts @@ -42,6 +42,7 @@ kotlin { sourceSets { androidMain.dependencies { implementation(libs.ktor.client.okhttp) + api(libs.di.koinAndroid) } commonMain { @@ -57,7 +58,7 @@ kotlin { implementation(compose.runtime) implementation(libs.slf4j.simple) // Logging - api(libs.di.koinComposeViewmodelNav) + api(libs.di.koinComposeViewmodel) } } diff --git a/feature/trip-planner/ui/build.gradle.kts b/feature/trip-planner/ui/build.gradle.kts index e2c3343d..1ae7ffed 100644 --- a/feature/trip-planner/ui/build.gradle.kts +++ b/feature/trip-planner/ui/build.gradle.kts @@ -57,7 +57,7 @@ kotlin { implementation(libs.slf4j.simple) // Logging // TODO - remove once DI added - end - implementation(libs.di.koinComposeViewmodelNav) + api(libs.di.koinComposeViewmodel) } } commonTest { diff --git a/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/savedtrips/SavedTripsDestination.kt b/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/savedtrips/SavedTripsDestination.kt index 17142bc2..36d07467 100644 --- a/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/savedtrips/SavedTripsDestination.kt +++ b/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/savedtrips/SavedTripsDestination.kt @@ -10,9 +10,7 @@ import androidx.navigation.NavGraphBuilder import androidx.navigation.NavHostController import androidx.navigation.NavOptions import androidx.navigation.compose.composable -import org.koin.compose.viewmodel.koinNavViewModel import org.koin.compose.viewmodel.koinViewModel -import org.koin.core.annotation.KoinExperimentalAPI import xyz.ksharma.krail.trip.planner.ui.navigation.SavedTripsRoute import xyz.ksharma.krail.trip.planner.ui.navigation.SearchStopFieldType import xyz.ksharma.krail.trip.planner.ui.navigation.SearchStopRoute @@ -22,10 +20,9 @@ import xyz.ksharma.krail.trip.planner.ui.state.searchstop.model.StopItem import xyz.ksharma.krail.trip.planner.ui.state.searchstop.model.StopItem.Companion.fromJsonString @Suppress("LongMethod") -@OptIn(KoinExperimentalAPI::class) internal fun NavGraphBuilder.savedTripsDestination(navController: NavHostController) { composable { backStackEntry -> - val viewModel: SavedTripsViewModel = koinNavViewModel() + val viewModel: SavedTripsViewModel = koinViewModel() val savedTripState by viewModel.uiState.collectAsStateWithLifecycle() val fromArg: String? = diff --git a/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/savedtrips/SavedTripsViewModel.kt b/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/savedtrips/SavedTripsViewModel.kt index 8df5dd07..344eba5f 100644 --- a/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/savedtrips/SavedTripsViewModel.kt +++ b/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/savedtrips/SavedTripsViewModel.kt @@ -11,6 +11,7 @@ import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch import xyz.ksharma.krail.sandook.Sandook +import xyz.ksharma.krail.sandook.SavedTrip import xyz.ksharma.krail.trip.planner.ui.state.savedtrip.SavedTripUiEvent import xyz.ksharma.krail.trip.planner.ui.state.savedtrip.SavedTripsState import xyz.ksharma.krail.trip.planner.ui.state.timetable.Trip @@ -23,22 +24,25 @@ class SavedTripsViewModel( val uiState: StateFlow = _uiState private fun loadSavedTrips() { - println("loadSavedTrips, keys: ${sandook.keys()}") viewModelScope.launch(context = Dispatchers.IO) { - val trips = persistentListOf() - sandook.keys().mapNotNull { key -> /// ERROR HERE TODO - keys mismatch - val tripString = sandook.getString(key, null) - tripString?.let { tripJsonString -> - Trip.fromJsonString(tripJsonString) - } - }.toImmutableList() + val trips = mutableSetOf() + + val savedTrips = sandook.selectAllTrips() + println("SavedTrips: $savedTrips") + savedTrips.forEachIndexed { index, savedTrip -> + val trip = savedTrip.toTrip() + println("Trip: #$index $trip") + trips.add(savedTrip.toTrip()) + } + + trips.addAll(savedTrips.map { savedTrip -> savedTrip.toTrip() }) println("SavedTrips: ${trips.size} number") trips.forEachIndexed { index, trip -> println("\t SavedTrip: #$index ${trip.fromStopName} -> ${trip.toStopName}") } - updateUiState { copy(savedTrips = trips) } + updateUiState { copy(savedTrips = trips.toImmutableList()) } } } @@ -50,9 +54,9 @@ class SavedTripsViewModel( } private fun onDeleteSavedTrip(savedTrip: Trip) { -// Timber.d("onDeleteSavedTrip: $savedTrip") + println("onDeleteSavedTrip: $savedTrip") viewModelScope.launch(context = Dispatchers.IO) { - sandook.remove(key = savedTrip.tripId) + sandook.deleteTrip(tripId = savedTrip.tripId) loadSavedTrips() } } @@ -61,3 +65,10 @@ class SavedTripsViewModel( _uiState.update(block) } } + +private fun SavedTrip.toTrip(): Trip = Trip( + fromStopId = fromStopId, + fromStopName = fromStopName, + toStopId = toStopId, + toStopName = toStopName, +) diff --git a/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/searchstop/SearchStopDestination.kt b/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/searchstop/SearchStopDestination.kt index a69ed1d8..4757f2a9 100644 --- a/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/searchstop/SearchStopDestination.kt +++ b/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/searchstop/SearchStopDestination.kt @@ -6,15 +6,12 @@ import androidx.navigation.NavGraphBuilder import androidx.navigation.NavHostController import androidx.navigation.compose.composable import androidx.navigation.toRoute -import org.koin.compose.viewmodel.koinNavViewModel import org.koin.compose.viewmodel.koinViewModel -import org.koin.core.annotation.KoinExperimentalAPI import xyz.ksharma.krail.trip.planner.ui.navigation.SearchStopRoute -@OptIn(KoinExperimentalAPI::class) fun NavGraphBuilder.searchStopDestination(navController: NavHostController) { composable { backStackEntry -> - val viewModel: SearchStopViewModel = koinNavViewModel() + val viewModel: SearchStopViewModel = koinViewModel() val searchStopState by viewModel.uiState.collectAsStateWithLifecycle() val route: SearchStopRoute = backStackEntry.toRoute() diff --git a/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/timetable/TimeTableDestination.kt b/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/timetable/TimeTableDestination.kt index 5ffa73d9..9b8ed166 100644 --- a/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/timetable/TimeTableDestination.kt +++ b/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/timetable/TimeTableDestination.kt @@ -7,17 +7,15 @@ import androidx.navigation.NavHostController import androidx.navigation.NavOptions import androidx.navigation.compose.composable import androidx.navigation.toRoute -import org.koin.compose.viewmodel.koinNavViewModel -import org.koin.core.annotation.KoinExperimentalAPI +import org.koin.compose.viewmodel.koinViewModel import xyz.ksharma.krail.trip.planner.ui.navigation.ServiceAlertRoute import xyz.ksharma.krail.trip.planner.ui.navigation.TimeTableRoute import xyz.ksharma.krail.trip.planner.ui.state.timetable.TimeTableUiEvent import xyz.ksharma.krail.trip.planner.ui.state.timetable.Trip -@OptIn(KoinExperimentalAPI::class) internal fun NavGraphBuilder.timeTableDestination(navController: NavHostController) { composable { backStackEntry -> - val viewModel: TimeTableViewModel = koinNavViewModel() + val viewModel: TimeTableViewModel = koinViewModel() val timeTableState by viewModel.uiState.collectAsStateWithLifecycle() val route: TimeTableRoute = backStackEntry.toRoute() val isLoading by viewModel.isLoading.collectAsStateWithLifecycle() diff --git a/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/timetable/TimeTableViewModel.kt b/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/timetable/TimeTableViewModel.kt index dcf2505c..8eb1679d 100644 --- a/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/timetable/TimeTableViewModel.kt +++ b/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/timetable/TimeTableViewModel.kt @@ -128,15 +128,19 @@ class TimeTableViewModel( viewModelScope.launch(Dispatchers.IO) { tripInfo?.let { trip -> println("Toggle Save Trip: $trip") - val savedTrip = sandook.getString(key = trip.tripId) - if (savedTrip != null && savedTrip != "null") { + val savedTrip = sandook.selectTripById(tripId = trip.tripId) + if (savedTrip != null) { // Trip is already saved, so delete it - sandook.remove(key = trip.tripId) - println("Deleted Trip (Pref): ${Trip.fromJsonString(savedTrip)}") + sandook.deleteTrip(tripId = trip.tripId) + println("Deleted Trip (Pref): $savedTrip") updateUiState { copy(isTripSaved = false) } } else { // Trip is not saved, so save it - sandook.putString(key = trip.tripId, value = trip.toJsonString()) + sandook.insertOrReplaceTrip( + tripId = trip.tripId, + fromStopId = trip.fromStopId, fromStopName = trip.fromStopName, + toStopId = trip.toStopId, toStopName = trip.toStopName + ) println("Saved Trip (Pref): $trip") updateUiState { copy(isTripSaved = true) } } @@ -152,7 +156,7 @@ class TimeTableViewModel( private fun onLoadTimeTable(trip: Trip) { println("onLoadTimeTable -- Trigger fromStopItem: ${trip.fromStopId}, toStopItem: ${trip.toStopId}") tripInfo = trip - val savedTrip = sandook.getString(key = trip.tripId) + val savedTrip = sandook.selectTripById(tripId = trip.tripId) println("Saved Trip[${trip.tripId}]: $savedTrip") updateUiState { copy( @@ -175,11 +179,11 @@ class TimeTableViewModel( toStopName = tripInfo!!.fromStopName, ) tripInfo = reverseTrip - val savedTrip = sandook.getString(key = reverseTrip.tripId) + val savedTrip = sandook.selectTripById(tripId = reverseTrip.tripId) updateUiState { copy( trip = reverseTrip, - isTripSaved = savedTrip != null && savedTrip != "null", + isTripSaved = savedTrip != null, isLoading = true, isError = false, ) diff --git a/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/usualride/UsualRideDestination.kt b/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/usualride/UsualRideDestination.kt index 4079a8eb..19ce8d44 100644 --- a/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/usualride/UsualRideDestination.kt +++ b/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/usualride/UsualRideDestination.kt @@ -4,14 +4,12 @@ import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue -import androidx.lifecycle.viewmodel.compose.viewModel import androidx.navigation.NavGraphBuilder import androidx.navigation.NavHostController import androidx.navigation.NavOptions import androidx.navigation.compose.composable import kotlinx.collections.immutable.toImmutableSet -import org.koin.compose.viewmodel.koinNavViewModel -import org.koin.core.annotation.KoinExperimentalAPI +import org.koin.compose.viewmodel.koinViewModel import xyz.ksharma.krail.taj.LocalThemeColor import xyz.ksharma.krail.taj.LocalThemeContentColor import xyz.ksharma.krail.taj.theme.getForegroundColor @@ -23,10 +21,9 @@ import xyz.ksharma.krail.trip.planner.ui.state.TransportMode import xyz.ksharma.krail.trip.planner.ui.state.TransportModeSortOrder import xyz.ksharma.krail.trip.planner.ui.state.usualride.UsualRideEvent -@OptIn(KoinExperimentalAPI::class) internal fun NavGraphBuilder.usualRideDestination(navController: NavHostController) { composable { - val viewModel:UsualRideViewModel = koinNavViewModel() + val viewModel:UsualRideViewModel = koinViewModel() var themeColor by LocalThemeColor.current var themeContentColor by LocalThemeContentColor.current var mode: TransportMode? by remember { mutableStateOf(null) } diff --git a/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/usualride/UsualRideViewModel.kt b/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/usualride/UsualRideViewModel.kt index bad69555..a5edb147 100644 --- a/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/usualride/UsualRideViewModel.kt +++ b/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/usualride/UsualRideViewModel.kt @@ -7,10 +7,7 @@ import kotlinx.coroutines.IO import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.launch -import me.tatarka.inject.annotations.Inject -import xyz.ksharma.krail.sandook.RealSandook import xyz.ksharma.krail.sandook.Sandook -import xyz.ksharma.krail.trip.planner.ui.state.TransportMode import xyz.ksharma.krail.trip.planner.ui.state.usualride.UsualRideEvent import xyz.ksharma.krail.trip.planner.ui.state.usualride.UsualRideState @@ -27,10 +24,7 @@ class UsualRideViewModel(private val sandook: Sandook) : ViewModel() { private fun onTransportModeSelected(productClass: Int) { viewModelScope.launch(Dispatchers.IO) { - TransportMode.toTransportModeType(productClass)?.let { mode -> - //Timber.d("onTransportModeSelected: $mode") - sandook.putString("selectedMode", mode.productClass.toString()) - } + sandook.insertOrReplaceTheme(productClass.toLong()) } } } diff --git a/gradle/build-logic/convention/src/main/kotlin/xyz/ksharma/krail/gradle/KotlinMultiplatformConventionPlugin.kt b/gradle/build-logic/convention/src/main/kotlin/xyz/ksharma/krail/gradle/KotlinMultiplatformConventionPlugin.kt index 3552daf5..fb7dc4d7 100644 --- a/gradle/build-logic/convention/src/main/kotlin/xyz/ksharma/krail/gradle/KotlinMultiplatformConventionPlugin.kt +++ b/gradle/build-logic/convention/src/main/kotlin/xyz/ksharma/krail/gradle/KotlinMultiplatformConventionPlugin.kt @@ -3,7 +3,9 @@ package xyz.ksharma.krail.gradle import org.gradle.api.Plugin import org.gradle.api.Project import org.gradle.kotlin.dsl.configure +import org.gradle.kotlin.dsl.withType import org.jetbrains.kotlin.gradle.dsl.KotlinMultiplatformExtension +import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinNativeTarget class KotlinMultiplatformConventionPlugin : Plugin { override fun apply(target: Project) = with(target) { @@ -22,6 +24,14 @@ class KotlinMultiplatformConventionPlugin : Plugin { iosArm64() iosSimulatorArm64() + targets.withType().configureEach { + binaries.configureEach { + // Add linker flag for SQLite. See: + // https://github.com/touchlab/SQLiter/issues/77 + linkerOpts("-lsqlite3") + } + } + configureJava() } } diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 15a713a1..39a2bceb 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -55,6 +55,13 @@ multiplatform-settings = { module = "com.russhwolf:multiplatform-settings-no-arg di-kotlinInjectRuntime = { module = "me.tatarka.inject:kotlin-inject-runtime-kmp", version.ref = "kotlinInject" } di-kotlinInjectCompilerKsp = { module = "me.tatarka.inject:kotlin-inject-compiler-ksp", version.ref = "kotlinInject" } +##di-koinAndroid = {module = "io.insert-koin:koin-android", version.ref = "koin"} +#di-koinComposeViewmodel = {module = "io.insert-koin:koin-compose-viewmodel", version.ref = "koin"} +#di-koinComposeViewmodelNav = {module = "io.insert-koin:koin-compose-viewmodel-navigation", version.ref = "koin"} + +#di-koinAndroidxCompose = { module = "io.insert-koin:koin-androidx-compose", version.ref = "koin" } +#di-koinCore = { module = "io.insert-koin:koin-core", version.ref = "koin" } + di-koinAndroid = {module = "io.insert-koin:koin-android", version.ref = "koin"} di-koinComposeViewmodel = {module = "io.insert-koin:koin-compose-viewmodel", version.ref = "koin"} di-koinComposeViewmodelNav = {module = "io.insert-koin:koin-compose-viewmodel-navigation", version.ref = "koin"} diff --git a/iosApp/iosApp.xcodeproj/project.xcworkspace/xcuserdata/parora.xcuserdatad/UserInterfaceState.xcuserstate b/iosApp/iosApp.xcodeproj/project.xcworkspace/xcuserdata/parora.xcuserdatad/UserInterfaceState.xcuserstate new file mode 100644 index 0000000000000000000000000000000000000000..c9a5dac251d772055c881d12c7e9958d1f92e14f GIT binary patch literal 14462 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 en3 + + + + SchemeUserState + + iosApp.xcscheme_^#shared#^_ + + orderHint + 0 + + + + diff --git a/sandook/build.gradle.kts b/sandook/build.gradle.kts index 82019c45..35338f7f 100644 --- a/sandook/build.gradle.kts +++ b/sandook/build.gradle.kts @@ -48,18 +48,17 @@ kotlin { implementation(libs.log.kermit) implementation(libs.kotlinx.datetime) implementation(libs.db.sqlRuntime) - - api(libs.di.koinComposeViewmodelNav) + api(libs.di.koinComposeViewmodel) } } iosMain.dependencies { implementation(libs.ktor.client.darwin) - implementation(libs.db.sqlNativeDriver) + api(libs.db.sqlNativeDriver) } } } - +/* dependencies { // 1. Configure code generation into the common source set kspCommonMainMetadata(libs.di.kotlinInjectRuntime) @@ -72,7 +71,7 @@ dependencies { ksp { arg("me.tatarka.inject.generateCompanionExtensions", "true") arg("me.tatarka.inject.dumpGraph", "true") -} +}*/ sqldelight { databases { diff --git a/sandook/src/androidMain/kotlin/xyz/ksharma/krail/sandook/AndroidSandookDriverFactory.kt b/sandook/src/androidMain/kotlin/xyz/ksharma/krail/sandook/AndroidSandookDriverFactory.kt new file mode 100644 index 00000000..04d9f39d --- /dev/null +++ b/sandook/src/androidMain/kotlin/xyz/ksharma/krail/sandook/AndroidSandookDriverFactory.kt @@ -0,0 +1,15 @@ +package xyz.ksharma.krail.sandook + +import android.content.Context +import app.cash.sqldelight.db.SqlDriver +import app.cash.sqldelight.driver.android.AndroidSqliteDriver + +class AndroidSandookDriverFactory(private val context: Context) : SandookDriverFactory { + override fun createDriver(): SqlDriver { + return AndroidSqliteDriver( + schema = KrailSandook.Schema, + context = context, + name = "krailSandook.db" + ) + } +} diff --git a/sandook/src/androidMain/kotlin/xyz/ksharma/krail/sandook/AndroidSandookModule.kt b/sandook/src/androidMain/kotlin/xyz/ksharma/krail/sandook/AndroidSandookModule.kt deleted file mode 100644 index d1a70edb..00000000 --- a/sandook/src/androidMain/kotlin/xyz/ksharma/krail/sandook/AndroidSandookModule.kt +++ /dev/null @@ -1,20 +0,0 @@ -/* -package xyz.ksharma.krail.sandook - -import org.koin.android.ext.koin.androidContext -import org.koin.core.module.dsl.bind -import org.koin.core.module.dsl.singleOf -import org.koin.dsl.module - -val androidDbModule = module { - singleOf(::AndroidSandookDriverFactory) { bind() } - - single { - RealSandookDb( - factory = AndroidSandookDriverFactory( - context = androidContext() - ) - ) - } -} -*/ diff --git a/sandook/src/androidMain/kotlin/xyz/ksharma/krail/sandook/SQLPlatformComponent.kt b/sandook/src/androidMain/kotlin/xyz/ksharma/krail/sandook/SQLPlatformComponent.kt deleted file mode 100644 index f15a4ac3..00000000 --- a/sandook/src/androidMain/kotlin/xyz/ksharma/krail/sandook/SQLPlatformComponent.kt +++ /dev/null @@ -1,25 +0,0 @@ -@file:Suppress("EXPECT_ACTUAL_CLASSIFIERS_ARE_IN_BETA_WARNING") - -package xyz.ksharma.krail.sandook - -import android.app.Application -import app.cash.sqldelight.db.SqlDriver -import app.cash.sqldelight.driver.android.AndroidSqliteDriver -import me.tatarka.inject.annotations.Component -import me.tatarka.inject.annotations.Provides - -@Component -actual interface SQLPlatformComponent { - - @Provides - fun provideSQLDriver(application: Application): SqlDriver = - AndroidSqliteDriver( - schema = KrailSandook.Schema, - context = application, - name = "krailSandook.db", - ) - - companion object -} - -//actual fun sandookDriverFactory(): SandookDriverFactory = AndroidSandookDriverFactory(context = context) diff --git a/sandook/src/androidMain/kotlin/xyz/ksharma/krail/sandook/di/AndroidSandookModule.kt b/sandook/src/androidMain/kotlin/xyz/ksharma/krail/sandook/di/AndroidSandookModule.kt new file mode 100644 index 00000000..f8b027e5 --- /dev/null +++ b/sandook/src/androidMain/kotlin/xyz/ksharma/krail/sandook/di/AndroidSandookModule.kt @@ -0,0 +1,21 @@ +package xyz.ksharma.krail.sandook.di + +import org.koin.android.ext.koin.androidContext +import org.koin.core.module.dsl.bind +import org.koin.core.module.dsl.singleOf +import org.koin.dsl.module +import xyz.ksharma.krail.sandook.AndroidSandookDriverFactory +import xyz.ksharma.krail.sandook.RealSandook +import xyz.ksharma.krail.sandook.SandookDriverFactory + +actual val sqlDriverModule = module { + singleOf(::AndroidSandookDriverFactory) { bind() } + + single { + RealSandook( + factory = AndroidSandookDriverFactory( + context = androidContext() + ) + ) + } +} diff --git a/sandook/src/commonMain/kotlin/xyz/ksharma/krail/sandook/DbModule.kt b/sandook/src/commonMain/kotlin/xyz/ksharma/krail/sandook/DbModule.kt deleted file mode 100644 index f650d415..00000000 --- a/sandook/src/commonMain/kotlin/xyz/ksharma/krail/sandook/DbModule.kt +++ /dev/null @@ -1,10 +0,0 @@ -package xyz.ksharma.krail.sandook - -import org.koin.core.module.dsl.bind -import org.koin.core.module.dsl.singleOf -import org.koin.dsl.module - -val dbModule = module { - singleOf(::RealSandook) { bind() } - singleOf(::RealSandookDb) { bind() } -} diff --git a/sandook/src/commonMain/kotlin/xyz/ksharma/krail/sandook/RealSandook.kt b/sandook/src/commonMain/kotlin/xyz/ksharma/krail/sandook/RealSandook.kt index 8f3b183e..de19e997 100644 --- a/sandook/src/commonMain/kotlin/xyz/ksharma/krail/sandook/RealSandook.kt +++ b/sandook/src/commonMain/kotlin/xyz/ksharma/krail/sandook/RealSandook.kt @@ -1,57 +1,56 @@ package xyz.ksharma.krail.sandook -import com.russhwolf.settings.Settings +internal class RealSandook(factory: SandookDriverFactory) : Sandook { -class RealSandook : Sandook { - private val settings: Settings = Settings() + private val sandook = KrailSandook(factory.createDriver()) + private val query = sandook.krailSandookQueries - override fun keys(): Set = settings.keys - - override fun putString(key: String, value: String) { - settings.putString(key, value) - } - - override fun getString(key: String, defaultValue: String?): String? { - return settings.getStringOrNull(key) ?: defaultValue - } - - override fun putInt(key: String, value: Int) { - settings.putInt(key, value) - } - - override fun getInt(key: String, defaultValue: Int): Int { - return settings.getInt(key, defaultValue) + // region Theme + override fun insertOrReplaceTheme(productClass: Long) { + query.insertOrReplaceProductClass(productClass) } - override fun putBoolean(key: String, value: Boolean) { - settings.putBoolean(key, value) + override fun getProductClass(): Long? { + return query.selectProductClass().executeAsOneOrNull() } - override fun getBoolean(key: String, defaultValue: Boolean): Boolean { - return settings.getBoolean(key, defaultValue) + override fun clearTheme() { + query.clearTheme() } - override fun putFloat(key: String, value: Float) { - settings.putFloat(key, value) - } + // endregion - override fun getFloat(key: String, defaultValue: Float): Float { - return settings.getFloat(key, defaultValue) + // region SavedTrip + override fun insertOrReplaceTrip( + tripId: String, + fromStopId: String, + fromStopName: String, + toStopId: String, + toStopName: String, + ) { + query.insertOrReplaceTrip( + tripId, + fromStopId, + fromStopName, + toStopId, + toStopName, + ) } - override fun putLong(key: String, value: Long) { - settings.putLong(key, value) + override fun deleteTrip(tripId: String) { + query.deleteTrip(tripId) } - override fun getLong(key: String, defaultValue: Long): Long { - return settings.getLong(key, defaultValue) + override fun selectAllTrips(): List { + return query.selectAllTrips().executeAsList() } - override fun remove(key: String) { - settings.remove(key) + override fun selectTripById(tripId: String): SavedTrip? { + return query.selectTripById(tripId).executeAsOneOrNull() } - override fun clear() { - settings.clear() + override fun clearSavedTrips() { + query.clearSavedTrips() } + // endregion } diff --git a/sandook/src/commonMain/kotlin/xyz/ksharma/krail/sandook/RealSandookDb.kt b/sandook/src/commonMain/kotlin/xyz/ksharma/krail/sandook/RealSandookDb.kt deleted file mode 100644 index e5f1e052..00000000 --- a/sandook/src/commonMain/kotlin/xyz/ksharma/krail/sandook/RealSandookDb.kt +++ /dev/null @@ -1,59 +0,0 @@ -package xyz.ksharma.krail.sandook - -import me.tatarka.inject.annotations.Inject - -@Inject -internal class RealSandookDb(factory: SandookFactory) : SandookDb { - - private val database = factory.build() - private val query = database.krailSandookQueries - - // region Theme - override fun insertOrReplaceTheme(productClass: Long) { - query.insertOrReplaceProductClass(productClass) - } - - override fun getProductClass(): Long? { - return query.selectProductClass().executeAsOneOrNull() - } - - override fun clearTheme() { - query.clearTheme() - } - - // endregion - - // region SavedTrip - override fun insertOrReplaceTrip( - tripId: String, - fromStopId: String, - fromStopName: String, - toStopId: String, - toStopName: String, - ) { - query.insertOrReplaceTrip( - tripId, - fromStopId, - fromStopName, - toStopId, - toStopName, - ) - } - - override fun deleteTrip(tripId: String) { - query.deleteTrip(tripId) - } - - override fun selectAllTrips(): List { - return query.selectAllTrips().executeAsList() - } - - override fun selectTripById(tripId: String): SavedTrip? { - return query.selectTripById(tripId).executeAsOneOrNull() - } - - override fun clearSavedTrips() { - query.clearSavedTrips() - } - // endregion -} diff --git a/sandook/src/commonMain/kotlin/xyz/ksharma/krail/sandook/SQLComponent.kt b/sandook/src/commonMain/kotlin/xyz/ksharma/krail/sandook/SQLComponent.kt deleted file mode 100644 index da6529c9..00000000 --- a/sandook/src/commonMain/kotlin/xyz/ksharma/krail/sandook/SQLComponent.kt +++ /dev/null @@ -1,20 +0,0 @@ -@file:Suppress("EXPECT_ACTUAL_CLASSIFIERS_ARE_IN_BETA_WARNING") - -package xyz.ksharma.krail.sandook - -import me.tatarka.inject.annotations.Component -import me.tatarka.inject.annotations.Provides - -expect interface SQLPlatformComponent - -@Component -interface SQLComponent : SQLPlatformComponent { - - // @ApplicationScope - @Provides - fun provideSqlDelightDatabase( - factory: SandookFactory, - ): KrailSandook = factory.build() - - companion object -} diff --git a/sandook/src/commonMain/kotlin/xyz/ksharma/krail/sandook/Sandook.kt b/sandook/src/commonMain/kotlin/xyz/ksharma/krail/sandook/Sandook.kt index d9c134ca..047718e4 100644 --- a/sandook/src/commonMain/kotlin/xyz/ksharma/krail/sandook/Sandook.kt +++ b/sandook/src/commonMain/kotlin/xyz/ksharma/krail/sandook/Sandook.kt @@ -2,32 +2,23 @@ package xyz.ksharma.krail.sandook interface Sandook { - /** - * Returns a set of all keys in the Sandook. - */ - fun keys(): Set - - fun getLong(key: String, defaultValue: Long = 0L): Long - - fun putLong(key: String, value: Long) - - fun getFloat(key: String, defaultValue: Float = 0f): Float - - fun putFloat(key: String, value: Float) - - fun getBoolean(key: String, defaultValue: Boolean = false): Boolean - - fun putBoolean(key: String, value: Boolean) - - fun getInt(key: String, defaultValue: Int = 0): Int - - fun putInt(key: String, value: Int) - - fun getString(key: String, defaultValue: String? = null): String? - - fun putString(key: String, value: String) - - fun clear() - - fun remove(key: String) + // region Theme + fun insertOrReplaceTheme(productClass: Long) + fun getProductClass(): Long? + fun clearTheme() + // endregion + + // region SavedTrip + fun insertOrReplaceTrip( + tripId: String, + fromStopId: String, + fromStopName: String, + toStopId: String, + toStopName: String + ) + fun deleteTrip(tripId: String) + fun selectAllTrips(): List + fun selectTripById(tripId: String): SavedTrip? + fun clearSavedTrips() + // endregion } diff --git a/sandook/src/commonMain/kotlin/xyz/ksharma/krail/sandook/SandookComponent.kt b/sandook/src/commonMain/kotlin/xyz/ksharma/krail/sandook/SandookComponent.kt deleted file mode 100644 index d760b329..00000000 --- a/sandook/src/commonMain/kotlin/xyz/ksharma/krail/sandook/SandookComponent.kt +++ /dev/null @@ -1,19 +0,0 @@ -package xyz.ksharma.krail.sandook - -import me.tatarka.inject.annotations.Component -import me.tatarka.inject.annotations.KmpComponentCreate -import me.tatarka.inject.annotations.Provides - -@Component -abstract class SandookComponent : SQLPlatformComponent { - - internal val RealSandookDb.bind: SandookDb - @Provides get() = this - - abstract val sandookDb: SandookDb - - companion object -} - -@KmpComponentCreate -expect fun createSandookComponent(): SandookComponent diff --git a/sandook/src/commonMain/kotlin/xyz/ksharma/krail/sandook/SandookDb.kt b/sandook/src/commonMain/kotlin/xyz/ksharma/krail/sandook/SandookDb.kt deleted file mode 100644 index 4225ca5c..00000000 --- a/sandook/src/commonMain/kotlin/xyz/ksharma/krail/sandook/SandookDb.kt +++ /dev/null @@ -1,24 +0,0 @@ -package xyz.ksharma.krail.sandook - -interface SandookDb { - - // region Theme - fun insertOrReplaceTheme(productClass: Long) - fun getProductClass(): Long? - fun clearTheme() - // endregion - - // region SavedTrip - fun insertOrReplaceTrip( - tripId: String, - fromStopId: String, - fromStopName: String, - toStopId: String, - toStopName: String - ) - fun deleteTrip(tripId: String) - fun selectAllTrips(): List - fun selectTripById(tripId: String): SavedTrip? - fun clearSavedTrips() - // endregion -} diff --git a/sandook/src/commonMain/kotlin/xyz/ksharma/krail/sandook/SandookFactory.kt b/sandook/src/commonMain/kotlin/xyz/ksharma/krail/sandook/SandookFactory.kt index 36f4ed12..678a5186 100644 --- a/sandook/src/commonMain/kotlin/xyz/ksharma/krail/sandook/SandookFactory.kt +++ b/sandook/src/commonMain/kotlin/xyz/ksharma/krail/sandook/SandookFactory.kt @@ -1,11 +1,7 @@ package xyz.ksharma.krail.sandook import app.cash.sqldelight.db.SqlDriver -import me.tatarka.inject.annotations.Inject -@Inject -class SandookFactory(private val driver: SqlDriver) { - fun build(): KrailSandook = KrailSandook(driver = driver) +interface SandookDriverFactory { + fun createDriver(): SqlDriver } - -//expect fun sandookDriverFactory(): SandookDriverFactory diff --git a/sandook/src/commonMain/kotlin/xyz/ksharma/krail/sandook/di/SandookModule.kt b/sandook/src/commonMain/kotlin/xyz/ksharma/krail/sandook/di/SandookModule.kt new file mode 100644 index 00000000..6ad33284 --- /dev/null +++ b/sandook/src/commonMain/kotlin/xyz/ksharma/krail/sandook/di/SandookModule.kt @@ -0,0 +1,15 @@ +package xyz.ksharma.krail.sandook.di + +import org.koin.core.module.Module +import org.koin.core.module.dsl.bind +import org.koin.core.module.dsl.singleOf +import org.koin.dsl.module +import xyz.ksharma.krail.sandook.RealSandook +import xyz.ksharma.krail.sandook.Sandook + +val sandookModule = module { + singleOf(::RealSandook) { bind() } + includes(sqlDriverModule) +} + +expect val sqlDriverModule: Module diff --git a/sandook/src/commonMain/kotlin/xyz/ksharma/krail/sandook/di/Singleton.kt b/sandook/src/commonMain/kotlin/xyz/ksharma/krail/sandook/di/Singleton.kt deleted file mode 100644 index 839ded8c..00000000 --- a/sandook/src/commonMain/kotlin/xyz/ksharma/krail/sandook/di/Singleton.kt +++ /dev/null @@ -1,8 +0,0 @@ -/* -package xyz.ksharma.krail.sandook.di -import me.tatarka.inject.annotations.Scope - -@Scope -@Target(AnnotationTarget.CLASS, AnnotationTarget.FUNCTION, AnnotationTarget.PROPERTY_GETTER) -annotation class Singleton -*/ diff --git a/sandook/src/iosMain/kotlin/xyz/ksharma/krail/sandook/IosSandookDriverFactory.kt b/sandook/src/iosMain/kotlin/xyz/ksharma/krail/sandook/IosSandookDriverFactory.kt new file mode 100644 index 00000000..e9d0d03d --- /dev/null +++ b/sandook/src/iosMain/kotlin/xyz/ksharma/krail/sandook/IosSandookDriverFactory.kt @@ -0,0 +1,10 @@ +package xyz.ksharma.krail.sandook + +import app.cash.sqldelight.db.SqlDriver +import app.cash.sqldelight.driver.native.NativeSqliteDriver + +class IosSandookDriverFactory : SandookDriverFactory { + override fun createDriver(): SqlDriver { + return NativeSqliteDriver(KrailSandook.Schema, name = "krailSandook.db") + } +} diff --git a/sandook/src/iosMain/kotlin/xyz/ksharma/krail/sandook/SQLPlatformComponent.kt b/sandook/src/iosMain/kotlin/xyz/ksharma/krail/sandook/SQLPlatformComponent.kt deleted file mode 100644 index f3a10f13..00000000 --- a/sandook/src/iosMain/kotlin/xyz/ksharma/krail/sandook/SQLPlatformComponent.kt +++ /dev/null @@ -1,18 +0,0 @@ -@file:Suppress("EXPECT_ACTUAL_CLASSIFIERS_ARE_IN_BETA_WARNING") - -package xyz.ksharma.krail.sandook - -import app.cash.sqldelight.db.SqlDriver -import app.cash.sqldelight.driver.native.NativeSqliteDriver -import me.tatarka.inject.annotations.Component -import me.tatarka.inject.annotations.Provides - -@Component -actual interface SQLPlatformComponent { - - @Provides - fun provideSQLDriver(): SqlDriver = - NativeSqliteDriver(schema = KrailSandook.Schema, name = "krailSandook.db") - - companion object -} diff --git a/sandook/src/iosMain/kotlin/xyz/ksharma/krail/sandook/di/IosSandookModule.kt b/sandook/src/iosMain/kotlin/xyz/ksharma/krail/sandook/di/IosSandookModule.kt new file mode 100644 index 00000000..79d3fec7 --- /dev/null +++ b/sandook/src/iosMain/kotlin/xyz/ksharma/krail/sandook/di/IosSandookModule.kt @@ -0,0 +1,16 @@ +package xyz.ksharma.krail.sandook.di + +import org.koin.core.module.dsl.bind +import org.koin.core.module.dsl.singleOf +import org.koin.dsl.module +import xyz.ksharma.krail.sandook.IosSandookDriverFactory +import xyz.ksharma.krail.sandook.RealSandook +import xyz.ksharma.krail.sandook.SandookDriverFactory + +actual val sqlDriverModule = module { + singleOf(::IosSandookDriverFactory) { bind() } + + single { + RealSandook(factory = IosSandookDriverFactory()) + } +} diff --git a/settings.gradle.kts b/settings.gradle.kts index 7964de8b..9988b52a 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -30,7 +30,7 @@ dependencyResolutionManagement { } rootProject.name = "Krail" -include(":android-app") +//include(":android-app") include(":composeApp") include(":taj") // Design System include(":core:di") From ef4b6861ea7d6dc1cfb63c703a60b7063bb4d90d Mon Sep 17 00:00:00 2001 From: Karan Sharma <55722391+ksharma-xyz@users.noreply.github.com> Date: Sat, 23 Nov 2024 15:53:24 +1100 Subject: [PATCH 61/67] Add linker flag for -lsqlite3 https://github.com/sqldelight/sqldelight/issues/5368#issuecomment-2324975285 https://www.jetbrains.com/help/kotlin-multiplatform-dev/multiplatform-ktor-sqldelight.html#add-the-dynamic-linking-flag-for-sqldelight --- .../KotlinMultiplatformConventionPlugin.kt | 1 + iosApp/iosApp.xcodeproj/project.pbxproj | 12 ++++++++---- .../UserInterfaceState.xcuserstate | Bin 14462 -> 168482 bytes 3 files changed, 9 insertions(+), 4 deletions(-) diff --git a/gradle/build-logic/convention/src/main/kotlin/xyz/ksharma/krail/gradle/KotlinMultiplatformConventionPlugin.kt b/gradle/build-logic/convention/src/main/kotlin/xyz/ksharma/krail/gradle/KotlinMultiplatformConventionPlugin.kt index fb7dc4d7..78727da7 100644 --- a/gradle/build-logic/convention/src/main/kotlin/xyz/ksharma/krail/gradle/KotlinMultiplatformConventionPlugin.kt +++ b/gradle/build-logic/convention/src/main/kotlin/xyz/ksharma/krail/gradle/KotlinMultiplatformConventionPlugin.kt @@ -28,6 +28,7 @@ class KotlinMultiplatformConventionPlugin : Plugin { binaries.configureEach { // Add linker flag for SQLite. See: // https://github.com/touchlab/SQLiter/issues/77 + // required for iOS targets linkerOpts("-lsqlite3") } } diff --git a/iosApp/iosApp.xcodeproj/project.pbxproj b/iosApp/iosApp.xcodeproj/project.pbxproj index 19b67fb2..70e9f1e3 100644 --- a/iosApp/iosApp.xcodeproj/project.pbxproj +++ b/iosApp/iosApp.xcodeproj/project.pbxproj @@ -16,8 +16,8 @@ /* Begin PBXFileReference section */ 058557BA273AAA24004C7B11 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 058557D8273AAEEB004C7B11 /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = ""; }; - 2152FB032600AC8F00CF470E /* iOSApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = iOSApp.swift; sourceTree = ""; }; - 7555FF7B242A565900829871 /* KMP App.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "KMP App.app"; sourceTree = BUILT_PRODUCTS_DIR; }; + 2152FB032600AC8F00CF470E /* iOSApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = iOSApp.swift; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.xcfilelist; }; + 7555FF7B242A565900829871 /* Krail App.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "Krail App.app"; sourceTree = BUILT_PRODUCTS_DIR; }; 7555FF82242A565900829871 /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = ""; }; 7555FF8C242A565B00829871 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; AB3632DC29227652001CCB65 /* Config.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = Config.xcconfig; sourceTree = ""; }; @@ -62,7 +62,7 @@ 7555FF7C242A565900829871 /* Products */ = { isa = PBXGroup; children = ( - 7555FF7B242A565900829871 /* KMP App.app */, + 7555FF7B242A565900829871 /* Krail App.app */, ); name = Products; sourceTree = ""; @@ -107,7 +107,7 @@ packageProductDependencies = ( ); productName = iosApp; - productReference = 7555FF7B242A565900829871 /* KMP App.app */; + productReference = 7555FF7B242A565900829871 /* Krail App.app */; productType = "com.apple.product-type.application"; }; /* End PBXNativeTarget section */ @@ -248,7 +248,9 @@ IPHONEOS_DEPLOYMENT_TARGET = 15.3; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; + NEW_SETTING = ""; ONLY_ACTIVE_ARCH = YES; + "OTHER_LDFLAGS[sdk=iphone*]" = "-lsqlite3"; SDKROOT = iphoneos; SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; @@ -305,6 +307,8 @@ IPHONEOS_DEPLOYMENT_TARGET = 15.3; MTL_ENABLE_DEBUG_INFO = NO; MTL_FAST_MATH = YES; + NEW_SETTING = ""; + "OTHER_LDFLAGS[sdk=iphone*]" = "-lsqlite3"; SDKROOT = iphoneos; SWIFT_COMPILATION_MODE = wholemodule; SWIFT_OPTIMIZATION_LEVEL = "-O"; diff --git a/iosApp/iosApp.xcodeproj/project.xcworkspace/xcuserdata/parora.xcuserdatad/UserInterfaceState.xcuserstate b/iosApp/iosApp.xcodeproj/project.xcworkspace/xcuserdata/parora.xcuserdatad/UserInterfaceState.xcuserstate index c9a5dac251d772055c881d12c7e9958d1f92e14f..45937bee90f430239ab1bd3d95194e4d131f728b 100644 GIT binary patch literal 168482 zcmb?k1$-OF(!bq1p=)s>cAN$qW@rkLCD~SETdpPBaneL}Y$b8y*sdYartNK+nK4ji zW@d(znVA`0;hVYBoh4avl;rFCy*9CD{yRInJ3Bi&v$w3Py{)Hr%9K|)!cmUr1Wv*dhKq8w+#+r!(Gcg&OO0B$vwqA%{{|C%RR?E&%MCC$i2k9&b`XL&Ar3D%YDdw z%Kggy#{JIy!Trho#r==_n+QZhw4{i5$rv)0Od>PLOfrk?PWIwnA$yY`iI9Cr4XGnB zvXJaYmXQ{+oU9-#iAWeZoE$-pBu9~>$uZmQs z3E^qsIpIa&W#Kj9P2nBkec>bFQ{fBYYvDWLN8uOYci}JL9}TDBHCl~9vx~;8v1x{A zTpF*YL^E77QZq&~PBT$6MKfJ9OEXuqhh}e0nI^2M)GW}{X!g~_HI14@nkAb3H7%Ou znl??lrc=|c>C>#!9IQE1!!$=~j?o;iIZ1P><_yi*n)5UlYA(@i)?B8!Qge;wdd*Fm zTQzrR?$+F=c~J9+=5ftanrAgHXtruz)x4p3Tl1dgL(M0e&oy6ZzSaDo`C0Ru=1?7E>kx6Y>(ce;*67yjHs}u19j-e{ zcdYIN-O0MsbZ6?$(VefmNVie9MR&RGD&4iZ8+148ZqwbVyGM7w?jhZyx+io`>z>oS zsC!xWn(j^AJG%FEAL%~TeWCkW_nq!X-7mV|b${vp(Q|rUuhkp$yXeh&n|_GirT6Mf z^uzTd^<(to^b_?{^wagT^mFxl==au_>BIU;{Q`ZBeqViD->6@tU!vb%-=bfxZ_~Hy zJN4cAKK(lV!TLk>On;>Q82$13lk}(R&(NQ(KTm(5{u2FW{bl+q_1EaH*WaYSRey*6 zZvB1w2lbEWAJ;#ne^&p3eyjdf{TuqX_3!CF)PJJ?T>q8+Tm29EpY^}#|J47j|Ia`T z8iU?YWH1@528Y3E@EH7tQo{(tXv1!X35LmrX@;4GIfi+Ly$k_E$WUR}$53skHN*@J zh9<*)hJ<06A!%4?SY_xi9BAk@tTh~DIK&_tjxZc;IL>gQ;S|H^hO-Rk8ZIzgY}jPD z)NqC2YQuGg8x6M@Za3UzxYzK2;bFsLh9?cr7@jx0WO&8!y5TLuyM_-89~(Y1d};W` z@V((D!>@)v4F5CyYa~X&s52Uk#YT(KZX9ZK8-2!M#$Ao0jAM=CjgyR1jWdk1jk_E7 zG|o2$jpfFOvC0@V)*0)K3yq77OO4IOR^tle0mjwFE@O{zjd8tkgYhuq;l`tk#~M#C zo@_kLc&70jCMWc(x6-_FdUNoa< zX3^e7^NRvS;iCGYhN8xzg+)z8i;5N(C5o06cNTXS_Z6=zKDhYMVpe=)@iE267oSvo zYVjGxXBVGWd|~k=#hZ&SE55S$n&Rt=Zz{gE_>SVci|;Fbu=tVU$BUmTezy39;;qH6 z7Qa#acJX_~9~OU7{CV+L#oreHQ2cZ8Z^eHW|6Tl_iJCMfy{X7#GFeRylhfoe`Awy! z5vI|m-AofqlTFi1Gfi_$^Gti00;Z6u!nBX6+EiT7)L}Z% z)N5L6I>>a0Ni-c{I@)xc=|s~hrqfMlna(v`V7l0}$#kjd3e(l5>r6MAZZX|%y32I0 z=>gNjrpHWAnw~K|Z+gk}is^OJTc&qSADBKieP;U7^o{9z(@&;fO@EmFXZqJn%z{~G zHkymg7PH+v)a*9<%)`vPnn#((n#Y?bnWvg(m}i@JH}7elZw{Kv%@K2zIclym*P9ob z7n_%wo6W7}73KrXtIb{J9`hRWdh-VJVdlflN12Z`pI|=Oe46=8^Eu}8%@>(BnzxuQ zH(zDG)_jBcX7g?4JI(i)?>9eWe$@Pg`DycW<`>N`n_n}(X@1B2zWF2br{*urUz@)( z|7iZj{JZ%t^FJ2O!dtW!gJl~V zmNHA&QfXOWsj=*9iCY>ii!4hl`&(Kp%Pnn|c1x$F+tO!QXF1q%sD)XMv>anO-g1)V zRLdEbvn}UYF0@=?*=)JYa;4=O%k`FljG&$^d2U=3L-tovB2t+m#ewZYnC-Ork^F0&@BE3K=n9o7S_z1FqX zgRF;GMe7mPqpinTPqdz5J>7bi^<3)(){Cv1te0A^uwHGw&U&Nu7VGWSyR7$GAFw`b zea!l#^%?8))|afWSYNlkWqsHBf%RkSXVx#R-&nu5{$%~t`iJ#@)_-lpCfIZ~qpjFx zvDs}yZEl;-Hq5rGZIo@SZM?qP99)y=|dwv2CfX z+16@XVLQOK+SX<3v8}PKw{5T;W;@(=ltyw|CmR?S1xj_Ji$*+L`@G`!V+8?I+n!v!8B1 z+kTGyV*6(M<@PJ=*Vu2e-)z6b{*e7)`y=*8?T^_Xx4&qA$-dS8vi*Jg2lfx`AK5>) ze{cW6{-gaT`_B%YL+>y+jE*A5E{Q^FRL5zK(;a6x z&U9SuxW;j<<2uLnjvE{|I&N~@?6}2otK&|`y^aSRk2s!iJmYxQ@uK4u$D59~9G^Ho zb$sUd-0_9u8^_;{e;ofh{u{y#Aw#Is?6f(DI9*P!v&1>vInp`CInFuJImJ2MImxzQs`Cuz+0OHv7dkI-ZgyVgywZ7%^Lpn^&Rd;#IPZ4e=X}uli1TshQ_g3dFF3b4 zUv<9WeB1e+^F!w+&d;4+Ilp!O;QZP7oAXcS-_HMB)TMFhT}3XF%j$BtoGy>c?<#eT zaE*5D=9=J|?3(79>6+u3=i18^aD`kIu6EUZJ=HzKJ=?vzdr$X#chFt#j<~DbQFooY-o4Ph z*uB)<>~3|ha3A1a?e22-xYxMXyEnKGb06+L%6+W+1oz4A)7)pe&vBpczR11Ny~TaG z`zrUf?i<`UyKi&f>AuH(zxyHgqwXi%PrILUzvzD1{hIqt_dD+Q-5b${Xh+WnpT zNB1x8-`#(?|M74h-lO#xJiBvpjP>dwBNt zlzGCQO3wmMjb~p^+|%e;|H9KJGgR1-cs)f?`ZFC z-U;5x-f7;M-Z|cR-o3m5Z^&EW-N#$)t@Xyd4c;d2e%^$4nK$WO>0RaR@E+*x^{(|E zE5%v=Xx*jUhLiEz0`Y!_iFET-W$EQcyIUK<-OPYfcIhV zW8Npd&v>8rzT|zy`?~in@4Ma)ydQf%^M2|5#{0eZC-1M`KfM3*{_7(?!Kd>XeZ@YD z&+Z%QbNhV0VZL2`qkLn1<9(BSQ++dhvwge!_Vms71%2hdh_A{Q_0{?6eG7eyeM^1K zzE7u@SW^C&3C5n9N+oAi+me>TYQ)MuJT>$ zyTNy}?>66^zI%N4`yTQ=>U+ZXwC_3Ji@ukAule5ez2ke|_mS^Y-xt2Gec$#6R3W(m%#O&OgyV)jz{O$3NE}^jG*R{Z;;I zf6~9)zrw%L-{wESzsle4U+wSkcly`**ZJ4`ng4MAiT;!PC;LzFpW#2pe~Evi|4#p1 z{=5D6`0w@K=fB_ofd4`NL;i>TkNcnVzv_R@|GNJ}|406h{h#>1@PFg~#s91SH~+u> z|4N)Ct`c{Nr^H+0EAf|Sy|FnvZ|z`q`PEY$%c}nOO7cyw&b{yQ%cS#IlJV-l1oZ1E4jSns*;CF z9xi#LEMtBb7X)`Czm^O9R#Il)lXHJ|uecIF+(`J>=ni86ous9;2aARlps-CXqmSm{2rEhhz zqgQF!!;Ro3T*-MkALr*vxM5r=H=Ns5)QDP9C+bCmXcUXYU9RLta--n?W4N(EG?E)D z7K?HE$?n#olICJ!VfB&d#c-3 zbVID9wV}DaFWFPx-MP9ZR+sGQ?d)z|)}EB&D52)w=7eRhbn)ZO%YvO9z1^Md?aA&$ zU`DWfZrPl<6X#5wK5HWU1-d?EX8FYOU|{Bq>C>l_%?OqyETdHkfV-@@yRo^ap{=J4 zT4`+ST^VcZSkaz@-y|%U777!-2G6Uo$bq-yCVuz!s6=xoz#r_D?`FE zeA=ujrE_P`DV;iF3j6{7FaLY$+-Xxvr%stUvs4+6Ioy11!WGj! zXc4WVO|**+afmqd3ebNU7vw@*82(fN+INZ*#EIez_|r@r2MY)iTiLm`rg=@0Dny=}>!#hRqo)hPT%FzhWGZJG88)D@F9ED@Yg>v* z>B_!bJvU(sSI5P;xabnyqGt=&z%_CUMX$J{>*TPM>luK~?bIsf`(I@)F5^>n2Tq~F4mUFFQ zsko~+0{#sxJIdSIdz0PLJS$sY4N^cf4_d2g?&(!q!XGCrL+d-1=OlK?7|GW9NG6UV zPY2h@4c)j=9KMz7=6bkZu5XdNpgUkCER_G4?Afm-7HWfO*V)nBy?!%Ltyx$Ti%P*p zc~)h}6YGQ_145;Z_1wYS&`sPy;>boiG_D2w0=uKub%qrzhD8%QM;3)|*@{b$&@t zYMdhQlb)qLEx1N&J9}V$b#_b-JihMvlCM@8uhkV>?L*w&VZ%p_8Mjq$FzyoXYVKXx z1ZE?-uClG8cXLruLtAH0psULQK|+(+vNXA_tGNRMvQjgM^xOoy?nw&=>^94$BA{|u zQa>iE;fF(An~R)_Bx|!4&7Oq^5|2zW{!{U4An}!KF7hv|?e1*tgNUw$0zs*QVeA5e zT}N&yDjESZt+qS4rY*U)6s-;@$RYsgXoXb&9gvQ-Y%bbu3H*L}+loFgX&@=kjNe5P zX>#o=_iUO7RFgo`%~Pgto;qy=m-=VKgCpfXZJsf6#QX#0KaZGy@{{tP^kDQyLg080 z2olF(J}-x$a36$&4}$?b8bZJqa~mQ2dj$l4Z{zOf?t}2}(-8E1146wYa9?s?b3Z_s z_g_MZgA9cL??|#6nE;{Py-6hmb)#fo5+{vhf6_+UNuS&xCGvkEIGyniIaHp$M{!4k zF%7TlYL{(vw6C|lts~h0>XC!sIG<8WVGDN*cPzK2VETk*St8zB5p0+`E7TD0Zv)K2 zv|0UsO=~i~zAM=?JJnpGA(ogvwVzI!DlxEs9V;TOFl=qh+hDtpyKy{sLc(INiBEK6mc3Nj_K=3cEdDWp6u>UwyNe?LJY5iNr`JC z66%3nCWA5v%X39%_xg02t&+E~k=wLUoGNQ$3wJ5_9f9udW-vWlxHU4x<=hn;#pyD| zRovA}Rc%RxTez!`RGqU*C$8h};wD_fUC-UX-N@a<-OSy>-3pWQcJ2=DPH~nvTbv`# z6?YfsiF=59ihGHBi}SC6nR^d+FU;Qixd*rhxrexixktE1#ef(TLtATh$vuBT@_IBw-m>mR#Q1oeXxa?rQFC>*?%}OnE(MGPU)~ ztg?YOr;Md!W7fAP7p4EQG?ZN4+}GY)i?>2OXah2>CFp%fmK@UTgP)J+>TX*Nrds*6 zbeq(g$qrjiD7ma}#R}Mqun)MJmR2{fYg^rRP_k7*g4qfNJtc-jEN{)FDKSB~wk~j) zkiAxJv8s7}XCL@@Te+9P8T_O04`<9z< zIrlmD1(>O?z)*c7M#O!@1!9$0E!JGleaC$dw(C3YC-5?(VlAVuh*73-ppTg`Fjl$y z&^q{ZilXG7Rl-2Q%!ePgtm;{kQWNauQ>Tw#jN8)!*lwil-Vbg}UwbP$GqvEc)+G-F zU$hry4Z6r_o%#n%BP`EK z`O(9{DM_M>jFxaxPj5FEyX?7T?!o)WeiN6lcsnGoFol#&Z_W5EwzF7~puVmOxRt;4 z8x_^OicR7HV!K!`o(O+hA+CCj_=umBK<9>$QZk(EN=CpgG>W?i)T5k2sybVw4RdL~ zm6q4<5Y+>$>+I|WKPxH!95`1uw{`TBgN`HxN0%#@{l63U7aPSEakRH>iychfrbpOa~GACj2!BXzuEuk^oqGY?(*1eg`#rgLHoSUr+2jS?&28<~M}F7ZIITkH{g#XfP()!>DW#s87@Xmg`uM-~;<4ZntP_ue zGcMR%Pk(E&G?V1R2W;T5dItUy%zJfP2kgaN;3VbzH3}Z=gj|p270}^fIcXvtYviy2 zx>^G$+NCX5wTrm@51^3`MX)XPCMR_+U*41K&4rXsg${PUE(Q8}S9W$oLj!w^)4sg3 zy%mg0E^V#tZtkiAyIn;gSo%!#zwKz8L!`nZRVv z($>^|C;KOs3l;9oYibu)Tq9%ED_HX z&&l)zUM8=|o&b46JY75`wLzaIZ?|u8n>LZR#Z&vY*?IlDnfPGQ-4=f(+1;}emj5U0gvHs?xq4D_7r5(ki<5A3g78+tGFvvX(!ZxeWCDL0 z>+9<3>>glpGia0*@;&*Ln{XNVf&55*B0rN~#Ph`S#S6p>#fvT@zmea`ALLK+mw2&w ziFh-kTN!;B4XJF|mFDD;mLeXVeLX1|;jbnsZRw-*fBpzw#_0&L(w21B^ZWK3(o5d~SrQ&7c<>D3Ml~<8IYNi%yr8a7(4myMmg}v%3@oMoJ z@mleE@doim@h0i0n$~E#a=-0JF4E)u|e7~Al+upaLt)sq2+R0J_HehhT$hEY0LJ$vq{2q{F+N3G{?adw7o~vx` zwCS_w%$+r@Y*yLSsWWFynKC6fd-{~wV0c&M=_FhLNHIVN>&ZPWpANjiaM`p#XwLLd zCqHvcDkgK=;V#!b&@foQ|UCYzp1DW4Dc5C z$#iZF_&4wu1#l*vl?gbP?k@izVVQC8#5t4Z&Y3e~*34P6XH17q&Ym+fIUf2rp)}ub z4pD&jqI;(cRt9#A2E|*%sjtv-aKOP0rxmo4M(93Gs;?s6F5V{I%IHh~Zw6)rMn@k# z@1~Cwv1(`?#NcU^*3x~&JH$K1yS9*dG*0WmGT$xUBaUrME%OFt(e9cyn3>BYZK>v8 zOOhF<`(gK&i1&gSm%{>df4Txr{j`}bqb;0r5fcA@SkMA>XqNyuN#A zJ9iHpX&wP@FDO0^{|6(LE)huEmF(_akJC1_QOhwLw_;dIrMX_3B80mQxF^for3{D> zX=F+}QcNV1DhPABxeJy~W=rc!Qvo-rNq-tvrWR3LSxIXkG(y*kk7A(BOb?<5(+y4F zK*%R~@v($uE&(TP6_~uziKP|E4scG}H*AQ;E5mh(s!(}Vpdz+mLmfoj!RBt*u(+pn z)t+r#D?2-q54%g#76d^hkOXJ(?av zkEO@aDsx6#|_9rR9m7rmR_L+_>c(fjEG z^g;R%eV9H%AEl4c$LSOFN%|Chnm$9HrO(ml=?nBl`V!qrU#73nSLti?b@~Qq>3j5j`T_lrendZ}pU_X~XY_OW1^tqKMZc!s&~NE?^n3aP{gM7ef2P0CU+Hi3 zclrnYlm12jNB^e((0}QFJjWBB@;oo_8eYrmcs+06jeHTm3t!Becr$O|t-Ou5^A3Ip zKa_X!F5b<1crWkc{d@^Oj4$Pf^SkmR_>uf5el$OZAItB?kK@Pl6ZnbzBz`hKg`dh# z_AtU(2uK*YgMQ2lE^FL-<4a!+4Qr{Nel&{E_@o{L%a| z{IUFT{PFw={E7TY{K@<&{HgqD{OSA|{F(e&{Mr0D{JH#j{Q3L^{Du5Q{Kfnw{6>Bg zznR~{U&>#`U(R2_U&&v^U(H{`U&~*|U(esb-^ky@-^|~_-^$;{-_GB`-^t&_-_75{ z-^<^}-_Jk5Kgd7CKg>VEKgvJGKh8hFKgmDEKg~bGKg&PIKhM9wzsSGDZ{=U+U*TWn zU*lir-{9Zm-{Rlq-{Ifo-{arsKj1&)KjJ^;KjA;+KjS~=zu>>*zv92 zf8c-Qf8u}Uf8l@Sf8&4W|KR`R|Kk70|IPoy|I7a;Z~_sizzc$)5wwC%&L+u&Xda7%7YrMhjzvvBGY` zIAOdnL6|5^5+(~%gsH+bVY)Cwm?_K>W(#wKxx((kJYf%EPhl@%Z(+U=5Xyw05E8;d zxlkcg3K3x+VS!L3R0}miRHzm973zeT5Etr&2BA?{C^QL+gvG*s!V+PrkP!A4nuTRT zi_j`0h2_EuVWrR}93ZR`+J)6ZhtMf>2?q+@LXXfZ^a*Q(wZb}Ky>O6lu&_ZmL^xD9 zOn|Hc;c(#y;Yi^q;b`F);aK4~;dtQ$;Y9HX@k#M1@oDiH@mcXX@pU z_=@TCl_@4N__<{JL_>uUr_=)(b_?h^*_=WhT_?7sz_>K6j z_?`H@_=EVP_>=gv_>1_f_?!5<_=otX_?P%U@o(`T@n7*jMmR>G#DFrwGa@jeVMNP_ zjuAa021bmG6fv?3BgKrE7%?+qVZ_RajS)K|4n~GBGL#V~BQ8eVjCdIFGU8*z&qxU) z!x$-LWH=+cGBSdZk&KLDWHcjV7#YjRZj6j$WIQ7i7@5e(Bt|APGKG<;j7(!>IwLa} znaRj3MrJcIhmpCA?9RwMM)qK2Pe%4)WN$|1GZJ8=jFBKCAx6TClrvJnNF^f?M)qN3 z0V7q6R5Mb;NR*LUM)qZ-j*%E6aYpJHX<($0k%f#jF|vq}#fk$SsWA%E)bu+|I}y zjNHk{U5wn#$UThQ%gB9<+|S4Zj6BH5LySDk$RmtA%E)7kJkH1yj6BK6Q;a;#$TN&Q z%gA$#JkQ7rjJ(LmON?w~{> zTFj`4Q8S|!My-t67_~F%U~~whLm724>SEN*sE1K6qdrFcjFvDujL}j?hcmh>qazp{ z$>=CXM>9Hx(Xou~#^^Xk$1^&C(TR*sVstX2Qy87f=rl&BGdhFOnT*b2bT*@N7@f=L z?u^c3bPq=NWOOe^_hxiHqX9QXIinSfRx%o4bRR|+Fj~cEHKR3*Mj3@$ zo_!guV>HHSoY8tl8yIb5bRnZnj4onyF{Ar2x`ffCj3yY}pV4MUmoeJHXe*;hMwc_X zg3*E=mtg) zVf0W&4`WnhlrefZqen1$B%?<$dNiZQFnTPb$1!?5qbD$WBBLiUdNQM@FnTJZr!jgu zqh~OBCZlIDdN!lyFnTVd=P`OdqZcrGA)^;DdNHGyFuIY^O^j}4bPJ=GGI|-Kmos_= zqgOI|6{A-(dJUu3GI|}O*E4zpqc<{o6QegXdJChsGI|@Mw=;SNqjxfT7o&GGdJm)b zGI}4Q_cQtcqYpCr5Tg$>`Usrj6Ct)<)~%KwMiDh?hs}s#OUtL549T z068nY*p*l;98c5+f(sJWftpBpI2KRfw;lbzAKSos@hZzN@xtU z54w-k#NvUfsze-of2F-^kbV51?AWz%JXo2C)`h}#3gh+2=qB=cNj=4F^F3kwpQVhDsOiMueFI5gG$cv4lKkvBpSwJOOH}2?gpxiK<9hU7#+~ul@TF zVp29lS$(8RA#O^*bOO0!@w!M^eR}f`B5z4<-UetTP!m^&=@F#$X46&#gNcT4T`a9n zk0asOY{F2utiA%qA7{3t0u&1Vq-f$PV&zO;#1(BKxrG*!e zbmRb1d3pz

QG?1$1ClIJP3ku6a1*4uzUvKot5{k$!wOJx-fQ7045*356HJNJi_z z33*j1jBn&*Os%{)EO6*XA{It70t-LhZ=Bvn#&Ov#s&+?SsEKlDK2Z~>?w>U8A>Wv6 zzKU=S^gkX(!vH+kDC$L#^23~sMXGD7BEd);%tLJ$dRP-|N`#`-frvWEKgrEklQNEZ zh5Q`(=4T73F2slokf;m?z$nBL<#o|&FlCV{Saq^t%UG;mA=-d-5~xM#5Z;^MvlrFE2g{1~ViCC~MQVZ=?24ek6{sFnBWy>E62A~13foc5) z;bDcQI;5_+pOJr$Z2ov%I1o=%M`AH)QlvGUh}Oqz>*Fyn^NDa>U9?U``VEmLW+SEM z7xbb|=|BvuOj&ec+N${zS)C-5gsi#sm{kJy73@f@6uwdP^EVRtGKd0I@o-%YPNs^w zXnn0h`QKoa(MFhmDM}4RN^c&_>i%h|(IDa2JcPC3I!RU08qhH;14ShoJ#x7-xT*tn z3&L@**!_E(rU=P)%OF#F7HJ4)7}Z2$Sg`~e6S9?Nu)*@EiZ)e;YvPHjs66A8u3C|L zbUy0ZXe(AzT%$m6}j?`hHuXaUw^ioy;#H)RcP^zJ z4{~`j+EF&~^num*^V4MvYbnx|TF5%m7lonAA=UTYM4T6wGY@+|Ham0c0AL!BiaxMq>-}vxIU8)-YHb6$2R- z)(yt64>Dvfx5fZ?mxJ+CBhRj+GTGb;*+m;RNQ0~FJazRoiF)wFL*a5|$ZC;(WFGny ztu)uw0f`|8JXz7PymOM2xLzt(mTcI7P7~}>;N~v~HzgVu)b(}rV7>t)FJkgq`}*i| zDNnQm{7*>QQbcG#z=?$bHa7RRtc-TFL#~h%$JaC=>#S|F!gUoSvte4o`emKX-L06I zq+sla81uG`QH6LcOyByN0)JV|7owrzQ;Vr7ttC8w68D|aRG&)hbDu)GYCQZT_=%IClv zBSnN&2(fno5LF9sW+>W)p-yGw+&VktL$6d+))Qh0Q1g+on7m@ z+g7Yp^>QHM6fof6p*8n)w5){NRVc5@F*dyjG-ulasX*%dOV`m_M47y86nPHUv@J`Y zyayq30b^91>;-K$cVUzv+<|IoRn&S2LKHAfYGS#EMMVDcLgYum;l6JTr1W&l*`U(p zc_;~qSZ&g&RFUZj#4KRmKtesHc!bx$wC~9Wb~J(&V6bXpWl$m43u&+`1MO>@*XIR1 z4gm{T=xKnE)DBmMc(IsI$`iLAv~eXUGy)k2$$X%vAn2Zjj7_k)9jtV7civ8)j!*@y zz91NY?&f?VorO4i6eJQ<0yRU`P(Gw{5h+jz63nnw@Uj#p>Lt7L!CioG1x*trrKV?a zz>Bv7aBW9>XLIW|05>6E0Yeat$p$%JPcJPHrzTbog{+ZKKAbBMr=S5SZ*Ff7B{9kg zTYo;ds}ZiUknv16B#?*%1$8ZXm%w$1T97Fzhm^=3%o**=JLPUfu=$0ESh1kIyA4)U z6;w#%>+mgz7B2)1+`0DNl|e}01plL5{##wLrL()WCkA=rc{_i5!JU^e2L-#U;Lgin z^qpO632j6v`M7)~q#|w0{Hm=0-p+WYEXAnEOJ=pQ&YRH&`nyc*Y_t(HEoB8ty zUeEw3ipa)PkX}Nh@b-wg&7Qx4kb4yZDR~;zeLZb0`NVs@V65sk$k>xoi1J~*g;)jH zdhiN62Q#YgB2FR8tUQPgbN=n|0|Y8)`qdI*$(eDLG8-U-p;DFWw3FHp+Z&!Ky|Lcv&1%Z_iMqz#h}Qxvm1;(95vQ7x2&&) z8vK^^aA~MmfIqe$Dxb>!hfoEa-XN{Dt-CEB%D;$G(A14}C6g^Hao1kaA=T>Vfz%R& zEXWUvt%o|<)qz$RQE)TllK6b60-_c)fn`*+^dujk4gvQmWS%s@fv+>xw+xSY%aYxJ zmKLaD$+w?tjfh*&!oUK$IM^7>+{`CsF`^YT$_<^7+I%P$L@8jGsE=S}Ya5i0hu}4X zmL#0Go0p}Y{zy$>tsSunh*_yvX>YtJ7()?b`nGj1SMB%pK;dp>TPpoc$_A9j`Ha4LzS zcP&4o(WBt)ig*Q_%eRG>Xsc~j!Xw&I2pZXTPYVFWlwn2Iu?SnhzO2IbD5uV{?#{I^ z@fD!)1vjrY6=<4g0BSwm3vk|vy+mbad zP&%8Y!}W5MFwhHG-^==XlhS(`3i9rVT#y@=0~r$B6`Va0rvU3+rH(Q_82}4TnKclI zhXTSDb^gsqz=Et$-pIK&STKeCE|O74iZZZJs5w@|L$wDm|>k_R#~vqZZPX@}>kV35={gkh&D<|X0o1?D>3cryxjzy%Dl>LZ@Ox`kZpXfCI|8)${=Gy8IvAl%r}PNnubVS zv<7e275X(uKPp#4AYXVP=JO>W87=MFRl7cKmt=ZSUy4IYS5R8ofNcIeE!9L5wGp)w zhaqWcE>cXwOF%LPWD}{>ha>frT+}$aiN;90GJo!w_9)~YmWvy1c_CXb5pSwhn>rSW zOL7rQS$!G(JppM)=cJX(hazS1XtXLl2}OG{^5k4mYF<-yAO=|}3CR2lL)s(egD46< z4ViOl4f2g7RlrPDC+3+*JvNtk3&P=ANI!}+VCD-Z6xG$JT<0KHAQu-*mwoG_z>!)~ zkULn96HU$wQWMDJbPYx1^ARrR%Bl;ilc~H8Y))geZb9a%ya;(GVb<+R_*h-S>5 zYmt0JE{()suB2utmaHlGH{|V3eT}OATxLgSMW0as& z4vK(R1KVgAvQ8yani@Ps+B=bbT3Yw=vxgF8k(xkVQ^w-G2l>~}WH8Gaz~O4J-q80{)2fbMM3&tMzWUM(b<->4{Jfe9Io}PE zij?FL@CefUkC)|^(%3K+vI!O_gx+NS>cU_YTsH&7g~dBcm>h{_dn(@Ar|bG@UEFFdVFpODjQ5 z>r~qK2&w&fsBv1TS@haZk#u}IX}Il-v`*Kx>@p#gc)RR=O`td2*zjA_*0 zi+-ULR4r1EP9F$dGH6SH7IJDEU~*TMLvkEg7KO)vJY&*%6kkEfB`c3s!Kp&Z@>ZC3 zL8e{Pnfk?7*wY4FXCBZhmnhUH=z zc$(9>kl2-rIL#{Qyht@NgGx$#!|tWhn=>bpt^_$UMxnO83{txL)4~TTbU5;47#Z1| zr%a}tMhz+Yko?|%5YmlAt_-u$SOvPUX&Ak z7837~MXc5$K>bWT)QLdSbXr5O|CK>OjpSkV*R<&7=0ZxP$72Cf;YfQR(u6FTVoh)i zsD^?8#WzlqNU<`yy^(Xb+?<1`xD45RS!`G+AoGC9QC3_SDYGYuG%W^!uSBL&5^ICY z4&3Xdx|LX@q6VgGJQA-8OUDMKi3KTP22#tbIo_mD)*vP5DFDGrRbz8P+tS5+91>t#Ot8G1&i=7%%TX? zg7j%)GmswIQ8I*e%aIy}KR-I`BDK&~1j_G}CfblGt(x;V4x?*aLhos6mlS ztIDgw3$YInAA%(7G=tiQtacKI3|(n1W1&+5vanT!u0w)pX#^RiFE}INybQZ6)VoN$ z<$*|5J!nno{=vvU8~E4tPAr2*Mp{duuxG;t)f0mugz^wLG@=t52P-Z`gV4FJSI_#o zLlI=c0FXG=2&H-mnv&Ltyl&Oz8FG#rz^OK$uf-#C^F?Z~w`y=fwO(p_#~|Ot0qx0> z%B83(cb2Hf7sQ&gmnR`>KK&@IW^BpI@SKXAe%CXRM?Pk$mg`^3+)mC#yQuU=}IrxCFUM25?F6Qt2%OR~6dLNINDs zZ54!J>!FGfrm-)zR-1Xwcm{5WZR}??PjiCCVL0 zTbdtUK2s=~xqBPbvHJ4<1ElW0fpVl;M?qvqL_vNKX@}>RLpo{pyWfu>`2s0@suXTn zq*s<8F$VHBdrD()qtgoyaCd`;fJ2*r`gbsl5bc19@pxnrIIXaa#bFb}a41Mr4k36& zj>i#rk4#{EUo`cG6Q&+1umh`lt8iaeke(_WY2ln%FfJHrgYk#`73$Jq>rrjnvxqcp z8!}-rGJ5h5+l2^GAgx?Z_X6_oSpdIkou#h9NgYQiJ%q2@icouR50tbyDaFN^KE|sE zHnjk-P!pV!;8-IC`-6&t-zc)G1ZEy0;7MmOMOCl_9L{3UHz! z7629RP@Sv}*Lw)Itto`VmD)k!9;qf=1uIx}3Y8vyh)83$p`fa&hU!FBJgP8#f=pAk z!Gz7%h0!%no&FT=&yjn20o*Va5Sy=tcvl$&=G0dubYCIBUfTeW*JZR8eALvV05CAJ zla+p${}!<(Yy%6sQVw?;=wc~FkGda_b7ldY@k;QkLa{)(v}MF2{loY(Ld-1yLWy(d zo?5>l%;W-K>TBwvP#Iknjn=AD=}%D~Ve}MXvZq&W7>ui<1qx;WXmWI;3haL$ zyqF2zEzE@=9D)*&7!)Pq(Gbox_=A)d^m=5UlsOui?5RZ=h*e|7A>1B?6y74_%^Jx} z-e5v;C9?ewy$P9j8?50VSP^J6)&yIV@{)<(ifrQsV~f;4*}G&{)9xGf4&<9X7@u^n zCtYzW);0U)LhnS7tmT*4;gqsq+W}Kpt3J)3_aJB1Hj$GPj3-?5!zkBA@l2^4>EMz> zt=851w+RN(5m?2r0Ki*GT^NQ^F$Xx31@MkT8N^Zqn=zPT`Y{j=+?mXPs(u6_3_6lI z*+y@(;gmAb(*czv%HhDB?gr?0LxijqoKvZ_ffy(-%d+byAbZwklF2TeUSU_NSD)X} zPe!J^bH3m5q>k%QK;D0^te=L=yAP&oc>_wl?xMVDAqS+=#%w0yWLaWGa(G=cIDWk1 zk_8to%)bQZAWR^)IGHf1kqF|6J`VFU2%EF)mi50Yp`V9vd6t+0m$u6z7{mj^u;=x= zcKW>#B+r=U0jYz_7G*XEki9W)m*fN^u;?j2SbChN2DWS|MvZ1tenv#P^HR^$saq*! z6VivUk%~eak;C8k`bbQ=fl_wGlsQ)Z_^(az>ug&MIstYbvFZ8%m`r3n?uFRVC#} zL~1G_r4Ns%RniZ@!mF#V1EI3);2==7;SfB}6;Y0Y`U8$&an- zEm#?b=k6d80w00H{V3y}Lw^vWRuzgWuR}><^pvW?Qp!RlY{B?S7d#gP6IU7ELl8MD z$Wnm3fik#6gav_)6bK6!3)!BGh``c|6-hOElOH%u`R^j;*6U7vHk3iVm z^RZ{pu?tt%LY`?^G^AV!>5twn3Du!0hsOb>^F{z}RHQHy#v%J9JPu*A%v1i3tFYk1 zhSU(1{zL>R+kpWugRDUeePxCK^`{_S)}EDLsI<|LE`y+ZIWSM(Q5n(u%z<|n;$`_D z1<9zs&{hs+2M+tu`U)jTpg$KugFB!|>C}=#$_o%M%fl>8%A88P7~yur7NLs=snhVD zT~z|@b`DWDA$GxQJ8jTag-RE%1838v2prx4r9!$1?x11iid%gsQhxuA0=l zn^M3Rx8!or1g!Epcsw)=W=XLcS0j4C%SlC-tz2Cg%zaIIwxIqx#0zb=w92D)^0jIu zB+;mL?M6i0ce@c~2^Czpw}fQHG@YpMw;+7M);LQ63Osm1@hCU{#f&ehaad7CO{88nRaovnW;g0|^t#l0ciH9v24s>+fP)S!bR6%1vuaQHA21 zg2M_*gM@kv&kStm18irX?dg-`W25x4|1O0z6LBb4fpBD8$%N5=jJO3G-?U+^$BXVd z>4EJANd3q2E35=|Yu42e`MMJ{6ja;Z?2kq{*Gsr^&tsj`j?#R!%=@PS9(Y?akPIs}xc zz!hU60G|VBibYf;3nJy;rLvJAPN3{6)sRD>bk2?_`PX%(o@56#QUkLx5=unN%b}1U zS`PQPP*p@ z;JhfPiiCS2UVcN8ftTiHNu9=5x1o+54v`Ald_>ECU`j(PZSH{7+dB2WN3sMGDrD}- z9}ke;2#;05!`f+A_=X@hQJ2@_ET{?94nmv)^4(A=isxx4|9`-T8xSml3*mJK7;tz6 zUcHJjL=e9q)00z0(q&j#T_gk>RuI#BU{?h-Dg#i3=ouTbd}UY)NtJE*d;{!$>Q_lp zS3arhLkNGSXK@>%2$eC@27!`uW3rGk>cI?kh$lZHn~H7^f|rfd|0$dSsU+&;tH%a1 z<V2fQq{iaih|wcJONfAIhfz;PRsuTm$gRu((MD zUyR_DI|?qB6GW;pwKuJ1%CHnsci&D_c?{FjiVV$&v)8t9lzT_`K1Kp!{8-TgsSg>S z1TwTD)*c08!LCv#TU790;i2i!ZS!v+Ko?Pw3ua~BmO zf7nhTDtb;I#lsPC)^^GWmk01|3{{CoA;#?O#K3Jo?ZVq|EQ0L2ogk{MmA=9u6&&F; zGaOiz&#Ne!I|1Q$crD2-mt^Mr>2ke6}O05S&R7;{lx=@ z({@Th@|QT^wp^O25c$OY2=Z%Hclb<%kMC#~8qqDmimBiN{16OO!VfeU=U@xf@Ju)q zph0TKa>(jvgJ(B8lBJ{J56!LMpk#blNPfj3mRN|%B=SXo{ADERLnDbuZ9uuQGMtal zHQNsjC+lruUxe6uC{KJz&ralY0`R*Z1FqwxZ+$7VWFsQY-8K@SsJZZ%z?b{BSrLMUW{5(s%)LTf1LQ1Z7kOBoE8?+C7Z22x<~$lA6Oh%LT|VYK_?Fas@9C9!7BQ>6Pc$*V}VriI>=Mj*Vf+J@AS)k zK?>PJ&FIiQ$Uc2gc9jtv8hlR!KLe?L8p&`!0_=e=ySKHJ20EJC*Y~tZNhZo4fG=sg73Lg;EJ42AXH!= zl=OWWP(q}qrw^auk;PPcDd!)+^uPya!ZktF06&dzdv7;fDAE91Mxv}K0V-6ZgwG*Z z*>;0fhpVIT)(^bh2ImS)B~tqOBH{%L#fwKmkXKM&0W%(VOeK#2!>kJ6%Lq89P{4lW zMQbpBLqT~BQ7Q{X$rKB{O?;9KwHGcghiWqg^-V-wFc4K9P*_-U3<9b(2aawu1{M*p z5?Fi&-{X>w>I(2X2prl$U<{Jh#DXwu;A4IY;`@lW?~WlV4sq&LK?o6+ zWGH0UL1t=yiofA^gsdJ!)U-Q{%q5E#E9kz!*#X~T0fDn@=wAq(!@8?0Hw{{~DHy5? zlw*2g`o+yZ2$jQvs!(b1#sM~7nwx<*lrAapIU|R7IV@=5c%d*x-Bmyy0ds7Vg#*gr zdnJ7&Jy*%7MexBa)i!%C1zY;72aN_q%wehv7dLg8oPH-@+yzkwTcz8S6C=Q|MX65( z8qGT`XpVJcv?1zXlRlPg>FaLmT|Zzh!7FO@b@=S7lw^r}sI)*;f5|ump(BH=r)@(+ zVIQOrKs>zv1p=cBVHfTcEEET$@L_x%17AylwCyldo@0h!Y6mcSu@!i~Jlkzlr^U8~ zPj3Ydp|y?hK%AOkY%IYBYX9FFs9u1t1u1TfaX6yy+_0vL57*Jk24ozG(1We-?b8h0 zEd^_v6t#~*@WHmC?E`NBqpV~*8OI^^U|UgrM;|`Fg0Bb+nC)QXLei5o_y_6h*h-km zI1zEH1{uj5xYGUsH-K2&9fGg~CTGJn6TW@`Tau!UDTvIVh6+m8Ya9}IX(K|Vq&#bQ{84NG5II;tXOA=Larp~7%DuYFq7)$t?`& zsyBDWxrjDpAevl+oKXQS%a(p_H12`idkx^0zp>fAVFPL#j>34BzzR(1;i$thAV>u?%shZwm)sF2LxsgqM+;X$&L4q(TAYUMO>^ z5_t!zQ&ouEiB!*}jIy+HIpYH4p0TZ#C6T48K=gt#Wv@Ym3EM)zP-E8Bk#S$-+@|`m zh((ScK;}3^{RT=9N1VCak^p?Sl;@5PNC4kOOr$>-Vr)d1y|)D;2YypUhbRjU>42*c z<01r`x7}a^ftD1cqybW8my#RzN1SN`1X7Cp;N4bfsq{~F$WVewqeKggEyzDm$=Up= z>pAJUSa8K53MFN{sY`A-!i?Rf7?}k!#x0j<_|wRFN) zc2<;@C*k{m@N|u$g;X!f@V$~4WF1t3HLnOOZvq%Q5w5y$xO(uQs&THA!(BZVr-5dI z5jX|FZ7SqSrB!SjyAir!C!i%m1({`-fdx;Y#V}9e}?87$dccKk7B?WKN zAcI0K2CD{tPBBXBu!%)G(?qIx84B6(@P$D&xDJfOqE%93N;?N*JMo=pCv~`k{AWnv z#UTn-MPq5NxET-KS!JmiAdtHS?<6jS3uZi@2Gr*jjBIB+ArG~@u_ztFG#qu+9f>V8 z?u42kq=v;2P@*g4{YnK4sY*+=kz=rtx}9l68ePa`NzKZb#n~`W(CXpN{m!7PU-i%ijdl?dYGl{F?u+L|`a%8*`3Ei25P%#B7MS751 zF;mwd*^o>!e0CJ>WVUkMT#xZOipt7G&vrm??4b|J;YM9iF- zScsL_$UO3l@gd{G#0h_Tk-Q`>5zl7y9Y$XlPlP|8DW3Bh@ew~M0fu3u)cB3|g!7uyf}PyXj+ z5`y1^$;YIERElSa%S295!8^u}xCxgT-!;BxeBbzi@k2)6W%NBp-)Hm#MnAmF__6U5 z4qW%J`%4C*#k?UyQ#Re>47W{KNPsqn|MPDWjh;3QGKf(JvYOiqWqb{pKpt zXZ+jvkMUpQe??poDWXNZxR%jx8U2pY?-~7p(H|N8iP4`K{RK5%nnzpkwP^Tg+=AqK zn3J<-&YW3ZK701G8PftYXU&{DWy+jsbLY&S+W)J-z+a6_aSiy@#M<^g_>@6?4?Jk4 zj-a$m(nl-Q*3;7731J2Nx>OIyF>TV6{`Te$Y|m9TciQyXbLP&PRyM0_>eQLDrc9X< zoIQQYY^vTpZW)hpb%N-3tV6mq40?SmW+#+U9@*F38b4B&ksXmxP7@ zMUtjXTc+;r?(XjH?(XjPtLuN}?Y_O;yIa^K|Mv4~QkeO^*(dYfn>S+ofUPHG*r4eb z^j*uTM<^qm)T5O##t-n4xZl(T(-tmRFlXM}dGqJY#xIz^U~YUee!!GMx8Ll~fyXNo z?5$45Rjf>bLBICRd}UB6u2WpC%5-IhGIP*U7=-iZcNp{=40;jYYF1)iv_;dWPbcNs zJ-xH33+oe4U(=0+2iG=DCu7F+Rjoa%dsj@a=xSdjgMvKAJg_Bg^H<1_8Jp> zY@g>we07NRrSp_U8B;DE6d6>bEbP0z5B1!i7iVs-ELL{t+Z_h|1%v*k$v4_IF3#0t zTZZoI#OUH8`q*L^^be{mLk%mXN>#>`%ak&uTnQ^nl?tU22K@_z{sSQcgiH_yfgoI# zIY^0Q+?{c^63w`~Zx0Ye)NsWh$oNN+XsCHQ<6S))Xe#2o>}+e=fc~gBdGopUFrVg4 zLd*(+`BGiK%>gmSM#kHzZ))pxHZ-Of=V?83zjln0fEl!ui^lt-1vl*g4P zlqZ#^l&6(vlxLOal;@Qfloyqkl$VuPlvkD4l-HFvlsA>Pl(&_4ly{Z)l=qbnln<4U zl#i89luwnaF|E59heD!(beD}N||Dt{?| zEB`3}D*ve&YNk3!6;x4`R9W?@Td7;C+o+1Fs(y8_nx$r|L)08KR}H9|s;hbGP&HpI zP=~48s>9V0>PWRv9i@&|$Eah~aq4#Jcy)q0QJthtR;Q>_)oJQn*twz)ub!RoI z#?)H1POVoP)Me^&br*G4bvJc)bq}>s-BWE+SE$WuiyBu~s;ku1YOA`Jx<+kN*Q)Jm zhuW#`t#+y1YLD8hu2a{m`=}e#ebxQcjq3jD0qTKjp9<cQ$E>Y?gk>f!1U>XGVE z>e1>k>apr^>hbCc>WS(}>dERU>Z$5!>gnnk>Y3_U>e=c!>bdH9>iOyg>V@h>>c#3M z>ZR&s>gDPc>XqtM>ecEs>b2^1>hW%76>dopc>aFT+>h0YeIc>fP!+>b>fH z>iy~i>VxV->ci?I>Z9so>f`DY>XYhI>eK2o>a*%|>htOg>Wk`2>dWdY>Z|H&>g(zo z>YM6Y>f7o&>bvTD>ig;k>WAt_>c{FQ>Zj^w>gVbg>X+(Q>euQw>bL55>i6mo>W}JA z>i^WA)nC+K)!)?L)j!lf)xXrg)qm7~)&Kk%{!ITMzu*`Bl3(`w{9F0A_HW}?{HovY zAMDTaXZwfvbNspffM4_L{yhIsf4;xKKg_?af4F~yf26lyK{u=+z{-{6Zul3jY>-`P>W&Y*d?0KE!qy;c1A+pA3W6Vm!60OT zkPX5R5OP4s1t9=}27(Sk9tcB0$OoYSgkd0T3&L;^Mu0F9ghCKTfiN0`F(8ZuVH^nC zfiNC~2_Q@aVG;n{H3-*$a4iVefp9$tH-K;>2seRnGYGeUa4QJ6fp9wrcYtsw2zP;SHwgEDa4!h= zfp9+v4}kC>2oHhqFbI!;@F)n6f$%s8Pk`_w2v33VGziat@GJ<=f$%&CFM#kO2rq%~ zG6=7L@G1zef$%yAZ-DS72ycP#HVE&4@Gc1Nf$%;EAAs;72p@s)F$kZ4@F@tNf$%v9 zUx4r>2w#ElH3;8;@GS`6f$%*DKY;Kf2tR@FKM;Nf;TI5o1>rXkeh1+X5dH+=FA)9) z;U5tG1>rvsGeFD)aS(_Ch$4s*h%$&i5VrzxYY?{qQ2|i}(GTKa5VJtc25|_8IUweO z7ywZNQ3o*(#GxSOgIECKFc7x|aX5%0KpY8TA&8?u91Y?a5XXWz4#e$191r3I5GR5- z3B<`DP62T$h|@rv4&n?DXM#8j#MvOu0dX#f^FW*r;sOvCg1898?Lk}&;tnA02;xp4 zE&(wJViAbNAeMj_057#0n5AL97C?8pH^QH6ZQ`Vid#}h_xWrfmjb> z1BlB&Tn^$cAnpp{ZXoUs;vOJ2g19G$O(3oSu^Ge`5aS@O1aTFJt3hl9aW4?pfY=7& zS`gbo>;SP7#Jxf60`Ks+AA6F@u>#FIcg8N^dSJQc*#Ks+79 zGeA5O#Iry=8^m)!JQu|CKs+DB3qZUO#EU?@7{p6JycERCK)f8pD?q#w#H&EO8pLZr zycWdkK)fEr8$i4f#G6388N^#aycNXTK)fBqJ3zb>#JfPe8^n7+ycfj#K)fHs2S9uf z#D_q97{o_Fd=$jTKztm;CqR4>#HT=f8pLNnd=|v#Kzts=7eIUw#Fs#P8N^pWd=6#`Kztv>4?z47#E(Gy7{pIN{1n8`K>QrUFF^bf#IHd7 z8pLlv{1(LTK>QxWA3*#O#GgR?ABaDL_zQ@?g7_PVzk~P(h<}3k7l?m@_z#Hxg7_au z86ahXGzcUCBoQPDBpD^7zrPsz1D+{;3hnaj*h@-_;j$Z$IBFRf8K@f^|G!MW2eTis9GLco+2m|sG zfcq2em}J^SKpPgytwsB=m0Z@}M-1aLnG-%fei-3BEGhs!GQ*`*5v49#>5L2TA52VB zET$0piJ^TSog1kl4dx-I95hbXWBx6Nneuga$9u3j^OXhdrmVmOl~!^Fa`}%WhM87V zX(*W|4I0z&Pvdp9Hc=$ge+;ot?w6fH9zvAXkJZ@Zf*wzd`I*L5#=7d^>(KHl``Dc1 zLWYlMcCu56Y+M>NOpJ_OXFhOJ!dC8P=|6*5=9ny26eYQ1H8h%$6?`y_qBvGfO8yCF z6T;k71Xcyi7&Ft9Yu4#VshjCPk07QcLl_kYd7fyTPAq0F8ZN@hzLtlB|3cy&k)D?& zu1MtM^It-=!;)#sFebtI4Cud{2qz>HrdENVIUclD&{f1WabT{b#;zr{;mK?ve8i4o zS5;TwV-fG`8;Epl3aL?mh&h+=@(Y~fX5tv_!eM+6s}{}aZzKB2$@Eqy z4EWs19@-L*%l?PbHfFt?;d>*#JfZJ8K4nI1&y4;@)AJd*urLChg@vXO@(JQwk}RaP z5cwOj;pl*M`J$03R%z3Usc{eYa7=+58}j{}tk2oXlU#GPCmZ1uD^-l@FXF;~Pi%z_wnXqDRp_<9kNbZj>h0X9YcTc+7bH1n*~aA; z;u)L7V^v16c6ytx_? zzVlG*8m5FRMUM#n;WLSTtP8zK>qv(&cn)zCXNKEXc1-JR!zbH9X28d`62a=|klol& z5$$d74r4C#&N%+3y{BzMWFR44%YkQ1dmsfN&b+zDgA3hMU_kh7W5XSZcV^p#fAg#vpx%m#Up>{*31+;{K$_56i zW9b1+pjdOfJzj-fvJxvtHX2V6^ghp}EB4qqm9e^Ts4Y%!x}3F?SZ56^Pi3qK>#eWq z>ga86v3G8m_~#DHUtC&*oNfFGhn|hN2$ck}lLv^(3Yr;hW~bD2wYE36b~d#Y)4MkZ zixAkNfx)U{<-Kd0+M~DxnpU*Mi#s|ubhWNpZS_l(a6Ak+)@^C(ZEs$U&!+Ksdz!JS zBcKHX3uFOV^UvN#%Lrxqz$nHXu5MjnpS-&gvxhONievhh;-*frGKAWxTUvOx?m-Y9 zrb%@y{jjk2v{Enfli>K0x2_dcQ%T2KT#@Wm9OK@byIMQ#v%ZC7@-T0zjkG+WbvW(2 z-N056mhuslG_KLLTI&K(C|3d*0Z{}t+gF@$Ts8u zqVeXAu9of?`mVTs{OO)Q-oWe^>@3e8Z-CWxwxG9$+sB_vXdZT9+|4`I#w%J~d+`DS z@}yA0bPB~7MK_Ty_WXfl#BHmN_pI;eTH}Ux8KD(<8C`A??tj<0lJKfG9WU74(y%(- z-tRtf4Pi$%1-mxBwiA7)x>7o`q?Ijl{0%=~TuQ<4n=NQcN3NeU5}^2j{cPDeT3s>nUxmPf!Td~e2{=V zO~2}RQ`d?O;dXSW#O<5zdTnhT>m&W>=tl_K)3~N!yY=7W1nFt@najAoqYXV`T{Ztx zgy-pWfH|RhIx1UN(Bi-n)@}kmOMsrX5<8$9+6#o{>7W;lV~i5n2Hc=tCa4lGD*~Z9 z*3n&JfWCXpvv9F^dv`|{Dto+YtZQoCU?knaM?*8&u5Lp=n;z38F5WSsv+Q7I4wyUhfTSELU z4{EpGo;aC=t!^Af$exaIX^<02BWncVc+lRJ)+k5N>W&MJ&l)tu*R=tdOfz9U()`-4P(3;y($ zHIE2KxIj8ROR^Rc?WlC|3})0KmSo3#eY}en6Zz*+QbFv~(z0U+@pAM~B4>z5gHfr7 z^^?V0O}uG86Z_QQ<*&WANYkvHiEVPaCR2G>Fff<3mKfc}FjS4zb0XEY^oLmuL_aQF zLnt4nGr`FIq;%q0ySRRmL66;MKGfP14rJ|4Y{Okzs>Whj;Sm3bJ&Ck19Vw-=!sNc_ z0cKSS%xWg;8R@8LbQ`glE~}-B5#<=(pPa5DYb9}yOvjC{yy&fGFVv9LO2i}55i_qo z$M?1o?RIHtYs;ByS5YliX|+3{WOWcv+7)H_HDP5fO6Z23f0T>SmeC=ri??-THS5`D+O(rWseP20M6EQO_P$wZ4$Xq%|#>5rFAtiq8jR-k$Lsjg_DwaXI)3kxT83j z4G&llZZEW7x5O@dC^%0 z_aQtrpsSOWZ$9f@qMP8L!`!fmWszvguDu^1u0lJPk&BwG1k0_H%knT$4|k!aX%Xf* zgt8tZ(#dwxgaA18FfSi>05wucSflbJF^;k`S}VJz0<&XL!gX{<+kQRMv>v3|Im!fkVY8KA(74eL>^YF6*_a!ThRMJ*s^IpvQmY7D{nG)i2cKd+; zkkUxHrENCICHo&Sjk7o6j1EanOrP~Dv5ZW|lIAw|2N4fVM{L(he-qVM2Nly@^m(Rx zbJ9fmk2oBoV3cgOobysUG}AP7@5Qn4egQ^J&%YNClOn*2$&$S4sWCAG;M1L_rD^e++!q~D43XC zkPgXokEZ}6??}T5X-bkzF|4}~s!HSov`b_SMXc-O3$h?dWHU$s7h0nkoWLo! zC(2O?M3WN3weZ{Uh=x}RDpSkJcO>$$Dda}<1D41wruuxOXRO`}5(lm>-X}(JHA)MJ z>H^c~Q6wE6s`!l~yM*X%W0Oj6t1FmYM$|a`?&!FS)L>iTMD^h8rNm_WX1;dG7G)ER ze{&U)>47TkRV3=Umqs(k+G#wL=diBtkZ3;3YZ-uuBph?)oSGY zij||;1M7gqP=Ok0=qfQW8h!&FmeI-DrcmpNH8-;sJ#^Vzra-4gs`SFeUrq$GYy^(b z7n+fHGsj&v7;9v_rI;PimK892H{zd<{QLAwEy9!|Erl4^vvH$!5@T5ytMJlDhgd2= zV5WwP)z(>e{p>~pnUVrhOEE&`7h+GbHDat=YyPYt&PgepR`YHx#?$kKtLbYkrY;oX}!C%eguOU(M# zBx!p*Fs7AtJu#+^h}lD|#k7*{OMIhJBsG>3<94j1{fT@+3b|FM$2Radi+FeR5!Z+m zE(Xs+Z_&8QX%8ma@#$$(3Sx4m!}@7}GjZA@i8l3%nJ{=E3729yn242kHv1SNADf9q zMd@kV*qKZlG+!LfYfSd>hA?&xlY(6*?YE=slZbYl1KmMu8UZMoCYS%zRQRf3v?9bJ zDU(DwgJ=ug;f*^5M?QN1>R4TA;sTj{UaA~s>!ekUg9t~ykZ4D_%Ta_5?}-`REe z!YDd}uPm4#7faBzw4mQ+cVP@)bb4C%jdxj@)JrhKehjT07%^TO#+pF1TTopO{2P|f zh+-9i1}?|t1e{bXJtZNzk(Dtus@I;ZIr}Q_NXr(?!{-Vke=O~9%VLX_oV2 zQ)HrGWID;CZ5J&>L0h<7_6@}EsDq1D-!Yw4VInjHHwNUlv@`oOmdDx|IhB1gLG9#0 z6bp)NPCU5SdB(Vnz-D>?D_M^BB-EkI*w3qdWZ&uavy9CHw@S=w%BMFJGS)?()C8a>@}5j_%>P>bHIE|Fv-&^J&c*Rx3Sm(@L8JANMv}Yxqww$ zC=E9TC7J~?=?A(UoNtL zCy=D&=lo!^vrvVgDy*?mGi3iooJre6T25BLhuyIvbf@H3I(+1?QiEszJ0KWZM{pJ6 z0>IOX`CwRL9=IE0IQofu*N{vCo6}#%BrpgLk25BcIg&#}Lg;rS)3W&nY}o8fI_cm^ zt+5o#1KEp&4cUeel2&lqPOS;Xumh7c`w%~|Cv7H9c6NKkU8&AWzcVD8m|W+4Li3o{ zbqpX+sP7@U#Js4#zBV=>W2NBH*SMw`CNkTY>4cM{iMix>?z0szV~MiS6Qhm_c_b~t zA^8LoOfQZT#vF-avb(TN0XJvkSQd$L-8^Jlf^%779L~1O6Byb9Lp6>u9Z4WAW9kAD z#k}2oE{-Pl2G>tQKeygCTfI$|oCk{<8t+b|CXFsXF#DKgC*nM0UMykXiV979ZtsjwgUkrotXQV$qv*I3i3i*LzWOy(xSv@zRs?WX1p4*ySd3~-j&#Q zRmIAva0C~pm_VwX-<1SH{fufJ53Q7%gfdLgi>~ohhu0KLN)|(lDoZoQb|h<>l@fNn z6T1#$Cswvn0q~;u>L$$8-j#ZX#X?=|%^2TYL*>ExFgBUE&XO$;lRk0z!_>wu|Icku zjoF~#k;mJqq;`^~lIh$~m?+0UUB!GP!qw&I(nr_RO7;WWwPQ$xppx`pKTtIxDq9xj zSHU4sVo#bt>Dg)0&_Jt=Bf`5e*<=sSgn65BqujIhg{m{2^{T5j(3hByAo=pS5%wC zbW*}oBe^V;;}%?r)(}h=gcFyK?;eDlWMuImZ?Fg-5phA#K?;S%hlS*mOcMdKj1}=h zZoP2_wuzilOrAUvt+8t%*!1&}mECq7JEIV9QKW=F2@P4bNfKH^Rf_4n*aZZO0U2f{ zI)o%7+>2n7)RX&%Td;VBEwM~gL)H>V(Pj*I5qb@h>C0&X9MVa6Nqd&NP`1%vk3m#E zFcq_Vb7iE{XBxb9gqL&%@g$>_p{*3v4qo;nb!Fvzes3Vq;?3wtc56u^UMmR>uN5O>~WL_HrRpC!yMi zyIT#<&O5EuS3YNc*7T4A2sG_8!Q zJDDxeCLThwSw)iBMTZfw$iqwU06CozuEImzHvafURxgoa_if1KYmkZ6X;o@+l4sZE* z;KRSB+pzB-_{~{aHPK*M6~0!ZR^_wlZo=Bb>rm5Z7^iZaMP}d+I!y2-K{~~{E~K8< z@1wSo?mF(BYio;l1wWkPffYLq9qREsoaSEeAhok}Q@)Aac}RNW{LW08jkOrAg67Ct zo-1$2BZTW&`PzoHj;vhK5(TWi>1y^k!Fry>)`Gy~d~}IrnlI-dj_?PWsS zbbVs_@#A2G_+$K#*9gkZ(qR{)(`xJRuv>z+ZvJ_n5?Ydl zu^)lZ@P>+!oaRsyq!@d@ATaj{!9=sh&LNS7#pU|igIpF=dAI}}`mFEc^VC{L-w{}P z!v|e?le-KHv7=#}o*1!6XRva4ENmhDNJ#FxR5B9U3HYv3g+3Jg<9;R-_jT>;C*yz` zuEtpzE@_OEmSTebNU0H5!U2CHKzFs1ELN}<%NQ3YHo917=f+~oscy)hgqHM@(9fK} z=Y$!{5+2T0U;RTc?nm1+HdHp}4QF4Y2rgR3(ME{R6y}HKWDusiamWqR7I|cziRw{p#d~%B-T+X~FDNJlUYbcWuH|IY-_+nHf62pQhLja<<+AnQhk*l65(IPQ}ZV zHP3U@O_14IhAc{#PPvB3j2;^Cx`U@u-p^Tt>VE6(zn|&oUy6yh8!>XyvP9!V;1{p%DRnl?0AXfXrlabqYUu=8XS;z{eL53W6)4 z=MGfhm<8T?ipiNo_?}cx+AboODm2!FOK`&~rlQ8UtKyIN08Axx$A)Y?85W{bWh=gc4|$4u)7%J7XzLUP1|<;*5LBZ;hO-R=i387WZ;C&iV- zntb!90mofriyE+S7ZPrSmD-`Am`!aRtI$tyZ6{`gb#y1Iq+`a6q6M(32H*TKZo3lI zST%W*W4r?1*sC3(p*f2QynJ)Pjc|f+6}{`*Vy1F-BGg5jh-!?X-K{96h;Vis7>B<* zR-+{mZTu9`gRT#(h=)DJP`rfsxV=h~XY(yHF#Wy~6oN;2cF0y5U8g z2m#93oxn>r_Y3U4i(JX?xkZS)pY5aAOo;O~QAT_`z}gsAC$1!n`J0GA7XaIXcTOvT z?7WE}mab)0I9OO1E2xwxLBV^jjo>$XEf}6tXmZ6PDJgR9a=_6$2)%x@g*T@UtKt@B zv3U46fdQfR=<}SeE$Se%0+~5e(eg>3f&R7Dak!V@Yd7~38t7O;5mUt#R0swm;ZqgH zKGZ@LrU}OYG<5A)iC!J8n0B*0Ubr3prKtrED2~EHMiz%y<1%t4F&+Xa+_%vvG=hHw z`IA-7egs{;>Cl+5VLLw4SFhBD+FzB#zY9f-pktt8BR-8%9Gb6E}c|b*Zih5hy zS`vqT<6DTrIH5W|=p167-7mYvhzA-By2f*@ttwj1`2?_-GP}1n7Y5s#+BS5zGA9%M zS0l|VR(!{GV%#T9S5jtp!#8REB^MD=!~;@V6c&kwk{R~AUGVVDoQ660r3Aji7J+l9 zD+nr>3dMqmuoJ@F-MtB)DE^g({{c=9N;ng$F18f#)daWGCc~A4>v79yELz@(UC3iz za;_t=qD=;?3ROig*AM1y!+V7-xmnJQgjehpuQptQeg$=9IOA!@T3k5lU4&YZifRlfE-b?afzg_l$D1W! zB!(Ihu^K7(Y&kA-x~g;FdkMH?Gl7eiV!T(Z7-tQ-=W)ab2yy4lL*xfyGpit)6U?7j zt{^!N6MXdl1>SHe!&3*vyK?a#BmC0M!)H4Wo-)W*Mo*L+^GU*7xOtdm#nzGH8G;FI zHcTU0TUnAD+X{c4AgebI61~8R@JPrfSF+Dq&P#;7bn~z=$b>@f@Z!#FV#RoVvqtFE zE$D`-FlN#sNzpUaHb8F>WK}<++TJjnOO_s1==g$n2g=2Q0w?Lvw+T9pcDGip4cc0= zWU336lDCQN;U?!jf=Z)7EhwA4k()9tNH#ZvwHUf&+2=kayfhlrJ6=hMtnL=zCj^*g zoAeH7nD4p!h}~By=W_z@Pg4!}^~~6kh#t)OiV)MNYVYEjkI9ZVfo}=5zg0S*oMZuJ zUSM2*{IJD>rdvnyTeJtMG+l6&gQsxq7QLBqm+E?aeNqZ<;po#O+uLR9m z>wDAsAK#XWYnGecp1T!+_qP>o8hAY_W$x{is}Od7TTxwmFQs3hi2Ib;j!LeCrKF+1 zu0b;eDgqdP6m zblUeY{iv;t0JX70Y<=s>9=rJQ| zKI5&&`mRF4TI7ypaI?LYd55wmVlzFK&8C?ewj+RPUIC1+ z;Jg!wH?=#h4lzE${Bq_|M&V?fdkS&S8K`9@GJ6W7Q;<{k>4Y$4AOtcsCOsYH&LYkM zyPtws4Eurc7?~=;T*6s6kOX*kGf(bx00~lF#729%klY0Xv(rE@hT+!?IwV9d zaOoh2s#2-v)wXUZN=R&G7DQMUzcSS~5_l7=1> zMl$3oJkN2pvYeXOWlNecgO@RojV@ngR3l4#EKD~mtbPM2Zvj)w~me@UnyVyIf>F$-L1J)B#>UL#$W+sA( z4J}I#11zIg(yMCjz64a1B9bf60MWp?`xBmChqZ^MmKY)0MxICe@vDz$@|-l*n+Mkv z2NTgqCy`;#XDKVfc#Mu(9eWrN4|5V%)#5%+ZcykX#{(F1k0io;Cm}{m;Yz_A5mxfv z++&DLbCOZoQGAnmIm4eZI3p|fxZLA2rd)7A-vq>4){Hlm+3YnX7Vn|-l-;40R;)Ar zQbt!sckao#ryv@dk&PdgW0km$t_^sbXkK$s?x`8;+4(JxD`eVj`ZOap8rz7l4%4H}mDp z>6tT-ZTpP7GiUeh-dEhWYwm&n8UMVTS%SX_WqzDlmRa6+a^H%+4E~YVxIDEF4!TR|EQ(wM&49ORDNdouKk zbMMT(EB9`YMu0RDq{54H@68ySdp}5{`ure`?i;l`q9#fS?MDCCUEkW>iX{sgv$OGk zskN#ms@m7v5?^lh=9nd|duGr-yt&77pHA1@GZ_l;OVi=iO#6}_ug zVKKa}c+;3AyFI{wvMs1ra^K9Da!Ky1xv%BEp8Ez!6F`~>(j<^3Uy}P)?%SDza^D4M z3P>~i_5f*7--KnA@s+GsmPe`7JxRPvWz&X^-X3%}!_S~OVD%hpn;PbVf0Fwpwf1T5 zXStu}egV={kfwn&9i$nTWK77oJL9Yj6dp%zMxURCVOTxZa-)h4*iG^EvDH|QEZVfb zz6qeUo!TNt{z?izM~8Ibq5JUr;=J+)}M3#$(V9Q z?k~B&=KhxZd+r~(f9C#``!`6lK$;EG9FXRMG!LZtAT0oC;T0%9{X5qDa{_(`{p&aX z+=sqrGIdT*S8p?&>{_snS$9WUochPC;u&hEk8Pn#IDW!ZH|p9}tRLFbw6@bpKgo^0 zCR~Cx04MPjH{z;zPZO3?ZgP@Oa3iN4bCSA>O@$d*9Z4tsbT{Z4BR>a~%5rkJ%8vet z@l~jI?BwQf2C@S=xXuGZK-&JoKrTp&`&s9KJY462p#fAlJAkyrTIYdn1BGc8USL$l z*uWT&cBFN_Q{O0Sjf`1xh_T+d5EBAZ((zA4eq01W;xFnOl~`J1mK=5jTdjq?pc6URcW>2UfI;!X8w*%ajZh$ zMZesQ6$y;Tnpely;=CVj{4WmwUbv!hw@K*CiueZ1sv7NW$Ayf05OYKcb~UeVrHb#k zrkFORGMGq~#oKSd%>^|FW>z-lMeS-_aV=$7DL&qXbOpxmgI!&i!UF|pM=gOWGu*zi z12+O}i|P$x!eryq5GG*A=ad3>`(&|c;xd7=Vy14gBncFu2x~e!THAZN85>UA7PiF% z_RP5OlFSzZD>Cx~Eq$ke6zf|BQXOq?yWD|`ByZY+YiwrM&U)~2s&?OENsVtPeaQ)}Dw){bs`xW!+zuWVh_YZP)d z|8LqpST2=*Y;UREXsI3zrJL7MqCu?k%C?JX-DLmcb9e7RPsWss0$qV_kd}e8{Gvc_ zU>!)ifbSGqp!3Wf&T^qhi6Q=G;mPh;J_h)Lj#9_v>QmfgM`~+BS?E* z8aN_wWZaWJS{c37WTQAlds_VuyK-LhZ z3|C2OcXL|@+WtvBbfX-zrp-vSw=lK6p+yU4&7Qwt;k;Qz^NME9oI7vEj2XrAXU~|A zRX*0Zd?Xg}#R91{q+g6LEuD?L#i62E!IA~DOG=6nqGf99!MKN+83n#F3#LO@L}MiK3v5cLE67> zLW8*$>&+FHJ=)g4&Sha+Pwo@m=&3JB=C43H0F}6LvkH70_&HggK?rCVn%;lxcvGdgSOE`P10n|r){Net!<+znyUG=!CIDcYb{z_TdA$mR%@-=UfLS1 zO7}RYX@itYJD1L2WbauhiHdthiQjv zM`%ZCM`=fE$7siD$7#oFCuk>XCut{Zr)Z~Yr)j5aXJ}_?XK80^=V<3@=V|9_7ibr1 z7ikx3muQ!2muZ)4S7=viS7}#k*J#&j*J;;lH)uC%H)%I(w`jL&w`sR)cW8HNcWHNP z_h|QO_i6WQ4`>f+4`~l;k7$o-k7i>f`k7^zr%x zeWE@|pR7;Or|Q%6>G}+PranubthtvZ`T~8SzDVC*U##z-@2KyjFVTZ~kzTBq z=pnsSFVoBQu)b8U&@1&Sy;_gxHTuqaRFCPkdYxXcH|Wds<@zrAuKI5J?)n~jqrRu! zq_5DM^%gy@uhduRtMyiWFMW;Prmxl8^$xvL-&^m}yY(KuS6`>E*Z0vk==-6jO8}u9X zoAjIYTl8D?+w|M@JM=sCyY##Dd-Qwt`}F(u2lNN^hxCW_NAySa$MnbbC-f)vr}U@w zXY^(8Y1nDG@P6i22&ZmNO8c3&ubOuOgf^-&0XM=PONauotM~(AALc7t0AYBB~#UNb* z(xo6>2GZpq;nCtskgfvhYLKo0=~|Gk1L=B@ZUE^(&r$30n(Qs;g0h)NZ)|;ElA&i^gT#Ffb=6sKY@h1&CekH0@AM_{RYzS zApHT-pCJ7O(%&Hc1Jb`B{ReUe$eADy0$BiA1X%)E2H6MlRv>Q;@-`qVAgdtzK^_cp z7RcEk4*@v`p^Y+c^SydLEZ)AT|wRrJ|J%Zd0&wC19>CJ`-6M{ z$OnSl2Qq+s5Xc9Edxf_xdsmxFu- z$X9}V706eEd=1Fgf_xpw*Mocm$Txy~6UaA%d<)38f_xjuw}X5K$ajK#7sz*md=JR? zf_xvy_k;WZ$Pa@25XcXM{0PX8g8UfBkAwUK$WMa&6v$75{0zv?g8UrF&x8B|$S;EY z638!u{0hjgg8UlDuY>#s$ZvxD7RYad{0_+Pg8UxH?}PjS$RC3I5y&5d{0YdPg8UiC zpM(4b$X|l|706$M{0+$8g8UuG--G-E$UlPo6UhGq`Dc)S0r^*ue*^hD!?-ze~n2HzO)jRoI0@NEaa z@!*>PzKP(Q1is1On*zS6;F|`%>EN3IzM0^g1-{wfn*+YN;F|}&`QTdszJ=gh1itOT zw-|gofNw|e?F7Ch;0uDU2z+RVOuLgWO zgD(oc82D%oV%qz&NP7kvAH zZzK5j2j2nUI}m(*-~;d-1ipj8cL?|n1>a%dI~;sRfbU4~9R3MbF`SXe1*gO+O_9fZlC%_JtVqaT5%ZX@QC>0$f=~(o-Y9b+ z8j(z775l>|^#mYu7#fpIn~3tp>hZPsXli9#&tqOe<1<-g8hr}7?NhC_;tC2=2$xcs zFf^n&gXQv=3(ypcDTL0`_-Jjhvi#o8cpUyM%=ZW7>+X*CU~{JH0X`7vF z#~glUS`29@nR_X|Fyo(w%TaC6sxOkqJbotk%T95CA|OUImwJoAvLPi{g$w=n>!mmJ28sh`ZnxeKka>U<{vT41=HpTn8$o0Cb$ry>n-al%HT+DZPwrz zjuH+oqaq$^Y;+FhQZPq}i4HRUWfa{?**UmU#Z)&%ob7npnhG6kiHLm6wq|&!aq5$Z zdOJ7j8Vm@=1&Ko0GRNngN<3qecoH>a`A%=s6=x8Kv!6;RL^oQ2S?+Ao89Nu3?QG&I zaJ0iWamT=(=T7Gs)(eSlL|Qx=RF*e~w|xoG1{}2L5pUnLFDHt82L+eT7CN1G6_HJK zkXfe~bEjrrUiD#g6EDTWa+TckChywR7MO3V*$fx;1|l2oXo1R~8yW$1KCT!}dNYwa zM~?lTACA~zg9S=+j@yW1r~};Ku$L6hyOXG#bIzn5Sul+I!kIL{?JBJJz`DXvgPUOr2G05xvCbQqwK`VS)Y={+Z?6>{O;>J=Sut?+I+tz+ly}^4_&g?NM9;$FmI}j zTy>#!s9w6=zMtFIUpKv$ESNIKyx_9PN|G&ME5 zk83_51-&5Q3|rIF(OKHjhBMDiux$y>(=_4UD(D&3AK=JM09@bR*3r~50N`i>^e_aG zn4yr}zI2>Noa$IvtO`9o(TTGwr87%f*&@f^@Xb6z;GPDEcZ6G)MhPjjDPj)z<#hzP zqZde)N~o&0yS3R(yk(xTs#?+Yt&=k9xnb=}SRQmeo(enqQ>uFqj+bRtT1*FK_wBKX zfILmV>UdMviVb0ONAAMyo9=pTZ5`_){pjcx!uB+-DcElPw~8P=tv+)Z*LSq_qN}4D z-d=>~>2!dJ5_>w#P>EnmiyPot0`#<%*a6+pItk6wK`$D|z+JKpxIuLjREd`rflwXm z=!!K!->vg3Tr7^kCS9oP@v5<|sdOpxsjke+}^5VvDU6uHUtpM4ymc^c*V zj&O||%87*HVV9^2N8@|+3A)Xr(|%KD(+Z08Kvz~ig-;l_ctGl$ft<5k( zaE#LlWA?y(U&oRS^>!CEb(Ocapb?G(oJ9a0F8wxuSkDH_Xg2g*;-5dT0JwNC%jBAP z7o99|m@4Dz;%!`r3kbx6jz%GjV2XGP+f`QLtt3LHYsy80;b|Q2V#4!qFCPf6k#gK} z(8~xkJn)x#0Hyt&!(K_S9`sd%A$U+ZP7AB~*=UKzh1uK_Ztv{vF)T(L_YT6X99Xi33jVRSKHOkf34=X7sNH&d z;$#xGy76v8_H>L(gKSe8_YsZTMO?Q<K7n`9U5%a`;m{~drOm!2A+Om8N{gl|or)9%jW7uQ~ zW|yI-%9?}&z|b# zz(ao{`f=$RLisSq7pA*OO1nMuPuEW}=&}3Ehgy3Uo}vE`+i;hbsy<=2GeCMnA)dj3&@Vq2P{ z{2XFV+iU1=#JU1Mo=?ntji@K26R#o^szLW7Owozi!O5X0YUSn4A4*)obX+)HSlL|W zvW5Z%qh)n8(G0&J%Yn>r*Wd#^jNsC)tZ2xZOr=)sUHhucA3?lR(urc@HHLe+V?F1O z+7wQ^uUh_C;!L|N@$>Nsoru#px#f>1#}g}oG=|X0mv_%3;!3N(`G`7Z z&r~8GldeZFgD1M|p{jSh*XPf0{hYdLtNYWe<^0*MpM(8io2oUA2|ACs>}t3wj={HN zB`7qEB6zC7Z8Q`u3o%t{4j%7~g~UF~_Id8?SVuXGb+wi|7Vl!>x9bU>KxgAdl$w(6 z*vby3=!u<(dy=j7RBki3Ez{7}Ix8YRoTg+JZrzvNO0Yfjp5lJAmXFIij&M~|RI-_z1_KoR?h~BCW?YU2iP&VGW+L}7t zZSC&f`TOlLUfL4l=zB-P<$F4 zPSo4k2ZELiX$7Q3pW1qy+~uX{9EX~P^BhGydxVng^B%gCxEyMu zA&MP~gO#FTt{~F!na*^e!Acg@5erc?5S7O?M%kLyR}<5?B&LKKF!>xus_TelbP|g( zuxU?HHxm7rBzk_#OV_|H#4s|6AsX5xE&A<5yf}&2ibcSzxpjCOMW<<7L%9DH88wh2 z(L@#D{JYX2neOovfaD$NUP79ZBvTBF5rwJ}NpkHHS;O%FaZXIn*-sZgOl;dGu~DFa z!GkJ?&!@+TGI^4)Y0(e-lf+b*8N&)vYb#mJ8tWULF@;H`4znU_ zwad9M&l4r~QyR)@c3EQOsM59Z5^?6I_ z)@TMNaLTuda#RA*q{MJ7{5CwI;gy1Fw{!CMhJ1|g=^3l{J|qrYUA#|> zildY(7u5ykmZcy(JXBdXj!%f*Ha4mBwqm>apA$6>zdJhaA~o1nxF~Tv`ihur-^|x8 z*`jQM@o)Z?$Z`4dHjHgNF=rfm_zy%pAwi7#zdlUnBV$XWh#*|MHKAgAsoebk5!Y1Y z>S{F-VaCeQ?19CxVmKp>+^m(DEfl|j@>CX=+Z5`r#G0E~%d&alxs?K)8masl7yl0; zm}Mhyguc*>#G5(pvcXs*<1NLMxwcff`F|7teB|G!XKE4VOv0+mHQ|jLt&gP}md~GpLBu&Jh0|)@rNsg%Jzuz* zzSc4>SUZE&-d4mnHKjd6(q+Y!btMVzBvBPltZBb;FtNJz4{z16C7Z)rFoZZKyUB}7 z%=*?OX#*Y@)5@AhjHx4H_7H0^t)vCSH!4L^V>vNy`${SpPUI6($gSdawt>f4#Ji)A zxJIOKF?bewi^f$>JBDb-r>9M+63v;m>!$(E#AzoIZR!^@Vemo{F2x#Y5i1#c!4x7N zn~9aS>1o^8nM@lrUmVVBOu=+R7(0ha!LF0`+fl(Rq8;ZzchDNR_V8>KB%+^T|;~>J3gG4*ZT@Ds%XuUWTln{A^ zQHc#-S=bu|I>w;iW_Mu>Uvzp}_l`V-;9T+iQ8^%I;v|CWIEBqVQl!{_$ zodzyP838YL0;g2zFj5t#M)le&7!)k^jjQ!%t=9SIk!(dhf=K0r@TM%svfo>NHAKMG-4^c|nYzcJd&K z1;sWe9$f4=V-(aA*h~*#CCl-igx4Bo?B^x03YL5QEMxP)trC;{^E;zqp5~*zo7c~x zCzmKQPa{&K3=Pa^3&4nXyw+I5)kt6iO(E)Ta|fmBYauLlo;Vb8u~rb$_yIbova-Ia zv9gvIe=LX-(~JR_sQGA!j^;FHsx8xB!D`~3?SUJ|0&VkElxH0cb5>S~f;9xN;{X80 z0!GO>3<&U`#EPwcPZ zV$>qoC{_)u4RRZ7!AZn6sXw-GH8U{cs-Y5oytz|}Z+?G#i6>63U6Wrf3eF&qq~+)Q zV6(GOg`g^|u~IV>oK2ib+eBJUR@IL7nVJaQDfyKSA2||x&UpiZp>+gTF)jc+t(Xsn zC8o`xky4mo1H5A{B(ORCbxZ<-;P5!(G^iF_LJ0kiWLmb%2W;5vOgib{D|KTj*35Gp z1Fj;3q!paDQ)_}T?7(Erel4*lZ6;23c6-HLsm@BjQ*Z+@xz727<}t797(i}Q-!~KU zqW=2Y*no_cf=6HDnr4{DY-4sC;UsBdE;;(9*s6N6>JjLPQAgFSl9%9}1QSdzjuXZl ziDI(5uuTCsXX98FiCVh(9)fdOVjRx4%M%#d14A{AF}_-uZN*c)6w z$#5{Dh;7nKhOY@8qNx}v11 zM`}vLN`2ueGR+)$g)oB6b#L2RV;XmptkoWm@xyB zK9={|y9AVUV(woiJ5WO?$Sdd-d_YJ^ixQ*xI*oz~>~3zdns+7kT~)C%s;0!n`Itbe zo!^xNLj8nz#wFzFMQ|4V9Pm;dKBsPKBw@W|usd_(Od zO(oO0p)gUSh`NgTNQA4)(WQ^Brw`2I`CNk$eA@&=3W5fK*zO??y=9~P2MGQ%-Epe?enb18iCkx%saz;6GySL!5X@pm@$>vo=oZ01l5Qq1p&pAS~GV1nXI? zxWHohK6?1$W3`AdlZ=a=78s_4GyDYV_+$I99SCc47AwEe7ojqlpVC9MMTZfw$ ziqwS=CB&qgrKd?t_I2((H>{3OJSzdaTxf<37l$Kt=-G+~KKyIC4SN~EZ_di9i3ZE6 z@UjAxQ9DaF<(t?Uo1{0+@64pxSc~B*XpXGqx$=g!5UywCYa7-&vT{YS z%4K7)9$i12SF>TO2-fo~wiW~?=c7w3+cXgvjT@FC+KUjAtPuToH0yx6>WXU2fr(#D z)5JnsOK6*O^ukw?5Qf(<(-Qge@HyAHx#F>lI-dj_t((v`U7whK{5TjP{un=O9YJ~C zW^5zD1Vhb)e(QLW3Er>qxJZZH|6}jIqok;|2T-3@aZn5(C;~GrNRW&JC^-x{=cpn% zN0|X6$+QF!6+r>`}>l0@-}eOtdAJ|a&=mp zo}F9fMw%Z*`txV0-$(L3LC&*ETh~zJOm0CG<?2CP~e;MApr&bfzS)?K>MS#I`QX3+iz3Fd-t|eGhe*j z+ntlS??e{c7=`@vCyqZ0nNxvw?VI`(utRQt-Tv>Yy5HM)xlNJv{$uuuQqKJEilYDd z`Njds=2msz8M7-{v#)(RpmXnyBL4Zy@;{Cky-OaQ#W^d=iJFt)15vy`e;_#^-hQW9 zS(A>wb=!UHUPR_ZdpL^r=P#J{i&nhvD3{)LYMk>ST-GB&R#aE+WuJ{_eMflb7A-p* z@IJ!kXoyaKddB&WhR8jHy-(qKx2Pz&XP#UB590d-B)SQ%N%V1t2XNm4pZifvvB#tM z|LCFT4@(qXQsvE>mQ6fiHFoQA^gQoB>jDxF4+8b5XqPFOl8%?)G2A`+cO}ehTNH zOG$E@Z)Y^X0k1{=(*U_~UykCo&-(3~=%+OMju<`Ib%Gi*+co>e+!KcYH+b+2m6 zHo4C#A4HMb{e7fAUXa{NFth&ryDK@1eH_I;`#;CdzCU;W5&qzO>9c689{+i)14hYx ze=E1{Uq+*J_}8PfY2U~zh_uFyeWawT+xUca|4#cRir(qpM$dVJEc)y-=kUmyPHy(! zNAbJ=*Z9BPv$_AB;<MF2l?di zpCabedB0UmJ~WDW`u~`t4-fn{Motlv507Hh_#b0LPy73Q;GKL#6r|(-7$oeX@SVt!37| zcs`tPL+Ji@pC=#pp9-|^7s&Q?s&_u4X}G>N=X5yv_$Yjre>;Wl(P>F^$5i9i(GS76 z6aJtFjN}ue5!(8ia5td2v}2H~I)?h%%~1o3m+>!s2RK1F;D?2>zr~?*?;g>`B-tMX zWdAZ!hZb22(IGA8Bdg?MQRsI6duZR-@VBw!QS7=oU-6aowG)4M5)l3F$3Y**XPpV; zbV-RQQmwy_BuehJ@X-Z)`%Zy!!jy`_wECxEdbp9vEfCpHOVOiYRt>Yxw-3CPA^DVl z6F2)cP)^wFQGOS6}<1cz2Eb9)_{qB2Dt`tRS_z$DFnIUV|x%XoK?#Vv+ z)F@ijzmJxEW|Xxc`=1^qSBv&<-nMW5?DIlj5&FCCb=!V)E~LS-&I{dR9KA$t;zkzV z5fNQma_}wm$)`s_tNcaK+9Tq^&K%WeQ4H2COQ9_{WY^w zrbNd{_Up-gmy%|^@#Zt>oS8I=BDMbqk^V4B)}3fF%YHxa3@6`N7Y*kGZyE)!_n(62 zglZ9mN;^1I)>p$RLd*2@alc&?l{3ri>kqU?^o27`+cnO80B#+HYw$mZYtpitr;Of> zdiGYroO@o9+eN_|{m;SLHf`J9cm4S8Hm@u8`*O47j#0eE|1@5gmQ7q&(6zaakDf7e zu49Pq`Tg=bwu~O=A=I;ir?(t z#m{=?ah4IiSGM2HfyqOon6>_0%;t@APbo%3VVeHyFulFon)M|&`yJt6^5oOkJ7!+ufVV z=SCsh{za<$eTH%1Ascq$}=&ic6X5Opu z)RFtaP4anBs6TiF%?-8R`;9qoW#)#=>dmw+Ze7ZGe=a!@#ruOt(0?AUNz>?E_uPOp zqJV$!H2Kd1X1{%(bNR^r*D57n5C#9MN2Tzui5UJSU3y2M^W%Jl%aCChz~5 zK=LI~)W14P|2EI)9pJ31z;Ygw=KbeH|L8cnJc{~PP2YK7|8Zl7W=uS&mwby_*G|#b zzGhuy89j$)9iYFRFD;5fXMG3xfoER1mHqqBZtQajf%n6I`v!vKtD>+y{^BtG`><{f zj*@R0eLIFro?A5aE{QwOyL9c;E~nZ{qEUQ*`R`8~|A*xFAH|E%4&B|hoqL6G^73ea z4*zEZv~3-ITTt$4%&I8*e_Pi5)Aw<7?i0|pQRu%qzW=xS`)sLkhn_iQzdj27S5HO% zTkviklyk0jO1?3Q{Z~&#T}O?JzJ4Y87J-Ah-Gf|{tgobr{w3?zv2)*Mx;cv5<}X(A z58`G$fA|D2y1BcF*Im)Y**<29zWu;cQcg8CM3KAwi^#d>xW7erIjv5Qp7(tH=ze3| z7L8G_^Ms*;GWLJ_;GnNk$bJc(bF)}(I$9#GO&7hW|8FQ!mat`7AZ-ySK8w zaw~dEH}{jfmL>Apsap>D|zyp zQJks=C6sfMpYLtWI#hmZ_VyjJ-r2}$ly{>24=(cW_uu#NT-MjcdPwpvRMr)5xrf~Q zQJAuSo5q1R$RvOC$H^b~4Tsqi$oYt5|8FWx{xsS@?VwS+j_E(zuj~vi-fW=X2lUl7 zIaSyA+m2`I1K?{%O&70ab>o&^1^6`8 zwNm@tv7P*N6uR4g2%Ysn<;t??3M^lR);W5k#*K?*4Fu4PPhCR4ROYQ8Dihf%Wh9v+-}?fU}1-*;(Bfhc^d{}6t^ zuJJZ=ZrX*SxOM+|+)HXIX%7?K z8-DA?*Go>Ba%TE~;r09=wNE(9_j)cIofY}Cl+#1S6)Q5!%Ckp*uYjs$eM-}LU`B># zzVxO8hWh>ToQ-Rz)DIO`r_@QQTc}?mX<;JiibS5yt5X`JG)zfLBwdwA+K@LKS!Y&j~?&$I0p~Z z+JCrB%TeQprVsTq4e5UrWklbJqy1`D&Tj(RrsTm<{HCcNs`Gxyz{;Zs4N4!F@kf#T z2w?73e$QAwe>|nMaT!BKk2TQ2e~x-H`sr<5`A5~7Ft+a)zlJn0J*QX4q@;%vR;8Si zGB%NPZ6aySs+5eBafzhs5=l2?mJq>DKxOpJE_Is&=jrPf{c6_4!DIbk;ebv9)5nd- zNdMg?IfG0}nQD;Ol_^s)rzVoFUlkU|mSmoqIW4oq*#rEr^hh1mX5gs78AEb*n4WS$ zIAL|ljFg!vvl2;b6G=BFl5Sd^GCSqM*bRxKb%~^#GcSnJD|1kx$NVm(&W=lUtnwEd zb{Rdf$#SdX|d+#Eh;^I(bSZm zHf7Gtng5k3S4RcAKV@;|oJ7(se_ybbDK|xj->Q_=Dc7W2o3bY5x|HivZb(_1qHr4% zNw+4FZc8NHo=CbQk#uJw>8?c5-HD`o5=r;2Oazk+dZ`8vgcp{LAAxXONdtUW<;sms4Izc{P!=DUtL@ zB5CvLl-E;sMaSNwiKNH=zd!cgbwIs$@Bvja&)wJ0uiUumZI>N2>(o-a@Z>I`}D!rzX)PgA~#iu_sZ%0$xE%&DtVz6_qgwt2kz{i9WZ z*ljsQ`#xn)_5u52bih90fKB-_^VDd!-|w_GlSq0tkz|#hPb9sNNZPqJepL1m8_%D8#KsFnN9>C^N9@Zv zN9=q5{}KCtf5gU5cErX@CX!y-cf`iaIAUW}!@ZHrGaRvzoFg_~DSoQHh*yp~D}5!A z^y;d3YP@P9>9s`CTmNTAY`jLijw3c+GhQoRJCXEyB57A5Y4_^b4e@&M`k5CblHN!p zy%~-6H&1E*FFu_<8*dVhjW<2`FznfG$C%{OCz!yE5J`I=y&%RlGyIVw0LGB>B~gYSBa#rSI5tfPmj+? zBz==e`aY5LW7gVBuG6?tS?BNPX85IT=UnNX`wtoDcimeLoamO;Z?o%>XUT^;fylD^9>+k*H)WozQ(c~yKt7@rgCiuhHzu{=co5S1zGhkxVC zqF0Kw(`uZWc6#H~)JkcMYot}G-l$p4N;RALzo)0Br8cV7sA+1Aw3@xMU+JX}9XW19 zU+0Rwz05kjdd>(d;;W1h_gZDo%J}L;(ofNAkpB=jd#=~TFUy)^#dxm)=9n|s+W76^ zgthS-<2S|E#cz(UkKYpC5Z@TTHSW~ymqgO9iKM-WydjY{mdJZZBJZJzyoV+7=1Jr| zd~GNdzcZ8yW#jk6?~UIVzd!y!{K3ql6M2(71Du=4n>Ug7h(z8a6M6F`@*b7Qdvs=r zGlr%&9Wiup^opi;{z4gj{Rmw~_K$Kqqt9@K??~?!?EjxzrfZ!3V+Ur~{&#=q?uUcD zF!Ev~fA$`oo3!rc_pJMl@)GBO*wGK7Mc>igJO6RnTc(ZeKV)czS8v`l?wPxD?w_+W zYSwq;&=LCc`2F*7pU}V&(JQy!`H%hm0{+2&sXt(R#(=CJ9k2Aet^d(lcFpPuKXdWB zol|R8PVJU`E%X`L|C3syV)lt#{JC)5ip**uguE2tWGa$MZ5q&#Zk)y0A>_|w7MF81 zYgordZsQK_<$fMw6CZ|9!0rW(CQfmTRiG4QC`);aU7#|?Es#cIn$nz>w5BcXkynAC zoXhnX5O9v=b>f=&u0eLp*{tTQ}7ku=3U;$914ESH+;tr?BQpA4WZES zl%o;77>0QkGS5Pzn1FE#UBs1`cOmOj=wY^FPKC^=&`w^$_=Ubj?Bk1(!bu?%uEgo| zW)PPmr^3I4aH6@NSd@CS!u(IP9w&C93*9j16E9!^S8+9RIdK)&Ag2?p*@@3$o+qkl z)P6+{As@ylav~)-8GTYjPDSdXCyKP8Cu0~(2ID!GNvL}fITTU%BI;2@-HXgZ4n>x; zlGR*`^(b-!`lHBt^hS}}xr=*vJ%pl1lFTWXbJ2dxW(hZQ$^GXQRf&`XFX8Ygx=X7BjD6=2dJO7c&=qTx6VS!WXvhWdZfrRWh-K(yvGN~H{}yPBv1U_qbf+#*7;)ZY*a>A#SX=TE<5*o`o#tZXUyU@yB_R=Xis+kV9M!@vqq% z!bwMvkE1!3Ga0}n)Z-*^Pg;ihpL8SZ_?#a?D1I3BEnb*%$h~+k%)j_0$fx)!tXc6} z*vM@mln5BNgz-ugqCDnRVlY!Uk4(;I2D6yW9P~;FbuFP+O6ZLedZUEgO31B*+)AuR zf0WQ4CDgsdov2BPdwGfPJp~k_9(}n0`<|>;Cw~}1$-_w^FUM1qWa5;hG^b#`C7Ur6 z^)G3?OX{JLYF1LtCFNXF&L!nsauISZDc6$bUs5h5@8>}t##)s$eo1Rpayw7+V+f@x z(1xz4X{m8|{41q4rQ}^oElMp!jZ58*xt4l}ZS3S#US~J&@E#xV5qhrF-VjQgXX%qD zMIH23X}wjt55_D#fI$pl7$ca8@k?LGMOf?7mogt~UV16ZS&6kTZS6~2`_krK+Pq7f zYiV;WZLX!wwM-GpP?qvk#9YgWS;kz-m`j;Ad7F26pAY$%@6d~7_V6?2bjl$dMs>{L z6nUMZ&Zn4j*`qLCS!0x~%&F+xvbHUIIyI?HUFy@3&UB?aJvozJ^rkQU8OUr_^8oTJ zR}f>AYspyDshs+hyP72|L+_Ql3B6lR@0Pm{H7X}YIcr(Y{L86VxhHvnw=nl|pJTq| ze&N>;lr5A$nqxSY0<=Kh<+~yG@^UY)9_8g)elSCs#0+L3|MGK?XL&U*znf3+SW*64 zzURjfv}LHE#ue1K!r|m4KZQ}Z3O!Jl3VNr4-l;Hzah#8_Dj2JRu__p=!o|!*O)4x! z-&9zMI##%r>$rjKeB{*4yeif}{VPsItcn++4=c*M;v!bCie2cHigKz``WQxNL7o}VT@oDV;D;Y<2e^=k~)R+$VA;z)h*RrQ`IeX4(gUVmw8;q0?a-2N*1#O zwMy?y3&I)Ih(!= zU@*fN$r#cZ&qSs$jq{nwY%XFh^I5TGNh>bfG(E(2G9wXAnaf!Dz-Zj&qsJc_f&@1Chzb*AMq()@HOA@BR{h@gwqZo4|&PQ zF%+N>g(*r3#VJV{%2AOjROK{kQiuAa(S+u-qAeZhOgDOR7QN}mK!z}!QJg~t6PUzQ zGMUaSF63e^C0DbIm0ZJhtYsaya4UCkH}~-%n|PG1Ji&IJ;dx%<6<+5J-sU|% zHuY#oW17*DHngV` zUFpG@oK0T_FqmPCWDMzyXChOW#`(--HWx9M`7GcH7PFKUtY!^2a1-m<$nD(4y*$9f zY-S6O^Au0>96NcL*VxTlyvqlC%x8SbH+;_?ehEP-LXAT?oFh1jV<|vU)U!r?nxj57 z)S!mB*BHSpF2%fR$hU?(Yuu0ZtnmrPta&0OD2bZZRLh#Zn9jwhOHI8}Q;*a%pPD~~ zP|F(El20vbSj*bgs)Vs?*}qm7)VP+~*17<7sihvZws~7ChQ6q64z=sij1~-KEE!zJ z)huNz&!B#__l8g>FZQofo@$)NSqx+_@~yKF>sRMdwxj-aehr~+5=U@44M<}+#;mJ1 z>aOM{Zst|q;k^**m7^-vF;2Zv=&5>gtalA-uzfw-*Ry^7(p2VD`e1JLjZ=RnbD4+! zsjqM9--Dj0uNUguzri8o<7jFk#|Dk*%@C|>gGDT7C7ZD(4R-KD2o2S@;i0HcL-lE> zJ`Ls2P!0{{&~P?-q2cA+!rk19o^1Fo@1s^}YL%u|Y37n$m~y+~^hL(pViDt3zXT zXlx#h>r$V=jKThmt#RWOtYRC_@_YzQ91~4SBKIa;IUBh*xeD7hxrVp+7`ZlefHiGM zBgAbwnW=2VyqcO<({E9iW&ztZYfW3`pq9-R@*Hw+wkw3@ML3yKbi?{J?~5FouR;#Z zU*;{;tVJdCWeas`F^TEumljW8PAztZ(6Tt?sK7YRBa??P|CY9G<@jh-0CjKGi8JYi zUTL)!{oU#-e&Ux9T3e6SZD_|uEI@y?Hka1s(%M|wm`599wvl6-b4eh-HjlEMr$cCK zytc{2ky~52wUt}jO~|Y5Qz5j=kGixgMo;=NfYr#W-OYT-9)1p?y?$xm3i-8PisP*P z_4s>-L&-xE+R`5V&|w9uc$-i7%vm>J1`lZt;jK(}W zS%*#!vX#d}=q&%v^6xDF&S#?+I}has^h)R3P}eSpaX6Z#i@dsYWIl^o!i&7YTOo8U zPc=?MUR^K5I&^)8S9s0a|K+Gkb>!d8cHQLPO<#6<4Ec3Gg5xNJdUV%w-Fq;XE4T`4 z)!o?LUk;&%_3B~HJ*-!c{)}K0ckwWfgwXR?PNXPe_3Vpnd#=ZJJ?{zOj6=xB(O8Q! zjD3bVpJAS7T+4NQ$KDXmj8UKFwB%B*L|x8&4s$(omybFMQ=F3-h`i2n%%8OZW1e*% zuV9VOdN+h#>fOuuz2w`g5~ouWJ=Uuq1DK69=yf@A?`4j?%(2(E{F?RAN;tcs|FsTh z*T7uQ9)`M{y@DIK5jmc1%(LGQp|=|LPDTxTx1lTDnZisiK&^TkueZE=zsKi%8A6`| z6vh618qfm0-e)SakZ+&0+{*3jWH)by(Dz7;-S>FZvTp}EF^==lAARN8S3mZ>gFPYi zJCr=AO+VxHOG8cjO=L30>SwHe#_Ff8{XXHd5c(IvI`l7v9Q${qGxqC$J~PW;hv)=Q@ltNRESc@Di_tFjy@HA47hsQJ4Chja&y08CjJjQFh%ljdmW3A39%_->XbA~Yjb3W%9)}Y4c*ykMe z8k>(ooIo?$V~xj}?^yF4YrbQRHP%>TjWzZgtj*ZHA*5TEbnB9CUD5|KiZPg9`cjtj zIL1r2KIz|Kzl<0asE++J^kPOItW(A;^mE31?&4vrQ^q@dinSV7nBtsFW7^P;bI{Y{ zreN%G)@j^Yp5S?QVvWXIqw&^gytnkb6|0cf)c3GfQ@;%1ylT{?KI1r# zOm5c_L z%-L+>37!feq304uaun*97>#~Os7FF?Cmsvo{Ja#PAZ_SMcP`^lA3t`4_6e9((X7s_FX57ksJP^Xn!svyWCnL^Gab}7$^KrzO`9cV@ z^wun~W)-3{dS=$ySo>LylUetKaDkq_;284Ln;{It-!E_+UGPW%|tO=(957P5>L*ydu}Tx^?5?0<=TE|JeAVqCI@>-myB{2anuYckh% zbLBMmV#Jzz1#j~SdhODdbfOD4aXWX0Fi)N5sq;K_p674#deIx>%v0-m>NHP`dFDOu z`w-^0#&+|&VT}3moxduC%MRrztjA^g_p%Je^9WC}J%r1PQj*e)U>p;$4wu{R@<&5h zkdH!~Ku`3}f&pB^I^?v#IxH~n1;2){us+Q(zl9fL?hEC$a0f5(N(hTmC_`EF$D(wM zz36rx;Gqz%I0R!~aWrk{N_Q^9+FqgdSG>(9sOOcb)TS=yl3+Sd@&Yf0aFzbPssfeB z@=gd92iHD{l>9 zl^$AEm?Df|920nw7kDv*)#|@m{a1^zdM;PsI9x6MYVlW#e@#^!hu4_*HRG9vb-LzW zHuG2r*A}HDrLm6J`unwNcdgo9tG3sw?HV!GG(m0FsN0$~$al>TAzT;ZP`c2I-Z+l0 zyN(+W<2wCvo#Xj>d0a2X_3D59LTq>a3SQ+M-V5P|a>)AzeR0E3#*%?Pyc;6)Q-5PwYr9T8*4cL5m+ax^5N_^(ST~=^E!@q$A*`4CdOfoK zSX!fY>${;I>(y=j4Ty1zy55q9PKa@f`rdLaHzS^Q2^-|SAs^yw5NCrp8?NRW*6<16 z@_h&!TO!s*+ikSXM%!$(%|nA@$3S&cDo730=lL%7YDx3!@w-Pwpa z-FANnw-+RtIOi~lDd^kVUqIj9QI4uqXC`|0j(NP!dwdYWoi%7kBjkCfJnxj}o&J93 z=g9Xi``@LX?rOkTCNmX%a+meIYg-6+AC4N|U4Z6TkGswP?&(~NvG0DE$C2Yb>V3~q z9D~^RG()f6BhPzmdyhQtiMBJBd(7qDBIvDqOQB!x9ghCGS0CJ~XYRe7kNBEzL%1)M z+SFw*W03oO*8IL(*vNP64dH&r)%_hggR>CpesjD3p%5N8krI?7lMA_sxA>S(LwK+$ z?J(yD#eY!z2gQHL{2n@_K*<1n|)>a)2Pb+C?`8`2mx+1#9#v}OQZ z%wRs3vydxT!8+Enfm?ZqC)kcUZGIl}-uw#hqPI6&pUv{!{3YLDy&qNYN4qc`aUNBp zM}H0BG4**YjEgzH9 zmJ>)tj4k?M%RH8{jODE4X3TYq_1L11wpfcT=DS6VEf#dklRU)^Uc(w~*~J^^$1Uc( z<#Wt?YgtZ1?ptdh|E-O%K3iL$54N7k+4RLaZM9BYjlK159>Z8$jkWa|)OqWx7;~%f zw!V#eZ#$AQR6&1jGv+qqZEJ!$ZBwUh>aNKZfdv^|%<1i}AP^j}K%NVm*Enw}-G@&fA~nId<|gYPEeg)_l91w~M)5k8D@3 z?P|6C8@^``zl31QVaK5yj$>%YQ5=ij-*G&8e@8LmltAC?&^J5u&5lZ(ioV&Q_B+&l zM_n4A{yUnY{ySRJ4mH|gUOUuihZ^lLza8ec!~Av(VkjdRje6}+uN|IOcT7gDb|jd= z1W*B#e$BR8{w+qjc^xSxl31U;~08&9GrJD%kQUP3SI*u|T? z!~1;1r+mTJe8-Rc%-#^5K7>5vB_GF7fI<|eC@B=DB>L*fQdoUiznANYx1LwF{}VI*-RN0Xm|oInwh zIf;`gO<5{XnN+G%gWA-iA&qH9OWM$$PIRRQXL2@u8NgtMF_JN)GoFb|VH)Q%li6Iv zT;{WYD_G1@RTGNh>bfG(E(2G9w zXAnaf!Dz-Zj&qsJc_f&@1fQdoUiznANYx1LwG*MVI*-R zN0Xm|oInwhIf;`gO<5{XnN+G%gWA-iA&qH9OWM$$PIRRQXL2@u8NgtMF_JN)GoFb| zVH)Q%li6IvT;{WYD_G1@Rh4h@;dtB zMRRzm2q#mDrnI92qnN-%u3;VO_tLApgZ*DVoMS0KP10zLd|y5X>-X|X)}sC|zrx$R z8^SBcQH&ItW6W3d#w*j9%^dFJAvT5ZY95XuKgM~r6?*DbIlh|7`Plwd+rMi2*J2#W zQPjoUUNg>XeHq3G^v`Sh=CwuWiP!YPYxaL_7w_|72(K4Hj<26Y9hzWWUmwe4rm_NS z^7@TD&+F_CVVC;sQlDMwvr7)U)QoMCXTpocc&+FF|W7H>+NSym$zRF;T_w(a|-1cgj&9H4(pNo zJNNJ{zlQK`peoky-FnF3-Sd#cySMWIYWCg{=*#!i>Ai0BrZ3lFPVa5x=Mdh{LlPZ1 zgR@wI`M+=5_Z=Vae~P+)P?;LkLa%%<3;q4Uc3$M=5I(dXAC{#&gBgSV`p{fHG?x#} zy-vte&qj+ z?Y@!!H~R9MRmks~clnGjLikoazSVQzR%aOM^6faR)wjm}_I9k-x90q<_4=*>Eoj98 zma?3W`G)U8_+G5<>tWmPFT!@;FJc$(^I-@-Sc@Nw{ewCGV4goDn9j4j%IhKgSd@~K zW;hwB%a7|Z*B|fUYkua}5cZ@YuRV_WJ(pn2Jy&uE)_BjusP`V@?~(6MM{pd4&|^Q< zry&Ed20x8P?mwC1Pv-d3GrYoUA^e;d>+o|y%=PDHsLRh&n8^jm@n>WH{0M6J^AD)u zFJ(!k8a?Psf7I$1|{4@hFCa`Vx-W9u5@P>molFR*~;U5%da683zVV~ zRj^;|Z2B;lE4YfyJjIR>JLFJ~;usoZyhGZNU=9~^H=D5EAz$+|#yhk+^=QCI#&a%f zxs}^_o%i@4#11QjIURNq&FMfVremDLe+mJ=yTZRDD#Dd|k% zJXT@YkukrKF;1VuH-SXawy5)V1 zcX>a=j>u0D^u!U(XirDZVG`zl#2VJKftPuUcS7t)xgV*Yj#T?2)$K@iJ5t?_9LyNT zVxJ@L;XZx}v3!SdIHyschKyhw6IjG@R`LYTqt^L;46&mQ!Tv{;q#~8+#UO@YzDJqw zQRaKpO_=LZcVhpec4N(t&VznD+Psghf$@)Sgq)9-^U-oXdLh>8=oP5-(a-S$ayUi~ z$H?KBGE^azvlxgN$6SH^j#3A)H9pop$Ew${*5=qR z_$tKmpG*a;aenj7Z@&4>H@~s+8!NxD@;{BW$^UAI6|gP^tV;pw;=2@L1zOPt^D8is z$*jS61*}hjXNmTEJ;aVnB0u&&PA?u;7wdFfKlJl)BU!*wtkZE1u?=f=+}HffuOU|O zB+62rw&>}CJur4b>r`+S*KrFQu|@^0Q9)}|NL>rnL{AlxZy~iQWS>InSm;T%huHDz zc>GaV%j4V8ot|9HHJIP=ukbc{{e;p~=2XTaj}xY%FHd-sEquuy%%QLx3d^Ce915#j zVR;m`Phr~^wtZpS7ygdDA$Fp5J+T&zXoB@T@j@=*eja0Mh!x380SeNb4s>EJ*0RV| z?BFHjUew%*7N!W+uxMwxasl(Wj0ccQQEO0CK1JnIOg_cxAeUmz7{__YtJo&2Rk5c+ zEcsYYq$nLZgR_{;d@e`-CqIY&PtjK?$DyxM^hAm|q_jj`QmjvkHBGTbDb^@udx*u4 z#9ZUY(-S=uAHXuM;|BCx{5?Jhv6IyAq}J${lhorRy?xRu-r-X|53%B9Nu?U2n7~BL ztGIa;H?I=;QL_@oI1^)*7{oQKV?CeoJwJxn$zq*c7jruKQm$k%U!xaJ{x!r(ic?aY zlH!zHgBT?@ptnkjRq~4vD^&$OQ>r%BzLevn)FSkBsgL+1#7ftp3C-|#_k_pX6CNx5 zG2dVf%bZRF(zpaM%E+nAv%HF$opLf2sKj`tk>D|Q@Jxu6Ek-HIFamv0R=vvJ%Vum_ zt}w+BuiO~avfN~>V>#r#V*P zDR%Nwh*h*E6>V2hP8EkBR>gE4WGi~DlH;^eWlm)_^SK;#uB6VD)VZ?1Rjx%Hj8j>y zE2~pwF)EvP<>x}I$|=~cN>z+eMZQ(eV>cgQJxS^emstw8HLgZA{I#e}p_p-*S6{RHRS8WL9UQJ%rZsa!Z;KvZF zeh7!5KdQIK*wyE=m?iAO*wsG_vD34tKK&{ZuP21t^GZYT2fiZED@c!|0dV zwyABK+P10P4Y}0ri#WB#sVz<&Fg!WvD_b;?x(XzBu*o<59MRSOc|dPy*}MKpqYB zT7yf`Lk+&>+YoEmf{t|NdNy!th^482n);`Skv5EU9EWM*r-`2?exqY>95yoVMx8hl z>(uB9RSoY3g`xCXZ%fG*ka(=U}^LQ@E3d*c4*T^C0i$`l5ML+R*`h+I%(F z@*>u&`CB2@LJeA+ggUn9P9OTQlC|7~K5UtUK5W?%wPJku~{M=?6Sf-yUl zC6#K-#hf}_#pnFMo)GKYmTvSw-*(=BzU`8SW5`cm^lq0C+|4F7hgjEwBojxTUFF$T zo?ZRD>l4VgoBg}#r*6e)M|XOnPr6ypZrAb_YTWJ95bItN>(Sl(yZ2@Y#_qn9HOR5M zdUyYTk3y`6*gZ~0ulA5<58Jw*DAvPvJdZ=rp<|b|qvEGM}5Bc^!gZ>z!_cg3z zJ)iO&*1JzVnju~vwd$i*eMX=Tebk|k-213kpZkzwpVxSo_d~4jp&Z3Al%X2caoqH+ zf!z9B_uY#A==(e`@(RZ4>safn5Bkcv@8`(5UmlV; z0(0$WP5SAjesR>UpLOpipMJGzjJfq|O*=Z!ivg&6KlAN35;^wMukJsJ^_#+ZsC&Pw zP}_cYAm@Hh@*F#P89BN?Dc0{jK0vPh3G({~2h(ACr z26Utg-O(!phNBh(Msp77j6+=psLO!KOyx%O`9SjM`hIF2%Tm z7P5rptmGQja0_>{9r+H@^MiimXROcQL&$@949&m%-n{_b=W|LFq~1GgZzh$NAAO(53%9af4Dfq^~P|=((sE| zhV>b4eTK_txO|4oXSj75ek64r0{F5cuF-p3q99K|U#VkqW4 zVk1xSU5Je=fLgoPD>kw?B`Ap=9BHm2)o!HPjkK;K#TaQlMmEK~N4Ce@M_R{`y>UFc zM=UmSG-ELDk?EMn$UC_U`H!?tBR8{!$1$Iguk!|P^DZCoCBKH)s66PwQTaHA<0*{( zbDvpkl-x&|E@KKzUfc%96NcL*VxTlm}9y*rhm+5m}~ks ze9sbghJm{%$)^yx46rd3LYFtrLC{9VrP>zaJK@W^OjhfVT+fZ%%m!}bPVV7;9^w%mV;fJhgJ*exmw1(3yvaMf z&qsX97ktfk{K(Jj4Y6|%ArE=U$1xP35QQm93dJc&8Ol+SDpch(YEp;#q|t=tw4yB? z=u9_yau&Vm$3TWKoKc)Z1{0XXR5F>)EH30?F6A;7awS)@jFnu&b*yC_w{RL2c^Mkj6BlC2eR=C%V#uGdY{S3}7(B7|9sY8P7zfFpcw>$!soSF7sKy z6)a{cD_G4MZr~=?vyt1mi+g#1huO>)9_J~Z<~erqGOw|V_fgMD`6z_?Oj3hM=02$w zojD8hnk3&z@|-jW>pAIej5+CDKI4lJo2;gj)pBxqI?xk!nXFeP>ygRkGkGi4c(QyZ zTf@oLZt|WGn_}!K_McJ$HJ+ljQ@WroQ`BS1BDSM1rkKOjBPq!7oK6GM=*eFC^@r*uhJ@5@P4)L5}C=ry{3eUC-~&2u3j_5vI&l<^S7O|X_I3{L&f!r@RnF`4Lftv28DQd|bQ>b-&~! z%2FP^a>)er_azUqmB&MDuJxE(gktodFZye)xy&_}x#lw0Jmwm6t{g9IN;~9t>1ABa zQjB-$hkP7j^W-*9Zu8_e?^5J7Z!xc|Z0{JbF z`vU#4;F}OzSQqnLXdM<_%tEd}{tM;5Q2vW5pcfaN&Up07q6F%?=oy|1u`3E9uPfr5 z&0vOd4eMCX_aSy=;1J|>WjCzDmCLw}8~BdBA$C;^`CnzbtK@%`zP##ky{$M(`kc#`cQwk#ioIDxYmh`KD(Z_CVe*^Ruz zr+gk_%c~-<<&OE~=V8p{7jhlec=<-ud%5wK%Xj&n5LFt)gHR2d&lCQ7^TQiW?Qwi;5s}Eo!YesdZFB!UzeFgd_waBLql5Fc4N`Lkt^2 zAjnGS*W2so^OZl|=lq`EI{EL6cjx$-=;8R?=%Mex!_4mV;5P2&_vlro`!eO7Sx6ak z+00HnKOu<>?D~Y;xd-`9c!iOSVgk8LV;%0EussN~^fIe6UFpjm*pDo^X4%KAw^%|o ztAa3FZ?fH&9Ys&GZ=pZ#%63<_yR!8)yOcT9V-DGef^ec7Cw8G5o}2hveuv$kI0pMT zu?Y1|oX-)OvAdJ5!hMta@FK4lYBuI6Ip>? zOs);WDds&zO;b|1mYe9uP~0;`j#IuQn@Q-!lnrbO!kmD361f<;=3K*Ln0?M*yk|L{ z&-ohl<>+Zn6>`lvhP|8G0lk^p9W$Kz-^hLHXx`@o^ku5&rY=KIr~drhsUXa?Ke?XE z?ZK_wg&oS(r(DnHzKyx$noI5+wz8YOL6~PQd0lXKo>}IZNuK-jUg8Z#qHlRw=v&@$ z{JqFqAB5BVO_`Q}otV~(8?i^z9^q-!KW!Y}kjYYNScBZB+o$PzKV9FZ>)UjFn|?nJ z^C+H~K9xN7(Zo+dIO9AnqbCD-oG17zWBG_O7NOTO_HY=_=cm(|uKb4kcmVb0t2bZ0 z`Cp;d{K|uoyWM$e}g#u<`R)vim3BsB7dS)-KL(MZ^UW%4NVOqug#&X+k~wvz_T_1lZM zlB+S#Z(rqgrcpvE$AfTggjQV3P4r^~W;yp==2L~-=c;X9TM{tCc{g(l|HGTS#dPE{ z&kW|tXP$iK$!Gp0$Yp*n9%nG}nqP=n&7T{D3j*Rvq%U_cfMJYcH1>Z%1@?b|y(&Kg zdsS{H%GFTb8+|D^pK>!TH=}YhDxb&6AY7={g>ASUJGAf*e84~WCw6Y(I%_FWoh(}h7i z&x@2WpYkAFoGSsu3yV@DZ<$mZAnGE6%V7AE1tm|SD51pbM)PM_PMVpI>|CFZh(svz`zap=3^aHX79K7d%h{S8;npcK2d%6q!1 zE9WqbQH(~PSLyRAeXj9a%|%>-dusH$MxScLs8M%KMG&q&6X#Z+i#vQ*7_J^nHMN+> z8vDEECi*d!&-sF#G|(7?YpygXa9YMJ6JnYW8o_NRBy^5UHnZr7D zuUi&`>k~;wed`}U?d#>W{!6l%#2yaQ6oj?*qxMGJT|0{Rc%Scax9{n~A3D;V9z2BE z{-F0i%%Bwg{GlZXH^dRoulWsk@j01fVZS#V<3tc{9KgNY&jjqs#%V#gDa8CXC2=2r z<`Hrz#6E309)z1Cw8ELq&TMvO^ArlOFPoj&;>;Fjw%m$bw%m<4Tg2HS&Q|wqO~amS zeTpHxL?tV!3BqlD-)7&o*|%+vVrREKM+M8#r|o{<{tM1$7^4`?&%f#6c0JtDfo^n1 zoE_rq5NAgo#moxAI=!olVSaV;sIzNzBe6qub<_vpkJr(coB5nfve5r}{jV3J{y`qY zdsr`iz4-Ow*Pjl;o!-Np>fYIpJ29u7WB8Dd*+~P9LAc8tclmvn-tE%cU3$AqZ+DBa zI~Bdzt#7-BAm81KSV46V?(t6Sxrj^f9`6~-%ZRbZzU=Wn@0G`1G4|^J-bZk5@3TxM zpF)lz?|t@S-!=5%ChY0HPxy>V%xhmw5boE5{VC|<{{H-qd-#Yi_zHWt{}}f0KyUQo zfLaf{gL4POJ+PK7YzxAJ-n)aB(UVs&lY^sB!@vF2h;`@!hVwRKkozG!a%f`^HlB&zHJ*!pH0oR9%ZSmauZ^pM@NieeIIO>iU*t8! zJ8T~h%lq*9AUq<@5pj-)bL0tLU8E$u%5L@s;jtUImD{kl$1<_E$BuG32#<%{jom#y zkQ@pr3c?euNTwa~JR#2$@;u@96J^Nvq~}lCr;|zap+C1{PkaX!p8Sj&^!ViFAUu_h zd7M)Jsk?Xpcc1z@|C>2n$^-=kKJlM6of7I zrR7@eSBpJpu`?~BC}BS3v;^VlIO4gA>yi6uGe13?5iI08Rs@lt3+Hnoqj-<^`H=%O z1d(t6_i{henZ@iN5{c1?&J1NZBdBFNbwT8e>*&kPe8N}!i^H6vC5W`TgWuzhR-f`U z->{jT><%KWe@QRIYkfBlGLTmoiQHRfk%t_8XBF|CRiyQLj?m2MAkwA--8h%?xPYEq z$zWbb9c|t~+%{h_g&7o5OeynO$YQEkhP!;56=}1PEkPviVy>k(zoHLg(Sx`GiqOlr zIm~4N6)a&XYKW`GEaLPbP9Ng-vLADaYea2vM>)<()Yyzj*n@ixt0&<$RAh#mg&RKJiT)LoNvsTGN&U+?{YW zgBZhyd`&)j>pQPVg4hZAmmp?>miC5R*jwBu~dFYzkuM`CZT=SKQ* z3%7A6zvVI9pZFx|OjKv$^SsDVUS=4tF`R#5-iezy9YlOT7D*B>$#Y46V=9%bWHsxk z#r`MBKWRS)IfR{0Qb)4BB*&pA$;rgX;7ra!{>gttp2^Sg0&+{X>&b8MCZl;9eN4`0 zM-cICStKf6RE+4QT!#IN{+jy{Gb(2E83rS6RJ~F4M%5ctZ&Xdu5BQSt*q!J^)EUiV z1_juKsJ|c4pYN?_H~Ua))V!kV^xaw{Y7e63<@>cryY_UX6P@WwcY1Ih7jQ9hZzuP5 zck(pyX{W|^YY;ERb1644kXLyNy-0b7_xOab_!nwOQA0{LlbDHlrj#*{3YOq~OZg7@ zrzRoK)NY)M+)^*(a<1TN^fk3N4`LRn#fXI4B=&l@j4@Tlew5_ zx_i=V*ou16#ZNy;OAyHjaZiStGR!9?8hPpC(Vdfdvb0humZe`rX zAj~2|?=x~)$a?%8$k503?a;6GS27fP+kPzL$Yd(H!Tz918+_`7YoO{mqe9xR| z+A8{^V(ru%7>C3t3aOC>#UU?BLdhrvrJ~NL3+jrxq3);$>WN08QD`(uL+L03jX`5k z9-4$EqbXoYj($Zq&~NA_AOOH1 z1Ql4o3QkCd6i9_`&{QSPSdmQP=<*;VIY#+u>Q*0XyL(cm?*r8v%F|-hu=0HXMcz;0SyO$Khi*4d1{; zxB@@J4Y&<|U=>zl4UWTFtiyV2!136KO*jF&aTnYbcf$klKs*RPfFHyoaVE~fd3X|@ ziVN^`T!t(0EIb!4z{~Lpyb`a%tMLZB5kG}D;mvqA?(s5y1@FOoaVvfmzlQhW1Nd$H z4}2V-z$fu3{4xFte~r)M@9-sj72m?Q@f{){LJ|?2m}Cg_Abm(*@&Fl03duB5M0})} zl#uDfPiByZNGYixwIo0&;mHcJlB^=D$)jXF*-V}$O{9f9N8Tj|$$R7<I6>5nK>Jm%%yOt zTxYH)*Nf}N4dfo=hH;~~3@)3?;U)xsPDuz-c|VSRLoLB~^wMtx9U)DQJX1JFPkO=GBn#!@9!QT29|fCi%>XefG+{Tt2} z)ld)h(yr`ZH@5JZnC0e7l!H9YC<~24*))!7sjeC2vdaln&n{g|N~^I3w=0sI`KSna zwxX%102QKX)Ij5@k(#z5A1X#A$Vbi8N)y<(Gx&$1B&HlyAk~&F)N&G4p=wlvW(8|w zt)vFk1~>6-{+ZG{>Rwy+mJ4K2l<0=ZOr%zz+^UWUaFRN}6N9Fj-s|Hn6 zOdecbR_rgSsVu1WmzT-WNs!4UQ+%^43d*Y3Kvs1@wXe&df6U(f)UC_?nW`8`qnOFW zO2UKJ2Ytu}Rq7_OSPpDFu5G;c?x${hernXQTl)BBvDh%lpOM8DORB2%7gx(klgZj- zwpda7%X%Oo{I4m&7F?q;2lAK?eW;uf!yL2(Ekmo(CbSJbhjyafXg@lD-bL@B56~&} z2|A0uLO-IP&^5*tcR>K1AUdoHL%VW&`)~UXXv6NVM@#bM_Mx*YO3Nz?7>F`zs!RQ4 zz8t@=c2F=w9V1xE)~yW|tJg+^qO$_64F0ax#IIm+s_nqnYP1%48qpf+ZbVrO3(jDU zW-K^^&uS(O2u*56TXWO1231xT%xPj5wI|z9xeaaKLc4^m*nwK6q-UkoREJDx6KqC1 zLZZRJam7*e6}pdpMo-f-O!dCFir_)5DL7I)Ft}5z4!X1^>y)&D+5VaSxxS*dIOWTu zhxiI>N=kf{Sqv7{p@#{6r|laE8FLz)$xF`~5kkNihJ_~Py4t@EOw6e8m5nGHS5R0b zNBQ!Y(FJqLYpU}{`--c_m1p`(rdNl}{~B#?MCa&m+NHw{=g}qP*@kwZ3+SKddvp=~ zKzq>nK?ZV&h8R#lEGUCp^)K4T zQ$Kx(j-#vTDq2iud1@@X|Q*}~bG zqbF4RXZotD3uac(sk9&{F^&mA6dr_O!GKZ478}kM3mCQPkuVBIPYh*#R&}Mntb`WP zY5DR2!H3@;pbs0E0b}xp*p(C4M5zMhSEl zLr&;Dmo8)AkkRFgtDUXMGCT%GLwyJ(OK2@kZ9^+8M~z!y1*{Bx=xjQt?UO1O$fMd( z2SHemR88<0JWd~>^O~Rmo}lyTVrpslP>zRLU=uVVRWodcr(p|SKm(LELlZQ^R?5>w zw2txeaqEIW3-io#@cbm^jhY&NQQ@3wUsZ-&{x6vM^QDJ}9Da0S*AYW>8AiG=?DrSo z#T@3E>=8qn-~~nuu#3&#egAy>ozt&spxlIwrgW?}Z-tT{uWr(3O9Y&n_-6EvlN* zfwa-SvXbiQIsP(#b)qwD_EGpKUmnW}@z5fFb$R7*R(Z-R=ZvWK&1`~WsP=E}1wuE& zp*X>a15VPljQijd_PC$IXZdo?w3^CF2J8D2(sgwA-zWO#@I^?+yJgnHtJT%4$>F z76c~ziA4-5#MeP_TXd|eC)+ZbQ|5?gi~-TeU`q^fp**- z_hx9vJ#bIli|(K;^too-2lr)Yf1d7aNBjH$9*kso$o+8r8@S_POmz7DqW2@a!^BZI zJ!Il&Xr(XG`X<&TRuS&S5~_^%o%D6AVMR4ZC z0OB~z$SCwr)3B}pVgr9E3%C$Z3zr23nAhl@{{Rg3Gr-^(_#s?M_tIAS>i-X5dcolT zKnxDxI<|?#6!Ul?UPSlP*XbMd&8>Jbu7@6YDSe9`WOd^xbv8UO4f?t88vIyDel1>y zAH_j>fWA%Nq3>>GFk+5l;E^$sObG#|VdpehFcLqFpGBT$@fO^On{YGUil4#T@OJth z{Rcfn-=~M^2lNR2@Yyg7*`~OSfLY-VlWu5N9)3Gp=NGZYDhBVzZ?Ihuew}{Qh~K2g z*h11`i0g^pK@4B-;)C=!{p8+e0UyFgLI8UoAI2Zh6Z9lK)r=`hz#p+EmC%nF5Em3e zQouT}?!aGZ4u68r+&7PB^S)sIk3_^PhrdA{w!e1DJF*d9z~A39^&&k> z8LhEC0sk1X^fLa5eons%TWSjpU&Gh$Tk==7TkFdzbt50HLj0CNl|>5N7)kp4oQ9mWnO53)U3hl(aLj0~sO zd0Qi;N@L^jCU={zh-oTl6-)L+{ex=^rg5H;gEf z7bZB8j}kQW&oHQXfH1}J5NZJ1t9lxl$vBRb@qpS%jy(K-ksO $~~l=8(BOU>;ao zJCDrgf#X5cPIL~kkkqrFkwv7AEapMLgOG;^R@KQ;vW(X8(20jgRzlqg=7zjzJSjld zAQ@SEKac$l<&hv0ee8bG|CRDc1KAK_vR3jWt>;19L^i^H9-=};yq(J$dN9g!ku9ik z3yWj8xR7SDt&IT5Gjs(H(!Wk?csK^DbmV!`8Vb%%@&b90yhL`9-Q;ER3fV*U20u)V zPii5rwnyXj_GlbH2|Q>!qM`4IMyM%okA{ML6pF?%9&{biIN5M63hU#^8S+KQ?`O&9 zJQ#S0Zz5lkuXr%>V7uq{^W^)G-@hXl$Uk{7@nGh`(o8Ng&tKxf8ghLC6W%4kSs}+e zBTtfR%$@&&=No!Tv01e6B)QJ6e`VJac5PeO;f$N)PH6F4(8`0I)eUl&Y~;Zaa-*eT zz7)q9ILry!{Kau0e>s`I$X#}yNimU-!vdU`ld!IvV`J_{PRfIaS|+poPI|#iUw9AQ zzF$9qWSoMBq_*0^DLEC}&ZXCsmNs$9_CAl(FgKxSitojX({u4G%R*y~9CJ!)JO6N& zP)ik(OzG&UI6LQJZsHtVBIo3x3lCj+=+?}+IS!H5E+SbdzzxNv+dWDVe&kbP44`F?N6W9M`ms~{m&fi}}~xxQhO|ENIB)bIUiEB zaCzJ$ZZbE8%jc$Y1zaIFjVt1TnaqfhnLLz-2YDFA!*Cu(@Gz2xQ7tHeD+yIKZU*-d zSIW)g%D8eW;bAmGIAhW@9@2Tp;9(3O0nDUKYH1y+#Dx&x7Pa;5s1rNtv>*>kW#>u* z*l}hKJIIcR|m9ptqo}pub?C z-~qu9L6#s}kSmxV$P-K!P}(*&~w%LJPQF9{9`J`-FJ{4BU7xFh&o@TX8H>?9Nk zql8jns&J?}95FSbFrp|T5V17knTYKXJ0iP8_KoyKmPS4vd0Rw8aUzq* zD@qY{7IhQ#5cLujh-QdtMe{@pL{zj`v_!N_v_iB;bVl^8=)CBH=!)ou=#H2Z3&as( zx!5SSi<8BD#aZHPajtlRI8QuToG&gAPZRsZC1Ssr2E+@+b>e#QQt@)}O7Uv(TJfXe z$HeQ!Pl)%6FN$wR38E6C21XS}&5L?E>V>FxqK-wKkGc_cGwOEKU5P*vEm27<60am# zGDMOl86(M*jFS{d$|Mz%N=db3mSnbMu4IX1nPi1zm1K=%og^rET(U#*mgK19jO3Q& zjmnO1%a@hO>Sa&L_Q+agugUhy-jKZ|dt3I4?5>=!Ry0B$DHqE%a;w}acgwx< zWO-kCfB7(ZvHT(Va(RRNN%=EVwN6(F(7rh|*K=k40Polqwz8HNwhQuVrbcvY~^H5AxOdy7jSroH4=0wcdm~$}~ zV(uv1ik^xzMTTOmB1@62$W=^Gu{ zG$<{~1f^Y>s2ru7uB=hcQ!Y?aa6Mu)mN(Xstc;`RX?b% zs{T+5)Kaxg?NSd?k5s3rGt^_%6V)~9TJ;?D2K7_w&FU@cCiPbJHubaW7WKR8_tb~f zht)^aN7cvFr`2Dpzfqr8Ur>LqzOKHZzNx;giO@u9#2Sf4s|n~e@fwrHt4Y?RYPx6! zYO*!CnhBab&16l!ra&`I z+BsSp&@R;0Y3sGCv}?5Mv_b9T+Bdb=bkRDk&Y|nA>#G~8%hdUFCAwkTg& zUN;;y95b9SoHBf3_{?y|@VVhj!xh6d!_S86h8u>PhTDd_hCkv_d{X?B_!aT{0`V7& zO5-r&EaM_$y>Y2=g>jW}jj`GIym6QDW#b-WtMQ=mi1DcLnDK=1wDGL*3*%SDbH*#i zJ0__~ZAvh?O`|)Z5h8)ZaAFG{Tf)$~BEQO*Bn1`Awy!GSkDRDpQSVfoYLx zv1y5EnQ6Uglj&(wqp8`{VhTKOdcpLPX}9Th(|e|qrZc9`O<$V6HvQ9d(R9gl*>uH> z%#mi5In~_7+|As>+{@g@+|N9~JjguQJk&hQJiFD;iWmn~N; z*Q|rA>DIBo)7N))wpY))%ZV zS$A6xSr1!}SdUtdSx;I&wti|oZT;N(rS)s;-2`32fP{jC#R<)H|@6_ z5soN_)Di7aIpQ2Thr!`-^m3#-#yZA1avc*LlO0nX(*llShu=}^;2m|2ddE`7a>pvi z8pk?E(6Qd}gkyuF)$yt0cA`FUU}ACN^28m9?Dd-r-@^M2$#?mgxG#QT}|toKXrIq!MzKfOPAfAro+8Z1e&Ck;r- xO!6l!NLre-DQQpAp`bRU@tr*Ge*n~d^J4%2 From 5c638c8a48b91c950170eb9a83802e8e92ca1468 Mon Sep 17 00:00:00 2001 From: Karan Sharma <55722391+ksharma-xyz@users.noreply.github.com> Date: Sat, 23 Nov 2024 16:29:51 +1100 Subject: [PATCH 62/67] Update ios app icon --- .../AppIcon.appiconset/app-icon-1024.png | Bin 67285 -> 14902 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/iosApp/iosApp/Assets.xcassets/AppIcon.appiconset/app-icon-1024.png b/iosApp/iosApp/Assets.xcassets/AppIcon.appiconset/app-icon-1024.png index 53fc536fb9ac5c1dbb27c7e1da13db3760070a11..bc98427ced9c160c77c7b3f535fd05c36efaffb4 100644 GIT binary patch literal 14902 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 literal 67285 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 BXNCX( From c9c8155cb117663e6bed5cf7ab610a41af04784a Mon Sep 17 00:00:00 2001 From: Karan Sharma <55722391+ksharma-xyz@users.noreply.github.com> Date: Sat, 23 Nov 2024 19:26:46 +1100 Subject: [PATCH 63/67] Clean up not required dependencies --- composeApp/build.gradle.kts | 4 -- core/di/build.gradle.kts | 43 ------------------- .../xyz/ksharma/krail/di/AppComponent.kt | 10 ----- .../kotlin/xyz/ksharma/krail/di/Singleton.kt | 7 --- feature/trip-planner/network/build.gradle.kts | 16 +------ .../planner/network/api/service/HttpClient.kt | 2 +- feature/trip-planner/ui/build.gradle.kts | 12 ------ gradle/libs.versions.toml | 14 +----- sandook/build.gradle.kts | 22 +--------- settings.gradle.kts | 1 - 10 files changed, 4 insertions(+), 127 deletions(-) delete mode 100644 core/di/build.gradle.kts delete mode 100644 core/di/src/commonMain/kotlin/xyz/ksharma/krail/di/AppComponent.kt delete mode 100644 core/di/src/commonMain/kotlin/xyz/ksharma/krail/di/Singleton.kt diff --git a/composeApp/build.gradle.kts b/composeApp/build.gradle.kts index 8b476c73..eed11ebd 100644 --- a/composeApp/build.gradle.kts +++ b/composeApp/build.gradle.kts @@ -97,8 +97,6 @@ kotlin { implementation(compose.components.uiToolingPreview) implementation(libs.androidx.lifecycle.runtime.compose) - implementation(libs.multiplatform.settings) - implementation(libs.ktor.client.core) implementation(libs.ktor.client.cio) implementation(libs.ktor.client.content.negotiation) @@ -106,8 +104,6 @@ kotlin { implementation(libs.ktor.serialization.kotlinx.json) api(libs.di.koinComposeViewmodel) - - implementation(libs.di.kotlinInjectRuntime) } } } diff --git a/core/di/build.gradle.kts b/core/di/build.gradle.kts deleted file mode 100644 index d3003c27..00000000 --- a/core/di/build.gradle.kts +++ /dev/null @@ -1,43 +0,0 @@ -plugins { - alias(libs.plugins.krail.android.library) - alias(libs.plugins.krail.kotlin.multiplatform) - alias(libs.plugins.krail.compose.multiplatform) - alias(libs.plugins.compose.compiler) - alias(libs.plugins.ksp) -} - -android { - namespace = "xyz.ksharma.core.di" -} - -kotlin { - applyDefaultHierarchyTemplate() - - androidTarget() - iosArm64() - iosSimulatorArm64() - - sourceSets { - commonMain { - dependencies { - implementation(libs.di.kotlinInjectRuntime) - implementation(libs.kotlinx.coroutines.core) - - implementation(compose.runtime) - } - } - } -} - -dependencies { - // 1. Configure code generation into the common source set - kspCommonMainMetadata(libs.di.kotlinInjectRuntime) - // 2. Configure code generation into each KMP target source set - kspAndroid(libs.di.kotlinInjectCompilerKsp) - kspIosArm64(libs.di.kotlinInjectCompilerKsp) - kspIosSimulatorArm64(libs.di.kotlinInjectCompilerKsp) -} - -ksp { - arg("me.tatarka.inject.generateCompanionExtensions", "true") -} diff --git a/core/di/src/commonMain/kotlin/xyz/ksharma/krail/di/AppComponent.kt b/core/di/src/commonMain/kotlin/xyz/ksharma/krail/di/AppComponent.kt deleted file mode 100644 index 0c1c4c9b..00000000 --- a/core/di/src/commonMain/kotlin/xyz/ksharma/krail/di/AppComponent.kt +++ /dev/null @@ -1,10 +0,0 @@ -package xyz.ksharma.krail.di - -import me.tatarka.inject.annotations.Component - -@Singleton -@Component -abstract class AppComponent { - - companion object -} diff --git a/core/di/src/commonMain/kotlin/xyz/ksharma/krail/di/Singleton.kt b/core/di/src/commonMain/kotlin/xyz/ksharma/krail/di/Singleton.kt deleted file mode 100644 index d327b40d..00000000 --- a/core/di/src/commonMain/kotlin/xyz/ksharma/krail/di/Singleton.kt +++ /dev/null @@ -1,7 +0,0 @@ -package xyz.ksharma.krail.di - -import me.tatarka.inject.annotations.Scope - -@Scope -@Target(AnnotationTarget.CLASS, AnnotationTarget.FUNCTION, AnnotationTarget.PROPERTY_GETTER) -annotation class Singleton diff --git a/feature/trip-planner/network/build.gradle.kts b/feature/trip-planner/network/build.gradle.kts index e8bb3e13..a54bfb17 100644 --- a/feature/trip-planner/network/build.gradle.kts +++ b/feature/trip-planner/network/build.gradle.kts @@ -47,7 +47,7 @@ kotlin { commonMain { dependencies { - implementation(libs.di.kotlinInjectRuntime) + implementation(libs.kotlinx.serialization.json) implementation(libs.ktor.client.core) implementation(libs.ktor.client.auth) @@ -78,20 +78,6 @@ kotlin { } } -dependencies { - // 1. Configure code generation into the common source set - kspCommonMainMetadata(libs.di.kotlinInjectRuntime) - // 2. Configure code generation into each KMP target source set - kspAndroid(libs.di.kotlinInjectCompilerKsp) - kspIosArm64(libs.di.kotlinInjectCompilerKsp) - kspIosSimulatorArm64(libs.di.kotlinInjectCompilerKsp) -} - -ksp { - arg("me.tatarka.inject.generateCompanionExtensions", "true") - arg("me.tatarka.inject.dumpGraph", "true") -} - // READ API KEY val localProperties = gradleLocalProperties(rootProject.rootDir, providers) val nswTransportApiKey: String = localProperties.getProperty("NSW_TRANSPORT_API_KEY", "") diff --git a/feature/trip-planner/network/src/androidMain/kotlin/xyz/ksharma/krail/trip/planner/network/api/service/HttpClient.kt b/feature/trip-planner/network/src/androidMain/kotlin/xyz/ksharma/krail/trip/planner/network/api/service/HttpClient.kt index ab51d313..2d2bedb3 100644 --- a/feature/trip-planner/network/src/androidMain/kotlin/xyz/ksharma/krail/trip/planner/network/api/service/HttpClient.kt +++ b/feature/trip-planner/network/src/androidMain/kotlin/xyz/ksharma/krail/trip/planner/network/api/service/HttpClient.kt @@ -23,7 +23,7 @@ actual fun httpClient(): HttpClient { } install(Logging) { // if(debug) - TODO - level = LogLevel.ALL + level = LogLevel.BODY } defaultRequest { diff --git a/feature/trip-planner/ui/build.gradle.kts b/feature/trip-planner/ui/build.gradle.kts index 1ae7ffed..dbebfa0a 100644 --- a/feature/trip-planner/ui/build.gradle.kts +++ b/feature/trip-planner/ui/build.gradle.kts @@ -43,10 +43,7 @@ kotlin { implementation(libs.navigation.compose) implementation(libs.lifecycle.viewmodel.compose) - implementation(libs.di.kotlinInjectRuntime) - // TODO - remove once DI added - start - implementation(libs.multiplatform.settings) implementation(libs.ktor.client.core) implementation(libs.ktor.client.cio) implementation(libs.ktor.client.auth) @@ -71,12 +68,3 @@ kotlin { android { namespace = "xyz.ksharma.krail.trip.planner.ui" } - -dependencies { - // 1. Configure code generation into the common source set - kspCommonMainMetadata(libs.di.kotlinInjectRuntime) - // 2. Configure code generation into each KMP target source set -// kspAndroid(libs.di.kotlinInjectCompilerKsp) - kspIosArm64(libs.di.kotlinInjectCompilerKsp) - kspIosSimulatorArm64(libs.di.kotlinInjectCompilerKsp) -} diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 39a2bceb..1b154c66 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -14,7 +14,6 @@ kotlinxCollectionsImmutable = "0.3.8" kotlinxDatetime = "0.6.1" ktorClientAuth = "2.3.12" lifecycleViewmodelCompose = "2.8.3" -multiplatformSettings = "1.2.0" navigationCompose = "2.8.0-alpha10" kotlinxSerializationJson = "1.7.3" ksp = "2.0.21-1.0.27" # ksp to kotlin version mapping https://github.com/google/ksp/releases @@ -27,11 +26,11 @@ buildkonfigGradlePlugin = "0.15.2" kermit = "2.0.4" sqlDelight = "2.0.2" koin = "4.0.1-Beta1" +slf4jSimple = "2.0.16" #SDK minSdk = "26" compileSdk = "35" -slf4jSimple = "2.0.16" targetSdk = "35" [libraries] @@ -49,19 +48,8 @@ log-kermit = { module = "co.touchlab:kermit", version.ref = "kermit" } lifecycle-viewmodel-compose = { module = "org.jetbrains.androidx.lifecycle:lifecycle-viewmodel-compose", version.ref = "lifecycleViewmodelCompose" } androidx-lifecycle-runtime-compose = { group = "org.jetbrains.androidx.lifecycle", name = "lifecycle-runtime-compose", version.ref = "androidx-lifecycle" } -multiplatform-settings = { module = "com.russhwolf:multiplatform-settings-no-arg", version.ref = "multiplatformSettings" } # DI -di-kotlinInjectRuntime = { module = "me.tatarka.inject:kotlin-inject-runtime-kmp", version.ref = "kotlinInject" } -di-kotlinInjectCompilerKsp = { module = "me.tatarka.inject:kotlin-inject-compiler-ksp", version.ref = "kotlinInject" } - -##di-koinAndroid = {module = "io.insert-koin:koin-android", version.ref = "koin"} -#di-koinComposeViewmodel = {module = "io.insert-koin:koin-compose-viewmodel", version.ref = "koin"} -#di-koinComposeViewmodelNav = {module = "io.insert-koin:koin-compose-viewmodel-navigation", version.ref = "koin"} - -#di-koinAndroidxCompose = { module = "io.insert-koin:koin-androidx-compose", version.ref = "koin" } -#di-koinCore = { module = "io.insert-koin:koin-core", version.ref = "koin" } - di-koinAndroid = {module = "io.insert-koin:koin-android", version.ref = "koin"} di-koinComposeViewmodel = {module = "io.insert-koin:koin-compose-viewmodel", version.ref = "koin"} di-koinComposeViewmodelNav = {module = "io.insert-koin:koin-compose-viewmodel-navigation", version.ref = "koin"} diff --git a/sandook/build.gradle.kts b/sandook/build.gradle.kts index 35338f7f..fb611254 100644 --- a/sandook/build.gradle.kts +++ b/sandook/build.gradle.kts @@ -1,6 +1,3 @@ -import org.jetbrains.kotlin.gradle.dsl.KotlinMultiplatformExtension -import org.jetbrains.kotlin.gradle.tasks.KotlinCompilationTask - plugins { alias(libs.plugins.krail.android.library) alias(libs.plugins.krail.kotlin.multiplatform) @@ -39,11 +36,8 @@ kotlin { commonMain { dependencies { -// implementation(projects.core.di) - implementation(libs.kotlinx.serialization.json) - implementation(libs.di.kotlinInjectRuntime) - implementation(libs.multiplatform.settings) + implementation(compose.runtime) implementation(libs.log.kermit) implementation(libs.kotlinx.datetime) @@ -58,20 +52,6 @@ kotlin { } } } -/* -dependencies { - // 1. Configure code generation into the common source set - kspCommonMainMetadata(libs.di.kotlinInjectRuntime) - // 2. Configure code generation into each KMP target source set - kspAndroid(libs.di.kotlinInjectCompilerKsp) - kspIosArm64(libs.di.kotlinInjectCompilerKsp) - kspIosSimulatorArm64(libs.di.kotlinInjectCompilerKsp) -} - -ksp { - arg("me.tatarka.inject.generateCompanionExtensions", "true") - arg("me.tatarka.inject.dumpGraph", "true") -}*/ sqldelight { databases { diff --git a/settings.gradle.kts b/settings.gradle.kts index 9988b52a..66f91389 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -33,7 +33,6 @@ rootProject.name = "Krail" //include(":android-app") include(":composeApp") include(":taj") // Design System -include(":core:di") include(":core:date-time") include(":feature:trip-planner:ui") include(":feature:trip-planner:state") From 26eb9f1a4ab6a7d649e2541cb81becc31bbb9a98 Mon Sep 17 00:00:00 2001 From: Karan Sharma <55722391+ksharma-xyz@users.noreply.github.com> Date: Sat, 23 Nov 2024 20:32:33 +1100 Subject: [PATCH 64/67] Clean up kotlin-inject lib --- .../planner/network/api/NetworkComponent.kt | 29 ---------------- .../api/ratelimit/NetworkRateLimiter.kt | 2 -- .../api/service/RealTripPlanningService.kt | 2 -- feature/trip-planner/ui/build.gradle.kts | 2 ++ .../trip/planner/ui/di/ViewModelComponent.kt | 33 ------------------- 5 files changed, 2 insertions(+), 66 deletions(-) delete mode 100644 feature/trip-planner/network/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/network/api/NetworkComponent.kt delete mode 100644 feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/di/ViewModelComponent.kt diff --git a/feature/trip-planner/network/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/network/api/NetworkComponent.kt b/feature/trip-planner/network/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/network/api/NetworkComponent.kt deleted file mode 100644 index 9175e142..00000000 --- a/feature/trip-planner/network/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/network/api/NetworkComponent.kt +++ /dev/null @@ -1,29 +0,0 @@ -package xyz.ksharma.krail.trip.planner.network.api - -import io.ktor.client.HttpClient -import me.tatarka.inject.annotations.Component -import me.tatarka.inject.annotations.KmpComponentCreate -import me.tatarka.inject.annotations.Provides -import xyz.ksharma.krail.trip.planner.network.api.ratelimit.NetworkRateLimiter -import xyz.ksharma.krail.trip.planner.network.api.ratelimit.RateLimiter -import xyz.ksharma.krail.trip.planner.network.api.service.RealTripPlanningService -import xyz.ksharma.krail.trip.planner.network.api.service.TripPlanningService -import xyz.ksharma.krail.trip.planner.network.api.service.httpClient - -@Component -abstract class NetworkComponent { - - @Provides - fun provideHttpClient(): HttpClient = httpClient() - - protected val RealTripPlanningService.bind: TripPlanningService - @Provides get() = this - - protected val NetworkRateLimiter.bind: RateLimiter - @Provides get() = this - - companion object -} - -@KmpComponentCreate -expect fun createNetworkComponent(): NetworkComponent diff --git a/feature/trip-planner/network/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/network/api/ratelimit/NetworkRateLimiter.kt b/feature/trip-planner/network/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/network/api/ratelimit/NetworkRateLimiter.kt index 4937eb25..1c677f83 100644 --- a/feature/trip-planner/network/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/network/api/ratelimit/NetworkRateLimiter.kt +++ b/feature/trip-planner/network/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/network/api/ratelimit/NetworkRateLimiter.kt @@ -9,7 +9,6 @@ import kotlinx.coroutines.flow.debounce import kotlinx.coroutines.flow.flatMapLatest import kotlinx.coroutines.flow.flow import kotlinx.coroutines.flow.update -import me.tatarka.inject.annotations.Inject import kotlin.time.Duration.Companion.milliseconds import kotlin.time.Duration.Companion.seconds @@ -19,7 +18,6 @@ import kotlin.time.Duration.Companion.seconds * * Note: This class should not be Singleton. It should be created per use-case. */ -@Inject class NetworkRateLimiter : RateLimiter { private val triggerFlow = MutableSharedFlow(replay = 1) diff --git a/feature/trip-planner/network/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/network/api/service/RealTripPlanningService.kt b/feature/trip-planner/network/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/network/api/service/RealTripPlanningService.kt index a8f32123..c10653ab 100644 --- a/feature/trip-planner/network/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/network/api/service/RealTripPlanningService.kt +++ b/feature/trip-planner/network/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/network/api/service/RealTripPlanningService.kt @@ -6,14 +6,12 @@ import io.ktor.client.request.get import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.IO import kotlinx.coroutines.withContext -import me.tatarka.inject.annotations.Inject import xyz.ksharma.krail.trip.planner.network.api.model.StopFinderResponse import xyz.ksharma.krail.trip.planner.network.api.model.StopType import xyz.ksharma.krail.trip.planner.network.api.model.TripResponse import xyz.ksharma.krail.trip.planner.network.api.service.stop_finder.StopFinderRequestParams import xyz.ksharma.krail.trip.planner.network.api.service.trip.TripRequestParams -@Inject class RealTripPlanningService(private val httpClient: HttpClient) : TripPlanningService { override suspend fun trip( diff --git a/feature/trip-planner/ui/build.gradle.kts b/feature/trip-planner/ui/build.gradle.kts index dbebfa0a..42ccf8e9 100644 --- a/feature/trip-planner/ui/build.gradle.kts +++ b/feature/trip-planner/ui/build.gradle.kts @@ -44,6 +44,7 @@ kotlin { implementation(libs.lifecycle.viewmodel.compose) // TODO - remove once DI added - start +/* implementation(libs.ktor.client.core) implementation(libs.ktor.client.cio) implementation(libs.ktor.client.auth) @@ -52,6 +53,7 @@ kotlin { implementation(libs.ktor.serialization.kotlinx.json) implementation(libs.kotlinx.datetime) implementation(libs.slf4j.simple) // Logging +*/ // TODO - remove once DI added - end api(libs.di.koinComposeViewmodel) diff --git a/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/di/ViewModelComponent.kt b/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/di/ViewModelComponent.kt deleted file mode 100644 index 220800bc..00000000 --- a/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/di/ViewModelComponent.kt +++ /dev/null @@ -1,33 +0,0 @@ -/* -package xyz.ksharma.krail.trip.planner.ui.di - -import me.tatarka.inject.annotations.Component -import me.tatarka.inject.annotations.KmpComponentCreate -import me.tatarka.inject.annotations.Provides -import xyz.ksharma.krail.sandook.Sandook -import xyz.ksharma.krail.trip.planner.ui.savedtrips.SavedTripsViewModel -import xyz.ksharma.krail.trip.planner.ui.timetable.TimeTableViewModel -import xyz.ksharma.krail.trip.planner.ui.usualride.UsualRideViewModel - -@Component -abstract class ViewModelComponent { - - @Provides - fun provideUsualRideViewModel( - sandook: Sandook, - ): UsualRideViewModel = UsualRideViewModel(sandook) - - @Provides - fun provideTimeTableViewModel( - sandook: Sandook, - ): TimeTableViewModel = TimeTableViewModel(sandook) - - - @Provides - fun provideSavedTripsViewModel( - sandook: Sandook, - ): SavedTripsViewModel = SavedTripsViewModel(sandook) - - companion object -} -*/ From 246677bd0bd8013fea195eaff70cf226a40df576 Mon Sep 17 00:00:00 2001 From: Karan Sharma <55722391+ksharma-xyz@users.noreply.github.com> Date: Sat, 23 Nov 2024 21:16:40 +1100 Subject: [PATCH 65/67] Update CI for Krail --- .github/workflows/ci.yml | 14 ++-- android-app/build.gradle.kts | 63 ------------------ android-app/src/main/AndroidManifest.xml | 31 --------- android-app/src/main/proguard-rules.pro | 65 ------------------- .../main/res/color/gradient_background.xml | 10 --- .../res/drawable/ic_launcher_background.xml | 11 ---- .../res/drawable/ic_launcher_foreground.xml | 9 --- .../res/mipmap-anydpi-v26/ic_launcher.xml | 6 -- .../mipmap-anydpi-v26/ic_launcher_round.xml | 6 -- .../src/main/res/values-night/colors.xml | 5 -- android-app/src/main/res/values/colors.xml | 5 -- android-app/src/main/res/values/strings.xml | 3 - android-app/src/main/res/values/themes.xml | 7 -- android-app/src/main/res/xml/backup_rules.xml | 13 ---- .../main/res/xml/data_extraction_rules.xml | 19 ------ composeApp/src/androidDebug/kotlin/.gitkeep | 0 feature/trip-planner/ui/build.gradle.kts | 14 ---- .../timetable/business/TripResponseMapper.kt | 12 ++-- 18 files changed, 14 insertions(+), 279 deletions(-) delete mode 100644 android-app/build.gradle.kts delete mode 100644 android-app/src/main/AndroidManifest.xml delete mode 100644 android-app/src/main/proguard-rules.pro delete mode 100644 android-app/src/main/res/color/gradient_background.xml delete mode 100644 android-app/src/main/res/drawable/ic_launcher_background.xml delete mode 100644 android-app/src/main/res/drawable/ic_launcher_foreground.xml delete mode 100644 android-app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml delete mode 100644 android-app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml delete mode 100644 android-app/src/main/res/values-night/colors.xml delete mode 100644 android-app/src/main/res/values/colors.xml delete mode 100644 android-app/src/main/res/values/strings.xml delete mode 100644 android-app/src/main/res/values/themes.xml delete mode 100644 android-app/src/main/res/xml/backup_rules.xml delete mode 100644 android-app/src/main/res/xml/data_extraction_rules.xml create mode 100644 composeApp/src/androidDebug/kotlin/.gitkeep diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index fe58573c..e1f91c15 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,10 +1,10 @@ -name: Android CI +name: Krail App CI on: push: - branches: [ commonMain ] + branches: [ main ] pull_request: - branches: [ commonMain ] + branches: [ main ] jobs: build: @@ -40,15 +40,15 @@ jobs: - name: Firebase (Release) - Google Services.json file env: DATA: ${{ secrets.FIREBASE_GOOGLE_SERVICES_JSON_RELEASE }} - run: echo $DATA | base64 -di > android-app/google-services.json + run: echo $DATA | base64 -di > composeApp/src/androidMain/google-services.json - name: Firebase (Debug) - Google Services.json file env: DATA: ${{ secrets.FIREBASE_GOOGLE_SERVICES_JSON_DEBUG }} - run: echo $DATA | base64 -di > android-app/src/debug/google-services.json + run: echo $DATA | base64 -di > composeApp/src/androidDebug/google-services.json - - name: Detekt Checks - run: ./gradlew detekt +# - name: Detekt Checks +# run: ./gradlew detekt - name: Build Debug run: ./gradlew assembleDebug test diff --git a/android-app/build.gradle.kts b/android-app/build.gradle.kts deleted file mode 100644 index ea91a28c..00000000 --- a/android-app/build.gradle.kts +++ /dev/null @@ -1,63 +0,0 @@ -plugins { - alias(libs.plugins.krail.kotlin.android) -} - -android { - namespace = "xyz.ksharma.krail" - - defaultConfig { - applicationId = "xyz.ksharma.krail" - versionCode = 12 - versionName = "1.0-alpha03" - - testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" - vectorDrawables { - useSupportLibrary = true - } - } - - buildTypes { - - debug { - applicationIdSuffix = ".debug" - isDebuggable = true - ndk { - isDebuggable = true - debugSymbolLevel = "FULL" - } - } - - release { - isMinifyEnabled = true - isDebuggable = false - isShrinkResources = true - ndk { - isDebuggable = false - debugSymbolLevel = "FULL" - } - proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro") - } - } -} - -dependencies { - - // Projects - implementation(projects.composeApp) - implementation(projects.sandook) - - /* implementation(projects.core.network) - implementation(projects.feature.tripPlanner.network.api) - implementation(projects.feature.tripPlanner.network.real) - implementation(projects.feature.tripPlanner.state) - implementation(projects.feature.tripPlanner.ui) - implementation(projects.sandook.api) - implementation(projects.sandook.real)*/ - - implementation(libs.activity.compose) - implementation(compose.foundation) - implementation(libs.core.ktx) - implementation(libs.kotlinx.serialization.json) - implementation(libs.lifecycle.runtime.ktx) - -} diff --git a/android-app/src/main/AndroidManifest.xml b/android-app/src/main/AndroidManifest.xml deleted file mode 100644 index 8226f6db..00000000 --- a/android-app/src/main/AndroidManifest.xml +++ /dev/null @@ -1,31 +0,0 @@ - - - - - - - - - - - - - - - - diff --git a/android-app/src/main/proguard-rules.pro b/android-app/src/main/proguard-rules.pro deleted file mode 100644 index 1b92f1c8..00000000 --- a/android-app/src/main/proguard-rules.pro +++ /dev/null @@ -1,65 +0,0 @@ -# Add project specific ProGuard rules here. -# You can control the set of applied configuration files using the -# proguardFiles setting in build.gradle. -# -# For more details, see -# http://developer.android.com/guide/developing/tools/proguard.html - -# If your project uses WebView with JS, uncomment the following -# and specify the fully qualified class name to the JavaScript interface -# class: -#-keepclassmembers class fqcn.of.javascript.interface.for.webview { -# public *; -#} - -# Uncomment this to preserve the line number information for -# debugging stack traces. -#-keepattributes SourceFile,LineNumberTable - -# If you keep the line number information, uncomment this to -# hide the original source file name. -#-renamesourcefileattribute SourceFile - -#Need this to keep serializable members as is --keepclassmembers class * implements java.io.Serializable { - static final long serialVersionUID; - private static final java.io.ObjectStreamField[] serialPersistentFields; - private void writeObject(java.io.ObjectOutputStream); - private void readObject(java.io.ObjectInputStream); - java.lang.Object writeReplace(); - java.lang.Object readResolve(); -} - - -# For native methods, see http://proguard.sourceforge.net/manual/examples.html#native --keepclasseswithmembernames class * { - native ; -} - -#This will print mappings - very useful for troubleshooting. --printseeds ./build/seeds.txt --printusage ./build/unused.txt --printmapping ./build/mapping.txt - -#Some recommended settings for running with Android --keep public class * extends android.app.Activity --keep public class * extends android.app.Application - --keep public enum * {} --keep public interface * {} - --keepattributes *Annotation* - --keepclassmembers,allowobfuscation class * { - @javax.inject.* *; - @dagger.* *; - (); -} - - --keepattributes *Annotation*,Signature --dontwarn retrofit.** --keep class retrofit.** { *; } --keepclasseswithmembers class * { - @retrofit.* ; -} diff --git a/android-app/src/main/res/color/gradient_background.xml b/android-app/src/main/res/color/gradient_background.xml deleted file mode 100644 index 7ce4c3e7..00000000 --- a/android-app/src/main/res/color/gradient_background.xml +++ /dev/null @@ -1,10 +0,0 @@ - - diff --git a/android-app/src/main/res/drawable/ic_launcher_background.xml b/android-app/src/main/res/drawable/ic_launcher_background.xml deleted file mode 100644 index 532196b3..00000000 --- a/android-app/src/main/res/drawable/ic_launcher_background.xml +++ /dev/null @@ -1,11 +0,0 @@ - - - - diff --git a/android-app/src/main/res/drawable/ic_launcher_foreground.xml b/android-app/src/main/res/drawable/ic_launcher_foreground.xml deleted file mode 100644 index e686afd5..00000000 --- a/android-app/src/main/res/drawable/ic_launcher_foreground.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - diff --git a/android-app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/android-app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml deleted file mode 100644 index b3e26b4c..00000000 --- a/android-app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/android-app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml b/android-app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml deleted file mode 100644 index b3e26b4c..00000000 --- a/android-app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/android-app/src/main/res/values-night/colors.xml b/android-app/src/main/res/values-night/colors.xml deleted file mode 100644 index fc622448..00000000 --- a/android-app/src/main/res/values-night/colors.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - #1F1B16 - #EAE1D9 - diff --git a/android-app/src/main/res/values/colors.xml b/android-app/src/main/res/values/colors.xml deleted file mode 100644 index 18779681..00000000 --- a/android-app/src/main/res/values/colors.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - #FFFBFF - #1F1B16 - diff --git a/android-app/src/main/res/values/strings.xml b/android-app/src/main/res/values/strings.xml deleted file mode 100644 index 53906e3d..00000000 --- a/android-app/src/main/res/values/strings.xml +++ /dev/null @@ -1,3 +0,0 @@ - - Krail App - diff --git a/android-app/src/main/res/values/themes.xml b/android-app/src/main/res/values/themes.xml deleted file mode 100644 index 931e639d..00000000 --- a/android-app/src/main/res/values/themes.xml +++ /dev/null @@ -1,7 +0,0 @@ - - - - diff --git a/android-app/src/main/res/xml/backup_rules.xml b/android-app/src/main/res/xml/backup_rules.xml deleted file mode 100644 index fa0f996d..00000000 --- a/android-app/src/main/res/xml/backup_rules.xml +++ /dev/null @@ -1,13 +0,0 @@ - - - - \ No newline at end of file diff --git a/android-app/src/main/res/xml/data_extraction_rules.xml b/android-app/src/main/res/xml/data_extraction_rules.xml deleted file mode 100644 index 9ee9997b..00000000 --- a/android-app/src/main/res/xml/data_extraction_rules.xml +++ /dev/null @@ -1,19 +0,0 @@ - - - - - - - \ No newline at end of file diff --git a/composeApp/src/androidDebug/kotlin/.gitkeep b/composeApp/src/androidDebug/kotlin/.gitkeep new file mode 100644 index 00000000..e69de29b diff --git a/feature/trip-planner/ui/build.gradle.kts b/feature/trip-planner/ui/build.gradle.kts index 42ccf8e9..3eaab54a 100644 --- a/feature/trip-planner/ui/build.gradle.kts +++ b/feature/trip-planner/ui/build.gradle.kts @@ -42,20 +42,6 @@ kotlin { implementation(libs.kotlinx.serialization.json) implementation(libs.navigation.compose) implementation(libs.lifecycle.viewmodel.compose) - - // TODO - remove once DI added - start -/* - implementation(libs.ktor.client.core) - implementation(libs.ktor.client.cio) - implementation(libs.ktor.client.auth) - implementation(libs.ktor.client.content.negotiation) - implementation(libs.ktor.client.logging) - implementation(libs.ktor.serialization.kotlinx.json) - implementation(libs.kotlinx.datetime) - implementation(libs.slf4j.simple) // Logging -*/ - // TODO - remove once DI added - end - api(libs.di.koinComposeViewmodel) } } diff --git a/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/timetable/business/TripResponseMapper.kt b/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/timetable/business/TripResponseMapper.kt index 2fc502f6..afc24d11 100644 --- a/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/timetable/business/TripResponseMapper.kt +++ b/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/timetable/business/TripResponseMapper.kt @@ -4,9 +4,11 @@ import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.toImmutableList import xyz.ksharma.krail.core.datetime.DateTimeHelper.calculateTimeDifference import xyz.ksharma.krail.core.datetime.DateTimeHelper.calculateTimeDifferenceFromNow +import xyz.ksharma.krail.core.datetime.DateTimeHelper.formatTo12HourTime import xyz.ksharma.krail.core.datetime.DateTimeHelper.toFormattedDurationTimeString import xyz.ksharma.krail.core.datetime.DateTimeHelper.toGenericFormattedTimeString import xyz.ksharma.krail.core.datetime.DateTimeHelper.toHHMM +import xyz.ksharma.krail.core.datetime.DateTimeHelper.utcToAEST import xyz.ksharma.krail.core.datetime.DateTimeHelper.utcToLocalDateTimeAEST import xyz.ksharma.krail.trip.planner.network.api.model.TripResponse import xyz.ksharma.krail.trip.planner.ui.state.TransportMode @@ -66,7 +68,7 @@ internal fun TripResponse.buildJourneyList(): ImmutableList leg.infos.orEmpty() }.toSet().size, ).also { - //println("\tJourneyId: ${it.journeyId}") + println("\tJourneyId: ${it.journeyId}") } } else { null @@ -246,7 +248,7 @@ private fun TripResponse.StopSequence.toUiModel(): TimeTableState.JourneyCardInf } } -/*internal fun TripResponse.logForUnderstandingData() { +internal fun TripResponse.logForUnderstandingData() { println("Journeys: ${journeys?.size}") journeys?.mapIndexed { jindex, j -> println("JOURNEY #${jindex + 1}") @@ -277,12 +279,12 @@ private fun TripResponse.StopSequence.toUiModel(): TimeTableState.JourneyCardInf ) } } -}*/ +} /** * Prints the stops for legs when interchange required. */ -/*private fun List.interchangeStopsList() = this.mapNotNull { +private fun List.interchangeStopsList() = this.mapNotNull { // TODO - figure role of ARR vs DEP time val timeArr = it.arrivalTimeEstimated?.utcToAEST() ?.formatTo12HourTime() ?: it.arrivalTimePlanned?.utcToAEST()?.formatTo12HourTime() @@ -296,7 +298,7 @@ private fun TripResponse.StopSequence.toUiModel(): TimeTableState.JourneyCardInf "\n\t\t\t\t Stop: ${it.name}," + " depTime: ${timeArr ?: depTime}" } -}*/ +} private fun String.fromUTCToDisplayTimeString() = this.utcToLocalDateTimeAEST().toHHMM() From 4ec5498d1923e4b92995b96db522536cc310c84a Mon Sep 17 00:00:00 2001 From: Karan Sharma <55722391+ksharma-xyz@users.noreply.github.com> Date: Sat, 23 Nov 2024 21:41:23 +1100 Subject: [PATCH 66/67] CI: Add iOS Build --- .github/workflows/ci.yml | 49 +++++++++++++++++++++++++++++++++++++--- 1 file changed, 46 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e1f91c15..a9b70b59 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -7,9 +7,11 @@ on: branches: [ main ] jobs: - build: + android: runs-on: ubuntu-latest environment: Firebase + timeout-minutes: 60 + steps: - uses: actions/checkout@v4 name: Setup environment variables @@ -51,7 +53,48 @@ jobs: # run: ./gradlew detekt - name: Build Debug - run: ./gradlew assembleDebug test + run: ./gradlew :composeApp:assembleDebug test - name: Build Release - run: ./gradlew assembleRelease test + run: ./gradlew :composeApp:assembleRelease test + + iOS: + runs-on: macos-14 + timeout-minutes: 60 + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: set up JDK + uses: actions/setup-java@v4 + with: + distribution: 'temurin' + java-version: 21 + + - name: Set up Kotlin + uses: actions/setup-java@v2 + with: + distribution: 'zulu' + java-version: '11' + + - uses: ruby/setup-ruby@v1 + with: + bundler-cache: true + + - run: brew install swiftlint + + - uses: gradle/actions/setup-gradle@v4 + with: + cache-disabled: true + + - name: Build iOS App - Debug + run: | + xcodebuild -project iosApp/iosApp.xcodeproj \ + -scheme iosApp \ + -configuration Debug \ + OBJROOT=$GITHUB_WORKSPACE/build/ios \ + SYMROOT=$GITHUB_WORKSPACE/build/ios \ + -destination 'platform=iOS Simulator,name=iPhone 12,OS=latest' \ + -allowProvisioningDeviceRegistration \ + -allowProvisioningUpdates From 17dd8978bfc62a2670152996eddc1f517085f3a3 Mon Sep 17 00:00:00 2001 From: Karan Sharma <55722391+ksharma-xyz@users.noreply.github.com> Date: Sat, 23 Nov 2024 22:04:05 +1100 Subject: [PATCH 67/67] Update CI Add x code and ruby version --- .github/workflows/{ci.yml => build.yml} | 35 +++++++++++-------- .ruby-version | 1 + .xcode-version | 1 + .../krail/core/datetime/DateTimeHelperTest.kt | 12 ++----- feature/trip-planner/network/build.gradle.kts | 15 ++++---- .../ui/timetable/business/PlatformTextTest.kt | 2 ++ gradle.properties | 1 + 7 files changed, 33 insertions(+), 34 deletions(-) rename .github/workflows/{ci.yml => build.yml} (75%) create mode 100644 .ruby-version create mode 100644 .xcode-version diff --git a/.github/workflows/ci.yml b/.github/workflows/build.yml similarity index 75% rename from .github/workflows/ci.yml rename to .github/workflows/build.yml index a9b70b59..6a250899 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/build.yml @@ -14,11 +14,9 @@ jobs: steps: - uses: actions/checkout@v4 - name: Setup environment variables - env: - NSW_TRANSPORT_API_KEY: ${{ secrets.NSW_TRANSPORT_API_KEY }} - run: | - echo "::set-env name=NSW_TRANSPORT_API_KEY::${{ secrets.NSW_TRANSPORT_API_KEY }}" + - name: Setup environment variables + run: | + echo "NSW_TRANSPORT_API_KEY=${{ secrets.NSW_TRANSPORT_API_KEY }}" >> $GITHUB_ENV - name: set up JDK uses: actions/setup-java@v4 @@ -53,16 +51,26 @@ jobs: # run: ./gradlew detekt - name: Build Debug + env: + NSW_TRANSPORT_API_KEY: ${{ secrets.NSW_TRANSPORT_API_KEY }} run: ./gradlew :composeApp:assembleDebug test - name: Build Release + env: + NSW_TRANSPORT_API_KEY: ${{ secrets.NSW_TRANSPORT_API_KEY }} run: ./gradlew :composeApp:assembleRelease test iOS: runs-on: macos-14 + environment: Firebase timeout-minutes: 60 steps: + - uses: actions/checkout@v4 + - name: Setup environment variables + run: | + echo "NSW_TRANSPORT_API_KEY=${{ secrets.NSW_TRANSPORT_API_KEY }}" >> $GITHUB_ENV + - name: Checkout code uses: actions/checkout@v4 @@ -72,12 +80,6 @@ jobs: distribution: 'temurin' java-version: 21 - - name: Set up Kotlin - uses: actions/setup-java@v2 - with: - distribution: 'zulu' - java-version: '11' - - uses: ruby/setup-ruby@v1 with: bundler-cache: true @@ -88,13 +90,16 @@ jobs: with: cache-disabled: true - - name: Build iOS App - Debug + - name: Build iOS App - Debug (Without Code Signing) + env: + NSW_TRANSPORT_API_KEY: ${{ secrets.NSW_TRANSPORT_API_KEY }} run: | xcodebuild -project iosApp/iosApp.xcodeproj \ -scheme iosApp \ -configuration Debug \ OBJROOT=$GITHUB_WORKSPACE/build/ios \ SYMROOT=$GITHUB_WORKSPACE/build/ios \ - -destination 'platform=iOS Simulator,name=iPhone 12,OS=latest' \ - -allowProvisioningDeviceRegistration \ - -allowProvisioningUpdates + CODE_SIGN_IDENTITY="" \ + CODE_SIGNING_REQUIRED=NO \ + CODE_SIGNING_ALLOWED=NO \ + -destination 'platform=iOS Simulator,name=iPhone 12,OS=latest' diff --git a/.ruby-version b/.ruby-version new file mode 100644 index 00000000..a0891f56 --- /dev/null +++ b/.ruby-version @@ -0,0 +1 @@ +3.3.4 diff --git a/.xcode-version b/.xcode-version new file mode 100644 index 00000000..dddffdec --- /dev/null +++ b/.xcode-version @@ -0,0 +1 @@ +15.3 diff --git a/core/date-time/src/commonTest/kotlin/xyz/ksharma/krail/core/datetime/DateTimeHelperTest.kt b/core/date-time/src/commonTest/kotlin/xyz/ksharma/krail/core/datetime/DateTimeHelperTest.kt index e08495c4..d5622640 100644 --- a/core/date-time/src/commonTest/kotlin/xyz/ksharma/krail/core/datetime/DateTimeHelperTest.kt +++ b/core/date-time/src/commonTest/kotlin/xyz/ksharma/krail/core/datetime/DateTimeHelperTest.kt @@ -53,20 +53,12 @@ class DateTimeHelperTest { assertEquals("2024-10-07T12:00:23", "2024-10-07T01:00:23Z".utcToAEST()) } -/* - @Test - fun testAestToHHMM() { - assertEquals("11:00 AM", "2024-10-07T00:00:00Z".aestToHHMM()) - assertEquals("12:00 PM", "2024-10-07T01:00:00Z".aestToHHMM()) - } -*/ - @Test fun testToGenericFormattedTimeString() { assertEquals("40 mins ago", (-40).minutes.toGenericFormattedTimeString()) assertEquals("Now", 0.minutes.toGenericFormattedTimeString()) - assertEquals("In 1h 20m", 80.minutes.toGenericFormattedTimeString()) - assertEquals("In 2h", 120.minutes.toGenericFormattedTimeString()) + assertEquals("in 1h 20m", 80.minutes.toGenericFormattedTimeString()) + assertEquals("in 2h", 120.minutes.toGenericFormattedTimeString()) } @Test diff --git a/feature/trip-planner/network/build.gradle.kts b/feature/trip-planner/network/build.gradle.kts index a54bfb17..93e709aa 100644 --- a/feature/trip-planner/network/build.gradle.kts +++ b/feature/trip-planner/network/build.gradle.kts @@ -5,13 +5,9 @@ android { namespace = "xyz.ksharma.krail.trip.planner.network" buildTypes { - debug { - buildConfigField("String", "NSW_TRANSPORT_API_KEY", "\"$nswTransportApiKey\"") - } + debug {} - release { - buildConfigField("String", "NSW_TRANSPORT_API_KEY", "\"$nswTransportApiKey\"") - } + release {} } } @@ -80,15 +76,16 @@ kotlin { // READ API KEY val localProperties = gradleLocalProperties(rootProject.rootDir, providers) -val nswTransportApiKey: String = localProperties.getProperty("NSW_TRANSPORT_API_KEY", "") +val nswTransportApiKey: String = localProperties.getProperty("NSW_TRANSPORT_API_KEY") + ?: System.getenv("NSW_TRANSPORT_API_KEY") require(nswTransportApiKey.isNotEmpty()) { - "Register your API key from the developer and place it in local.properties as `API_KEY`" + "Register API key and put in local.properties as `NSW_TRANSPORT_API_KEY`" } buildkonfig { packageName = "xyz.ksharma.krail.trip.planner.network" require(nswTransportApiKey.isNotEmpty()) { - "Register your api key from developer and place it in local.properties as `API_KEY`" + "Register API key and put in local.properties as `NSW_TRANSPORT_API_KEY`" } defaultConfigs { diff --git a/feature/trip-planner/ui/src/commonTest/kotlin/xyz/ksharma/krail/trip/planner/ui/timetable/business/PlatformTextTest.kt b/feature/trip-planner/ui/src/commonTest/kotlin/xyz/ksharma/krail/trip/planner/ui/timetable/business/PlatformTextTest.kt index 058e2c7a..407eec37 100644 --- a/feature/trip-planner/ui/src/commonTest/kotlin/xyz/ksharma/krail/trip/planner/ui/timetable/business/PlatformTextTest.kt +++ b/feature/trip-planner/ui/src/commonTest/kotlin/xyz/ksharma/krail/trip/planner/ui/timetable/business/PlatformTextTest.kt @@ -1,6 +1,8 @@ package xyz.ksharma.krail.trip.planner.ui.timetable.business import xyz.ksharma.krail.trip.planner.network.api.model.TripResponse +import kotlin.test.Test +import kotlin.test.assertEquals class PlatformTextTest { diff --git a/gradle.properties b/gradle.properties index 7fedda27..2d2f0c0a 100644 --- a/gradle.properties +++ b/gradle.properties @@ -14,3 +14,4 @@ org.gradle.jvmargs=-Xmx2048M -Dkotlin.daemon.jvm.options\="-Xmx2048M" android.useAndroidX=true org.gradle.caching=true +kotlin.native.ignoreDisabledTargets=true