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
+