Skip to content

Commit bd70c9f

Browse files
committed
v1.0.5
1 parent bd8caac commit bd70c9f

19 files changed

+2432
-179
lines changed

CHANGELOG.md

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,26 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1111
***
1212

1313

14+
## [1.0.5] - 2025-11-26
15+
16+
17+
### Added
18+
- 🎥 Motion detection (Beta): Camera-based motion detection to exit screensaver mode
19+
- 🍪 Cookie management: Basic cookie handling via react-native-cookies for web session persistence
20+
21+
22+
### Changed
23+
- 🚀 WebView optimization: Performance improvements specifically for Fire OS tablets
24+
- 🔒 Enhanced WebView security: Additional security measures for safe web content display
25+
26+
27+
### Fixed
28+
- 🐛 WebView stability improvements on Fire OS devices
29+
30+
31+
***
32+
33+
1434
## [1.0.4] - 2025-11-19
1535

1636

@@ -117,8 +137,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
117137
***
118138

119139

140+
[1.0.5]: https://github.com/rushb-fr/freekiosk/releases/tag/v1.0.5
120141
[1.0.4]: https://github.com/rushb-fr/freekiosk/releases/tag/v1.0.4
121142
[1.0.3]: https://github.com/rushb-fr/freekiosk/releases/tag/v1.0.3
122143
[1.0.2]: https://github.com/rushb-fr/freekiosk/releases/tag/v1.0.2
123144
[1.0.1]: https://github.com/rushb-fr/freekiosk/releases/tag/v1.0.1
124-
[Unreleased]: https://github.com/rushb-fr/freekiosk/compare/v1.0.4...HEAD
145+
[Unreleased]: https://github.com/rushb-fr/freekiosk/compare/v1.0.5...HEAD

android/app/build.gradle

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -89,8 +89,8 @@ android {
8989
applicationId "com.freekiosk"
9090
minSdkVersion rootProject.ext.minSdkVersion
9191
targetSdkVersion rootProject.ext.targetSdkVersion
92-
versionCode 5
93-
versionName "1.0.4"
92+
versionCode 6
93+
versionName "1.0.5"
9494

9595
manifestPlaceholders = [usesCleartextTraffic: "true"]
9696
}

android/app/src/main/java/com/freekiosk/CertificateModule.kt

Lines changed: 85 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,17 @@ import com.facebook.react.bridge.ReactApplicationContext
55
import com.facebook.react.bridge.ReactContextBaseJavaModule
66
import com.facebook.react.bridge.ReactMethod
77
import com.facebook.react.bridge.Promise
8+
import com.facebook.react.bridge.WritableMap
9+
import com.facebook.react.bridge.WritableArray
10+
import com.facebook.react.bridge.Arguments
811
import android.util.Log
12+
import android.content.SharedPreferences
13+
import java.util.Date
14+
import java.text.SimpleDateFormat
15+
import java.util.Locale
916

