@cap-kit/rank 8.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (36) hide show
  1. package/CapKitRank.podspec +17 -0
  2. package/LICENSE +21 -0
  3. package/Package.swift +28 -0
  4. package/README.md +574 -0
  5. package/android/build.gradle +110 -0
  6. package/android/src/main/AndroidManifest.xml +16 -0
  7. package/android/src/main/java/io/capkit/rank/RankConfig.kt +72 -0
  8. package/android/src/main/java/io/capkit/rank/RankError.kt +40 -0
  9. package/android/src/main/java/io/capkit/rank/RankImpl.kt +240 -0
  10. package/android/src/main/java/io/capkit/rank/RankPlugin.kt +312 -0
  11. package/android/src/main/java/io/capkit/rank/utils/RankLogger.kt +85 -0
  12. package/android/src/main/java/io/capkit/rank/utils/RankUtils.kt +14 -0
  13. package/android/src/main/res/.gitkeep +0 -0
  14. package/dist/docs.json +441 -0
  15. package/dist/esm/definitions.d.ts +330 -0
  16. package/dist/esm/definitions.js +21 -0
  17. package/dist/esm/definitions.js.map +1 -0
  18. package/dist/esm/index.d.ts +16 -0
  19. package/dist/esm/index.js +19 -0
  20. package/dist/esm/index.js.map +1 -0
  21. package/dist/esm/web.d.ts +81 -0
  22. package/dist/esm/web.js +157 -0
  23. package/dist/esm/web.js.map +1 -0
  24. package/dist/plugin.cjs.js +204 -0
  25. package/dist/plugin.cjs.js.map +1 -0
  26. package/dist/plugin.js +207 -0
  27. package/dist/plugin.js.map +1 -0
  28. package/ios/Sources/RankPlugin/RankConfig.swift +89 -0
  29. package/ios/Sources/RankPlugin/RankError.swift +49 -0
  30. package/ios/Sources/RankPlugin/RankImpl.swift +186 -0
  31. package/ios/Sources/RankPlugin/RankPlugin.swift +258 -0
  32. package/ios/Sources/RankPlugin/Utils/RankLogger.swift +69 -0
  33. package/ios/Sources/RankPlugin/Utils/RankUtils.swift +45 -0
  34. package/ios/Sources/RankPlugin/Version.swift +16 -0
  35. package/ios/Tests/RankPluginTests/RankPluginTests.swift +10 -0
  36. package/package.json +102 -0
