From f864ea6d1c3d5f71c889dfc046b4c29eee5c2892 Mon Sep 17 00:00:00 2001 From: tvmon-dev Date: Thu, 16 Apr 2026 19:55:32 +0900 Subject: [PATCH] =?UTF-8?q?=EB=B2=84=EA=B7=B8=20=EC=88=98=EC=A0=95=20?= =?UTF-8?q?=EB=B0=8F=20=EC=B5=9C=EC=A0=81=ED=99=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/example/tvmon/ui/main/MainFragment.kt | 82 +++++++-- .../tvmon/ui/playback/PlaybackActivity.kt | 169 ++++-------------- .../com/example/tvmon/util/UpdateChecker.kt | 4 +- .../res/drawable/ic_launcher_foreground.xml | 75 ++++---- .../main/res/xml/network_security_config.xml | 14 +- 5 files changed, 148 insertions(+), 196 deletions(-) diff --git a/tvmon-app/app/src/main/java/com/example/tvmon/ui/main/MainFragment.kt b/tvmon-app/app/src/main/java/com/example/tvmon/ui/main/MainFragment.kt index b9b9222..7c587bd 100644 --- a/tvmon-app/app/src/main/java/com/example/tvmon/ui/main/MainFragment.kt +++ b/tvmon-app/app/src/main/java/com/example/tvmon/ui/main/MainFragment.kt @@ -16,7 +16,11 @@ import com.example.tvmon.data.scraper.TvmonScraper import com.example.tvmon.ui.detail.DetailsActivity import com.example.tvmon.ui.presenter.ContentCardPresenter import com.example.tvmon.ui.settings.SettingsActivity +import kotlinx.coroutines.async +import kotlinx.coroutines.awaitAll +import kotlinx.coroutines.coroutineScope import kotlinx.coroutines.launch +import kotlinx.coroutines.runBlocking import org.koin.android.ext.android.inject class MainFragment : BrowseSupportFragment(), OnItemViewClickedListener, OnItemViewSelectedListener { @@ -96,23 +100,67 @@ private fun setupUI() { var successCount = 0 var failCount = 0 - for (category in TvmonScraper.CATEGORIES.values) { - Log.w(TAG, "Loading category: ${category.key} - ${category.name}") - val success = loadCategoryWithCache(category) - if (success) successCount++ else failCount++ + val deferredRows = coroutineScope { + TvmonScraper.CATEGORIES.values.map { category -> + Log.w(TAG, "Loading category: ${category.key} - ${category.name}") + async { + val row = loadCategoryRow(category) + category.key to row + } + } } -Log.w(TAG, "=== loadCategories COMPLETE: success=$successCount, fail=$failCount ===") -isDataLoaded = true + for (deferred in deferredRows) { + val (key, row) = deferred.await() + if (row != null) { + activity?.runOnUiThread { + rowsAdapter.add(row) + } + successCount++ + } else { + failCount++ + } + } -if (rowsAdapter.size() == 0) { -Toast.makeText(requireContext(), "카테고리를 불러오지 못했습니다", Toast.LENGTH_LONG).show() -} + Log.w(TAG, "=== loadCategories COMPLETE: success=$successCount, fail=$failCount ===") + isDataLoaded = true -activity?.runOnUiThread { -addSettingsRow() -} -} + if (rowsAdapter.size() == 0) { + Toast.makeText(requireContext(), "카테고리를 불러오지 못했습니다", Toast.LENGTH_LONG).show() + } + + activity?.runOnUiThread { + addSettingsRow() + } + } + + private suspend fun loadCategoryRow(category: com.example.tvmon.data.model.Category): ListRow? { + return try { + Log.w(TAG, "loadCategoryRow: ${category.key}") + + val result = categoryCacheRepository.getCategoryWithCache(category.key, page = 1) + + if (result.success && result.items.isNotEmpty()) { + val listRowAdapter = ArrayObjectAdapter(ContentCardPresenter()) + result.items.forEach { content -> + listRowAdapter.add(content) + } + + val header = HeaderItem(category.name) + val row = ListRow(header, listRowAdapter) + + categoryRowAdapters[category.key] = listRowAdapter + Log.w(TAG, "Prepared ROW: ${category.name} with ${result.items.size} items (fromCache=${result.fromCache})") + row + } else { + Log.w(TAG, "No items for ${category.key}") + null + } + } catch (e: Exception) { + Log.e(TAG, "ERROR loading ${category.key}: ${e.javaClass.simpleName}: ${e.message}", e) + null + } + } private fun addSettingsRow() { val settingsAdapter = ArrayObjectAdapter(ContentCardPresenter()) @@ -186,11 +234,17 @@ Log.w(TAG, "Unknown item type: ${item?.javaClass?.simpleName}") rowViewHolder: RowPresenter.ViewHolder?, row: Row? ) { + // Early return for null items + if (item == null) return + + // Early return for non-Content items (e.g., SettingsItem) + if (item !is Content) return + if (row is ListRow) { val rowIndex = rowsAdapter.indexOf(row) if (rowIndex >= 0) { val categoryKey = findCategoryKeyForRow(rowIndex) - if (categoryKey != null && item is Content) { + if (categoryKey != null) { checkAndLoadMore(categoryKey) } } diff --git a/tvmon-app/app/src/main/java/com/example/tvmon/ui/playback/PlaybackActivity.kt b/tvmon-app/app/src/main/java/com/example/tvmon/ui/playback/PlaybackActivity.kt index 138c43e..ff6f363 100644 --- a/tvmon-app/app/src/main/java/com/example/tvmon/ui/playback/PlaybackActivity.kt +++ b/tvmon-app/app/src/main/java/com/example/tvmon/ui/playback/PlaybackActivity.kt @@ -177,44 +177,12 @@ webSettings.mediaPlaybackRequiresUserGesture = false webSettings.blockNetworkImage = false webView.addJavascriptInterface(object { -@android.webkit.JavascriptInterface -fun hideLoadingOverlay() { -runOnUiThread { -loadingOverlay.visibility = View.GONE -} -} - -@android.webkit.JavascriptInterface -fun isVideoPlaying() { -webView.evaluateJavascript( -""" -(function() { -var videos = document.querySelectorAll('video'); -var playing = false; -videos.forEach(function(v) { -if (!v.paused && v.currentTime > 0) { -playing = true; -} -}); -return playing ? 'playing' : 'not_playing'; -})(); -""".trimIndent() -) { result -> -android.util.Log.i("PlaybackActivity", "Video playing status: $result") -if (result == "\"playing\"") { -runOnUiThread { -loadingOverlay.visibility = View.GONE -} -} -} -} -@android.webkit.JavascriptInterface -fun onVideoPlay() { -runOnUiThread { -loadingOverlay.visibility = View.GONE -android.util.Log.d("PlaybackActivity", "Video started playing") -} -} + @android.webkit.JavascriptInterface + fun hideLoadingOverlay() { + runOnUiThread { + loadingOverlay.visibility = View.GONE + } + } }, "Android") val cookieManager = CookieManager.getInstance() @@ -277,30 +245,29 @@ android.util.Log.w("PlaybackActivity", "Intercept failed for $url: ${e.message}" return super.shouldInterceptRequest(view, request) } -override fun onPageStarted(view: WebView?, url: String?, favicon: android.graphics.Bitmap?) { -super.onPageStarted(view, url, favicon) -runOnUiThread { -loadingOverlay.visibility = View.VISIBLE -} -} + override fun onPageStarted(view: WebView?, url: String?, favicon: android.graphics.Bitmap?) { + super.onPageStarted(view, url, favicon) + handler.removeCallbacksAndMessages(null) + runOnUiThread { + loadingOverlay.visibility = View.VISIBLE + } + } -override fun onPageFinished(view: WebView?, url: String?) { -super.onPageFinished(view, url) -android.util.Log.i("PlaybackActivity", "Page finished: $url") + override fun onPageFinished(view: WebView?, url: String?) { + super.onPageFinished(view, url) + android.util.Log.i("PlaybackActivity", "Page finished: $url") -// Reduced delays and calls to minimize memory pressure -handler.postDelayed({ -injectFullscreenScript() -injectEnhancedAutoPlayScript() -}, 300) + handler.postDelayed({ + injectFullscreenScript() + }, 300) -handler.postDelayed({ -runOnUiThread { -loadingOverlay.visibility = View.GONE -} -}, 1500) -} -} + handler.postDelayed({ + runOnUiThread { + loadingOverlay.visibility = View.GONE + } + }, 1500) + } + } // Use hardware layer only when needed, not always webView.setLayerType(View.LAYER_TYPE_NONE, null) @@ -455,73 +422,8 @@ webView.setLayerType(View.LAYER_TYPE_NONE, null) }); }; -makePlayerFullscreen(); -setTimeout(makePlayerFullscreen, 500); -setTimeout(makePlayerFullscreen, 2000); -})(); - """.trimIndent(), - null - ) - } - - private fun injectEnhancedAutoPlayScript() { - webView.evaluateJavascript( - """ - (function() { - console.log('AutoPlay: Starting...'); - - var tryPlay = function(v) { - console.log('AutoPlay: Trying to play video...'); - v.muted = true; - v.setAttribute('playsinline', 'true'); - - v.play().then(function() { - console.log('AutoPlay: Video play() succeeded'); - if (window.Android) { - window.Android.onVideoPlay(); - } - }).catch(function(e) { - console.log('AutoPlay: Video play() failed: ' + e.message); - }); - }; - - var initVideo = function(v) { - v.muted = true; - v.setAttribute('playsinline', 'true'); - v.setAttribute('webkit-playsinline', 'true'); - - v.addEventListener('canplaythrough', function() { - console.log('AutoPlay: canplaythrough event fired'); - tryPlay(v); - }, {once: true}); - - v.addEventListener('playing', function() { - console.log('AutoPlay: Video is now playing'); - if (window.Android) { - window.Android.onVideoPlay(); - } - }, {once: true}); - - if (v.readyState >= 2) { - tryPlay(v); - } - }; - - var videos = document.querySelectorAll('video'); - console.log('AutoPlay: Found ' + videos.length + ' videos'); - videos.forEach(initVideo); - - var iframes = document.querySelectorAll('iframe'); - iframes.forEach(function(f) { - try { - var innerDoc = f.contentDocument || f.contentWindow.document; - var innerVideos = innerDoc.querySelectorAll('video'); - innerVideos.forEach(initVideo); - } catch(e) { - console.log('AutoPlay: Cannot access iframe'); - } - }); - })(); + makePlayerFullscreen(); + })(); """.trimIndent(), null ) @@ -531,14 +433,13 @@ setTimeout(makePlayerFullscreen, 2000); val keyCode = event?.keyCode ?: return super.dispatchKeyEvent(event) val action = event.action - if ((keyCode == KeyEvent.KEYCODE_DPAD_CENTER || keyCode == KeyEvent.KEYCODE_ENTER) - && action == KeyEvent.ACTION_DOWN) { - android.util.Log.d("PlaybackActivity", "OK button pressed, triggering enhanced autoplay") - simulateCenterClick() - injectEnhancedAutoPlayScript() - toggleVideoPlayback() - return true - } +if ((keyCode == KeyEvent.KEYCODE_DPAD_CENTER || keyCode == KeyEvent.KEYCODE_ENTER) + && action == KeyEvent.ACTION_DOWN) { + android.util.Log.d("PlaybackActivity", "OK button pressed") + simulateCenterClick() + toggleVideoPlayback() + return true + } if (keyCode == KeyEvent.KEYCODE_BACK && action == KeyEvent.ACTION_DOWN) { if (webView.canGoBack()) { diff --git a/tvmon-app/app/src/main/java/com/example/tvmon/util/UpdateChecker.kt b/tvmon-app/app/src/main/java/com/example/tvmon/util/UpdateChecker.kt index 71f5b54..165afe0 100644 --- a/tvmon-app/app/src/main/java/com/example/tvmon/util/UpdateChecker.kt +++ b/tvmon-app/app/src/main/java/com/example/tvmon/util/UpdateChecker.kt @@ -17,8 +17,8 @@ data class VersionInfo( object UpdateChecker { private const val TAG = "UpdateChecker" - private const val VERSION_JSON_URL = "https://tvmon.site/version.json" - private const val APK_BASE_URL = "https://tvmon.site/releases" + private const val VERSION_JSON_URL = "https://git.webpluss.net/sanjeok77/tvmon_release/releases/download/version/version.json" + private const val APK_BASE_URL = "https://git.webpluss.net/sanjeok77/tvmon_release/releases/download" fun getCurrentVersionCode(context: Context): Int { return try { diff --git a/tvmon-app/app/src/main/res/drawable/ic_launcher_foreground.xml b/tvmon-app/app/src/main/res/drawable/ic_launcher_foreground.xml index ace717b..7f6eabb 100644 --- a/tvmon-app/app/src/main/res/drawable/ic_launcher_foreground.xml +++ b/tvmon-app/app/src/main/res/drawable/ic_launcher_foreground.xml @@ -1,47 +1,40 @@ + android:width="108dp" + android:height="108dp" + android:viewportWidth="108" + android:viewportHeight="108"> - - + + - - - + + - - + + - - - - - - - - - - - - \ No newline at end of file + + + + + + + + + + diff --git a/tvmon-app/app/src/main/res/xml/network_security_config.xml b/tvmon-app/app/src/main/res/xml/network_security_config.xml index d7b4192..c2acbf5 100644 --- a/tvmon-app/app/src/main/res/xml/network_security_config.xml +++ b/tvmon-app/app/src/main/res/xml/network_security_config.xml @@ -1,8 +1,12 @@ - - - - - + + + + + + +tvmon.site +git.webpluss.net +