diff --git a/.github/workflows/build_debug_app.yml b/.github/workflows/build_debug_app.yml
index 01ca453..ca0289b 100644
--- a/.github/workflows/build_debug_app.yml
+++ b/.github/workflows/build_debug_app.yml
@@ -16,6 +16,8 @@ jobs:
runs-on: macos-latest
env:
API_KEY: ${{ secrets.API_KEY }}
+ REVENUECAT_API_KEY_ANDROID: ${{ secrets.REVENUECAT_API_KEY_ANDROID }}
+ REVENUECAT_API_KEY_IOS: ${{ secrets.REVENUECAT_API_KEY_IOS }}
steps:
- uses: actions/checkout@v3
diff --git a/.github/workflows/publish-playstore-appstore.yml b/.github/workflows/publish-playstore-appstore.yml
index fd2cb47..c9a9349 100644
--- a/.github/workflows/publish-playstore-appstore.yml
+++ b/.github/workflows/publish-playstore-appstore.yml
@@ -14,6 +14,8 @@ jobs:
env:
github_token: ${{ secrets.GITHUB_TOKEN }}
API_KEY: ${{ secrets.API_KEY }}
+ REVENUECAT_API_KEY_ANDROID: ${{ secrets.REVENUECAT_API_KEY_ANDROID }}
+ REVENUECAT_API_KEY_IOS: ${{ secrets.REVENUECAT_API_KEY_IOS }}
steps:
diff --git a/androidApp/src/androidMain/AndroidManifest.xml b/androidApp/src/androidMain/AndroidManifest.xml
index 75c3f79..666d5bc 100644
--- a/androidApp/src/androidMain/AndroidManifest.xml
+++ b/androidApp/src/androidMain/AndroidManifest.xml
@@ -35,7 +35,7 @@
android:name="com.google.firebase.messaging.default_notification_color"
android:resource="@color/orange" />
-
+
\ No newline at end of file
diff --git a/distribution/whatsnew/whatsnew-en-US b/distribution/whatsnew/whatsnew-en-US
index 0323d17..62f27aa 100644
--- a/distribution/whatsnew/whatsnew-en-US
+++ b/distribution/whatsnew/whatsnew-en-US
@@ -1 +1 @@
-Authentication with Google, Apple, Github is added.
\ No newline at end of file
+Subscription Premium is added for premium features.
\ No newline at end of file
diff --git a/gradle.properties b/gradle.properties
index f766c3d..736caeb 100644
--- a/gradle.properties
+++ b/gradle.properties
@@ -17,7 +17,7 @@ android.useAndroidX=true
android.targetSdk=34
android.compileSdk=34
android.minSdk=24
-android.versionCode=17
-android.versionName=2.2.0
+android.versionCode=18
+android.versionName=2.3.0
diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml
index 2894cbd..4daf07f 100644
--- a/gradle/libs.versions.toml
+++ b/gradle/libs.versions.toml
@@ -21,6 +21,7 @@ firebaseCrashlytics = "2.9.9"
kmpNotifier = "0.3.0"
kmpAuth = "1.1.0"
coil = "3.0.0-alpha03"
+kmpRevenueCat="0.0.6"
@@ -79,6 +80,11 @@ kmpAuth-uihelper = { group = "io.github.mirzemehdi", name = "kmpauth-uihelper",
coil-compose = { group = "io.coil-kt.coil3", name = "coil-compose", version.ref = "coil" }
coil-ktor = { group = "io.coil-kt.coil3", name = "coil-network-ktor", version.ref = "coil" }
+# KMPRevenuecat - Subscription lib
+kmprevenuecat-purchases = { group = "io.github.mirzemehdi", name = "kmprevenuecat-purchases", version.ref = "kmpRevenueCat" }
+kmprevenuecat-purchases-ui = { group = "io.github.mirzemehdi", name = "kmprevenuecat-purchases-ui", version.ref = "kmpRevenueCat" }
+
+
[bundles]
voyager = ["voyager-navigator", "voyager-tabNavigator", "voyager-transitions"]
diff --git a/iosApp/FindTravelNow StoreKit.storekit b/iosApp/FindTravelNow StoreKit.storekit
new file mode 100644
index 0000000..21dee6a
--- /dev/null
+++ b/iosApp/FindTravelNow StoreKit.storekit
@@ -0,0 +1,80 @@
+{
+ "identifier" : "69C00004",
+ "nonRenewingSubscriptions" : [
+
+ ],
+ "products" : [
+ {
+ "displayPrice" : "0.99",
+ "familyShareable" : false,
+ "internalID" : "770F93F8",
+ "localizations" : [
+ {
+ "description" : "",
+ "displayName" : "",
+ "locale" : "en_US"
+ }
+ ],
+ "productID" : "lifetime_premium_v2",
+ "referenceName" : "lifetime_premium_v2",
+ "type" : "NonConsumable"
+ }
+ ],
+ "settings" : {
+ "_failTransactionsEnabled" : false,
+ "_storeKitErrors" : [
+ {
+ "current" : null,
+ "enabled" : false,
+ "name" : "Load Products"
+ },
+ {
+ "current" : null,
+ "enabled" : false,
+ "name" : "Purchase"
+ },
+ {
+ "current" : null,
+ "enabled" : false,
+ "name" : "Verification"
+ },
+ {
+ "current" : null,
+ "enabled" : false,
+ "name" : "App Store Sync"
+ },
+ {
+ "current" : null,
+ "enabled" : false,
+ "name" : "Subscription Status"
+ },
+ {
+ "current" : null,
+ "enabled" : false,
+ "name" : "App Transaction"
+ },
+ {
+ "current" : null,
+ "enabled" : false,
+ "name" : "Manage Subscriptions Sheet"
+ },
+ {
+ "current" : null,
+ "enabled" : false,
+ "name" : "Refund Request Sheet"
+ },
+ {
+ "current" : null,
+ "enabled" : false,
+ "name" : "Offer Code Redeem Sheet"
+ }
+ ]
+ },
+ "subscriptionGroups" : [
+
+ ],
+ "version" : {
+ "major" : 3,
+ "minor" : 0
+ }
+}
diff --git a/iosApp/Podfile.lock b/iosApp/Podfile.lock
index 934c571..2bce42a 100644
--- a/iosApp/Podfile.lock
+++ b/iosApp/Podfile.lock
@@ -11,6 +11,6 @@ EXTERNAL SOURCES:
SPEC CHECKSUMS:
shared: 45f487984c6fbabf19d6b4eb91ffb5129a2bfc20
-PODFILE CHECKSUM: eae97542391f0ffa4310330e32cfe71726dbe5ec
+PODFILE CHECKSUM: 0a20442581fb5fc64112ac1d7a63be54e50e4662
COCOAPODS: 1.14.3
diff --git a/iosApp/Pods/Manifest.lock b/iosApp/Pods/Manifest.lock
index 934c571..2bce42a 100644
--- a/iosApp/Pods/Manifest.lock
+++ b/iosApp/Pods/Manifest.lock
@@ -11,6 +11,6 @@ EXTERNAL SOURCES:
SPEC CHECKSUMS:
shared: 45f487984c6fbabf19d6b4eb91ffb5129a2bfc20
-PODFILE CHECKSUM: eae97542391f0ffa4310330e32cfe71726dbe5ec
+PODFILE CHECKSUM: 0a20442581fb5fc64112ac1d7a63be54e50e4662
COCOAPODS: 1.14.3
diff --git a/iosApp/Pods/Pods.xcodeproj/xcuserdata/mirzemehdi.xcuserdatad/xcschemes/xcschememanagement.plist b/iosApp/Pods/Pods.xcodeproj/xcuserdata/mirzemehdi.xcuserdatad/xcschemes/xcschememanagement.plist
index 7dce8f0..84bd584 100644
--- a/iosApp/Pods/Pods.xcodeproj/xcuserdata/mirzemehdi.xcuserdatad/xcschemes/xcschememanagement.plist
+++ b/iosApp/Pods/Pods.xcodeproj/xcuserdata/mirzemehdi.xcuserdatad/xcschemes/xcschememanagement.plist
@@ -8,11 +8,15 @@
isShown
+ orderHint
+ 2
shared.xcscheme
isShown
+ orderHint
+ 6
SuppressBuildableAutocreation
diff --git a/iosApp/StoreKitTestCertificate.cer b/iosApp/StoreKitTestCertificate.cer
new file mode 100644
index 0000000..5896156
Binary files /dev/null and b/iosApp/StoreKitTestCertificate.cer differ
diff --git a/iosApp/iosApp.xcodeproj/project.pbxproj b/iosApp/iosApp.xcodeproj/project.pbxproj
index c18b956..e07f8ea 100644
--- a/iosApp/iosApp.xcodeproj/project.pbxproj
+++ b/iosApp/iosApp.xcodeproj/project.pbxproj
@@ -23,11 +23,13 @@
A6886A9B2AFBF140000FC219 /* GoogleService-Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = A6886A9A2AFBF140000FC219 /* GoogleService-Info.plist */; };
A689DB122AF5F91200EC7353 /* FirebaseAnalytics in Frameworks */ = {isa = PBXBuildFile; productRef = A689DB112AF5F91200EC7353 /* FirebaseAnalytics */; };
A68EBF9F2AF5ACA30038BFFD /* FirebaseCrashlytics in Frameworks */ = {isa = PBXBuildFile; productRef = A68EBF9E2AF5ACA30038BFFD /* FirebaseCrashlytics */; };
+ A6BD7BF22B86F6940052C214 /* StoreKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A6BD7BF12B86F6940052C214 /* StoreKit.framework */; };
A6D548BA2B0ECD9A0060761F /* FirebaseMessaging in Frameworks */ = {isa = PBXBuildFile; productRef = A6D548B92B0ECD9A0060761F /* FirebaseMessaging */; };
+ A6DB22392B85B77B0086C4D3 /* RevenueCat in Frameworks */ = {isa = PBXBuildFile; productRef = A6DB22382B85B77B0086C4D3 /* RevenueCat */; };
+ A6DB223B2B85B77B0086C4D3 /* RevenueCatUI in Frameworks */ = {isa = PBXBuildFile; productRef = A6DB223A2B85B77B0086C4D3 /* RevenueCatUI */; };
/* End PBXBuildFile section */
/* Begin PBXFileReference section */
- 034C794B9477822EA66F43DC /* Pods-iosApp.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-iosApp.release.xcconfig"; path = "Target Support Files/Pods-iosApp/Pods-iosApp.release.xcconfig"; sourceTree = ""; };
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 = ""; };
1C775BE0B2EC6251EEB5DDB2 /* libPods-iosApp.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-iosApp.a"; sourceTree = BUILT_PRODUCTS_DIR; };
@@ -35,11 +37,13 @@
7555FF7B242A565900829871 /* FindTravelNow.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = FindTravelNow.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 = ""; };
- A0A10663FE7537E54A8D6A37 /* Pods-iosApp.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-iosApp.debug.xcconfig"; path = "Target Support Files/Pods-iosApp/Pods-iosApp.debug.xcconfig"; sourceTree = ""; };
A621B42A2AF4899C00D87165 /* iosAppRelease.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = iosAppRelease.entitlements; sourceTree = ""; };
A6886A9A2AFBF140000FC219 /* GoogleService-Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = "GoogleService-Info.plist"; path = "../iosApp/GoogleService-Info.plist"; sourceTree = ""; };
+ A6BD7BF12B86F6940052C214 /* StoreKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = StoreKit.framework; path = System/Library/Frameworks/StoreKit.framework; sourceTree = SDKROOT; };
A6D548B82B0ECD840060761F /* iosApp.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = iosApp.entitlements; sourceTree = ""; };
AB3632DC29227652001CCB65 /* Config.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = Config.xcconfig; sourceTree = ""; };
+ AEC633E835AD47E2894F710A /* Pods-iosApp.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-iosApp.release.xcconfig"; path = "Target Support Files/Pods-iosApp/Pods-iosApp.release.xcconfig"; sourceTree = ""; };
+ B27AACAA71233EC440B04D5C /* Pods-iosApp.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-iosApp.debug.xcconfig"; path = "Target Support Files/Pods-iosApp/Pods-iosApp.debug.xcconfig"; sourceTree = ""; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
@@ -49,6 +53,7 @@
files = (
A648BF9C2B5D48CC00896CA6 /* FirebaseAnalyticsSwift in Frameworks */,
A648BF9E2B5D48CC00896CA6 /* FirebaseAnalyticsWithoutAdIdSupport in Frameworks */,
+ A6DB223B2B85B77B0086C4D3 /* RevenueCatUI in Frameworks */,
A648BF982B5D48CC00896CA6 /* FirebaseAnalytics in Frameworks */,
A648BF942B5D48AA00896CA6 /* GoogleSignIn in Frameworks */,
A6D548BA2B0ECD9A0060761F /* FirebaseMessaging in Frameworks */,
@@ -56,7 +61,9 @@
A648BF962B5D48AA00896CA6 /* GoogleSignInSwift in Frameworks */,
A648BFA22B5D49FD00896CA6 /* FirebaseAuth in Frameworks */,
A68EBF9F2AF5ACA30038BFFD /* FirebaseCrashlytics in Frameworks */,
+ A6DB22392B85B77B0086C4D3 /* RevenueCat in Frameworks */,
769F3BF91D39AE2AF514DB56 /* libPods-iosApp.a in Frameworks */,
+ A6BD7BF22B86F6940052C214 /* StoreKit.framework in Frameworks */,
A648BF9A2B5D48CC00896CA6 /* FirebaseAnalyticsOnDeviceConversion in Frameworks */,
A648BFA02B5D48CC00896CA6 /* FirebaseAppCheck in Frameworks */,
);
@@ -76,20 +83,12 @@
42799AB246E5F90AF97AA0EF /* Frameworks */ = {
isa = PBXGroup;
children = (
+ A6BD7BF12B86F6940052C214 /* StoreKit.framework */,
1C775BE0B2EC6251EEB5DDB2 /* libPods-iosApp.a */,
);
name = Frameworks;
sourceTree = "";
};
- 5BDE45FF738E720B07260B4E /* Pods */ = {
- isa = PBXGroup;
- children = (
- A0A10663FE7537E54A8D6A37 /* Pods-iosApp.debug.xcconfig */,
- 034C794B9477822EA66F43DC /* Pods-iosApp.release.xcconfig */,
- );
- path = Pods;
- sourceTree = "";
- };
7555FF72242A565900829871 = {
isa = PBXGroup;
children = (
@@ -97,7 +96,7 @@
7555FF7D242A565900829871 /* iosApp */,
7555FF7C242A565900829871 /* Products */,
42799AB246E5F90AF97AA0EF /* Frameworks */,
- 5BDE45FF738E720B07260B4E /* Pods */,
+ A75D105B3C54BB927D9736BE /* Pods */,
);
sourceTree = "";
};
@@ -124,6 +123,15 @@
path = iosApp;
sourceTree = "";
};
+ A75D105B3C54BB927D9736BE /* Pods */ = {
+ isa = PBXGroup;
+ children = (
+ B27AACAA71233EC440B04D5C /* Pods-iosApp.debug.xcconfig */,
+ AEC633E835AD47E2894F710A /* Pods-iosApp.release.xcconfig */,
+ );
+ path = Pods;
+ sourceTree = "";
+ };
AB1DB47929225F7C00F7AF9C /* Configuration */ = {
isa = PBXGroup;
children = (
@@ -164,6 +172,8 @@
A648BF9D2B5D48CC00896CA6 /* FirebaseAnalyticsWithoutAdIdSupport */,
A648BF9F2B5D48CC00896CA6 /* FirebaseAppCheck */,
A648BFA12B5D49FD00896CA6 /* FirebaseAuth */,
+ A6DB22382B85B77B0086C4D3 /* RevenueCat */,
+ A6DB223A2B85B77B0086C4D3 /* RevenueCatUI */,
);
productName = iosApp;
productReference = 7555FF7B242A565900829871 /* FindTravelNow.app */;
@@ -196,6 +206,7 @@
packageReferences = (
A68EBF792AF5AC5A0038BFFD /* XCRemoteSwiftPackageReference "firebase-ios-sdk" */,
A648BF922B5D48AA00896CA6 /* XCRemoteSwiftPackageReference "GoogleSignIn-iOS" */,
+ A6DB22372B85B77B0086C4D3 /* XCRemoteSwiftPackageReference "purchases-ios" */,
);
productRefGroup = 7555FF7C242A565900829871 /* Products */;
projectDirPath = "";
@@ -434,10 +445,11 @@
};
7555FFA6242A565B00829871 /* Debug */ = {
isa = XCBuildConfiguration;
- baseConfigurationReference = A0A10663FE7537E54A8D6A37 /* Pods-iosApp.debug.xcconfig */;
+ baseConfigurationReference = B27AACAA71233EC440B04D5C /* Pods-iosApp.debug.xcconfig */;
buildSettings = {
APP_NAME = FindTravelNow;
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
+ "ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME[sdk=*]" = AccentColor;
BUNDLE_ID = com.measify.findtravelnow;
CODE_SIGN_ENTITLEMENTS = iosApp/iosApp.entitlements;
CODE_SIGN_IDENTITY = "Apple Development";
@@ -454,7 +466,7 @@
INFOPLIST_FILE = iosApp/Info.plist;
INFOPLIST_KEY_CFBundleDisplayName = FindTravelNow;
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.travel";
- IPHONEOS_DEPLOYMENT_TARGET = 14.1;
+ IPHONEOS_DEPLOYMENT_TARGET = 15.0;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
@@ -477,10 +489,11 @@
};
7555FFA7242A565B00829871 /* Release */ = {
isa = XCBuildConfiguration;
- baseConfigurationReference = 034C794B9477822EA66F43DC /* Pods-iosApp.release.xcconfig */;
+ baseConfigurationReference = AEC633E835AD47E2894F710A /* Pods-iosApp.release.xcconfig */;
buildSettings = {
APP_NAME = FindTravelNow;
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
+ "ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME[sdk=*]" = AccentColor;
BUNDLE_ID = com.measify.findtravelnow;
CODE_SIGN_ENTITLEMENTS = iosApp/iosAppRelease.entitlements;
CODE_SIGN_IDENTITY = "Apple Development";
@@ -497,7 +510,7 @@
INFOPLIST_FILE = iosApp/Info.plist;
INFOPLIST_KEY_CFBundleDisplayName = FindTravelNow;
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.travel";
- IPHONEOS_DEPLOYMENT_TARGET = 14.1;
+ IPHONEOS_DEPLOYMENT_TARGET = 15.0;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
@@ -558,6 +571,14 @@
minimumVersion = 10.17.0;
};
};
+ A6DB22372B85B77B0086C4D3 /* XCRemoteSwiftPackageReference "purchases-ios" */ = {
+ isa = XCRemoteSwiftPackageReference;
+ repositoryURL = "https://github.com/RevenueCat/purchases-ios";
+ requirement = {
+ kind = upToNextMajorVersion;
+ minimumVersion = 4.36.3;
+ };
+ };
/* End XCRemoteSwiftPackageReference section */
/* Begin XCSwiftPackageProductDependency section */
@@ -611,6 +632,16 @@
package = A68EBF792AF5AC5A0038BFFD /* XCRemoteSwiftPackageReference "firebase-ios-sdk" */;
productName = FirebaseMessaging;
};
+ A6DB22382B85B77B0086C4D3 /* RevenueCat */ = {
+ isa = XCSwiftPackageProductDependency;
+ package = A6DB22372B85B77B0086C4D3 /* XCRemoteSwiftPackageReference "purchases-ios" */;
+ productName = RevenueCat;
+ };
+ A6DB223A2B85B77B0086C4D3 /* RevenueCatUI */ = {
+ isa = XCSwiftPackageProductDependency;
+ package = A6DB22372B85B77B0086C4D3 /* XCRemoteSwiftPackageReference "purchases-ios" */;
+ productName = RevenueCatUI;
+ };
/* End XCSwiftPackageProductDependency section */
};
rootObject = 7555FF73242A565900829871 /* Project object */;
diff --git a/iosApp/iosApp.xcworkspace/contents.xcworkspacedata b/iosApp/iosApp.xcworkspace/contents.xcworkspacedata
index c009e7d..552cf8a 100644
--- a/iosApp/iosApp.xcworkspace/contents.xcworkspacedata
+++ b/iosApp/iosApp.xcworkspace/contents.xcworkspacedata
@@ -1,6 +1,12 @@
+
+
+
+
diff --git a/iosApp/iosApp.xcworkspace/xcshareddata/swiftpm/Package.resolved b/iosApp/iosApp.xcworkspace/xcshareddata/swiftpm/Package.resolved
index ed8857e..e825b15 100644
--- a/iosApp/iosApp.xcworkspace/xcshareddata/swiftpm/Package.resolved
+++ b/iosApp/iosApp.xcworkspace/xcshareddata/swiftpm/Package.resolved
@@ -135,6 +135,15 @@
"version" : "2.3.1"
}
},
+ {
+ "identity" : "purchases-ios",
+ "kind" : "remoteSourceControl",
+ "location" : "https://github.com/RevenueCat/purchases-ios",
+ "state" : {
+ "revision" : "c598bfa60672881933d8945584cc327acd6f018a",
+ "version" : "4.36.3"
+ }
+ },
{
"identity" : "swift-protobuf",
"kind" : "remoteSourceControl",
diff --git a/iosApp/iosApp.xcworkspace/xcuserdata/mirzemehdi.xcuserdatad/UserInterfaceState.xcuserstate b/iosApp/iosApp.xcworkspace/xcuserdata/mirzemehdi.xcuserdatad/UserInterfaceState.xcuserstate
index 8d3e69e..861c182 100644
Binary files a/iosApp/iosApp.xcworkspace/xcuserdata/mirzemehdi.xcuserdatad/UserInterfaceState.xcuserstate and b/iosApp/iosApp.xcworkspace/xcuserdata/mirzemehdi.xcuserdatad/UserInterfaceState.xcuserstate differ
diff --git a/iosApp/iosApp.xcworkspace/xcuserdata/mirzemehdi.xcuserdatad/xcschemes/xcschememanagement.plist b/iosApp/iosApp.xcworkspace/xcuserdata/mirzemehdi.xcuserdatad/xcschemes/xcschememanagement.plist
index a607980..b7a9846 100644
--- a/iosApp/iosApp.xcworkspace/xcuserdata/mirzemehdi.xcuserdatad/xcschemes/xcschememanagement.plist
+++ b/iosApp/iosApp.xcworkspace/xcuserdata/mirzemehdi.xcuserdatad/xcschemes/xcschememanagement.plist
@@ -9,7 +9,21 @@
isShown
orderHint
- 7
+ 8
+
+ Promises (Playground) 10.xcscheme
+
+ isShown
+
+ orderHint
+ 14
+
+ Promises (Playground) 11.xcscheme
+
+ isShown
+
+ orderHint
+ 15
Promises (Playground) 2.xcscheme
@@ -23,49 +37,56 @@
isShown
orderHint
- 1
+ 4
Promises (Playground) 4.xcscheme
isShown
orderHint
- 2
+ 5
Promises (Playground) 5.xcscheme
isShown
orderHint
- 4
+ 6
Promises (Playground) 6.xcscheme
isShown
orderHint
- 15
+ 7
Promises (Playground) 7.xcscheme
isShown
orderHint
- 16
+ 8
Promises (Playground) 8.xcscheme
isShown
orderHint
- 17
+ 9
+
+ Promises (Playground) 9.xcscheme
+
+ isShown
+
+ orderHint
+ 13
Promises (Playground).xcscheme
isShown
orderHint
- 3
+ 7
diff --git a/iosApp/iosApp/Assets.xcassets/AccentColor.colorset/Contents.json b/iosApp/iosApp/Assets.xcassets/AccentColor.colorset/Contents.json
index ee7e3ca..89b5d90 100644
--- a/iosApp/iosApp/Assets.xcassets/AccentColor.colorset/Contents.json
+++ b/iosApp/iosApp/Assets.xcassets/AccentColor.colorset/Contents.json
@@ -1,6 +1,10 @@
{
"colors" : [
{
+ "color" : {
+ "platform" : "ios",
+ "reference" : "systemYellowColor"
+ },
"idiom" : "universal"
}
],
@@ -8,4 +12,4 @@
"author" : "xcode",
"version" : 1
}
-}
\ No newline at end of file
+}
diff --git a/iosApp/iosApp/Info.plist b/iosApp/iosApp/Info.plist
index 68bbe1b..a81cdc2 100644
--- a/iosApp/iosApp/Info.plist
+++ b/iosApp/iosApp/Info.plist
@@ -17,7 +17,7 @@
CFBundlePackageType
$(PRODUCT_BUNDLE_PACKAGE_TYPE)
CFBundleShortVersionString
- 2.2.0
+ 2.3.0
CFBundleURLTypes
@@ -36,7 +36,7 @@
CFBundleVersion
- 22
+ 23
GIDClientID
400988245981-9tv75mdhd0he2unncn3t1g3dq0gur3oc.apps.googleusercontent.com
GIDServerClientID
diff --git a/secrets.properties b/secrets.properties
index 2387a6f..1b5203b 100644
--- a/secrets.properties
+++ b/secrets.properties
@@ -1 +1,3 @@
-API_KEY=
\ No newline at end of file
+API_KEY=
+REVENUECAT_API_KEY_ANDROID=
+REVENUECAT_API_KEY_IOS=
\ No newline at end of file
diff --git a/settings.gradle.kts b/settings.gradle.kts
index 0ad46e0..81bd399 100644
--- a/settings.gradle.kts
+++ b/settings.gradle.kts
@@ -14,6 +14,7 @@ plugins {
dependencyResolutionManagement {
repositories {
mavenCentral()
+ mavenLocal()
google()
maven("https://maven.pkg.jetbrains.space/public/p/compose/dev")
}
diff --git a/shared/build.gradle.kts b/shared/build.gradle.kts
index 04db505..fae3010 100644
--- a/shared/build.gradle.kts
+++ b/shared/build.gradle.kts
@@ -38,6 +38,8 @@ kotlin {
|
|object BuildConfig {
| const val API_KEY = "${secretProperties.getPropertyValue("API_KEY")}"
+ | const val REVENUECAT_API_KEY_ANDROID = "${secretProperties.getPropertyValue("REVENUECAT_API_KEY_ANDROID")}"
+ | const val REVENUECAT_API_KEY_IOS = "${secretProperties.getPropertyValue("REVENUECAT_API_KEY_IOS")}"
|}
|
""".trimMargin()
@@ -78,6 +80,9 @@ kotlin {
implementation(libs.coil.compose)
implementation(libs.coil.ktor)
+ implementation(libs.kmprevenuecat.purchases)
+ implementation(libs.kmprevenuecat.purchases.ui)
+
}
}
@@ -95,8 +100,6 @@ kotlin {
api(libs.firebase.crashlytics)
api(libs.firebase.messaging)
-// implementation("io.ktor:ktor-client-okhttp:2.3.7")
-
}
}
iosMain {
diff --git a/shared/src/androidMain/kotlin/util/PlatformSpecific.kt b/shared/src/androidMain/kotlin/util/PlatformSpecific.kt
index d2e3bd8..275d4f4 100644
--- a/shared/src/androidMain/kotlin/util/PlatformSpecific.kt
+++ b/shared/src/androidMain/kotlin/util/PlatformSpecific.kt
@@ -37,3 +37,5 @@ internal class AndroidAppVersion(private val context: Context) : AppVersion {
context.packageManager.getPackageInfo(context.packageName, 0)
}
+
+actual fun isAndroid() = true
\ No newline at end of file
diff --git a/shared/src/commonMain/kotlin/data/repository/UserRepository.kt b/shared/src/commonMain/kotlin/data/repository/UserRepository.kt
index e9cff1f..f594ee1 100644
--- a/shared/src/commonMain/kotlin/data/repository/UserRepository.kt
+++ b/shared/src/commonMain/kotlin/data/repository/UserRepository.kt
@@ -1,19 +1,33 @@
package data.repository
+import com.mmk.kmprevenuecat.purchases.Purchases
+import com.mmk.kmprevenuecat.purchases.PurchasesException
+import com.mmk.kmprevenuecat.purchases.awaitCustomerInfo
+import com.mmk.kmprevenuecat.purchases.data.CustomerInfo
import data.source.remote.apiservice.UserApiService
import data.source.remote.request.UserUpdateRequest
import dev.gitlive.firebase.Firebase
import dev.gitlive.firebase.auth.auth
import domain.model.AuthProvider
+import domain.model.Subscription
import domain.model.User
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.IO
import kotlinx.coroutines.MainScope
+import kotlinx.coroutines.coroutineScope
+import kotlinx.coroutines.delay
+import kotlinx.coroutines.flow.MutableSharedFlow
+import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.flow.onStart
import kotlinx.coroutines.flow.shareIn
import kotlinx.coroutines.flow.stateIn
+import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import util.logging.AppLogger
import kotlin.coroutines.CoroutineContext
@@ -23,26 +37,77 @@ class UserRepository(
private val backgroundScope: CoroutineContext = Dispatchers.IO
) {
+ private val currentUserSubscriptionFlow = MutableStateFlow(null)
+ private val firebaseCurrentUserFlow = Firebase.auth.authStateChanged
+ .onEach { firebaseUser ->
+ firebaseUser?.let {
+ Purchases.login(firebaseUser.uid) { subscriptionLoginResult ->
+ val subscription =
+ subscriptionLoginResult.getOrNull()?.customerInfo?.asSubscription()
+ currentUserSubscriptionFlow.tryEmit(subscription)
+ }
+ }
+ }
+ .map { firebaseUser ->
+ if (firebaseUser == null) null
+ else userApiService.createOrGetUser().data?.mapToDomainModel().also {
+ Purchases.setAttributes(mapOf("\$email" to it?.email,"\$displayName" to it?.displayName))
+ }
+ }
+
+
+ val currentUser =
+ combine(firebaseCurrentUserFlow, currentUserSubscriptionFlow) { user, subscription ->
+ user?.copy(subscription = subscription)
+ }
+ .flowOn(backgroundScope)
+ .stateIn(MainScope(), SharingStarted.WhileSubscribed(5000), null)
+
- val currentUser = Firebase.auth.authStateChanged.map {
- if (it == null) null
- else userApiService.createOrGetUser().data?.mapToDomainModel()
- }.flowOn(backgroundScope).stateIn(MainScope(), SharingStarted.Eagerly,null)
+ fun refreshUserSubscription(){
+ MainScope().launch {
+ currentUserSubscriptionFlow.emit(getUserSubscription())
+ }
+ }
+ private suspend fun getUserSubscription(): Subscription? {
+ val customerInfo = try {
+ Purchases.awaitCustomerInfo()
+ } catch (e: PurchasesException) {
+ null
+ } ?: return null
+ return customerInfo.asSubscription()
+ }
+
+ private fun CustomerInfo?.asSubscription(): Subscription? {
+ val customerInfo = this ?: return null
+ val premiumSubscriptionEntitlement = customerInfo.entitlements.all["Premium"]
+ return premiumSubscriptionEntitlement?.let {
+ Subscription(
+ entitlementId = it.identifier,
+ expirationDate = it.expirationDate,
+ isActive = it.isActive
+ )
+ }
+ }
suspend fun logOut() = withContext(backgroundScope) {
Firebase.auth.signOut()
+ Purchases.logOut { }
}
suspend fun deleteAccount() = withContext(backgroundScope) {
val currentUser = Firebase.auth.currentUser
currentUser?.delete()
- kotlin.runCatching { Firebase.auth.signOut() }
+ kotlin.runCatching { logOut() }
}
suspend fun updateProfile(user: User) = withContext(backgroundScope) {
- userApiService.updateUser(userUpdateRequest = UserUpdateRequest(displayName = user.displayName,profilePicUrl = user.profilePicSrc))
-// val currentUser = Firebase.auth.currentUser
-// currentUser?.updateProfile(displayName = user.displayName, photoUrl = user.profilePicSrc)
+ userApiService.updateUser(
+ userUpdateRequest = UserUpdateRequest(
+ displayName = user.displayName,
+ profilePicUrl = user.profilePicSrc
+ )
+ )
}
fun getCurrentUserProviders(): List {
diff --git a/shared/src/commonMain/kotlin/domain/model/Subscription.kt b/shared/src/commonMain/kotlin/domain/model/Subscription.kt
new file mode 100644
index 0000000..c7f717c
--- /dev/null
+++ b/shared/src/commonMain/kotlin/domain/model/Subscription.kt
@@ -0,0 +1,7 @@
+package domain.model
+
+data class Subscription(
+ val entitlementId:String,
+ val expirationDate:Long?,
+ val isActive:Boolean
+)
diff --git a/shared/src/commonMain/kotlin/domain/model/User.kt b/shared/src/commonMain/kotlin/domain/model/User.kt
index d707811..b212c1c 100644
--- a/shared/src/commonMain/kotlin/domain/model/User.kt
+++ b/shared/src/commonMain/kotlin/domain/model/User.kt
@@ -5,4 +5,7 @@ data class User(
val displayName: String = "",
val profilePicSrc: String? = "",
val email: String? = null,
-)
\ No newline at end of file
+ val subscription: Subscription? = null
+) {
+ fun hasPremiumSubscription() = subscription != null && subscription.isActive
+}
\ No newline at end of file
diff --git a/shared/src/commonMain/kotlin/presentation/components/SubscriptionPaywall.kt b/shared/src/commonMain/kotlin/presentation/components/SubscriptionPaywall.kt
new file mode 100644
index 0000000..ed0f48c
--- /dev/null
+++ b/shared/src/commonMain/kotlin/presentation/components/SubscriptionPaywall.kt
@@ -0,0 +1,36 @@
+package presentation.components
+
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.material3.Surface
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.window.Dialog
+import androidx.compose.ui.window.DialogProperties
+import com.mmk.kmprevenuecat.purchases.ui.Paywall
+import com.mmk.kmprevenuecat.purchases.ui.PaywallListener
+import util.isAndroid
+
+@Composable
+fun SubscriptionPaywall(onDismiss: () -> Unit, listener: PaywallListener? = null) {
+ //In ios no need for fullscreen dialog,in android we add extra full screen dialog
+ if (isAndroid())
+ Dialog(
+ properties = DialogProperties(usePlatformDefaultWidth = false),
+ onDismissRequest = onDismiss
+ ) {
+ Surface(modifier = Modifier.fillMaxSize()) {
+ Paywall(
+ shouldDisplayDismissButton = true,
+ onDismiss = onDismiss,
+ listener = listener
+ )
+ }
+ }
+ else {
+ Paywall(
+ shouldDisplayDismissButton = true,
+ onDismiss = onDismiss,
+ listener = listener
+ )
+ }
+}
\ No newline at end of file
diff --git a/shared/src/commonMain/kotlin/presentation/screens/account/profile/ProfileScreen.kt b/shared/src/commonMain/kotlin/presentation/screens/account/profile/ProfileScreen.kt
index 343ae32..f9b0341 100644
--- a/shared/src/commonMain/kotlin/presentation/screens/account/profile/ProfileScreen.kt
+++ b/shared/src/commonMain/kotlin/presentation/screens/account/profile/ProfileScreen.kt
@@ -26,6 +26,7 @@ import androidx.compose.material3.ModalBottomSheet
import androidx.compose.material3.Scaffold
import androidx.compose.material3.SnackbarHost
import androidx.compose.material3.SnackbarHostState
+import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.material3.TextButton
import androidx.compose.material3.rememberModalBottomSheetState
@@ -42,8 +43,13 @@ import androidx.compose.ui.graphics.Color
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
+import androidx.compose.ui.window.Dialog
+import androidx.compose.ui.window.DialogProperties
import coil3.compose.AsyncImage
import coil3.compose.AsyncImagePainter
+import com.mmk.kmprevenuecat.purchases.data.CustomerInfo
+import com.mmk.kmprevenuecat.purchases.ui.Paywall
+import com.mmk.kmprevenuecat.purchases.ui.PaywallListener
import dev.gitlive.firebase.auth.FirebaseUser
import domain.model.AuthProvider
import domain.model.User
@@ -55,11 +61,13 @@ import presentation.components.ExpandableBoxItem
import presentation.components.GradientButton
import presentation.components.MyAppCircularProgressIndicator
import presentation.components.MyAppLabelledTextInput
+import presentation.components.SubscriptionPaywall
import presentation.components.TitleDescription
import presentation.theme.Alabaster
import presentation.theme.Black_22
import presentation.theme.strings.Strings
import util.asState
+import util.isAndroid
@OptIn(ExperimentalResourceApi::class)
@@ -83,10 +91,11 @@ fun ProfileScreen(modifier: Modifier = Modifier, uiStateHolder: ProfileUiStateHo
}
val snackbarHostState = remember { SnackbarHostState() }
- Scaffold(modifier = modifier.fillMaxSize(),
+ Scaffold(
+ modifier = modifier.fillMaxSize(),
snackbarHost = {
SnackbarHost(hostState = snackbarHostState)
- }
+ },
) {
LaunchedEffect(uiState.message) {
if (uiState.message.isNullOrEmpty().not()) {
@@ -104,7 +113,8 @@ fun ProfileScreen(modifier: Modifier = Modifier, uiStateHolder: ProfileUiStateHo
onClickDeleteAccount = uiStateHolder::onClickDeleteAccount,
onClickEnableEditMode = uiStateHolder::onCLickEnableEditMode,
onClickUpdateProfile = uiStateHolder::onClickUpdateProfile,
- onChangeDisplayName = uiStateHolder::onChangeDisplayName
+ onChangeDisplayName = uiStateHolder::onChangeDisplayName,
+ onClickUpgradePremium = uiStateHolder::onClickUpgradePremium,
)
}
@@ -116,6 +126,33 @@ fun ProfileScreen(modifier: Modifier = Modifier, uiStateHolder: ProfileUiStateHo
MyAppCircularProgressIndicator()
}
}
+ if (uiState.isSubscribePlansViewVisible) {
+ SubscriptionPaywall(
+ onDismiss = uiStateHolder::onDismissSubscriptionPlansView,
+ listener = object:PaywallListener{
+ override fun onPurchaseCompleted(customerInfo: CustomerInfo?) {
+ super.onPurchaseCompleted(customerInfo)
+ uiStateHolder.onSubscriptionPurchaseCompleted()
+ }
+
+ override fun onPurchaseError(error: String?) {
+ super.onPurchaseError(error)
+ uiStateHolder.onSubscriptionPurchaseError(error)
+ }
+
+
+ override fun onRestoreCompleted(customerInfo: CustomerInfo?) {
+ super.onRestoreCompleted(customerInfo)
+ uiStateHolder.onSubscriptionPurchaseCompleted()
+ }
+
+ override fun onRestoreError(error: String?) {
+ super.onRestoreError(error)
+ uiStateHolder.onSubscriptionPurchaseError(error)
+ }
+ }
+ )
+ }
}
@OptIn(ExperimentalResourceApi::class, ExperimentalMaterial3Api::class)
@@ -130,6 +167,7 @@ private fun ProfileScreen(
onClickEnableEditMode: () -> Unit,
onClickUpdateProfile: () -> Unit,
onChangeDisplayName: (String) -> Unit,
+ onClickUpgradePremium: () -> Unit,
) {
Box(modifier = modifier) {
@@ -208,6 +246,12 @@ private fun ProfileScreen(
modifier = Modifier.fillMaxWidth().padding(top = 24.dp),
currentUser = currentUser
)
+
+ SubscriptionInfo(
+ modifier = Modifier.fillMaxWidth().padding(top = 20.dp),
+ currentUser = currentUser,
+ onClickUpgradePremium = onClickUpgradePremium
+ )
GradientButton(
modifier = Modifier.padding(top = 32.dp).fillMaxWidth(),
text = Strings.btn_log_out,
@@ -272,6 +316,54 @@ private fun BasicInfo(modifier: Modifier = Modifier, currentUser: User) {
}
}
+@Composable
+private fun SubscriptionInfo(
+ modifier: Modifier = Modifier,
+ currentUser: User,
+ onClickUpgradePremium: () -> Unit
+) {
+ var isExpanded by remember { mutableStateOf(false) }
+ ExpandableBoxItem(modifier = modifier,
+ text = Strings.subscription_info,
+ isExpanded = isExpanded,
+ onToggle = { isExpanded = !isExpanded }
+ ) {
+ Column(verticalArrangement = Arrangement.spacedBy(10.dp)) {
+
+
+ TitleDescription(
+ title = Strings.subscription_plan_title,
+ description = if (currentUser.hasPremiumSubscription()) Strings.subscription_plan_premium else Strings.subscription_plan_free,
+ )
+
+ if (
+ currentUser.hasPremiumSubscription() &&
+ (currentUser.subscription?.expirationDate ?: 0L) == 0L
+ )
+ TitleDescription(
+ title = Strings.subscription_plan_expiration_date,
+ description = Strings.subscription_lifetime,
+ )
+ if (currentUser.hasPremiumSubscription().not()) {
+ TextButton(
+ onClick = { onClickUpgradePremium() },
+ colors = ButtonDefaults.buttonColors(
+ containerColor = MaterialTheme.colorScheme.primary,
+ contentColor = MaterialTheme.colorScheme.onPrimary
+ )
+ ) {
+ Text(
+ text = Strings.btn_get_premium_subscription_plan_free,
+ style = MaterialTheme.typography.bodyMedium,
+ textAlign = TextAlign.Start,
+ )
+ }
+ }
+
+ }
+ }
+}
+
@Composable
private fun EditInfoContainer(
modifier: Modifier = Modifier,
diff --git a/shared/src/commonMain/kotlin/presentation/screens/account/profile/ProfileScreenUiState.kt b/shared/src/commonMain/kotlin/presentation/screens/account/profile/ProfileScreenUiState.kt
index 07928ea..f31defe 100644
--- a/shared/src/commonMain/kotlin/presentation/screens/account/profile/ProfileScreenUiState.kt
+++ b/shared/src/commonMain/kotlin/presentation/screens/account/profile/ProfileScreenUiState.kt
@@ -11,6 +11,7 @@ data class ProfileScreenUiState(
val reAuthenticateUserViewShown: Boolean = false,
val isEditMode: Boolean = false,
val isEditInProgress: Boolean = false,
+ val isSubscribePlansViewVisible: Boolean = false,
val currentUserAuthProviderList: List = emptyList(),
val message: String? = null,
)
\ No newline at end of file
diff --git a/shared/src/commonMain/kotlin/presentation/screens/account/profile/ProfileUiStateHolder.kt b/shared/src/commonMain/kotlin/presentation/screens/account/profile/ProfileUiStateHolder.kt
index b585630..69661ae 100644
--- a/shared/src/commonMain/kotlin/presentation/screens/account/profile/ProfileUiStateHolder.kt
+++ b/shared/src/commonMain/kotlin/presentation/screens/account/profile/ProfileUiStateHolder.kt
@@ -60,6 +60,15 @@ class ProfileUiStateHolder(private val userRepository: UserRepository) : UiState
_profileScreenUiState.update { it.copy(currentUser = it.currentUser?.copy(displayName = displayName)) }
}
+ fun onClickUpgradePremium(){
+ _profileScreenUiState.update { it.copy(isSubscribePlansViewVisible = true) }
+ }
+
+ fun onDismissSubscriptionPlansView(){
+ _profileScreenUiState.update { it.copy(isSubscribePlansViewVisible = false) }
+ userRepository.refreshUserSubscription()
+ }
+
fun onConfirmDeleteAccount() = uiStateHolderScope.launch {
_profileScreenUiState.update {
it.copy(
@@ -78,6 +87,14 @@ class ProfileUiStateHolder(private val userRepository: UserRepository) : UiState
_profileScreenUiState.update { it.copy(message = null) }
}
+ fun onSubscriptionPurchaseCompleted() = uiStateHolderScope.launch {
+ userRepository.refreshUserSubscription()
+ }
+
+ fun onSubscriptionPurchaseError(errorMessage:String?){
+ _profileScreenUiState.update { it.copy(message = errorMessage) }
+ }
+
fun onUserReAuthenticatedResult(result: Result) = uiStateHolderScope.launch {
_profileScreenUiState.update { it.copy(isDeleteInProgress = true) }
result.onSuccess { user ->
diff --git a/shared/src/commonMain/kotlin/presentation/screens/main/navigation/MainScreenNavigation.kt b/shared/src/commonMain/kotlin/presentation/screens/main/navigation/MainScreenNavigation.kt
index b692d59..12e49f5 100644
--- a/shared/src/commonMain/kotlin/presentation/screens/main/navigation/MainScreenNavigation.kt
+++ b/shared/src/commonMain/kotlin/presentation/screens/main/navigation/MainScreenNavigation.kt
@@ -11,16 +11,25 @@ import androidx.compose.animation.fadeOut
import androidx.compose.animation.scaleIn
import androidx.compose.animation.scaleOut
import androidx.compose.animation.togetherWith
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
+import androidx.compose.ui.Modifier
import cafe.adriel.voyager.core.screen.Screen
import cafe.adriel.voyager.core.stack.StackEvent
import cafe.adriel.voyager.navigator.LocalNavigator
import cafe.adriel.voyager.navigator.Navigator
import cafe.adriel.voyager.navigator.currentOrThrow
+import com.mmk.kmprevenuecat.purchases.ui.Paywall
import data.repository.UserRepository
+import domain.model.User
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.map
import org.koin.compose.koinInject
+import presentation.components.MyAppCircularProgressIndicator
import presentation.screens.about.AboutScreen
import presentation.screens.account.profile.ProfileScreen
import presentation.screens.account.profile.ProfileUiStateHolder
@@ -134,31 +143,46 @@ interface MainScreenDestination {
val navigator = LocalNavigator.currentOrThrow
val userRepository = koinInject()
- val currentUser by userRepository.currentUser.collectAsState(null)
- if (currentUser == null) {
- SignInScreen(
- onNavigatePrivacyPolicy = {
- navigator.navigate(
- WebView(
- url = Strings.url_privacy_policy,
- title = Strings.privacy_policy
+ val currentUserState by userRepository.currentUser
+ .map { Pair(false, it) }
+ .collectAsState(Pair(true, null))
+ val (isLoading, currentUser) = currentUserState
+ if (isLoading) Box(modifier = Modifier.fillMaxSize()) {
+ MyAppCircularProgressIndicator()
+ }
+ when {
+ isLoading -> {
+ Box(modifier = Modifier.fillMaxSize()) {
+ MyAppCircularProgressIndicator()
+ }
+ }
+
+ isLoading.not() && currentUser != null -> {
+ val uiStateHolder = getUiStateHolder()
+ ProfileScreen(uiStateHolder = uiStateHolder)
+ }
+
+ else -> {
+ SignInScreen(
+ onNavigatePrivacyPolicy = {
+ navigator.navigate(
+ WebView(
+ url = Strings.url_privacy_policy,
+ title = Strings.privacy_policy
+ )
)
- )
- },
- onNavigateTermsConditions = {
- navigator.navigate(
- WebView(
- url = Strings.url_terms_conditions,
- title = Strings.terms_conditions
+ },
+ onNavigateTermsConditions = {
+ navigator.navigate(
+ WebView(
+ url = Strings.url_terms_conditions,
+ title = Strings.terms_conditions
+ )
)
- )
- }
- )
- } else {
- val uiStateHolder = getUiStateHolder()
- ProfileScreen(uiStateHolder = uiStateHolder)
+ }
+ )
+ }
}
-
}
override fun getTitle(): String = Strings.title_screen_account
diff --git a/shared/src/commonMain/kotlin/presentation/theme/strings/Strings.kt b/shared/src/commonMain/kotlin/presentation/theme/strings/Strings.kt
index dd32ca1..bf769ea 100644
--- a/shared/src/commonMain/kotlin/presentation/theme/strings/Strings.kt
+++ b/shared/src/commonMain/kotlin/presentation/theme/strings/Strings.kt
@@ -1,6 +1,7 @@
package presentation.theme.strings
object Strings {
+ const val subscription_lifetime: String = "Lifetime"
const val btn_update_profile: String = "UPDATE PROFILE"
const val btn_cancel: String = "Cancel"
const val btn_delete: String = "Delete"
@@ -10,7 +11,13 @@ object Strings {
const val display_name_default: String = "Traveller"
const val btn_delete_account: String = "Delete Account"
const val display_name_title: String = "Display Name"
+ const val subscription_plan_title: String = "Subscription Plan"
+ const val subscription_plan_expiration_date: String = "Expiration Date"
+ const val subscription_plan_free: String = "Free"
+ const val subscription_plan_premium: String = "Premium"
+ const val btn_get_premium_subscription_plan_free: String = "Get Premium access"
const val basic_info: String = "Basic Info"
+ const val subscription_info: String = "Subscription Info"
const val edit_profile: String = "Edit Profile"
const val btn_log_out: String = "LOG OUT"
const val btn_sign_in_with_github: String = "Continue with Github"
diff --git a/shared/src/commonMain/kotlin/root/AppInitializer.kt b/shared/src/commonMain/kotlin/root/AppInitializer.kt
index 42f66ca..bc21d34 100644
--- a/shared/src/commonMain/kotlin/root/AppInitializer.kt
+++ b/shared/src/commonMain/kotlin/root/AppInitializer.kt
@@ -3,11 +3,15 @@ package root
import com.mmk.kmpauth.google.GoogleAuthCredentials
import com.mmk.kmpauth.google.GoogleAuthProvider
import com.mmk.kmpnotifier.notification.NotifierManager
+import com.mmk.kmprevenuecat.purchases.LogLevel
+import com.mmk.kmprevenuecat.purchases.Purchases
import di.appModules
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
import org.koin.core.KoinApplication
import org.koin.core.context.startKoin
+import secrets.BuildConfig
+import util.isAndroid
import util.logging.AppLogger
object AppInitializer {
@@ -20,6 +24,9 @@ object AppInitializer {
}
if (isDebug) AppLogger.initialize()
+
+ Purchases.logLevel = LogLevel.DEBUG
+ Purchases.configure(if (isAndroid()) BuildConfig.REVENUECAT_API_KEY_ANDROID else BuildConfig.REVENUECAT_API_KEY_IOS)
}
private fun KoinApplication.onApplicationStart() {
diff --git a/shared/src/commonMain/kotlin/util/PlatformSpecific.kt b/shared/src/commonMain/kotlin/util/PlatformSpecific.kt
index acb39e4..11835c9 100644
--- a/shared/src/commonMain/kotlin/util/PlatformSpecific.kt
+++ b/shared/src/commonMain/kotlin/util/PlatformSpecific.kt
@@ -21,3 +21,5 @@ interface AppVersion {
fun name(): String
}
+expect fun isAndroid():Boolean
+
diff --git a/shared/src/iosMain/kotlin/util/PlatformSpecific.kt b/shared/src/iosMain/kotlin/util/PlatformSpecific.kt
index 73970a8..bcd6ee0 100644
--- a/shared/src/iosMain/kotlin/util/PlatformSpecific.kt
+++ b/shared/src/iosMain/kotlin/util/PlatformSpecific.kt
@@ -40,7 +40,8 @@ internal class IosAppVersion : AppVersion {
}.getOrDefault("")
-
private fun getInfoDictionary() = NSBundle.mainBundle.infoDictionary
}
+
+actual fun isAndroid() = false
\ No newline at end of file