@@ -0,0 +1,110 @@
1
+ buildscript {
2
+ ext.kotlin_version = project.hasProperty("kotlin_version") ? rootProject.ext.kotlin_version : '2.2.20'
3
+ repositories {
4
+ google()
5
+ mavenCentral()
6
+ }
7
+ dependencies {
8
+ classpath 'com.android.tools.build:gradle:8.13.0'
9
+ classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
10
+ }
11
+ }
12
+
13
+ plugins {
14
+ id "org.jlleitschuh.gradle.ktlint" version "12.1.1" apply false
15
+ }
16
+
17
+ ext {
18
+ junitVersion = project.hasProperty('junitVersion') ? rootProject.ext.junitVersion : '4.13.2'
19
+ androidxAppCompatVersion = project.hasProperty('androidxAppCompatVersion') ? rootProject.ext.androidxAppCompatVersion : '1.7.1'
20
+ androidxJunitVersion = project.hasProperty('androidxJunitVersion') ? rootProject.ext.androidxJunitVersion : '1.3.0'
21
+ androidxEspressoCoreVersion = project.hasProperty('androidxEspressoCoreVersion') ? rootProject.ext.androidxEspressoCoreVersion : '3.7.0'
22
+ androidxCoreKTXVersion = project.hasProperty('androidxCoreKTXVersion') ? rootProject.ext.androidxCoreKTXVersion : '1.17.0'
23
+ }
24
+
25
+ apply plugin: 'com.android.library'
26
+ apply plugin: 'kotlin-android'
27
+ apply plugin: 'kotlin-parcelize'
28
+ apply plugin: 'org.jlleitschuh.gradle.ktlint'
29
+
30
+ import groovy.json.JsonSlurper
31
+
32
+ def getPluginVersion() {
33
+ try {
34
+ def packageJsonFile = file('../package.json')
35
+ if (packageJsonFile.exists()) {
36
+ def packageJson = new JsonSlurper().parseText(packageJsonFile.text)
37
+ return packageJson.version
38
+ }
39
+ } catch (Exception e) {
40
+ // Ignore errors and fallback
41
+ logger.info("Exception", e)
42
+ }
43
+ return "8.0.0"
44
+ }
45
+
46
+ def pluginVersion = getPluginVersion()
47
+
48
+ android {
49
+ namespace = "io.capkit.rank"
50
+ compileSdk = project.hasProperty('compileSdkVersion') ? rootProject.ext.compileSdkVersion as Integer : 36
51
+
52
+ // AGP 8.0+ disables BuildConfig by default for libraries.
53
+ // We need to enable it to inject the plugin version.
54
+ buildFeatures {
55
+ buildConfig = true
56
+ }
57
+
58
+ defaultConfig {
59
+ minSdkVersion project.hasProperty('minSdkVersion') ? rootProject.ext.minSdkVersion as Integer : 24
60
+ targetSdkVersion project.hasProperty('targetSdkVersion') ? rootProject.ext.targetSdkVersion as Integer : 36
61
+ versionCode 1
62
+
63
+ // Dynamic versioning (feature enabled)
64
+ versionName = pluginVersion
65
+
66
+ // Injects the version into the BuildConfig class ONLY if feature is enabled
67
+ buildConfigField "String", "PLUGIN_VERSION", "\"${pluginVersion}\""
68
+
69
+ testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
70
+ }
71
+ buildTypes {
72
+ release {
73
+ minifyEnabled = false
74
+ proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
75
+ }
76
+ }
77
+ lint {
78
+ abortOnError = false
79
+ }
80
+ compileOptions {
81
+ sourceCompatibility = JavaVersion.VERSION_21
82
+ targetCompatibility = JavaVersion.VERSION_21
83
+ }
84
+ kotlinOptions {
85
+ jvmTarget = JavaVersion.VERSION_21
86
+ }
87
+ }
88
+
89
+ repositories {
90
+ google()
91
+ mavenCentral()
92
+ }
93
+
94
+ dependencies {
95
+ implementation fileTree(dir: 'libs', include: ['*.jar'])
96
+ implementation project(':capacitor-android')
97
+
98
+ // Google Play Review Library
99
+ implementation "com.google.android.play:review:2.0.2"
100
+ // Kotlin extension for ReviewManager
101
+ implementation "com.google.android.play:review-ktx:2.0.2"
102
+
103
+ implementation "androidx.appcompat:appcompat:$androidxAppCompatVersion"
104
+ implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
105
+ implementation "androidx.core:core-ktx:$androidxCoreKTXVersion"
106
+
107
+ testImplementation "junit:junit:$junitVersion"
108
+ androidTestImplementation "androidx.test.ext:junit:$androidxJunitVersion"
109
+ androidTestImplementation "androidx.test.espresso:espresso-core:$androidxEspressoCoreVersion"
110
+ }
@@ -0,0 +1,16 @@
1
+ <manifest xmlns:android="http://schemas.android.com/apk/res/android">
2
+ <uses-permission android:name="android.permission.INTERNET" />
3
+ <queries>
4
+ <intent>
5
+ <action android:name="android.intent.action.VIEW" />
6
+ <data android:scheme="market" />
7
+ </intent>
8
+ <intent>
9
+ <action android:name="android.intent.action.VIEW" />
10
+ <data android:scheme="https" android:host="play.google.com" />
11
+ </intent>
12
+ </queries>
13
+
14
+ <application>
15
+ </application>
16
+ </manifest>
@@ -0,0 +1,72 @@
1
+ package io.capkit.rank
2
+
3
+ import android.content.Context
4
+ import com.getcapacitor.Plugin
5
+
6
+ /**
7
+ * Plugin configuration holder for the Rank plugin.
8
+ *
9
+ * This class is responsible for reading and parsing static configuration values
10
+ * defined under the `plugins.Rank` key in `capacitor.config.ts`.
11
+ *
12
+ * Architectural rules:
13
+ * - Read once during plugin initialization in the load() phase.
14
+ * - Configuration values are read-only at runtime.
15
+ * - Consumed only by native code.
16
+ */
17
+ class RankConfig(plugin: Plugin) {
18
+ // ---------------------------------------------------------------------------
19
+ // Properties
20
+ // ---------------------------------------------------------------------------
21
+
22
+ /**
23
+ * Android application context.
24
+ * Accessible for native implementation components that require system services.
25
+ */
26
+ val context: Context = plugin.context
27
+
28
+ /**
29
+ * Enables verbose native logging via RankLogger.
30
+ *
31
+ * When true, additional debug information and lifecycle events are printed to Logcat.
32
+ * Default: false
33
+ */
34
+ val verboseLogging: Boolean
35
+
36
+ /**
37
+ * The Android Package Name used for Play Store redirection.
38
+ *
39
+ * If provided, this value overrides the host application's package name
40
+ * during store navigation.
41
+ * Default: null (falls back to host app package)
42
+ */
43
+ val androidPackageName: String?
44
+
45
+ /**
46
+ * Global policy for review request resolution.
47
+ *
48
+ * If true, the `requestReview` method resolves the promise immediately
49
+ * without waiting for the Google Play review flow to complete.
50
+ * Default: false
51
+ */
52
+ val fireAndForget: Boolean
53
+
54
+ // ---------------------------------------------------------------------------
55
+ // Initialization
56
+ // ---------------------------------------------------------------------------
57
+
58
+ init {
59
+ // Access the plugin-specific configuration object
60
+ val config = plugin.getConfig()
61
+
62
+ // Extract verboseLogging flag
63
+ verboseLogging = config.getBoolean("verboseLogging", false)
64
+
65
+ // Extract and validate the Android Package Name
66
+ val apm = config.getString("androidPackageName")
67
+ androidPackageName = if (!apm.isNullOrBlank()) apm else null
68
+
69
+ // Extract the fireAndForget resolution policy
70
+ fireAndForget = config.getBoolean("fireAndForget", false)
71
+ }
72
+ }
@@ -0,0 +1,40 @@
1
+ package io.capkit.rank
2
+
3
+ /**
4
+ * Native error model for the Rank plugin (Android).
5
+ *
6
+ * Architectural rules:
7
+ * - Must NOT reference Capacitor APIs
8
+ * - Must NOT reference JavaScript
9
+ * - Must be throwable from the Impl layer
10
+ * - Mapping to JS-facing error codes happens ONLY in the Plugin layer
11
+ */
12
+ sealed class RankError(
13
+ message: String,
14
+ ) : Throwable(message) {
15
+ /**
16
+ * Feature or capability is not available
17
+ * due to device or configuration limitations.
18
+ */
19
+ class Unavailable(message: String) :
20
+ RankError(message)
21
+
22
+ /**
23
+ * Required permission was denied or not granted.
24
+ */
25
+ class PermissionDenied(message: String) :
26
+ RankError(message)
27
+
28
+ /**
29
+ * Plugin failed to initialize or perform
30
+ * a required operation.
31
+ */
32
+ class InitFailed(message: String) :
33
+ RankError(message)
34
+
35
+ /**
36
+ * Invalid or unsupported input was provided.
37
+ */
38
+ class UnknownType(message: String) :
39
+ RankError(message)
40
+ }
@@ -0,0 +1,240 @@
1
+ package io.capkit.rank
2
+
3
+ import android.app.Activity
4
+ import android.content.Context
5
+ import android.content.Intent
6
+ import android.net.Uri
7
+ import com.google.android.play.core.review.ReviewManagerFactory
8
+ import io.capkit.rank.utils.RankLogger
9
+
10
+ /**
11
+ * Platform-specific native implementation for the Rank plugin.
12
+ *
13
+ * This class contains pure Android logic and MUST NOT depend directly on
14
+ * Capacitor bridge APIs or PluginCall objects.
15
+ *
16
+ * Responsibilities:
17
+ * - Orchestrating the Google Play Review SDK.
18
+ * - Managing Intent-based Store navigation.
19
+ * - Translating configuration into native behavior.
20
+ */
21
+ class RankImpl(
22
+ private val context: Context,
23
+ ) {
24
+ // ---------------------------------------------------------------------------
25
+ // Properties
26
+ // ---------------------------------------------------------------------------
27
+
28
+ /**
29
+ * Cached plugin configuration container.
30
+ * Provided once during initialization via [updateConfig].
31
+ */
32
+ private lateinit var config: RankConfig
33
+
34
+ // ---------------------------------------------------------------------------
35
+ // Configuration
36
+ // ---------------------------------------------------------------------------
37
+
38
+ /**
39
+ * Applies the plugin configuration to the implementation layer.
40
+ *
41
+ * This method MUST be called exactly once during the plugin [RankPlugin.load]
42
+ * phase. It initializes internal state and configures logging verbosity.
43
+ *
44
+ * @param newConfig The immutable configuration instance.
45
+ */
46
+ fun updateConfig(newConfig: RankConfig) {
47
+ this.config = newConfig
48
+ RankLogger.verbose = newConfig.verboseLogging
49
+ RankLogger.debug(
50
+ "Configuration applied. Verbose logging:",
51
+ newConfig.verboseLogging.toString(),
52
+ )
53
+ }
54
+
55
+ // ---------------------------------------------------------------------------
56
+ // Availability check
57
+ // ---------------------------------------------------------------------------
58
+
59
+ /**
60
+ * Checks whether the In-App Review feature is available on this device.
61
+ *
62
+ * This is a semantic availability check intended for UI decisions.
63
+ * Internally, it delegates to the diagnostic environment check.
64
+ */
65
+ fun isAvailable(onResult: (Boolean) -> Unit) {
66
+ checkReviewEnvironment { canRequest ->
67
+ onResult(canRequest)
68
+ }
69
+ }
70
+
71
+ /**
72
+ * Performs a diagnostic check for the Google Play Review environment.
73
+ *
74
+ * This verifies whether Google Play Services can provide
75
+ * a ReviewInfo instance for the current application and device.
76
+ *
77
+ * No UI is triggered by this operation.
78
+ */
79
+ fun checkReviewEnvironment(onResult: (Boolean) -> Unit) {
80
+ val manager = ReviewManagerFactory.create(context)
81
+ val request = manager.requestReviewFlow()
82
+
83
+ request.addOnCompleteListener { task ->
84
+ onResult(task.isSuccessful)
85
+ }
86
+ }
87
+
88
+ // ---------------------------------------------------------------------------
89
+ // Pre-warm Logic
90
+ // ---------------------------------------------------------------------------
91
+
92
+ /**
93
+ * Cached ReviewInfo object to speed up the review flow display.
94
+ */
95
+ private var cachedReviewInfo: com.google.android.play.core.review.ReviewInfo? = null
96
+
97
+ /**
98
+ * Pre-fetches the ReviewInfo from Google Play Services.
99
+ * This should be called early (e.g., during plugin load).
100
+ */
101
+ fun preloadReviewInfo() {
102
+ val manager = ReviewManagerFactory.create(context)
103
+ val request = manager.requestReviewFlow()
104
+ request.addOnCompleteListener { task ->
105
+ if (task.isSuccessful) {
106
+ cachedReviewInfo = task.result
107
+ RankLogger.debug("ReviewInfo pre-loaded successfully.")
108
+ } else {
109
+ RankLogger.debug("ReviewInfo pre-load failed.")
110
+ }
111
+ }
112
+ }
113
+
114
+ // ---------------------------------------------------------------------------
115
+ // In-App Review Logic
116
+ // ---------------------------------------------------------------------------
117
+
118
+ /**
119
+ * Triggers the Google Play In-App Review flow.
120
+ * Uses the cached ReviewInfo if available to ensure an immediate prompt.
121
+ *
122
+ * @param activity The current Android Activity required to display the UI.
123
+ * @param onComplete Callback invoked when the flow completes or fails.
124
+ */
125
+ fun requestReview(
126
+ activity: Activity,
127
+ onComplete: (Exception?) -> Unit,
128
+ ) {
129
+ val manager = ReviewManagerFactory.create(context)
130
+
131
+ // Use cache if available, otherwise fetch new info on the fly
132
+ if (cachedReviewInfo != null) {
133
+ val flow = manager.launchReviewFlow(activity, cachedReviewInfo!!)
134
+ flow.addOnCompleteListener { _ ->
135
+ cachedReviewInfo = null // Clear cache after use to prevent reuse of expired info
136
+ onComplete(null)
137
+ }
138
+ } else {
139
+ val request = manager.requestReviewFlow()
140
+ request.addOnCompleteListener { task ->
141
+ if (task.isSuccessful) {
142
+ val reviewInfo = task.result
143
+ val flow = manager.launchReviewFlow(activity, reviewInfo)
144
+ flow.addOnCompleteListener { _ -> onComplete(null) }
145
+ } else {
146
+ onComplete(task.exception)
147
+ }
148
+ }
149
+ }
150
+ }
151
+
152
+ // ---------------------------------------------------------------------------
153
+ // Store Navigation Logic
154
+ // ---------------------------------------------------------------------------
155
+
156
+ /**
157
+ * Opens the Google Play Store page for the specified package.
158
+ *
159
+ * It attempts to use the "market://" URI scheme first to open the native
160
+ * Play Store app. If the app is missing, it falls back to a browser-based
161
+ * "https://" URL.
162
+ *
163
+ * @param packageName The application package name. Defaults to the host app if null.
164
+ */
165
+ fun openStore(packageName: String?) {
166
+ val targetPackage = packageName ?: context.packageName
167
+
168
+ // Construct the native market Intent
169
+ val intent =
170
+ Intent(Intent.ACTION_VIEW, Uri.parse("market://details?id=$targetPackage")).apply {
171
+ addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
172
+ }
173
+
174
+ try {
175
+ context.startActivity(intent)
176
+ } catch (e: Exception) {
177
+ // Fallback to web browser if Play Store application is unavailable
178
+ val webIntent =
179
+ Intent(
180
+ Intent.ACTION_VIEW,
181
+ Uri.parse("https://play.google.com/store/apps/details?id=$targetPackage"),
182
+ ).apply {
183
+ addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
184
+ }
185
+ context.startActivity(webIntent)
186
+ }
187
+ }
188
+
189
+ /**
190
+ * Opens the Google Play Store listing for a specific application ID.
191
+ *
192
+ * @param appId The Android package name or application ID to display.
193
+ */
194
+ fun openStoreListing(appId: String) {
195
+ val intent =
196
+ Intent(Intent.ACTION_VIEW, Uri.parse("market://details?id=$appId")).apply {
197
+ addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
198
+ }
199
+ context.startActivity(intent)
200
+ }
201
+
202
+ /**
203
+ * Launches the Google Play Store search results for the provided terms.
204
+ *
205
+ * @param terms The search query string.
206
+ */
207
+ fun search(terms: String) {
208
+ val intent =
209
+ Intent(Intent.ACTION_VIEW, Uri.parse("market://search?q=$terms")).apply {
210
+ addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
211
+ }
212
+ context.startActivity(intent)
213
+ }
214
+
215
+ /**
216
+ * Navigates to a specific developer's page on the Google Play Store.
217
+ *
218
+ * @param devId The unique developer identifier (numeric or string ID).
219
+ */
220
+ fun openDevPage(devId: String) {
221
+ val intent =
222
+ Intent(Intent.ACTION_VIEW, Uri.parse("https://play.google.com/store/apps/dev?id=$devId")).apply {
223
+ addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
224
+ }
225
+ context.startActivity(intent)
226
+ }
227
+
228
+ /**
229
+ * Opens a curated collection or category on the Google Play Store.
230
+ *
231
+ * @param name The collection identifier (e.g., "featured", "editors_choice").
232
+ */
233
+ fun openCollection(name: String) {
234
+ val intent =
235
+ Intent(Intent.ACTION_VIEW, Uri.parse("https://play.google.com/store/apps/collection/$name")).apply {
236
+ addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
237
+ }
238
+ context.startActivity(intent)
239
+ }
240
+ }