Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 6 additions & 2 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -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 && \
Expand All @@ -24,4 +24,8 @@ ENV GRADLE_OPTS="-Xmx4096m -Dfile.encoding=UTF-8 --add-opens=java.base/java.util

WORKDIR /workspace

CMD ["./gradlew", "assembleRelease"]
# Copy project in (optional if using bind mount)
# COPY . .

# Default command to build the release APK (Android 15 compatible)
CMD ["./gradlew", "assembleRelease"]
8 changes: 7 additions & 1 deletion app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
60 changes: 51 additions & 9 deletions app/proguard-rules.pro
Original file line number Diff line number Diff line change
Expand Up @@ -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
-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 <methods>;
}

# Keep setters and getters
-keepclassmembers public class * {
public void set*(***);
public *** get*();
}
4 changes: 4 additions & 0 deletions app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />

<application
android:allowBackup="true"
android:dataExtractionRules="@xml/data_extraction_rules"
Expand All @@ -11,6 +13,8 @@
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/Theme.GoFarsi"
android:networkSecurityConfig="@xml/network_security_config"
android:usesCleartextTraffic="true"
tools:targetApi="35">
<activity
android:name=".WebActivity"
Expand Down
122 changes: 89 additions & 33 deletions app/src/main/java/com/book/gofarsi/WebActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -29,33 +29,43 @@ class WebActivity : AppCompatActivity() {
var currentUrl = ""
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_web)

// Find the fastest URL before loading
findViewById<TextView>(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<TextView>(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 {
Expand Down Expand Up @@ -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<RelativeLayout>(R.id.loading).visibility = View.INVISIBLE
// Handle case where no URL is responsive - load first URL anyway
try {
myWebView?.loadUrl(list[0])
findViewById<RelativeLayout>(R.id.loading).visibility = View.VISIBLE
} catch (e: Exception) {
Log.e("WebActivity", "Error loading fallback URL: ${e.message}")
findViewById<RelativeLayout>(R.id.loading).visibility = View.INVISIBLE
}
}
}
}
Expand Down Expand Up @@ -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}")
}
}
}
12 changes: 12 additions & 0 deletions app/src/main/res/xml/network_security_config.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<network-security-config>
<domain-config cleartextTrafficPermitted="true">
<domain includeSubdomains="true">gofarsi.ir</domain>
<domain includeSubdomains="true">book.gofarsi.ir</domain>
<domain includeSubdomains="true">cloud-book.gofarsi.ir</domain>
<domain includeSubdomains="true">ir1-book.gofarsi.ir</domain>
<domain includeSubdomains="true">ipfs-book.gofarsi.ir</domain>
<domain includeSubdomains="true">hku1-book.gofarsi.ir</domain>
<domain includeSubdomains="true">aws1-book.gofarsi.ir</domain>
</domain-config>
</network-security-config>