1017
class CertificateModule(reactContext: ReactApplicationContext) : ReactContextBaseJavaModule(reactContext) {
11-
18+
1219
override fun getName(): String {
1320
return "CertificateModule"
1421
}
@@ -17,7 +24,7 @@ class CertificateModule(reactContext: ReactApplicationContext) : ReactContextBas
1724
fun clearAcceptedCertificates(promise: Promise) {
1825
try {
1926
val prefs = reactApplicationContext.getSharedPreferences(
20-
"freekiosk_ssl_certs",
27+
"freekiosk_ssl_certs",
2128
android.content.Context.MODE_PRIVATE
2229
)
2330
prefs.edit().clear().apply()
@@ -28,4 +35,80 @@ class CertificateModule(reactContext: ReactApplicationContext) : ReactContextBas
2835
promise.reject("ERROR", e.message)
2936
}
3037
}
38+
39+
@ReactMethod
40+
fun getAcceptedCertificates(promise: Promise) {
41+
try {
42+
val prefs = reactApplicationContext.getSharedPreferences(
43+
"freekiosk_ssl_certs",
44+
android.content.Context.MODE_PRIVATE
45+
)
46+
47+
val certificates = Arguments.createArray()
48+
val allEntries = prefs.all
49+
val processedFingerprints = mutableSetOf<String>()
50+
51+
for ((key, value) in allEntries) {
52+
// Only process "cert_" keys (not "cert_expiry_" or "cert_url_")
53+
if (key.startsWith("cert_") && !key.contains("_expiry_") && !key.contains("_url_")) {
54+
val fingerprint = key.substring(5) // Remove "cert_" prefix
55+
56+
// Avoid duplicates
57+
if (processedFingerprints.contains(fingerprint)) {
58+
continue
59+
}
60+
processedFingerprints.add(fingerprint)
61+
62+
val isAccepted = prefs.getBoolean(key, false)
63+
if (!isAccepted) continue
64+
65+
val url = prefs.getString("cert_url_$fingerprint", "Unknown") ?: "Unknown"
66+
val expiryTime = prefs.getLong("cert_expiry_$fingerprint", 0)
67+
68+
val certInfo = Arguments.createMap()
69+
certInfo.putString("fingerprint", fingerprint)
70+
certInfo.putString("url", url)
71+
certInfo.putDouble("expiryTime", expiryTime.toDouble())
72+
73+
// Add human-readable expiry date
74+
val dateFormat = SimpleDateFormat("yyyy-MM-dd HH:mm", Locale.getDefault())
75+
certInfo.putString("expiryDate", dateFormat.format(Date(expiryTime)))
76+
77+
// Check if expired
78+
val isExpired = System.currentTimeMillis() > expiryTime
79+
certInfo.putBoolean("isExpired", isExpired)
80+
81+
certificates.pushMap(certInfo)
82+
}
83+
}
84+
85+
Log.i("CertificateModule", "Found ${certificates.size()} accepted certificates")
86+
promise.resolve(certificates)
87+
} catch (e: Exception) {
88+
Log.e("CertificateModule", "Error getting certificates", e)
89+
promise.reject("ERROR", e.message)
90+
}
91+
}
92+
93+
@ReactMethod
94+
fun removeCertificate(fingerprint: String, promise: Promise) {
95+
try {
96+
val prefs = reactApplicationContext.getSharedPreferences(
97+
"freekiosk_ssl_certs",
98+
android.content.Context.MODE_PRIVATE
99+
)
100+
101+
prefs.edit()
102+
.remove("cert_$fingerprint")
103+
.remove("cert_expiry_$fingerprint")
104+
.remove("cert_url_$fingerprint")
105+
.apply()
106+
107+
Log.i("CertificateModule", "Certificate removed: $fingerprint")
108+
promise.resolve(true)
109+
} catch (e: Exception) {
110+
Log.e("CertificateModule", "Error removing certificate", e)
111+
promise.reject("ERROR", e.message)
112+
}
113+
}
31114
}

