버그 수정 및 최적화
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.detail.DetailsActivity
|
||||||
import com.example.tvmon.ui.presenter.ContentCardPresenter
|
import com.example.tvmon.ui.presenter.ContentCardPresenter
|
||||||
import com.example.tvmon.ui.settings.SettingsActivity
|
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.launch
|
||||||
|
import kotlinx.coroutines.runBlocking
|
||||||
import org.koin.android.ext.android.inject
|
import org.koin.android.ext.android.inject
|
||||||
|
|
||||||
class MainFragment : BrowseSupportFragment(), OnItemViewClickedListener, OnItemViewSelectedListener {
|
class MainFragment : BrowseSupportFragment(), OnItemViewClickedListener, OnItemViewSelectedListener {
|
||||||
@@ -96,23 +100,67 @@ private fun setupUI() {
|
|||||||
var successCount = 0
|
var successCount = 0
|
||||||
var failCount = 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}")
|
Log.w(TAG, "Loading category: ${category.key} - ${category.name}")
|
||||||
val success = loadCategoryWithCache(category)
|
async {
|
||||||
if (success) successCount++ else failCount++
|
val row = loadCategoryRow(category)
|
||||||
|
category.key to row
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Log.w(TAG, "=== loadCategories COMPLETE: success=$successCount, fail=$failCount ===")
|
for (deferred in deferredRows) {
|
||||||
isDataLoaded = true
|
val (key, row) = deferred.await()
|
||||||
|
if (row != null) {
|
||||||
|
activity?.runOnUiThread {
|
||||||
|
rowsAdapter.add(row)
|
||||||
|
}
|
||||||
|
successCount++
|
||||||
|
} else {
|
||||||
|
failCount++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (rowsAdapter.size() == 0) {
|
Log.w(TAG, "=== loadCategories COMPLETE: success=$successCount, fail=$failCount ===")
|
||||||
Toast.makeText(requireContext(), "카테고리를 불러오지 못했습니다", Toast.LENGTH_LONG).show()
|
isDataLoaded = true
|
||||||
}
|
|
||||||
|
|
||||||
activity?.runOnUiThread {
|
if (rowsAdapter.size() == 0) {
|
||||||
addSettingsRow()
|
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() {
|
private fun addSettingsRow() {
|
||||||
val settingsAdapter = ArrayObjectAdapter(ContentCardPresenter())
|
val settingsAdapter = ArrayObjectAdapter(ContentCardPresenter())
|
||||||
@@ -186,11 +234,17 @@ Log.w(TAG, "Unknown item type: ${item?.javaClass?.simpleName}")
|
|||||||
rowViewHolder: RowPresenter.ViewHolder?,
|
rowViewHolder: RowPresenter.ViewHolder?,
|
||||||
row: Row?
|
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) {
|
if (row is ListRow) {
|
||||||
val rowIndex = rowsAdapter.indexOf(row)
|
val rowIndex = rowsAdapter.indexOf(row)
|
||||||
if (rowIndex >= 0) {
|
if (rowIndex >= 0) {
|
||||||
val categoryKey = findCategoryKeyForRow(rowIndex)
|
val categoryKey = findCategoryKeyForRow(rowIndex)
|
||||||
if (categoryKey != null && item is Content) {
|
if (categoryKey != null) {
|
||||||
checkAndLoadMore(categoryKey)
|
checkAndLoadMore(categoryKey)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -177,44 +177,12 @@ webSettings.mediaPlaybackRequiresUserGesture = false
|
|||||||
webSettings.blockNetworkImage = false
|
webSettings.blockNetworkImage = false
|
||||||
|
|
||||||
webView.addJavascriptInterface(object {
|
webView.addJavascriptInterface(object {
|
||||||
@android.webkit.JavascriptInterface
|
@android.webkit.JavascriptInterface
|
||||||
fun hideLoadingOverlay() {
|
fun hideLoadingOverlay() {
|
||||||
runOnUiThread {
|
runOnUiThread {
|
||||||
loadingOverlay.visibility = View.GONE
|
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")
|
}, "Android")
|
||||||
|
|
||||||
val cookieManager = CookieManager.getInstance()
|
val cookieManager = CookieManager.getInstance()
|
||||||
@@ -277,30 +245,29 @@ android.util.Log.w("PlaybackActivity", "Intercept failed for $url: ${e.message}"
|
|||||||
return super.shouldInterceptRequest(view, request)
|
return super.shouldInterceptRequest(view, request)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onPageStarted(view: WebView?, url: String?, favicon: android.graphics.Bitmap?) {
|
override fun onPageStarted(view: WebView?, url: String?, favicon: android.graphics.Bitmap?) {
|
||||||
super.onPageStarted(view, url, favicon)
|
super.onPageStarted(view, url, favicon)
|
||||||
runOnUiThread {
|
handler.removeCallbacksAndMessages(null)
|
||||||
loadingOverlay.visibility = View.VISIBLE
|
runOnUiThread {
|
||||||
}
|
loadingOverlay.visibility = View.VISIBLE
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override fun onPageFinished(view: WebView?, url: String?) {
|
override fun onPageFinished(view: WebView?, url: String?) {
|
||||||
super.onPageFinished(view, url)
|
super.onPageFinished(view, url)
|
||||||
android.util.Log.i("PlaybackActivity", "Page finished: $url")
|
android.util.Log.i("PlaybackActivity", "Page finished: $url")
|
||||||
|
|
||||||
// Reduced delays and calls to minimize memory pressure
|
handler.postDelayed({
|
||||||
handler.postDelayed({
|
injectFullscreenScript()
|
||||||
injectFullscreenScript()
|
}, 300)
|
||||||
injectEnhancedAutoPlayScript()
|
|
||||||
}, 300)
|
|
||||||
|
|
||||||
handler.postDelayed({
|
handler.postDelayed({
|
||||||
runOnUiThread {
|
runOnUiThread {
|
||||||
loadingOverlay.visibility = View.GONE
|
loadingOverlay.visibility = View.GONE
|
||||||
}
|
}
|
||||||
}, 1500)
|
}, 1500)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Use hardware layer only when needed, not always
|
// Use hardware layer only when needed, not always
|
||||||
webView.setLayerType(View.LAYER_TYPE_NONE, null)
|
webView.setLayerType(View.LAYER_TYPE_NONE, null)
|
||||||
@@ -455,72 +422,7 @@ webView.setLayerType(View.LAYER_TYPE_NONE, null)
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
makePlayerFullscreen();
|
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');
|
|
||||||
}
|
|
||||||
});
|
|
||||||
})();
|
})();
|
||||||
""".trimIndent(),
|
""".trimIndent(),
|
||||||
null
|
null
|
||||||
@@ -531,11 +433,10 @@ setTimeout(makePlayerFullscreen, 2000);
|
|||||||
val keyCode = event?.keyCode ?: return super.dispatchKeyEvent(event)
|
val keyCode = event?.keyCode ?: return super.dispatchKeyEvent(event)
|
||||||
val action = event.action
|
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) {
|
&& action == KeyEvent.ACTION_DOWN) {
|
||||||
android.util.Log.d("PlaybackActivity", "OK button pressed, triggering enhanced autoplay")
|
android.util.Log.d("PlaybackActivity", "OK button pressed")
|
||||||
simulateCenterClick()
|
simulateCenterClick()
|
||||||
injectEnhancedAutoPlayScript()
|
|
||||||
toggleVideoPlayback()
|
toggleVideoPlayback()
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,8 +17,8 @@ data class VersionInfo(
|
|||||||
object UpdateChecker {
|
object UpdateChecker {
|
||||||
private const val TAG = "UpdateChecker"
|
private const val TAG = "UpdateChecker"
|
||||||
|
|
||||||
private const val VERSION_JSON_URL = "https://tvmon.site/version.json"
|
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://tvmon.site/releases"
|
private const val APK_BASE_URL = "https://git.webpluss.net/sanjeok77/tvmon_release/releases/download"
|
||||||
|
|
||||||
fun getCurrentVersionCode(context: Context): Int {
|
fun getCurrentVersionCode(context: Context): Int {
|
||||||
return try {
|
return try {
|
||||||
|
|||||||
@@ -1,47 +1,40 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
android:width="108dp"
|
android:width="108dp"
|
||||||
android:height="108dp"
|
android:height="108dp"
|
||||||
android:viewportWidth="108"
|
android:viewportWidth="108"
|
||||||
android:viewportHeight="108">
|
android:viewportHeight="108">
|
||||||
|
|
||||||
<!-- TV Screen with white color -->
|
<!-- TV Screen outline - white stroke style for visibility -->
|
||||||
<path
|
<path
|
||||||
android:fillColor="#FFFFFF"
|
android:fillColor="#FFFFFF"
|
||||||
android:pathData="M34,38h40v28h-40z"/>
|
android:pathData="M26,26h56v40h-56z" />
|
||||||
|
|
||||||
<!-- TV Stand -->
|
<!-- Inner screen area (transparent to show background) -->
|
||||||
<path
|
<path
|
||||||
android:fillColor="#FFFFFF"
|
android:fillColor="#00000000"
|
||||||
android:pathData="M44,70h20v4h-20z"/>
|
android:strokeColor="#E50914"
|
||||||
<path
|
android:strokeWidth="2"
|
||||||
android:fillColor="#FFFFFF"
|
android:pathData="M30,30h48v32h-48z" />
|
||||||
android:pathData="M40,74h28v2h-28z"/>
|
|
||||||
|
|
||||||
<!-- Play Button - using Netflix red -->
|
<!-- Play Button - white triangle centered in screen -->
|
||||||
<path
|
<path
|
||||||
android:fillColor="#E50914"
|
android:fillColor="#FFFFFF"
|
||||||
android:pathData="M50,44l12,8l-12,8z"/>
|
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>
|
</vector>
|
||||||
@@ -1,8 +1,12 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<network-security-config>
|
<network-security-config>
|
||||||
<base-config cleartextTrafficPermitted="true">
|
<base-config cleartextTrafficPermitted="true">
|
||||||
<trust-anchors>
|
<trust-anchors>
|
||||||
<certificates src="system" />
|
<certificates src="system" />
|
||||||
</trust-anchors>
|
</trust-anchors>
|
||||||
</base-config>
|
</base-config>
|
||||||
|
<domain-config cleartextTrafficPermitted="true">
|
||||||
|
<domain includeSubdomains="true">tvmon.site</domain>
|
||||||
|
<domain includeSubdomains="true">git.webpluss.net</domain>
|
||||||
|
</domain-config>
|
||||||
</network-security-config>
|
</network-security-config>
|
||||||
|
|||||||
Reference in New Issue
Block a user