버그 수정 및 최적화
This commit is contained in:
@@ -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) {
|
||||
val deferredRows = coroutineScope {
|
||||
TvmonScraper.CATEGORIES.values.map { category ->
|
||||
Log.w(TAG, "Loading category: ${category.key} - ${category.name}")
|
||||
val success = loadCategoryWithCache(category)
|
||||
if (success) successCount++ else failCount++
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,72 +422,7 @@ 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,11 +433,10 @@ 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)
|
||||
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")
|
||||
android.util.Log.d("PlaybackActivity", "OK button pressed")
|
||||
simulateCenterClick()
|
||||
injectEnhancedAutoPlayScript()
|
||||
toggleVideoPlayback()
|
||||
return true
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -1,47 +1,40 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="108dp"
|
||||
android:height="108dp"
|
||||
android:viewportWidth="108"
|
||||
android:viewportHeight="108">
|
||||
android:width="108dp"
|
||||
android:height="108dp"
|
||||
android:viewportWidth="108"
|
||||
android:viewportHeight="108">
|
||||
|
||||
<!-- TV Screen with white color -->
|
||||
<path
|
||||
android:fillColor="#FFFFFF"
|
||||
android:pathData="M34,38h40v28h-40z"/>
|
||||
<!-- TV Screen outline - white stroke style for visibility -->
|
||||
<path
|
||||
android:fillColor="#FFFFFF"
|
||||
android:pathData="M26,26h56v40h-56z" />
|
||||
|
||||
<!-- TV Stand -->
|
||||
<path
|
||||
android:fillColor="#FFFFFF"
|
||||
android:pathData="M44,70h20v4h-20z"/>
|
||||
<path
|
||||
android:fillColor="#FFFFFF"
|
||||
android:pathData="M40,74h28v2h-28z"/>
|
||||
<!-- Inner screen area (transparent to show background) -->
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:strokeColor="#E50914"
|
||||
android:strokeWidth="2"
|
||||
android:pathData="M30,30h48v32h-48z" />
|
||||
|
||||
<!-- Play Button - using Netflix red -->
|
||||
<path
|
||||
android:fillColor="#E50914"
|
||||
android:pathData="M50,44l12,8l-12,8z"/>
|
||||
<!-- Play Button - white triangle centered in screen -->
|
||||
<path
|
||||
android:fillColor="#FFFFFF"
|
||||
android:pathData="M46,38 L46,56 L62,47 Z" />
|
||||
|
||||
<!-- TV Stand - left leg -->
|
||||
<path
|
||||
android:fillColor="#FFFFFF"
|
||||
android:pathData="M38,66 L42,66 L42,76 L38,76 Z" />
|
||||
|
||||
<!-- TV Stand - right leg -->
|
||||
<path
|
||||
android:fillColor="#FFFFFF"
|
||||
android:pathData="M66,66 L70,66 L70,76 L70,76 Z" />
|
||||
|
||||
<!-- TV Stand base -->
|
||||
<path
|
||||
android:fillColor="#FFFFFF"
|
||||
android:pathData="M34,76 L74,76 L74,80 L34,80 Z" />
|
||||
|
||||
<!-- tvmon Text - white -->
|
||||
<!-- Letter 't' -->
|
||||
<path
|
||||
android:fillColor="#FFFFFF"
|
||||
android:pathData="M38,82h4v-2h-4v-2h6v10h-2v-6h-4z"/>
|
||||
<!-- Letter 'v' -->
|
||||
<path
|
||||
android:fillColor="#FFFFFF"
|
||||
android:pathData="M45,78h2l2,6l2,-6h2l-3,8h-2z"/>
|
||||
<!-- Letter 'm' -->
|
||||
<path
|
||||
android:fillColor="#FFFFFF"
|
||||
android:pathData="M54,86v-8h2v1c0.5,-0.7 1.2,-1 2,-1c0.9,0 1.6,0.4 2,1.1c0.5,-0.7 1.3,-1.1 2.2,-1.1c1.6,0 2.8,1.2 2.8,2.8v5.2h-2v-5c0,-0.9 -0.7,-1.5 -1.5,-1.5c-0.9,0 -1.5,0.6 -1.5,1.5v5h-2v-5c0,-0.9 -0.7,-1.5 -1.5,-1.5c-0.9,0 -1.5,0.6 -1.5,1.5v5z"/>
|
||||
<!-- Letter 'o' -->
|
||||
<path
|
||||
android:fillColor="#FFFFFF"
|
||||
android:pathData="M66,82c0,-2.2 1.8,-4 4,-4s4,1.8 4,4s-1.8,4 -4,4s-4,-1.8 -4,-4zm2,0c0,1.1 0.9,2 2,2s2,-0.9 2,-2s-0.9,-2 -2,-2s-2,0.9 -2,2z"/>
|
||||
<!-- Letter 'n' -->
|
||||
<path
|
||||
android:fillColor="#FFFFFF"
|
||||
android:pathData="M76,86v-8h2v1c0.6,-0.7 1.4,-1 2.3,-1c1.9,0 3.2,1.3 3.2,3.2v4.8h-2v-4.5c0,-1 -0.7,-1.7 -1.7,-1.7c-1,0 -1.8,0.7 -1.8,1.7v4.5z"/>
|
||||
</vector>
|
||||
@@ -1,8 +1,12 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<network-security-config>
|
||||
<base-config cleartextTrafficPermitted="true">
|
||||
<trust-anchors>
|
||||
<certificates src="system" />
|
||||
</trust-anchors>
|
||||
</base-config>
|
||||
<base-config cleartextTrafficPermitted="true">
|
||||
<trust-anchors>
|
||||
<certificates src="system" />
|
||||
</trust-anchors>
|
||||
</base-config>
|
||||
<domain-config cleartextTrafficPermitted="true">
|
||||
<domain includeSubdomains="true">tvmon.site</domain>
|
||||
<domain includeSubdomains="true">git.webpluss.net</domain>
|
||||
</domain-config>
|
||||
</network-security-config>
|
||||
|
||||
Reference in New Issue
Block a user