android/app/src/main/java/com/freekiosk/MainApplication.kt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,8 @@ class MainApplication : Application(), ReactApplication {
1616
PackageList(this).packages.apply {
1717
// Packages that cannot be autolinked yet can be added manually here
1818
add(KioskPackage())
19-
add(CertificatePackage()) // ← Add this
19+
add(CertificatePackage())
20+
add(MotionDetectionPackage())
2021
},
2122
)
2223
}
Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
package com.freekiosk
2+
3+
import android.graphics.Bitmap
4+
import android.graphics.BitmapFactory
5+
import com.facebook.react.bridge.*
6+
import java.io.File
7+
import kotlin.math.abs
8+
9+
class MotionDetectionModule(reactContext: ReactApplicationContext) : ReactContextBaseJavaModule(reactContext) {
10+
11+
private var previousBitmap: Bitmap? = null
12+
13+
override fun getName(): String {
14+
return "MotionDetectionModule"
15+
}
16+
17+
@ReactMethod
18+
fun compareImages(
19+
imagePath: String,
20+
threshold: Double,
21+
promise: Promise
22+
) {
23+
try {
24+
val file = File(imagePath)
25+
if (!file.exists()) {
26+
promise.reject("FILE_NOT_FOUND", "Image file not found: $imagePath")
27+
return
28+
}
29+
30+
// Load and resize image for performance
31+
val options = BitmapFactory.Options().apply {
32+
inSampleSize = 4 // Reduce to 1/4 size for faster processing
33+
}
34+
val currentBitmap = BitmapFactory.decodeFile(imagePath, options)
35+
36+
if (currentBitmap == null) {
37+
promise.reject("DECODE_ERROR", "Failed to decode image")
38+
return
39+
}
40+
41+
// Delete file after loading
42+
file.delete()
43+
44+
val hasMotion = if (previousBitmap != null) {
45+
detectMotion(previousBitmap!!, currentBitmap, threshold)
46+
} else {
47+
false // First image, no comparison
48+
}
49+
50+
// Store current for next comparison
51+
previousBitmap?.recycle()
52+
previousBitmap = currentBitmap
53+
54+
promise.resolve(hasMotion)
55+
56+
} catch (e: Exception) {
57+
promise.reject("ERROR", "Motion detection error: ${e.message}")
58+
}
59+
}
60+
61+
@ReactMethod
62+
fun reset(promise: Promise) {
63+
try {
64+
previousBitmap?.recycle()
65+
previousBitmap = null
66+
promise.resolve(true)
67+
} catch (e: Exception) {
68+
promise.reject("ERROR", "Reset error: ${e.message}")
69+
}
70+
}
71+
72+
private fun detectMotion(previous: Bitmap, current: Bitmap, threshold: Double): Boolean {
73+
if (previous.width != current.width || previous.height != current.height) {
74+
return false
75+
}
76+
77+
val width = previous.width
78+
val height = previous.height
79+
val sampleSize = 50 // Sample every N pixels
80+
81+
var differences = 0
82+
var samples = 0
83+
84+
for (y in 0 until height step sampleSize) {
85+
for (x in 0 until width step sampleSize) {
86+
val pixel1 = previous.getPixel(x, y)
87+
val pixel2 = current.getPixel(x, y)
88+
89+
val r1 = (pixel1 shr 16) and 0xFF
90+
val g1 = (pixel1 shr 8) and 0xFF
91+
val b1 = pixel1 and 0xFF
92+
93+
val r2 = (pixel2 shr 16) and 0xFF
94+
val g2 = (pixel2 shr 8) and 0xFF
95+
val b2 = pixel2 and 0xFF
96+
97+
val diff = abs(r1 - r2) + abs(g1 - g2) + abs(b1 - b2)
98+
99+
// If pixel difference > 30 (out of 765 max), count as changed
100+
if (diff > 30) {
101+
differences++
102+
}
103+
samples++
104+
}
105+
}
106+
107+
val changeRatio = if (samples > 0) differences.toDouble() / samples else 0.0
108+
return changeRatio > threshold
109+
}
110+
111+
override fun onCatalystInstanceDestroy() {
112+
super.onCatalystInstanceDestroy()
113+
previousBitmap?.recycle()
114+
previousBitmap = null
115+
}
116+
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
package com.freekiosk
2+
3+
import com.facebook.react.ReactPackage
4+
import com.facebook.react.bridge.NativeModule
5+
import com.facebook.react.bridge.ReactApplicationContext
6+
import com.facebook.react.uimanager.ViewManager
7+
8+
class MotionDetectionPackage : ReactPackage {
9+
override fun createNativeModules(reactContext: ReactApplicationContext): List<NativeModule> {
10+
return listOf(MotionDetectionModule(reactContext))
11+
}
12+
13+
override fun createViewManagers(reactContext: ReactApplicationContext): List<ViewManager<*, *>> {
14+
return emptyList()
15+
}
16+
}

0 commit comments

Comments
 (0)