diff --git a/Dockerfile b/Dockerfile index d17d6c2..6091739 100644 --- a/Dockerfile +++ b/Dockerfile @@ -8,7 +8,7 @@ RUN apt-get update && apt-get install -y \ wget unzip git curl ca-certificates && \ mkdir -p ${ANDROID_HOME}/cmdline-tools && \ cd /opt && \ - wget https://dl.google.com/android/repository/commandlinetools-linux-10406996_latest.zip -O cmdline-tools.zip && \ + wget https://dl.google.com/android/repository/commandlinetools-linux-11076708_latest.zip -O cmdline-tools.zip && \ unzip cmdline-tools.zip -d ${ANDROID_HOME}/cmdline-tools && \ mv ${ANDROID_HOME}/cmdline-tools/cmdline-tools ${ANDROID_HOME}/cmdline-tools/latest && \ rm cmdline-tools.zip && \ @@ -24,4 +24,8 @@ ENV GRADLE_OPTS="-Xmx4096m -Dfile.encoding=UTF-8 --add-opens=java.base/java.util WORKDIR /workspace -CMD ["./gradlew", "assembleRelease"] \ No newline at end of file +# Copy project in (optional if using bind mount) +# COPY . . + +# Default command to build the release APK (Android 15 compatible) +CMD ["./gradlew", "assembleRelease"] diff --git a/app/build.gradle b/app/build.gradle index 7cfe521..1c0a8b6 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -24,8 +24,14 @@ android { buildTypes { release { signingConfig signingConfigs.debug - minifyEnabled false + minifyEnabled true + shrinkResources true proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' + debuggable false + } + debug { + minifyEnabled false + debuggable true } } compileOptions { diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro index 481bb43..4a508d0 100644 --- a/app/proguard-rules.pro +++ b/app/proguard-rules.pro @@ -5,17 +5,59 @@ # For more details, see # http://developer.android.com/guide/developing/tools/proguard.html -# If your project uses WebView with JS, uncomment the following -# and specify the fully qualified class name to the JavaScript interface -# class: -#-keepclassmembers class fqcn.of.javascript.interface.for.webview { -# public *; -#} - # Uncomment this to preserve the line number information for # debugging stack traces. -#-keepattributes SourceFile,LineNumberTable +-keepattributes SourceFile,LineNumberTable # If you keep the line number information, uncomment this to # hide the original source file name. -#-renamesourcefileattribute SourceFile \ No newline at end of file +-renamesourcefileattribute SourceFile + +# Keep WebView related classes +-keep class android.webkit.** { *; } +-keep class androidx.webkit.** { *; } + +# Keep application class +-keep class com.book.gofarsi.** { *; } + +# Keep coroutines +-keepclassmembers class kotlinx.coroutines.** { *; } +-dontwarn kotlinx.coroutines.** + +# Keep BuildConfig +-keep class com.book.gofarsi.BuildConfig { *; } + +# General Android rules +-keep class androidx.** { *; } +-dontwarn androidx.** + +# Keep all fragments and activities +-keep class * extends androidx.fragment.app.Fragment +-keep class * extends androidx.appcompat.app.AppCompatActivity + +# Remove logging in release +-assumenosideeffects class android.util.Log { + public static boolean isLoggable(java.lang.String, int); + public static int v(...); + public static int i(...); + public static int w(...); + public static int d(...); + public static int e(...); +} + +# Optimize +-optimizations !code/simplification/arithmetic,!code/simplification/cast,!field/*,!class/merging/* +-optimizationpasses 5 +-allowaccessmodification +-dontpreverify + +# Keep native methods +-keepclasseswithmembernames class * { + native ; +} + +# Keep setters and getters +-keepclassmembers public class * { + public void set*(***); + public *** get*(); +} \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 9f211a0..7be1554 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -2,6 +2,8 @@ + + (R.id.tvVersion).text = BuildConfig.VERSION_NAME - myWebView = findViewById(R.id.webview) - - // --- WebView settings for PWA and offline caching --- - val webSettings = myWebView!!.settings - webSettings.javaScriptEnabled = true - webSettings.domStorageEnabled = true - webSettings.cacheMode = if (isNetworkAvailable()) { - WebSettings.LOAD_DEFAULT - } else { - WebSettings.LOAD_CACHE_ELSE_NETWORK - } + try { + setContentView(R.layout.activity_web) - if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.N) { - ServiceWorkerController.getInstance().setServiceWorkerClient(object : ServiceWorkerClient() { - override fun shouldInterceptRequest(request: WebResourceRequest): WebResourceResponse? { - return null - } - }) - } - // --- End WebView settings --- + // Find the fastest URL before loading + findViewById(R.id.tvVersion).text = BuildConfig.VERSION_NAME + myWebView = findViewById(R.id.webview) + + // --- WebView settings for PWA and offline caching --- + val webSettings = myWebView!!.settings + webSettings.javaScriptEnabled = true + webSettings.domStorageEnabled = true + webSettings.allowFileAccess = true + webSettings.allowContentAccess = true + webSettings.allowFileAccessFromFileURLs = true + webSettings.allowUniversalAccessFromFileURLs = true + webSettings.mixedContentMode = WebSettings.MIXED_CONTENT_ALWAYS_ALLOW + webSettings.cacheMode = if (isNetworkAvailable()) { + WebSettings.LOAD_DEFAULT + } else { + WebSettings.LOAD_CACHE_ELSE_NETWORK + } - // Launch coroutine to find fastest URL - findFastestUrlAndLoad() + if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.N) { + ServiceWorkerController.getInstance().setServiceWorkerClient(object : ServiceWorkerClient() { + override fun shouldInterceptRequest(request: WebResourceRequest): WebResourceResponse? { + return null + } + }) + } + // --- End WebView settings --- + + // Launch coroutine to find fastest URL + findFastestUrlAndLoad() + } catch (e: Exception) { + Log.e("WebActivity", "Error in onCreate: ${e.message}", e) + finish() + } myWebView?.webViewClient = object : WebViewClient() { override fun shouldOverrideUrlLoading(view: WebView, url: String): Boolean { @@ -106,19 +116,31 @@ class WebActivity : AppCompatActivity() { private fun findFastestUrlAndLoad() { CoroutineScope(Dispatchers.IO).launch { var isUrlLoaded = false - for (url in list) { - if (isUrlResponsive(url)) { - withContext(Dispatchers.Main) { - myWebView?.loadUrl(url) + for ((index, url) in list.withIndex()) { + try { + if (isUrlResponsive(url)) { + urlIndex = index + withContext(Dispatchers.Main) { + myWebView?.loadUrl(url) + } + isUrlLoaded = true + break } - isUrlLoaded = true - break + } catch (e: Exception) { + Log.e("WebActivity", "Error checking URL $url: ${e.message}") + continue } } if (!isUrlLoaded) { withContext(Dispatchers.Main) { - // Handle case where no URL is responsive - findViewById(R.id.loading).visibility = View.INVISIBLE + // Handle case where no URL is responsive - load first URL anyway + try { + myWebView?.loadUrl(list[0]) + findViewById(R.id.loading).visibility = View.VISIBLE + } catch (e: Exception) { + Log.e("WebActivity", "Error loading fallback URL: ${e.message}") + findViewById(R.id.loading).visibility = View.INVISIBLE + } } } } @@ -211,4 +233,38 @@ class WebActivity : AppCompatActivity() { } return false } + + override fun onDestroy() { + try { + myWebView?.clearHistory() + myWebView?.clearCache(true) + myWebView?.loadUrl("about:blank") + myWebView?.onPause() + myWebView?.removeAllViews() + myWebView?.destroyDrawingCache() + myWebView?.destroy() + myWebView = null + } catch (e: Exception) { + Log.e("WebActivity", "Error in onDestroy: ${e.message}") + } + super.onDestroy() + } + + override fun onPause() { + super.onPause() + try { + myWebView?.onPause() + } catch (e: Exception) { + Log.e("WebActivity", "Error in onPause: ${e.message}") + } + } + + override fun onResume() { + super.onResume() + try { + myWebView?.onResume() + } catch (e: Exception) { + Log.e("WebActivity", "Error in onResume: ${e.message}") + } + } } \ No newline at end of file diff --git a/app/src/main/res/xml/network_security_config.xml b/app/src/main/res/xml/network_security_config.xml new file mode 100644 index 0000000..6a6be57 --- /dev/null +++ b/app/src/main/res/xml/network_security_config.xml @@ -0,0 +1,12 @@ + + + + gofarsi.ir + book.gofarsi.ir + cloud-book.gofarsi.ir + ir1-book.gofarsi.ir + ipfs-book.gofarsi.ir + hku1-book.gofarsi.ir + aws1-book.gofarsi.ir + +