@@ -14,7 +14,11 @@ import android.widget.TextView
1414import androidx.activity.OnBackPressedCallback
1515import androidx.appcompat.app.AppCompatActivity
1616import kotlinx.coroutines.*
17+ import okhttp3.OkHttpClient
18+ import okhttp3.Request
19+ import java.io.IOException
1720import java.util.concurrent.ConcurrentHashMap
21+ import java.util.concurrent.TimeUnit
1822import kotlin.system.measureTimeMillis
1923
2024
@@ -24,15 +28,24 @@ class WebActivity : AppCompatActivity() {
2428 var myWebView: WebView ? = null
2529 private val list = arrayListOf (
2630 " https://book.gofarsi.ir/" ,
27- " https://cloud-book.gofarsi.ir/" ,
28- " https://ir1-book.gofarsi.ir/" ,
29- " https://ipfs-book.gofarsi.ir/" ,
30- " https://hku1-book.gofarsi.ir/" ,
31- " https://aws1-book.gofarsi.ir/"
31+ // "https://cloud-book.gofarsi.ir/",
32+ // "https://ir1-book.gofarsi.ir/",
33+ // "https://ipfs-book.gofarsi.ir/",
34+ // "https://hku1-book.gofarsi.ir/",
35+ // "https://aws1-book.gofarsi.ir/"
3236 )
3337 var urlIndex = 0
3438 var hasErrorInLoading = false
3539 var currentUrl = " "
40+ private val client = OkHttpClient .Builder ()
41+ .connectTimeout(3 , TimeUnit .SECONDS )
42+ .readTimeout(3 , TimeUnit .SECONDS )
43+ .build()
44+
45+ private val scope = CoroutineScope (SupervisorJob () + Dispatchers .Main )
46+ private val job = Job ()
47+ private val coroutineScope = CoroutineScope (Dispatchers .Main + job)
48+ private lateinit var cacheInterceptor: CacheInterceptor
3649
3750 companion object {
3851 private const val TAG = " WebActivity" // Better log tag
@@ -83,7 +96,7 @@ class WebActivity : AppCompatActivity() {
8396 super .onCreate(savedInstanceState)
8497 try {
8598 setContentView(R .layout.activity_web)
86-
99+ cacheInterceptor = CacheInterceptor (applicationContext)
87100 // Find the fastest URL before loading
88101 findViewById<TextView >(R .id.tvVersion).text = BuildConfig .VERSION_NAME
89102 myWebView = findViewById(R .id.webview)
@@ -97,23 +110,38 @@ class WebActivity : AppCompatActivity() {
97110 // Note: allowFileAccessFromFileURLs and allowUniversalAccessFromFileURLs are deprecated
98111 // and removed for security reasons. Modern apps should use alternative approaches.
99112 webSettings.mixedContentMode = WebSettings .MIXED_CONTENT_ALWAYS_ALLOW
100- webSettings.cacheMode = if (isNetworkAvailable()) {
101- WebSettings .LOAD_DEFAULT
102- } else {
103- WebSettings .LOAD_CACHE_ELSE_NETWORK
113+ // webSettings.cacheMode = if (isNetworkAvailable()) {
114+ // WebSettings.LOAD_DEFAULT
115+ // } else {
116+ // WebSettings.LOAD_CACHE_ELSE_NETWORK
117+ // }
118+ if (! isNetworkAvailable()){
119+ myWebView?.loadUrl(" javascript:document.body.innerHTML='" +
120+ getOfflineHtml() + " '" )
104121 }
105122
106123 if (android.os.Build .VERSION .SDK_INT >= android.os.Build .VERSION_CODES .N ) {
107124 ServiceWorkerController .getInstance().setServiceWorkerClient(object : ServiceWorkerClient () {
108125 override fun shouldInterceptRequest (request : WebResourceRequest ): WebResourceResponse ? {
109- return null
126+ // return null
127+ if (! isNetworkAvailable()) {
128+ return cacheInterceptor.getResponse(request)
129+ }
130+
131+
132+ val networkResponse = super .shouldInterceptRequest(request)
133+ networkResponse?.let {
134+ cacheInterceptor.saveResponse(request.url.toString(), it)
135+ }
136+ return networkResponse
110137 }
111138 })
112139 }
113140 // --- End WebView settings ---
114141
115142 // Launch coroutine to find fastest URL
116- findFastestUrlAndLoad()
143+ // findFastestUrlAndLoad(list)
144+ myWebView?.loadUrl(list[0 ])
117145 } catch (e: Exception ) {
118146 Log .e(" WebActivity" , " Error in onCreate: ${e.message} " , e)
119147 finish()
@@ -146,7 +174,8 @@ class WebActivity : AppCompatActivity() {
146174 override fun onPageFinished (view : WebView ? , url : String? ) {
147175 super .onPageFinished(view, url)
148176 currentUrl = url ? : " "
149- if (! hasErrorInLoading) findViewById<RelativeLayout >(R .id.loading).visibility = View .INVISIBLE
177+ // if (!hasErrorInLoading)
178+ findViewById<RelativeLayout >(R .id.loading).visibility = View .INVISIBLE
150179 Log .d(TAG , " onPageFinished: $url " )
151180 }
152181
@@ -165,6 +194,21 @@ class WebActivity : AppCompatActivity() {
165194 onBackPressedDispatcher.addCallback(this , callback)
166195 }
167196
197+
198+ private fun getOfflineHtml (): String {
199+ return try {
200+ val inputStream = application.assets.open(" offline.html" )
201+ val size = inputStream.available()
202+ val buffer = ByteArray (size)
203+ inputStream.read(buffer)
204+ inputStream.close()
205+ String (buffer)
206+ } catch (e: IOException ) {
207+ " <h1>You're offline</h1><p>No cached content available</p>"
208+ }
209+ }
210+
211+
168212 private fun findFastestUrlAndLoad () {
169213 CoroutineScope (Dispatchers .IO ).launch {
170214 val fastestUrl = findFastestUrlOptimized()
@@ -238,6 +282,7 @@ class WebActivity : AppCompatActivity() {
238282 Log .e(" WebActivity" , " Error in concurrent URL testing: ${e.message} " )
239283 }
240284
285+
241286 fastestResult?.first
242287 }
243288 }
@@ -263,6 +308,7 @@ class WebActivity : AppCompatActivity() {
263308 throw Exception (" HTTP $responseCode " )
264309 }
265310 }
311+ Log .i(" aaa" ,responseTime.toString())
266312 responseTime
267313 } catch (e: Exception ) {
268314 Log .d(" WebActivity" , " URL $url failed: ${e.message} " )
@@ -271,6 +317,48 @@ class WebActivity : AppCompatActivity() {
271317 }
272318 }
273319
320+
321+ private fun findFastestUrlAndLoad (urls : List <String >) {
322+ coroutineScope.launch {
323+ val fastestUrl = findFastestUrl(urls)
324+ fastestUrl?.let {
325+ myWebView?.loadUrl(it)
326+ } ? : run {
327+ myWebView?.loadUrl(list[0 ])
328+ }
329+ }
330+ }
331+
332+ private suspend fun findFastestUrl (urls : List <String >): String? = coroutineScope {
333+ val jobs = urls.map { url ->
334+ async(Dispatchers .IO ) {
335+ try {
336+ val request = Request .Builder ()
337+ .url(url)
338+ .head() // فقط هدرها را دریافت میکند
339+ .build()
340+
341+ val responseTime = measureTimeMillis {
342+ client.newCall(request).execute().use { response ->
343+ if (! response.isSuccessful) null
344+ }
345+ }
346+
347+ if (responseTime > 0 ) url to responseTime else null
348+ } catch (e: Exception ) {
349+ null
350+ }
351+ }
352+ }
353+
354+ // منتظر ماندن برای اولین پاسخ موفق
355+ val results = jobs.awaitAll().filterNotNull()
356+
357+ // پیدا کردن سریعترین پاسخ
358+ results.minByOrNull { it.second }?.first
359+ }
360+
361+
274362 fun loadBrowser (url : String ){
275363 val uri: Uri = Uri .parse(url)
276364 val intent = Intent (Intent .ACTION_VIEW , uri)
@@ -341,6 +429,7 @@ class WebActivity : AppCompatActivity() {
341429 super .onResume()
342430 try {
343431 myWebView?.onResume()
432+ scope.cancel()
344433 } catch (e: Exception ) {
345434 Log .e(" WebActivity" , " Error in onResume: ${e.message} " )
346435 }
0 commit comments