diff --git a/app/src/main/java/org/ole/planet/myplanet/base/BaseContainerFragment.kt b/app/src/main/java/org/ole/planet/myplanet/base/BaseContainerFragment.kt index 9514ddd210..ce5b816888 100644 --- a/app/src/main/java/org/ole/planet/myplanet/base/BaseContainerFragment.kt +++ b/app/src/main/java/org/ole/planet/myplanet/base/BaseContainerFragment.kt @@ -131,25 +131,30 @@ abstract class BaseContainerFragment : BaseResourceFragment() { } fun openResource(items: RealmMyLibrary) { - val matchingItems = mRealm.where(RealmMyLibrary::class.java) - .equalTo("resourceLocalAddress", items.resourceLocalAddress) - .findAll() - val anyOffline = matchingItems.any { it.isResourceOffline() } - if (anyOffline) { - val offlineItem = matchingItems.first { it.isResourceOffline()} - openFileType(offlineItem, "offline") - } else { - if (items.isResourceOffline()) { - openFileType(items, "offline") - } else if (FileUtils.getFileExtension(items.resourceLocalAddress) == "mp4") { - openFileType(items, "online") - } else { - val arrayList = ArrayList() - arrayList.add(Utilities.getUrl(items)) - startDownload(arrayList) - profileDbHandler.setResourceOpenCount(items, KEY_RESOURCE_DOWNLOAD) - } + val intent = Intent(context, WebViewActivity::class.java).apply { + putExtra("isLocalFile", true) + putExtra("RESOURCE_ID", "6f7b1d7a7c9c73e736b38657bc013556") // The _id from the library item } + startActivity(intent) +// val matchingItems = mRealm.where(RealmMyLibrary::class.java) +// .equalTo("resourceLocalAddress", items.resourceLocalAddress) +// .findAll() +// val anyOffline = matchingItems.any { it.isResourceOffline() } +// if (anyOffline) { +// val offlineItem = matchingItems.first { it.isResourceOffline()} +// openFileType(offlineItem, "offline") +// } else { +// if (items.isResourceOffline()) { +// openFileType(items, "offline") +// } else if (FileUtils.getFileExtension(items.resourceLocalAddress) == "mp4") { +// openFileType(items, "online") +// } else { +// val arrayList = ArrayList() +// arrayList.add(Utilities.getUrl(items)) +// startDownload(arrayList) +// profileDbHandler.setResourceOpenCount(items, KEY_RESOURCE_DOWNLOAD) +// } +// } } private fun checkFileExtension(items: RealmMyLibrary) { diff --git a/app/src/main/java/org/ole/planet/myplanet/ui/viewer/WebViewActivity.kt b/app/src/main/java/org/ole/planet/myplanet/ui/viewer/WebViewActivity.kt index a847dda0de..00211064e7 100644 --- a/app/src/main/java/org/ole/planet/myplanet/ui/viewer/WebViewActivity.kt +++ b/app/src/main/java/org/ole/planet/myplanet/ui/viewer/WebViewActivity.kt @@ -1,27 +1,42 @@ package org.ole.planet.myplanet.ui.viewer +import android.annotation.TargetApi import android.graphics.Bitmap import android.net.Uri +import android.os.Build import android.os.Bundle import android.text.TextUtils import android.util.Log import android.view.View import android.webkit.CookieManager import android.webkit.WebChromeClient +import android.webkit.WebResourceRequest +import android.webkit.WebResourceResponse import android.webkit.WebSettings import android.webkit.WebView import android.webkit.WebViewClient import androidx.appcompat.app.AppCompatActivity +import androidx.core.content.FileProvider +import androidx.lifecycle.lifecycleScope +import io.realm.Realm +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext import org.ole.planet.myplanet.MainApplication import org.ole.planet.myplanet.databinding.ActivityWebViewBinding +import org.ole.planet.myplanet.model.RealmAttachment +import org.ole.planet.myplanet.model.RealmMyLibrary import org.ole.planet.myplanet.utilities.Utilities import java.io.File +import java.net.HttpURLConnection +import java.net.URL class WebViewActivity : AppCompatActivity() { private lateinit var activityWebViewBinding: ActivityWebViewBinding private var fromDeepLink = false private lateinit var link: String private var isLocalFile = false + var resourceId: String? = null override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) @@ -33,6 +48,16 @@ class WebViewActivity : AppCompatActivity() { link = intent.getStringExtra("link") ?: "" isLocalFile = intent.getBooleanExtra("isLocalFile", false) + if (isLocalFile) { + resourceId = intent.getStringExtra("RESOURCE_ID") + Log.d("okuro", "$resourceId") + if (!resourceId.isNullOrEmpty()) { + lifecycleScope.launch { + loadLocalHtmlResource(resourceId) + } + } + } + Log.d("WebViewActivity", "onCreate: $link, isLocalFile: $isLocalFile") clearCookie() @@ -52,26 +77,420 @@ class WebViewActivity : AppCompatActivity() { builtInZoomControls = true } -// activityWebViewBinding.contentWebView.wv.settings.javaScriptEnabled = true -// activityWebViewBinding.contentWebView.wv.settings.javaScriptCanOpenWindowsAutomatically = true - if (isLocalFile) { - val touchedFile = intent.getStringExtra("TOUCHED_FILE") - if (!touchedFile.isNullOrEmpty()) { - val localFilePath = File(MainApplication.context.getExternalFilesDir(null), touchedFile).absolutePath - activityWebViewBinding.contentWebView.wv.loadUrl("file://$localFilePath") - } - } else { - // Existing remote URL loading logic - val headers = mapOf("Authorization" to Utilities.header) - activityWebViewBinding.contentWebView.wv.loadUrl(link, headers) - } +// if (isLocalFile) { +// val touchedFile = intent.getStringExtra("TOUCHED_FILE") +// if (!touchedFile.isNullOrEmpty()) { +// val localFilePath = File(MainApplication.context.getExternalFilesDir(null), touchedFile).absolutePath +// activityWebViewBinding.contentWebView.wv.loadUrl("file://$localFilePath") +// } +// } else { +// val headers = mapOf("Authorization" to Utilities.header) +// activityWebViewBinding.contentWebView.wv.loadUrl(link, headers) +// } activityWebViewBinding.contentWebView.finish.setOnClickListener { finish() } setWebClient() } + private suspend fun loadLocalHtmlResource(resourceId: String?) { + withContext(Dispatchers.IO) { + try { + withContext(Dispatchers.Main) { + activityWebViewBinding.contentWebView.pBar.visibility = View.VISIBLE + } + + ensureResourceDownloaded(resourceId) + + val htmlContent = retrieveHtmlContentFromRealm(resourceId) + Log.d("okuro", "html content: $htmlContent") + + if (htmlContent != null) { + withContext(Dispatchers.Main) { + try { + // Create games directory in app's files directory + val gamesDir = File(filesDir, "games") + gamesDir.mkdirs() + + // Create resource-specific directory + val resourceDir = File(gamesDir, resourceId) + resourceDir.mkdirs() + + val htmlFile = File(resourceDir, "index.html") + htmlFile.writeText(htmlContent) + + // Create necessary directories + File(resourceDir, "js").mkdirs() + File(resourceDir, "style").mkdirs() + File(resourceDir, "style/fonts").mkdirs() + File(resourceDir, "meta").mkdirs() + + // Copy all required files + copyJsFiles(resourceDir) + copyCssFiles(resourceDir) + copyFontFiles(resourceDir) + copyMetaFiles(resourceDir) + + // Create URI using FileProvider + val contentUri = FileProvider.getUriForFile( + this@WebViewActivity, + "${packageName}.fileprovider", + htmlFile + ) + + // Grant read permission to WebView + activityWebViewBinding.contentWebView.wv.settings.allowFileAccess = true + activityWebViewBinding.contentWebView.wv.settings.allowFileAccessFromFileURLs = true + activityWebViewBinding.contentWebView.wv.loadUrl(contentUri.toString()) + Log.d("okuro", "Loading from: ${contentUri}") + } catch (e: Exception) { + Log.e("WebViewActivity", "Error preparing HTML content", e) + e.printStackTrace() + } finally { + activityWebViewBinding.contentWebView.pBar.visibility = View.GONE + } + } + } + } catch (e: Exception) { + Log.e("WebViewActivity", "Error loading local HTML", e) + withContext(Dispatchers.Main) { + activityWebViewBinding.contentWebView.pBar.visibility = View.GONE + } + } + } + } + + private suspend fun copyJsFiles(resourceDir: File) { + withContext(Dispatchers.IO) { + val jsDir = File(resourceDir, "js") + copyFiles(resourceDir, "js/", "application/javascript", jsDir) + } + } + + private suspend fun copyCssFiles(resourceDir: File) { + withContext(Dispatchers.IO) { + val styleDir = File(resourceDir, "style") + copyFiles(resourceDir, "style/", "text/css", styleDir) + } + } + + private suspend fun copyFontFiles(resourceDir: File) { + withContext(Dispatchers.IO) { + val fontsDir = File(resourceDir, "style/fonts") + val fontTypes = listOf( + "application/font-woff", + "application/vnd.ms-fontobject", + "image/svg+xml" + ) + fontTypes.forEach { type -> + copyFiles(resourceDir, "style/fonts/", type, fontsDir) + } + } + } + + private suspend fun copyMetaFiles(resourceDir: File) { + withContext(Dispatchers.IO) { + val metaDir = File(resourceDir, "meta") + copyFiles(resourceDir, "meta/", "image/png", metaDir) + } + } + + private suspend fun copyFiles(resourceDir: File, prefix: String, contentType: String, destDir: File) { + val realm = Realm.getDefaultInstance() + try { + val libraryItem = realm.where(RealmMyLibrary::class.java) + .equalTo("_id", resourceId) + .findFirst() + + val attachments = libraryItem?.attachments?.filter { + it.name?.startsWith(prefix) == true && it.contentType == contentType + } + + attachments?.forEach { attachment -> + val fileName = attachment.name?.substringAfterLast("/") + if (fileName != null) { + val destFile = File(destDir, fileName) + + val fullAttachment = realm.where(RealmAttachment::class.java) + .equalTo("id", attachment.id) + .findFirst() + + fullAttachment?.let { + val fileContent = readAttachmentContent(it) + if (fileContent != null) { + destFile.writeText(fileContent) + Log.d("WebViewActivity", "Copied file: ${attachment.name}") + } + } + } + } + } catch (e: Exception) { + Log.e("WebViewActivity", "Error copying files for prefix $prefix", e) + } finally { + realm.close() + } + } + + private suspend fun retrieveHtmlContentFromRealm(resourceId: String?): String? { + return withContext(Dispatchers.IO) { + val realm = Realm.getDefaultInstance() + try { + val libraryItem = realm.where(RealmMyLibrary::class.java) + .equalTo("_id", resourceId) + .findFirst() + + val htmlAttachment = libraryItem?.attachments?.firstOrNull { + it.name == "index.html" + } + + Log.d("okuro", "htmlAttachment: $htmlAttachment") + + htmlAttachment?.let { attachment -> + realm.where(RealmAttachment::class.java) + .equalTo("id", attachment.id) + .findFirst() + ?.let { fullAttachment -> + readAttachmentContent(fullAttachment) + } + } + } catch (e: Exception) { + Log.e("WebViewActivity", "Error retrieving HTML from Realm", e) + null + } finally { + realm.close() + } + } + } + + private suspend fun readAttachmentContent(attachment: RealmAttachment): String? { + return withContext(Dispatchers.IO) { + val file = File(MainApplication.context.getExternalFilesDir(null), attachment.name) + Log.d("WebViewActivity", "Checking file: ${file.absolutePath}, exists: ${file.exists()}") + if (file.exists()) { + file.readText() + } else { + null + } + } + } + + private suspend fun ensureResourceDownloaded(resourceId: String?) { + if (resourceId == null) return + + withContext(Dispatchers.IO) { + val resourceDir = createResourceDirectoryStructure(resourceId) + val realm = Realm.getDefaultInstance() + + try { + Log.d("WebViewActivity", "Looking for resource with ID: $resourceId") + val libraryItem = realm.where(RealmMyLibrary::class.java) + .equalTo("_id", resourceId) + .findFirst() + + if (libraryItem == null) { + Log.e("WebViewActivity", "Library item not found for ID: $resourceId") + return@withContext + } + + Log.d("WebViewActivity", "Found library item with ${libraryItem.attachments?.size} attachments") + + libraryItem.attachments?.forEach { attachment -> + if (attachment.isStub) { + val destFile = File(resourceDir, attachment.name) + if (!destFile.exists()) { + Log.d("WebViewActivity", "Attempting to download: ${attachment.name}") + try { + val content = downloadAttachmentContent(attachment) + if (content != null) { + destFile.parentFile?.mkdirs() + destFile.writeText(content) + Log.d("WebViewActivity", "Successfully saved: ${attachment.name}") + } else { + Log.e("WebViewActivity", "Failed to download: ${attachment.name}") + } + } catch (e: Exception) { + Log.e("WebViewActivity", "Error downloading ${attachment.name}", e) + } + } else { + Log.d("WebViewActivity", "File already exists: ${attachment.name}") + } + } + } + } catch (e: Exception) { + Log.e("WebViewActivity", "Error in ensureResourceDownloaded", e) + } finally { + realm.close() + } + } + } + + private suspend fun downloadAttachmentContent(attachment: RealmAttachment): String? { + return withContext(Dispatchers.IO) { + val realm = Realm.getDefaultInstance() + try { + val libraryItem = realm.where(RealmMyLibrary::class.java) + .equalTo("_id", resourceId) + .findFirst() + + Log.d("WebViewActivity", "Found library item: ${libraryItem?._id}") + Log.d("WebViewActivity", "Remote address: ${libraryItem?.resourceRemoteAddress}") + + val remoteAddress = libraryItem?.resourceRemoteAddress + if (remoteAddress == null) { + Log.e("WebViewActivity", "Remote address not found for resource") + return@withContext null + } + + val baseUrl = if (remoteAddress.contains("@")) { + val urlParts = remoteAddress.split("@") + val auth = urlParts[0] + val restOfUrl = urlParts[1] + "${Utilities.getUrl()}$restOfUrl" + } else { + remoteAddress + }.substringBeforeLast("/") + + val attachmentUrl = "$baseUrl/${attachment.name}" + Log.d("WebViewActivity", "Attempting to download from: $attachmentUrl") + + try { + val url = URL(attachmentUrl) + val connection = url.openConnection() as HttpURLConnection + connection.requestMethod = "GET" + + if (remoteAddress.contains("@")) { + val auth = remoteAddress.split("@")[0].split("://")[1] + val base64Auth = android.util.Base64.encodeToString( + auth.toByteArray(), + android.util.Base64.NO_WRAP + ) + connection.setRequestProperty("Authorization", "Basic $base64Auth") + } + + try { + val responseCode = connection.responseCode + Log.d("WebViewActivity", "Response code for ${attachment.name}: $responseCode") + + if (responseCode == HttpURLConnection.HTTP_OK) { + val content = connection.inputStream.bufferedReader().use { it.readText() } + + val localFile = File(MainApplication.context.getExternalFilesDir(null), attachment.name) + localFile.parentFile?.mkdirs() + localFile.writeText(content) + + Log.d("WebViewActivity", "Successfully downloaded ${attachment.name}") + return@withContext content + } else { + val errorStream = connection.errorStream?.bufferedReader()?.use { it.readText() } + Log.e("WebViewActivity", "Failed to download attachment: HTTP $responseCode") + Log.e("WebViewActivity", "Error response: $errorStream") + return@withContext null + } + } catch (e: Exception) { + Log.e("WebViewActivity", "Connection error for ${attachment.name}", e) + return@withContext null + } finally { + connection.disconnect() + } + } catch (e: Exception) { + Log.e("WebViewActivity", "Error processing URL for ${attachment.name}", e) + return@withContext null + } + } catch (e: Exception) { + Log.e("WebViewActivity", "Error in Realm query", e) + return@withContext null + } finally { + realm.close() + } + } + } + + private fun createResourceDirectoryStructure(resourceId: String): File { + val gamesDir = File(filesDir, "games") + gamesDir.mkdirs() + + val resourceDir = File(gamesDir, resourceId) + resourceDir.mkdirs() + + File(resourceDir, "js").mkdirs() + File(resourceDir, "style").mkdirs() + File(resourceDir, "style/fonts").mkdirs() + File(resourceDir, "meta").mkdirs() + + return resourceDir + } private fun setWebClient() { activityWebViewBinding.contentWebView.wv.webViewClient = object : WebViewClient() { + @TargetApi(Build.VERSION_CODES.LOLLIPOP) + override fun shouldInterceptRequest(view: WebView?, request: WebResourceRequest?): WebResourceResponse? { + if (request == null) return null + + val url = request.url.toString() + val gamesDir = File(filesDir, "games") + val resourceDir = File(gamesDir, resourceId) + + try { + // Handle JS files + if (url.contains("/js/")) { + val fileName = url.substringAfterLast("/") + val file = File(resourceDir, "js/$fileName") + if (file.exists()) { + return WebResourceResponse( + "application/javascript", + "UTF-8", + file.inputStream() + ) + } + } + + // Handle CSS files + if (url.contains("/style/") && !url.contains("/fonts/")) { + val fileName = url.substringAfterLast("/") + val file = File(resourceDir, "style/$fileName") + if (file.exists()) { + return WebResourceResponse( + "text/css", + "UTF-8", + file.inputStream() + ) + } + } + + // Handle font files + if (url.contains("/fonts/")) { + val fileName = url.substringAfterLast("/") + val file = File(resourceDir, "style/fonts/$fileName") + if (file.exists()) { + val mimeType = when { + fileName.endsWith(".woff") -> "application/font-woff" + fileName.endsWith(".eot") -> "application/vnd.ms-fontobject" + fileName.endsWith(".svg") -> "image/svg+xml" + else -> "application/octet-stream" + } + return WebResourceResponse(mimeType, "UTF-8", file.inputStream()) + } + } + + // Handle meta files + if (url.contains("/meta/")) { + val fileName = url.substringAfterLast("/") + val file = File(resourceDir, "meta/$fileName") + if (file.exists()) { + return WebResourceResponse( + "image/png", + "UTF-8", + file.inputStream() + ) + } + } + + Log.d("WebViewActivity", "Resource not found: $url") + } catch (e: Exception) { + Log.e("WebViewActivity", "Error intercepting request: $url", e) + } + + return super.shouldInterceptRequest(view, request) + } + override fun onPageStarted(view: WebView, url: String, favicon: Bitmap?) { super.onPageStarted(view, url, favicon) if (url.endsWith("/eng/")) { @@ -80,10 +499,6 @@ class WebViewActivity : AppCompatActivity() { val i = Uri.parse(url) activityWebViewBinding.contentWebView.webSource.text = i.host } - - override fun onPageFinished(view: WebView, url: String) { - super.onPageFinished(view, url) - } } } @@ -112,5 +527,4 @@ class WebViewActivity : AppCompatActivity() { } } } - } diff --git a/app/src/main/res/xml/file_paths.xml b/app/src/main/res/xml/file_paths.xml index 8248868e52..b8ff724e09 100644 --- a/app/src/main/res/xml/file_paths.xml +++ b/app/src/main/res/xml/file_paths.xml @@ -1,4 +1,5 @@ + \ No newline at end of file