catalyst-core-internal 0.0.1-beta.50 → 0.0.1-beta.52
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.
- package/babel.config.js +30 -0
- package/changelog.md +7 -1
- package/dist/native/androidProject/app/build.gradle.kts +180 -0
- package/dist/native/androidProject/app/proguard-rules.pro +21 -0
- package/dist/native/androidProject/app/src/androidTest/java/com/example/myapplication/ExampleInstrumentedTest.kt +24 -0
- package/dist/native/androidProject/app/src/main/AndroidManifest.xml +28 -0
- package/dist/native/androidProject/app/src/main/java/com/example/myapplication/MainActivity.kt +278 -0
- package/dist/native/androidProject/app/src/main/java/com/example/myapplication/WebCacheManager.kt +331 -0
- package/dist/native/androidProject/app/src/main/res/drawable/ic_launcher_background.xml +170 -0
- package/dist/native/androidProject/app/src/main/res/drawable/ic_launcher_foreground.xml +30 -0
- package/dist/native/androidProject/app/src/main/res/layout/activity_main.xml +22 -0
- package/dist/native/androidProject/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml +6 -0
- package/dist/native/androidProject/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml +6 -0
- package/dist/native/androidProject/app/src/main/res/mipmap-hdpi/ic_launcher.webp +0 -0
- package/dist/native/androidProject/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp +0 -0
- package/dist/native/androidProject/app/src/main/res/mipmap-mdpi/ic_launcher.webp +0 -0
- package/dist/native/androidProject/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp +0 -0
- package/dist/native/androidProject/app/src/main/res/mipmap-xhdpi/ic_launcher.webp +0 -0
- package/dist/native/androidProject/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp +0 -0
- package/dist/native/androidProject/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp +0 -0
- package/dist/native/androidProject/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp +0 -0
- package/dist/native/androidProject/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp +0 -0
- package/dist/native/androidProject/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp +0 -0
- package/dist/native/androidProject/app/src/main/res/values/colors.xml +10 -0
- package/dist/native/androidProject/app/src/main/res/values/strings.xml +3 -0
- package/dist/native/androidProject/app/src/main/res/values/themes.xml +15 -0
- package/dist/native/androidProject/app/src/main/res/values-night/themes.xml +15 -0
- package/dist/native/androidProject/app/src/main/res/xml/backup_rules.xml +13 -0
- package/dist/native/androidProject/app/src/main/res/xml/data_extraction_rules.xml +19 -0
- package/dist/native/androidProject/app/src/test/java/com/example/myapplication/ExampleUnitTest.kt +17 -0
- package/dist/native/androidProject/build.gradle.kts +5 -0
- package/dist/native/androidProject/gradle/libs.versions.toml +26 -0
- package/dist/native/androidProject/gradle/wrapper/gradle-wrapper.jar +0 -0
- package/dist/native/androidProject/gradle/wrapper/gradle-wrapper.properties +6 -0
- package/dist/native/androidProject/gradle.properties +23 -0
- package/dist/native/androidProject/gradlew +185 -0
- package/dist/native/androidProject/gradlew.bat +89 -0
- package/dist/native/androidProject/settings.gradle.kts +35 -0
- package/dist/native/androidSetup.js +2 -0
- package/dist/native/build.swift +31 -0
- package/dist/native/buildAppAndroid.js +8 -0
- package/dist/native/buildAppIos.js +41 -0
- package/dist/native/iosnativeWebView/iosnativeWebView/Assets.xcassets/AccentColor.colorset/Contents.json +11 -0
- package/dist/native/iosnativeWebView/iosnativeWebView/Assets.xcassets/AppIcon.appiconset/Contents.json +13 -0
- package/dist/native/iosnativeWebView/iosnativeWebView/Assets.xcassets/Contents.json +6 -0
- package/dist/native/iosnativeWebView/iosnativeWebView/CacheManager.swift +249 -0
- package/dist/native/iosnativeWebView/iosnativeWebView/ContentView.swift +41 -0
- package/dist/native/iosnativeWebView/iosnativeWebView/Info.plist +13 -0
- package/dist/native/iosnativeWebView/iosnativeWebView/Preview Content/Preview Assets.xcassets/Contents.json +6 -0
- package/dist/native/iosnativeWebView/iosnativeWebView/ResourceURLProtocol.swift +134 -0
- package/dist/native/iosnativeWebView/iosnativeWebView/WebView.swift +79 -0
- package/dist/native/iosnativeWebView/iosnativeWebView/WebViewModel.swift +47 -0
- package/dist/native/iosnativeWebView/iosnativeWebView/WebViewNavigationDelegate.swift +145 -0
- package/dist/native/iosnativeWebView/iosnativeWebView/iosnativeWebViewApp.swift +17 -0
- package/dist/native/iosnativeWebView/iosnativeWebView.xcodeproj/project.pbxproj +601 -0
- package/dist/native/iosnativeWebView/iosnativeWebView.xcodeproj/project.xcworkspace/contents.xcworkspacedata +7 -0
- package/dist/native/iosnativeWebView/iosnativeWebView.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist +8 -0
- package/dist/native/iosnativeWebView/iosnativeWebView.xcodeproj/xcshareddata/xcschemes/iosnativeWebView.xcscheme +112 -0
- package/dist/native/setupEmulatorIos.js +19 -0
- package/dist/native/terminalProgress.js +11 -0
- package/dist/native/utils.js +13 -0
- package/dist/scripts/build.js +2 -9
- package/dist/scripts/devBuild.js +2 -9
- package/dist/scripts/devServe.js +2 -4
- package/dist/scripts/loadScriptsBeforeServerStarts.js +2 -2
- package/dist/scripts/registerAliases.js +1 -1
- package/dist/scripts/scriptUtils.js +2 -2
- package/dist/scripts/serve.js +2 -4
- package/dist/scripts/start.js +3 -3
- package/dist/server/expressServer.js +1 -1
- package/dist/server/renderer/document/Body.js +3 -3
- package/dist/server/renderer/document/Head.js +2 -2
- package/dist/server/renderer/extract.js +1 -1
- package/dist/server/renderer/handler.js +10 -6
- package/dist/server/renderer/index.js +1 -1
- package/dist/server/startServer.js +4 -3
- package/dist/server/utils/userAgentUtil.js +1 -1
- package/dist/server/utils/validator.js +1 -1
- package/dist/webpack/babel.config.client.js +1 -1
- package/dist/webpack/babel.config.ssr.js +1 -1
- package/dist/webpack/base.babel.js +1 -1
- package/dist/webpack/development.client.babel.js +1 -1
- package/dist/webpack/production.client.babel.js +1 -1
- package/dist/webpack/production.ssr.babel.js +1 -1
- package/package.json +5 -1
- package/run.sh +2 -2
- package/babel.config.json +0 -28
package/dist/native/androidProject/app/src/main/java/com/example/myapplication/WebCacheManager.kt
ADDED
|
@@ -0,0 +1,331 @@
|
|
|
1
|
+
package com.example.myapplication
|
|
2
|
+
|
|
3
|
+
import android.content.Context
|
|
4
|
+
import android.util.Log
|
|
5
|
+
import android.util.LruCache
|
|
6
|
+
import android.webkit.WebResourceResponse
|
|
7
|
+
import kotlinx.coroutines.*
|
|
8
|
+
import java.io.*
|
|
9
|
+
import java.net.HttpURLConnection
|
|
10
|
+
import java.net.URL
|
|
11
|
+
import java.security.MessageDigest
|
|
12
|
+
import java.util.concurrent.TimeUnit
|
|
13
|
+
|
|
14
|
+
class WebCacheManager(private val context: Context) {
|
|
15
|
+
private val TAG = "WebCacheManager"
|
|
16
|
+
private val cacheDir = File(context.cacheDir, "webview_cache")
|
|
17
|
+
private val scope = CoroutineScope(Dispatchers.IO + SupervisorJob())
|
|
18
|
+
|
|
19
|
+
// Cache timing configurations
|
|
20
|
+
private val maxAge = TimeUnit.HOURS.toMillis(24) // Time until content becomes stale
|
|
21
|
+
private val staleWhileRevalidate = TimeUnit.HOURS.toMillis(1) // Additional time content can be served while revalidating
|
|
22
|
+
|
|
23
|
+
// Track ongoing revalidations to prevent duplicate requests
|
|
24
|
+
private val ongoingRevalidations = mutableSetOf<String>()
|
|
25
|
+
|
|
26
|
+
// Memory cache
|
|
27
|
+
private val memoryCache: LruCache<String, CacheEntry> = LruCache<String, CacheEntry>(
|
|
28
|
+
(Runtime.getRuntime().maxMemory() / 1024 / 8).toInt()
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
init {
|
|
32
|
+
cacheDir.mkdirs()
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
data class CacheEntry(
|
|
36
|
+
val response: WebResourceResponse,
|
|
37
|
+
val timestamp: Long = System.currentTimeMillis(),
|
|
38
|
+
val eTag: String? = null,
|
|
39
|
+
val lastModified: String? = null
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
suspend fun getCachedResponse(url: String, headers: Map<String, String>): WebResourceResponse? =
|
|
43
|
+
withContext(Dispatchers.IO) {
|
|
44
|
+
try {
|
|
45
|
+
val cacheKey = generateCacheKey(url)
|
|
46
|
+
val currentTime = System.currentTimeMillis()
|
|
47
|
+
|
|
48
|
+
// Check memory cache first
|
|
49
|
+
val memoryCacheEntry = memoryCache.get(cacheKey)
|
|
50
|
+
if (memoryCacheEntry != null) {
|
|
51
|
+
val age = currentTime - memoryCacheEntry.timestamp
|
|
52
|
+
|
|
53
|
+
when {
|
|
54
|
+
age <= maxAge -> {
|
|
55
|
+
// Fresh content
|
|
56
|
+
Log.d(TAG, "Serving fresh content from memory cache: $url")
|
|
57
|
+
return@withContext memoryCacheEntry.response
|
|
58
|
+
}
|
|
59
|
+
age <= maxAge + staleWhileRevalidate -> {
|
|
60
|
+
// Stale content, but within revalidate window
|
|
61
|
+
Log.d(TAG, "Serving stale content while revalidating: $url")
|
|
62
|
+
revalidateInBackground(url, headers, cacheKey, memoryCacheEntry)
|
|
63
|
+
return@withContext memoryCacheEntry.response
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// Check disk cache
|
|
69
|
+
val cacheFile = File(cacheDir, cacheKey)
|
|
70
|
+
if (cacheFile.exists()) {
|
|
71
|
+
val fileAge = currentTime - cacheFile.lastModified()
|
|
72
|
+
val metadata = loadMetadata(cacheKey)
|
|
73
|
+
|
|
74
|
+
when {
|
|
75
|
+
fileAge <= maxAge -> {
|
|
76
|
+
// Fresh content from disk
|
|
77
|
+
val response = createResponseFromCache(cacheFile, metadata)
|
|
78
|
+
memoryCache.put(cacheKey, CacheEntry(response))
|
|
79
|
+
return@withContext response
|
|
80
|
+
}
|
|
81
|
+
fileAge <= maxAge + staleWhileRevalidate -> {
|
|
82
|
+
// Stale content from disk, revalidate in background
|
|
83
|
+
val response = createResponseFromCache(cacheFile, metadata)
|
|
84
|
+
val cacheEntry = CacheEntry(
|
|
85
|
+
response = response,
|
|
86
|
+
timestamp = cacheFile.lastModified(),
|
|
87
|
+
eTag = metadata.eTag,
|
|
88
|
+
lastModified = metadata.lastModified
|
|
89
|
+
)
|
|
90
|
+
revalidateInBackground(url, headers, cacheKey, cacheEntry)
|
|
91
|
+
return@withContext response
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// No cache or cache too old, fetch fresh content
|
|
97
|
+
Log.d(TAG, "❌ Cache miss, fetching fresh content: $url")
|
|
98
|
+
fetchAndCacheResource(url, headers, cacheKey)
|
|
99
|
+
} catch (e: Exception) {
|
|
100
|
+
Log.e(TAG, "Error in getCachedResponse for URL: $url", e)
|
|
101
|
+
null
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
suspend fun cleanup() {
|
|
106
|
+
withContext(Dispatchers.IO) {
|
|
107
|
+
try {
|
|
108
|
+
val currentTime = System.currentTimeMillis()
|
|
109
|
+
val maxCacheSize = 100 * 1024 * 1024 // 100MB max cache size
|
|
110
|
+
var totalSize = 0L
|
|
111
|
+
|
|
112
|
+
// Get all cache files sorted by last modified time (oldest first)
|
|
113
|
+
val cacheFiles = cacheDir.listFiles()?.sortedBy { it.lastModified() } ?: return@withContext
|
|
114
|
+
|
|
115
|
+
for (file in cacheFiles) {
|
|
116
|
+
val age = currentTime - file.lastModified()
|
|
117
|
+
|
|
118
|
+
// Delete if expired (older than maxAge + staleWhileRevalidate)
|
|
119
|
+
if (age > maxAge + staleWhileRevalidate) {
|
|
120
|
+
file.delete()
|
|
121
|
+
// Also delete corresponding metadata file if it exists
|
|
122
|
+
val metaFile = File(cacheDir, "${file.name}.meta")
|
|
123
|
+
if (metaFile.exists()) {
|
|
124
|
+
metaFile.delete()
|
|
125
|
+
}
|
|
126
|
+
continue
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
totalSize += file.length()
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// If cache is too large, remove oldest files until under limit
|
|
133
|
+
if (totalSize > maxCacheSize) {
|
|
134
|
+
val sortedFiles = cacheDir.listFiles()?.sortedBy { it.lastModified() } ?: return@withContext
|
|
135
|
+
for (file in sortedFiles) {
|
|
136
|
+
if (totalSize <= maxCacheSize) break
|
|
137
|
+
|
|
138
|
+
totalSize -= file.length()
|
|
139
|
+
file.delete()
|
|
140
|
+
// Delete corresponding metadata file
|
|
141
|
+
val metaFile = File(cacheDir, "${file.name}.meta")
|
|
142
|
+
if (metaFile.exists()) {
|
|
143
|
+
metaFile.delete()
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
Log.d(TAG, "Cache cleanup completed. Current size: ${totalSize / 1024}KB")
|
|
149
|
+
} catch (e: Exception) {
|
|
150
|
+
Log.e(TAG, "Error during cache cleanup", e)
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
private fun revalidateInBackground(
|
|
156
|
+
url: String,
|
|
157
|
+
headers: Map<String, String>,
|
|
158
|
+
cacheKey: String,
|
|
159
|
+
cacheEntry: CacheEntry
|
|
160
|
+
) {
|
|
161
|
+
if (!ongoingRevalidations.add(cacheKey)) {
|
|
162
|
+
// Revalidation already in progress
|
|
163
|
+
return
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
scope.launch {
|
|
167
|
+
try {
|
|
168
|
+
val revalidationHeaders = headers.toMutableMap()
|
|
169
|
+
cacheEntry.eTag?.let { revalidationHeaders["If-None-Match"] = it }
|
|
170
|
+
cacheEntry.lastModified?.let { revalidationHeaders["If-Modified-Since"] = it }
|
|
171
|
+
|
|
172
|
+
val connection = URL(url).openConnection() as HttpURLConnection
|
|
173
|
+
revalidationHeaders.forEach { (key, value) ->
|
|
174
|
+
connection.setRequestProperty(key, value)
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
when (connection.responseCode) {
|
|
178
|
+
HttpURLConnection.HTTP_NOT_MODIFIED -> {
|
|
179
|
+
// Content still valid, update timestamp
|
|
180
|
+
val updatedEntry = cacheEntry.copy(timestamp = System.currentTimeMillis())
|
|
181
|
+
memoryCache.put(cacheKey, updatedEntry)
|
|
182
|
+
updateCacheFileTimestamp(cacheKey)
|
|
183
|
+
Log.d(TAG, "✅ Content revalidated, not modified: $url")
|
|
184
|
+
}
|
|
185
|
+
HttpURLConnection.HTTP_OK -> {
|
|
186
|
+
// Content changed, update cache
|
|
187
|
+
Log.d(TAG, "⚡ Content changed, updating cache: $url")
|
|
188
|
+
fetchAndCacheResource(url, headers, cacheKey)
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
} catch (e: Exception) {
|
|
192
|
+
Log.e(TAG, "Error during revalidation for URL: $url", e)
|
|
193
|
+
} finally {
|
|
194
|
+
ongoingRevalidations.remove(cacheKey)
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
private fun updateCacheFileTimestamp(cacheKey: String) {
|
|
200
|
+
val cacheFile = File(cacheDir, cacheKey)
|
|
201
|
+
if (cacheFile.exists()) {
|
|
202
|
+
cacheFile.setLastModified(System.currentTimeMillis())
|
|
203
|
+
File(cacheDir, "${cacheKey}.meta").setLastModified(System.currentTimeMillis())
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
private suspend fun fetchAndCacheResource(
|
|
208
|
+
url: String,
|
|
209
|
+
headers: Map<String, String>,
|
|
210
|
+
cacheKey: String
|
|
211
|
+
): WebResourceResponse? = withContext(Dispatchers.IO) {
|
|
212
|
+
var connection: HttpURLConnection? = null
|
|
213
|
+
try {
|
|
214
|
+
connection = URL(url).openConnection() as HttpURLConnection
|
|
215
|
+
headers.forEach { (key, value) ->
|
|
216
|
+
connection.setRequestProperty(key, value)
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
connection.connectTimeout = 15000
|
|
220
|
+
connection.readTimeout = 15000
|
|
221
|
+
connection.connect()
|
|
222
|
+
|
|
223
|
+
if (connection.responseCode == HttpURLConnection.HTTP_OK) {
|
|
224
|
+
val mimeType = connection.contentType ?: "application/octet-stream"
|
|
225
|
+
val encoding = connection.contentEncoding ?: "utf-8"
|
|
226
|
+
val eTag = connection.getHeaderField("ETag")
|
|
227
|
+
val lastModified = connection.getHeaderField("Last-Modified")
|
|
228
|
+
|
|
229
|
+
val responseBytes = connection.inputStream.use { it.readBytes() }
|
|
230
|
+
if (!isValidResponse(mimeType, responseBytes)) {
|
|
231
|
+
return@withContext null
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
// Create response for immediate use
|
|
235
|
+
val response = WebResourceResponse(
|
|
236
|
+
mimeType,
|
|
237
|
+
encoding,
|
|
238
|
+
ByteArrayInputStream(responseBytes)
|
|
239
|
+
)
|
|
240
|
+
|
|
241
|
+
// Cache the response
|
|
242
|
+
val cacheEntry = CacheEntry(
|
|
243
|
+
response = WebResourceResponse(
|
|
244
|
+
mimeType,
|
|
245
|
+
encoding,
|
|
246
|
+
ByteArrayInputStream(responseBytes.clone())
|
|
247
|
+
),
|
|
248
|
+
eTag = eTag,
|
|
249
|
+
lastModified = lastModified
|
|
250
|
+
)
|
|
251
|
+
memoryCache.put(cacheKey, cacheEntry)
|
|
252
|
+
|
|
253
|
+
// Save to disk cache
|
|
254
|
+
scope.launch {
|
|
255
|
+
try {
|
|
256
|
+
val cacheFile = File(cacheDir, cacheKey)
|
|
257
|
+
FileOutputStream(cacheFile).use { it.write(responseBytes) }
|
|
258
|
+
saveMetadata(cacheKey, CacheMetadata(
|
|
259
|
+
mimeType = mimeType,
|
|
260
|
+
encoding = encoding,
|
|
261
|
+
eTag = eTag,
|
|
262
|
+
lastModified = lastModified
|
|
263
|
+
))
|
|
264
|
+
Log.d(TAG, "✅ Successfully cached response for: $url")
|
|
265
|
+
} catch (e: Exception) {
|
|
266
|
+
Log.e(TAG, "Error saving to disk cache for URL: $url", e)
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
return@withContext response
|
|
271
|
+
}
|
|
272
|
+
return@withContext null
|
|
273
|
+
} finally {
|
|
274
|
+
connection?.disconnect()
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
private data class CacheMetadata(
|
|
279
|
+
val mimeType: String,
|
|
280
|
+
val encoding: String,
|
|
281
|
+
val eTag: String? = null,
|
|
282
|
+
val lastModified: String? = null
|
|
283
|
+
) : Serializable
|
|
284
|
+
|
|
285
|
+
private fun createResponseFromCache(cacheFile: File, metadata: CacheMetadata): WebResourceResponse {
|
|
286
|
+
return WebResourceResponse(
|
|
287
|
+
metadata.mimeType,
|
|
288
|
+
metadata.encoding,
|
|
289
|
+
BufferedInputStream(FileInputStream(cacheFile))
|
|
290
|
+
)
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
private fun generateCacheKey(url: String): String {
|
|
294
|
+
val md = MessageDigest.getInstance("MD5")
|
|
295
|
+
val digest = md.digest(url.toByteArray())
|
|
296
|
+
return digest.joinToString("") { "%02x".format(it) }
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
private fun isValidResponse(mimeType: String, responseBytes: ByteArray): Boolean {
|
|
300
|
+
return try {
|
|
301
|
+
if (responseBytes.isEmpty()) return false
|
|
302
|
+
if (mimeType.isEmpty()) return false
|
|
303
|
+
|
|
304
|
+
if (mimeType.startsWith("text/") ||
|
|
305
|
+
mimeType.contains("json") ||
|
|
306
|
+
mimeType.contains("javascript")
|
|
307
|
+
) {
|
|
308
|
+
val content = String(responseBytes)
|
|
309
|
+
if (content.isBlank()) return false
|
|
310
|
+
}
|
|
311
|
+
true
|
|
312
|
+
} catch (e: Exception) {
|
|
313
|
+
Log.e(TAG, "Error validating response", e)
|
|
314
|
+
false
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
private fun saveMetadata(cacheKey: String, metadata: CacheMetadata) {
|
|
319
|
+
val metadataFile = File(cacheDir, "${cacheKey}.meta")
|
|
320
|
+
ObjectOutputStream(FileOutputStream(metadataFile)).use {
|
|
321
|
+
it.writeObject(metadata)
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
private fun loadMetadata(cacheKey: String): CacheMetadata {
|
|
326
|
+
val metadataFile = File(cacheDir, "${cacheKey}.meta")
|
|
327
|
+
return ObjectInputStream(FileInputStream(metadataFile)).use {
|
|
328
|
+
it.readObject() as CacheMetadata
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
}
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
<?xml version="1.0" encoding="utf-8"?>
|
|
2
|
+
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
|
3
|
+
android:width="108dp"
|
|
4
|
+
android:height="108dp"
|
|
5
|
+
android:viewportWidth="108"
|
|
6
|
+
android:viewportHeight="108">
|
|
7
|
+
<path
|
|
8
|
+
android:fillColor="#3DDC84"
|
|
9
|
+
android:pathData="M0,0h108v108h-108z" />
|
|
10
|
+
<path
|
|
11
|
+
android:fillColor="#00000000"
|
|
12
|
+
android:pathData="M9,0L9,108"
|
|
13
|
+
android:strokeWidth="0.8"
|
|
14
|
+
android:strokeColor="#33FFFFFF" />
|
|
15
|
+
<path
|
|
16
|
+
android:fillColor="#00000000"
|
|
17
|
+
android:pathData="M19,0L19,108"
|
|
18
|
+
android:strokeWidth="0.8"
|
|
19
|
+
android:strokeColor="#33FFFFFF" />
|
|
20
|
+
<path
|
|
21
|
+
android:fillColor="#00000000"
|
|
22
|
+
android:pathData="M29,0L29,108"
|
|
23
|
+
android:strokeWidth="0.8"
|
|
24
|
+
android:strokeColor="#33FFFFFF" />
|
|
25
|
+
<path
|
|
26
|
+
android:fillColor="#00000000"
|
|
27
|
+
android:pathData="M39,0L39,108"
|
|
28
|
+
android:strokeWidth="0.8"
|
|
29
|
+
android:strokeColor="#33FFFFFF" />
|
|
30
|
+
<path
|
|
31
|
+
android:fillColor="#00000000"
|
|
32
|
+
android:pathData="M49,0L49,108"
|
|
33
|
+
android:strokeWidth="0.8"
|
|
34
|
+
android:strokeColor="#33FFFFFF" />
|
|
35
|
+
<path
|
|
36
|
+
android:fillColor="#00000000"
|
|
37
|
+
android:pathData="M59,0L59,108"
|
|
38
|
+
android:strokeWidth="0.8"
|
|
39
|
+
android:strokeColor="#33FFFFFF" />
|
|
40
|
+
<path
|
|
41
|
+
android:fillColor="#00000000"
|
|
42
|
+
android:pathData="M69,0L69,108"
|
|
43
|
+
android:strokeWidth="0.8"
|
|
44
|
+
android:strokeColor="#33FFFFFF" />
|
|
45
|
+
<path
|
|
46
|
+
android:fillColor="#00000000"
|
|
47
|
+
android:pathData="M79,0L79,108"
|
|
48
|
+
android:strokeWidth="0.8"
|
|
49
|
+
android:strokeColor="#33FFFFFF" />
|
|
50
|
+
<path
|
|
51
|
+
android:fillColor="#00000000"
|
|
52
|
+
android:pathData="M89,0L89,108"
|
|
53
|
+
android:strokeWidth="0.8"
|
|
54
|
+
android:strokeColor="#33FFFFFF" />
|
|
55
|
+
<path
|
|
56
|
+
android:fillColor="#00000000"
|
|
57
|
+
android:pathData="M99,0L99,108"
|
|
58
|
+
android:strokeWidth="0.8"
|
|
59
|
+
android:strokeColor="#33FFFFFF" />
|
|
60
|
+
<path
|
|
61
|
+
android:fillColor="#00000000"
|
|
62
|
+
android:pathData="M0,9L108,9"
|
|
63
|
+
android:strokeWidth="0.8"
|
|
64
|
+
android:strokeColor="#33FFFFFF" />
|
|
65
|
+
<path
|
|
66
|
+
android:fillColor="#00000000"
|
|
67
|
+
android:pathData="M0,19L108,19"
|
|
68
|
+
android:strokeWidth="0.8"
|
|
69
|
+
android:strokeColor="#33FFFFFF" />
|
|
70
|
+
<path
|
|
71
|
+
android:fillColor="#00000000"
|
|
72
|
+
android:pathData="M0,29L108,29"
|
|
73
|
+
android:strokeWidth="0.8"
|
|
74
|
+
android:strokeColor="#33FFFFFF" />
|
|
75
|
+
<path
|
|
76
|
+
android:fillColor="#00000000"
|
|
77
|
+
android:pathData="M0,39L108,39"
|
|
78
|
+
android:strokeWidth="0.8"
|
|
79
|
+
android:strokeColor="#33FFFFFF" />
|
|
80
|
+
<path
|
|
81
|
+
android:fillColor="#00000000"
|
|
82
|
+
android:pathData="M0,49L108,49"
|
|
83
|
+
android:strokeWidth="0.8"
|
|
84
|
+
android:strokeColor="#33FFFFFF" />
|
|
85
|
+
<path
|
|
86
|
+
android:fillColor="#00000000"
|
|
87
|
+
android:pathData="M0,59L108,59"
|
|
88
|
+
android:strokeWidth="0.8"
|
|
89
|
+
android:strokeColor="#33FFFFFF" />
|
|
90
|
+
<path
|
|
91
|
+
android:fillColor="#00000000"
|
|
92
|
+
android:pathData="M0,69L108,69"
|
|
93
|
+
android:strokeWidth="0.8"
|
|
94
|
+
android:strokeColor="#33FFFFFF" />
|
|
95
|
+
<path
|
|
96
|
+
android:fillColor="#00000000"
|
|
97
|
+
android:pathData="M0,79L108,79"
|
|
98
|
+
android:strokeWidth="0.8"
|
|
99
|
+
android:strokeColor="#33FFFFFF" />
|
|
100
|
+
<path
|
|
101
|
+
android:fillColor="#00000000"
|
|
102
|
+
android:pathData="M0,89L108,89"
|
|
103
|
+
android:strokeWidth="0.8"
|
|
104
|
+
android:strokeColor="#33FFFFFF" />
|
|
105
|
+
<path
|
|
106
|
+
android:fillColor="#00000000"
|
|
107
|
+
android:pathData="M0,99L108,99"
|
|
108
|
+
android:strokeWidth="0.8"
|
|
109
|
+
android:strokeColor="#33FFFFFF" />
|
|
110
|
+
<path
|
|
111
|
+
android:fillColor="#00000000"
|
|
112
|
+
android:pathData="M19,29L89,29"
|
|
113
|
+
android:strokeWidth="0.8"
|
|
114
|
+
android:strokeColor="#33FFFFFF" />
|
|
115
|
+
<path
|
|
116
|
+
android:fillColor="#00000000"
|
|
117
|
+
android:pathData="M19,39L89,39"
|
|
118
|
+
android:strokeWidth="0.8"
|
|
119
|
+
android:strokeColor="#33FFFFFF" />
|
|
120
|
+
<path
|
|
121
|
+
android:fillColor="#00000000"
|
|
122
|
+
android:pathData="M19,49L89,49"
|
|
123
|
+
android:strokeWidth="0.8"
|
|
124
|
+
android:strokeColor="#33FFFFFF" />
|
|
125
|
+
<path
|
|
126
|
+
android:fillColor="#00000000"
|
|
127
|
+
android:pathData="M19,59L89,59"
|
|
128
|
+
android:strokeWidth="0.8"
|
|
129
|
+
android:strokeColor="#33FFFFFF" />
|
|
130
|
+
<path
|
|
131
|
+
android:fillColor="#00000000"
|
|
132
|
+
android:pathData="M19,69L89,69"
|
|
133
|
+
android:strokeWidth="0.8"
|
|
134
|
+
android:strokeColor="#33FFFFFF" />
|
|
135
|
+
<path
|
|
136
|
+
android:fillColor="#00000000"
|
|
137
|
+
android:pathData="M19,79L89,79"
|
|
138
|
+
android:strokeWidth="0.8"
|
|
139
|
+
android:strokeColor="#33FFFFFF" />
|
|
140
|
+
<path
|
|
141
|
+
android:fillColor="#00000000"
|
|
142
|
+
android:pathData="M29,19L29,89"
|
|
143
|
+
android:strokeWidth="0.8"
|
|
144
|
+
android:strokeColor="#33FFFFFF" />
|
|
145
|
+
<path
|
|
146
|
+
android:fillColor="#00000000"
|
|
147
|
+
android:pathData="M39,19L39,89"
|
|
148
|
+
android:strokeWidth="0.8"
|
|
149
|
+
android:strokeColor="#33FFFFFF" />
|
|
150
|
+
<path
|
|
151
|
+
android:fillColor="#00000000"
|
|
152
|
+
android:pathData="M49,19L49,89"
|
|
153
|
+
android:strokeWidth="0.8"
|
|
154
|
+
android:strokeColor="#33FFFFFF" />
|
|
155
|
+
<path
|
|
156
|
+
android:fillColor="#00000000"
|
|
157
|
+
android:pathData="M59,19L59,89"
|
|
158
|
+
android:strokeWidth="0.8"
|
|
159
|
+
android:strokeColor="#33FFFFFF" />
|
|
160
|
+
<path
|
|
161
|
+
android:fillColor="#00000000"
|
|
162
|
+
android:pathData="M69,19L69,89"
|
|
163
|
+
android:strokeWidth="0.8"
|
|
164
|
+
android:strokeColor="#33FFFFFF" />
|
|
165
|
+
<path
|
|
166
|
+
android:fillColor="#00000000"
|
|
167
|
+
android:pathData="M79,19L79,89"
|
|
168
|
+
android:strokeWidth="0.8"
|
|
169
|
+
android:strokeColor="#33FFFFFF" />
|
|
170
|
+
</vector>
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
|
2
|
+
xmlns:aapt="http://schemas.android.com/aapt"
|
|
3
|
+
android:width="108dp"
|
|
4
|
+
android:height="108dp"
|
|
5
|
+
android:viewportWidth="108"
|
|
6
|
+
android:viewportHeight="108">
|
|
7
|
+
<path android:pathData="M31,63.928c0,0 6.4,-11 12.1,-13.1c7.2,-2.6 26,-1.4 26,-1.4l38.1,38.1L107,108.928l-32,-1L31,63.928z">
|
|
8
|
+
<aapt:attr name="android:fillColor">
|
|
9
|
+
<gradient
|
|
10
|
+
android:endX="85.84757"
|
|
11
|
+
android:endY="92.4963"
|
|
12
|
+
android:startX="42.9492"
|
|
13
|
+
android:startY="49.59793"
|
|
14
|
+
android:type="linear">
|
|
15
|
+
<item
|
|
16
|
+
android:color="#44000000"
|
|
17
|
+
android:offset="0.0" />
|
|
18
|
+
<item
|
|
19
|
+
android:color="#00000000"
|
|
20
|
+
android:offset="1.0" />
|
|
21
|
+
</gradient>
|
|
22
|
+
</aapt:attr>
|
|
23
|
+
</path>
|
|
24
|
+
<path
|
|
25
|
+
android:fillColor="#FFFFFF"
|
|
26
|
+
android:fillType="nonZero"
|
|
27
|
+
android:pathData="M65.3,45.828l3.8,-6.6c0.2,-0.4 0.1,-0.9 -0.3,-1.1c-0.4,-0.2 -0.9,-0.1 -1.1,0.3l-3.9,6.7c-6.3,-2.8 -13.4,-2.8 -19.7,0l-3.9,-6.7c-0.2,-0.4 -0.7,-0.5 -1.1,-0.3C38.8,38.328 38.7,38.828 38.9,39.228l3.8,6.6C36.2,49.428 31.7,56.028 31,63.928h46C76.3,56.028 71.8,49.428 65.3,45.828zM43.4,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2c-0.3,-0.7 -0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C45.3,56.528 44.5,57.328 43.4,57.328L43.4,57.328zM64.6,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2s-0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C66.5,56.528 65.6,57.328 64.6,57.328L64.6,57.328z"
|
|
28
|
+
android:strokeWidth="1"
|
|
29
|
+
android:strokeColor="#00000000" />
|
|
30
|
+
</vector>
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
<?xml version="1.0" encoding="utf-8"?>
|
|
2
|
+
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
|
3
|
+
android:layout_width="match_parent"
|
|
4
|
+
android:layout_height="match_parent">
|
|
5
|
+
<RelativeLayout
|
|
6
|
+
android:layout_height="match_parent"
|
|
7
|
+
android:layout_width="match_parent">
|
|
8
|
+
|
|
9
|
+
<WebView
|
|
10
|
+
android:id="@+id/webview"
|
|
11
|
+
android:layout_height="match_parent"
|
|
12
|
+
android:layout_width="match_parent" />
|
|
13
|
+
|
|
14
|
+
<ProgressBar
|
|
15
|
+
android:id="@+id/progress"
|
|
16
|
+
android:layout_centerInParent="true"
|
|
17
|
+
android:layout_height="wrap_content"
|
|
18
|
+
android:layout_width="wrap_content"
|
|
19
|
+
android:visibility="gone" />
|
|
20
|
+
</RelativeLayout>
|
|
21
|
+
|
|
22
|
+
</androidx.constraintlayout.widget.ConstraintLayout>
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
<?xml version="1.0" encoding="utf-8"?>
|
|
2
|
+
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
|
3
|
+
<background android:drawable="@drawable/ic_launcher_background" />
|
|
4
|
+
<foreground android:drawable="@drawable/ic_launcher_foreground" />
|
|
5
|
+
<monochrome android:drawable="@drawable/ic_launcher_foreground" />
|
|
6
|
+
</adaptive-icon>
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
<?xml version="1.0" encoding="utf-8"?>
|
|
2
|
+
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
|
3
|
+
<background android:drawable="@drawable/ic_launcher_background" />
|
|
4
|
+
<foreground android:drawable="@drawable/ic_launcher_foreground" />
|
|
5
|
+
<monochrome android:drawable="@drawable/ic_launcher_foreground" />
|
|
6
|
+
</adaptive-icon>
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
<?xml version="1.0" encoding="utf-8"?>
|
|
2
|
+
<resources>
|
|
3
|
+
<color name="purple_200">#FFBB86FC</color>
|
|
4
|
+
<color name="purple_500">#FF6200EE</color>
|
|
5
|
+
<color name="purple_700">#FF3700B3</color>
|
|
6
|
+
<color name="teal_200">#FF03DAC5</color>
|
|
7
|
+
<color name="teal_700">#FF018786</color>
|
|
8
|
+
<color name="black">#FF000000</color>
|
|
9
|
+
<color name="white">#FFFFFFFF</color>
|
|
10
|
+
</resources>
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
<resources xmlns:tools="http://schemas.android.com/tools">
|
|
2
|
+
<!-- Base application theme. -->
|
|
3
|
+
<style name="Theme.AndroidProject" parent="Theme.MaterialComponents.DayNight.DarkActionBar">
|
|
4
|
+
<!-- Primary brand color. -->
|
|
5
|
+
<item name="colorPrimary">@color/purple_500</item>
|
|
6
|
+
<item name="colorPrimaryVariant">@color/purple_700</item>
|
|
7
|
+
<item name="colorOnPrimary">@color/white</item>
|
|
8
|
+
<!-- Secondary brand color. -->
|
|
9
|
+
<item name="colorSecondary">@color/teal_200</item>
|
|
10
|
+
<item name="colorSecondaryVariant">@color/teal_700</item>
|
|
11
|
+
<item name="colorOnSecondary">@color/black</item>
|
|
12
|
+
<!-- Status bar color. -->
|
|
13
|
+
<!-- Customize your theme here. -->
|
|
14
|
+
</style>
|
|
15
|
+
</resources>
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
<resources xmlns:tools="http://schemas.android.com/tools">
|
|
2
|
+
<!-- Base application theme. -->
|
|
3
|
+
<style name="Theme.AndroidProject" parent="Theme.MaterialComponents.DayNight.DarkActionBar">
|
|
4
|
+
<!-- Primary brand color. -->
|
|
5
|
+
<item name="colorPrimary">@color/purple_500</item>
|
|
6
|
+
<item name="colorPrimaryVariant">@color/purple_700</item>
|
|
7
|
+
<item name="colorOnPrimary">@color/white</item>
|
|
8
|
+
<!-- Secondary brand color. -->
|
|
9
|
+
<item name="colorSecondary">@color/teal_200</item>
|
|
10
|
+
<item name="colorSecondaryVariant">@color/teal_700</item>
|
|
11
|
+
<item name="colorOnSecondary">@color/black</item>
|
|
12
|
+
<!-- Status bar color. -->
|
|
13
|
+
<!-- Customize your theme here. -->
|
|
14
|
+
</style>
|
|
15
|
+
</resources>
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
<?xml version="1.0" encoding="utf-8"?><!--
|
|
2
|
+
Sample backup rules file; uncomment and customize as necessary.
|
|
3
|
+
See https://developer.android.com/guide/topics/data/autobackup
|
|
4
|
+
for details.
|
|
5
|
+
Note: This file is ignored for devices older that API 31
|
|
6
|
+
See https://developer.android.com/about/versions/12/backup-restore
|
|
7
|
+
-->
|
|
8
|
+
<full-backup-content>
|
|
9
|
+
<!--
|
|
10
|
+
<include domain="sharedpref" path="."/>
|
|
11
|
+
<exclude domain="sharedpref" path="device.xml"/>
|
|
12
|
+
-->
|
|
13
|
+
</full-backup-content>
|