Add CategoryContent entity, DAO, and CategoryCacheRepository
This commit is contained in:
@@ -54,12 +54,13 @@
|
||||
android:screenOrientation="landscape"
|
||||
android:theme="@style/Theme.Tvmon.Search" />
|
||||
|
||||
<activity
|
||||
android:name=".ui.search.SearchActivity"
|
||||
android:exported="true"
|
||||
android:label="@string/search_title"
|
||||
android:screenOrientation="landscape"
|
||||
android:theme="@style/Theme.Leanback">
|
||||
<activity
|
||||
android:name=".ui.search.SearchActivity"
|
||||
android:exported="true"
|
||||
android:label="@string/search_title"
|
||||
android:screenOrientation="landscape"
|
||||
android:theme="@style/Theme.Tvmon.Search"
|
||||
android:launchMode="singleTop">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.SEARCH" />
|
||||
</intent-filter>
|
||||
|
||||
@@ -3,9 +3,11 @@ package com.example.tvmon.data.local
|
||||
import androidx.room.Database
|
||||
import androidx.room.RoomDatabase
|
||||
import com.example.tvmon.data.local.dao.BookmarkDao
|
||||
import com.example.tvmon.data.local.dao.CategoryContentDao
|
||||
import com.example.tvmon.data.local.dao.SearchHistoryDao
|
||||
import com.example.tvmon.data.local.dao.WatchHistoryDao
|
||||
import com.example.tvmon.data.local.entity.Bookmark
|
||||
import com.example.tvmon.data.local.entity.CategoryContent
|
||||
import com.example.tvmon.data.local.entity.SearchHistory
|
||||
import com.example.tvmon.data.local.entity.WatchHistory
|
||||
|
||||
@@ -13,13 +15,15 @@ import com.example.tvmon.data.local.entity.WatchHistory
|
||||
entities = [
|
||||
WatchHistory::class,
|
||||
Bookmark::class,
|
||||
SearchHistory::class
|
||||
SearchHistory::class,
|
||||
CategoryContent::class
|
||||
],
|
||||
version = 1,
|
||||
version = 2,
|
||||
exportSchema = false
|
||||
)
|
||||
abstract class AppDatabase : RoomDatabase() {
|
||||
abstract fun watchHistoryDao(): WatchHistoryDao
|
||||
abstract fun bookmarkDao(): BookmarkDao
|
||||
abstract fun searchHistoryDao(): SearchHistoryDao
|
||||
abstract fun categoryContentDao(): CategoryContentDao
|
||||
}
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
package com.example.tvmon.data.local.dao
|
||||
|
||||
import androidx.room.*
|
||||
import com.example.tvmon.data.local.entity.CategoryContent
|
||||
|
||||
@Dao
|
||||
interface CategoryContentDao {
|
||||
@Query("SELECT * FROM category_content WHERE categoryKey = :categoryKey ORDER BY cachedAt ASC")
|
||||
suspend fun getByCategory(categoryKey: String): List<CategoryContent>
|
||||
|
||||
@Query("SELECT * FROM category_content WHERE categoryKey = :categoryKey AND pageNumber <= :page ORDER BY cachedAt ASC")
|
||||
suspend fun getByCategoryUntilPage(categoryKey: String, page: Int): List<CategoryContent>
|
||||
|
||||
@Query("SELECT MAX(pageNumber) FROM category_content WHERE categoryKey = :categoryKey")
|
||||
suspend fun getMaxPage(categoryKey: String): Int?
|
||||
|
||||
@Query("SELECT MAX(cachedAt) FROM category_content WHERE categoryKey = :categoryKey")
|
||||
suspend fun getLastCacheTime(categoryKey: String): Long?
|
||||
|
||||
@Insert(onConflict = OnConflictStrategy.REPLACE)
|
||||
suspend fun insert(contents: List<CategoryContent>)
|
||||
|
||||
@Insert(onConflict = OnConflictStrategy.REPLACE)
|
||||
suspend fun insertOne(content: CategoryContent)
|
||||
|
||||
@Query("DELETE FROM category_content WHERE categoryKey = :categoryKey")
|
||||
suspend fun deleteByCategory(categoryKey: String)
|
||||
|
||||
@Query("DELETE FROM category_content WHERE categoryKey = :categoryKey AND pageNumber >= :fromPage")
|
||||
suspend fun deleteFromPage(categoryKey: String, fromPage: Int)
|
||||
|
||||
@Query("SELECT COUNT(*) FROM category_content WHERE categoryKey = :categoryKey")
|
||||
suspend fun getCount(categoryKey: String): Int
|
||||
|
||||
@Query("DELETE FROM category_content")
|
||||
suspend fun deleteAll()
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
package com.example.tvmon.data.local.entity
|
||||
|
||||
import androidx.room.Entity
|
||||
import androidx.room.PrimaryKey
|
||||
|
||||
@Entity(tableName = "category_content")
|
||||
data class CategoryContent(
|
||||
@PrimaryKey
|
||||
val contentUrl: String,
|
||||
val categoryKey: String,
|
||||
val contentId: String,
|
||||
val title: String,
|
||||
val thumbnail: String,
|
||||
val pageNumber: Int = 1,
|
||||
val cachedAt: Long = System.currentTimeMillis()
|
||||
)
|
||||
@@ -0,0 +1,132 @@
|
||||
package com.example.tvmon.data.repository
|
||||
|
||||
import android.util.Log
|
||||
import com.example.tvmon.data.local.dao.CategoryContentDao
|
||||
import com.example.tvmon.data.local.entity.CategoryContent
|
||||
import com.example.tvmon.data.model.Category
|
||||
import com.example.tvmon.data.model.Content
|
||||
import com.example.tvmon.data.model.Pagination
|
||||
import com.example.tvmon.data.scraper.TvmonScraper
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
|
||||
class CategoryCacheRepository(
|
||||
private val categoryContentDao: CategoryContentDao
|
||||
) {
|
||||
companion object {
|
||||
private const val TAG = "CategoryCacheRepo"
|
||||
private const val CACHE_TTL_MS = 30 * 60 * 1000L
|
||||
}
|
||||
|
||||
private val loadedPages = mutableMapOf<String, Int>()
|
||||
|
||||
suspend fun getCategoryWithCache(categoryKey: String, page: Int = 1): CategoryResultCache {
|
||||
return withContext(Dispatchers.IO) {
|
||||
val cachedItems = categoryContentDao.getByCategoryUntilPage(categoryKey, page)
|
||||
val lastCacheTime = categoryContentDao.getLastCacheTime(categoryKey)
|
||||
val cachedMaxPage = categoryContentDao.getMaxPage(categoryKey) ?: 1
|
||||
|
||||
val now = System.currentTimeMillis()
|
||||
val isCacheValid = lastCacheTime != null && (now - lastCacheTime) < CACHE_TTL_MS
|
||||
|
||||
Log.w(TAG, "getCategoryWithCache: $categoryKey page=$page, cached=${cachedItems.size}, cacheValid=$isCacheValid, cachedMaxPage=$cachedMaxPage")
|
||||
|
||||
if (cachedItems.isNotEmpty() && isCacheValid && page <= cachedMaxPage) {
|
||||
val contents = cachedItems.map { it.toContent() }
|
||||
CategoryResultCache(
|
||||
success = true,
|
||||
items = contents,
|
||||
page = page,
|
||||
pagination = Pagination(page, cachedMaxPage),
|
||||
fromCache = true
|
||||
)
|
||||
} else {
|
||||
val result = scraper.getCategory(categoryKey, page)
|
||||
if (result.success && result.items.isNotEmpty()) {
|
||||
val entities = result.items.map { it.toEntity(categoryKey, page) }
|
||||
categoryContentDao.insert(entities)
|
||||
|
||||
val newMaxPage = result.pagination.maxPage
|
||||
if (page == 1) {
|
||||
loadedPages[categoryKey] = 1
|
||||
}
|
||||
Log.w(TAG, "getCategoryWithCache: fetched from network, saved ${entities.size} items, maxPage=$newMaxPage")
|
||||
}
|
||||
CategoryResultCache(
|
||||
success = result.success,
|
||||
items = result.items,
|
||||
page = result.page,
|
||||
pagination = result.pagination,
|
||||
fromCache = false
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun loadMoreForCategory(categoryKey: String): List<Content> {
|
||||
return withContext(Dispatchers.IO) {
|
||||
val currentPage = loadedPages[categoryKey] ?: 1
|
||||
val nextPage = currentPage + 1
|
||||
|
||||
Log.w(TAG, "loadMoreForCategory: $categoryKey currentPage=$currentPage, nextPage=$nextPage")
|
||||
|
||||
val result = scraper.getCategory(categoryKey, nextPage)
|
||||
if (result.success && result.items.isNotEmpty()) {
|
||||
val entities = result.items.map { it.toEntity(categoryKey, nextPage) }
|
||||
categoryContentDao.insert(entities)
|
||||
loadedPages[categoryKey] = nextPage
|
||||
Log.w(TAG, "loadMoreForCategory: saved ${entities.size} items for page $nextPage")
|
||||
} else {
|
||||
Log.w(TAG, "loadMoreForCategory: no more items for $categoryKey")
|
||||
}
|
||||
result.items
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun getLoadedPageCount(categoryKey: String): Int {
|
||||
return loadedPages[categoryKey] ?: 0
|
||||
}
|
||||
|
||||
suspend fun getCachedItems(categoryKey: String): List<Content> {
|
||||
return withContext(Dispatchers.IO) {
|
||||
categoryContentDao.getByCategory(categoryKey).map { it.toContent() }
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun clearCache() {
|
||||
categoryContentDao.deleteAll()
|
||||
loadedPages.clear()
|
||||
}
|
||||
|
||||
private val scraper = TvmonScraper()
|
||||
}
|
||||
|
||||
data class CategoryResultCache(
|
||||
val success: Boolean,
|
||||
val items: List<Content>,
|
||||
val page: Int,
|
||||
val pagination: Pagination,
|
||||
val fromCache: Boolean
|
||||
)
|
||||
|
||||
private fun CategoryContent.toContent(): Content {
|
||||
return Content(
|
||||
id = contentId,
|
||||
title = title,
|
||||
url = contentUrl,
|
||||
thumbnail = thumbnail,
|
||||
category = categoryKey
|
||||
)
|
||||
}
|
||||
|
||||
private fun Content.toEntity(categoryKey: String, page: Int): CategoryContent {
|
||||
return CategoryContent(
|
||||
contentUrl = url,
|
||||
categoryKey = categoryKey,
|
||||
contentId = id,
|
||||
title = title,
|
||||
thumbnail = thumbnail,
|
||||
pageNumber = page,
|
||||
cachedAt = System.currentTimeMillis()
|
||||
)
|
||||
}
|
||||
@@ -3,6 +3,7 @@ package com.example.tvmon.di
|
||||
import androidx.room.Room
|
||||
import com.example.tvmon.data.local.AppDatabase
|
||||
import com.example.tvmon.data.repository.BookmarkRepository
|
||||
import com.example.tvmon.data.repository.CategoryCacheRepository
|
||||
import com.example.tvmon.data.repository.WatchHistoryRepository
|
||||
import com.example.tvmon.data.scraper.TvmonScraper
|
||||
import org.koin.android.ext.koin.androidContext
|
||||
@@ -15,18 +16,20 @@ val databaseModule = module {
|
||||
AppDatabase::class.java,
|
||||
"tvmon_database"
|
||||
)
|
||||
.fallbackToDestructiveMigration()
|
||||
.build()
|
||||
.fallbackToDestructiveMigration()
|
||||
.build()
|
||||
}
|
||||
|
||||
|
||||
single { get<AppDatabase>().watchHistoryDao() }
|
||||
single { get<AppDatabase>().bookmarkDao() }
|
||||
single { get<AppDatabase>().searchHistoryDao() }
|
||||
single { get<AppDatabase>().categoryContentDao() }
|
||||
}
|
||||
|
||||
val repositoryModule = module {
|
||||
single { WatchHistoryRepository(get()) }
|
||||
single { BookmarkRepository(get()) }
|
||||
single { CategoryCacheRepository(get()) }
|
||||
}
|
||||
|
||||
val scraperModule = module {
|
||||
|
||||
@@ -2,8 +2,6 @@ package com.example.tvmon.ui.main
|
||||
|
||||
import android.content.Intent
|
||||
import android.os.Bundle
|
||||
import android.os.Handler
|
||||
import android.os.Looper
|
||||
import android.util.Log
|
||||
import android.widget.Toast
|
||||
import androidx.core.content.ContextCompat
|
||||
@@ -11,8 +9,8 @@ import androidx.leanback.app.BrowseSupportFragment
|
||||
import androidx.leanback.widget.*
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import com.example.tvmon.R
|
||||
import com.example.tvmon.data.model.Category
|
||||
import com.example.tvmon.data.model.Content
|
||||
import com.example.tvmon.data.repository.CategoryCacheRepository
|
||||
import com.example.tvmon.data.repository.WatchHistoryRepository
|
||||
import com.example.tvmon.data.scraper.TvmonScraper
|
||||
import com.example.tvmon.ui.detail.DetailsActivity
|
||||
@@ -24,33 +22,20 @@ class MainFragment : BrowseSupportFragment(), OnItemViewClickedListener, OnItemV
|
||||
|
||||
companion object {
|
||||
private const val TAG = "TVMON_MAIN"
|
||||
private const val PRELOAD_THRESHOLD = 20
|
||||
}
|
||||
|
||||
private val scraper: TvmonScraper by inject()
|
||||
private val categoryCacheRepository: CategoryCacheRepository by inject()
|
||||
private val watchHistoryRepository: WatchHistoryRepository by inject()
|
||||
private val rowsAdapter = ArrayObjectAdapter(ListRowPresenter())
|
||||
private val categoryPages = mutableMapOf<String, Int>()
|
||||
private val categoryLoading = mutableMapOf<String, Boolean>()
|
||||
private val categoryMaxPage = mutableMapOf<String, Int>()
|
||||
private val categoryItems = mutableMapOf<String, MutableList<Content>>()
|
||||
private val categoryRowAdapters = mutableMapOf<String, ArrayObjectAdapter>()
|
||||
private val handler = Handler(Looper.getMainLooper())
|
||||
private var currentSelectedRowIndex = -1
|
||||
private val loadingStates = mutableMapOf<String, Boolean>()
|
||||
private var isDataLoaded = false
|
||||
private var lastPreloadedPage = mutableMapOf<String, Int>()
|
||||
private var currentCategoryKey: String? = null
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
Log.w(TAG, "=== MainFragment onCreate ===")
|
||||
try {
|
||||
setupUI()
|
||||
setupEventListeners()
|
||||
Log.w(TAG, "setupUI completed successfully")
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Error in onCreate", e)
|
||||
}
|
||||
setupUI()
|
||||
setupEventListeners()
|
||||
}
|
||||
|
||||
override fun onStart() {
|
||||
@@ -58,16 +43,9 @@ class MainFragment : BrowseSupportFragment(), OnItemViewClickedListener, OnItemV
|
||||
Log.w(TAG, "=== MainFragment onStart ===")
|
||||
if (!isDataLoaded) {
|
||||
loadData()
|
||||
} else {
|
||||
Log.w(TAG, "Data already loaded, skipping reload")
|
||||
}
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
handler.removeCallbacksAndMessages(null)
|
||||
super.onDestroy()
|
||||
}
|
||||
|
||||
private fun setupUI() {
|
||||
Log.w(TAG, "setupUI: Setting up UI")
|
||||
headersState = HEADERS_ENABLED
|
||||
@@ -89,114 +67,6 @@ class MainFragment : BrowseSupportFragment(), OnItemViewClickedListener, OnItemV
|
||||
onItemViewClickedListener = this
|
||||
}
|
||||
|
||||
private fun handleRowSelection(position: Int) {
|
||||
if (position == currentSelectedRowIndex) return
|
||||
currentSelectedRowIndex = position
|
||||
|
||||
if (position >= 0 && position < rowsAdapter.size()) {
|
||||
val categoryKey = findCategoryKeyForRow(position)
|
||||
|
||||
if (categoryKey != null && categoryLoading[categoryKey] != true) {
|
||||
val currentPage = categoryPages[categoryKey] ?: 1
|
||||
val maxPage = categoryMaxPage[categoryKey] ?: 1
|
||||
|
||||
Log.w(TAG, "handleRowSelection: $categoryKey currentPage=$currentPage maxPage=$maxPage")
|
||||
|
||||
if (currentPage < maxPage) {
|
||||
preloadNextPage(categoryKey, currentPage + 1)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun preloadNextPage(categoryKey: String, page: Int) {
|
||||
if (categoryLoading[categoryKey] == true) return
|
||||
if (categoryPages[categoryKey] ?: 0 >= page) return
|
||||
categoryLoading[categoryKey] = true
|
||||
|
||||
lifecycleScope.launch {
|
||||
try {
|
||||
val result = scraper.getCategory(categoryKey, page)
|
||||
Log.w(TAG, "preloadNextPage: $categoryKey page $page success=${result.success}, items=${result.items.size}")
|
||||
|
||||
if (result.success && result.items.isNotEmpty()) {
|
||||
val items = categoryItems.getOrPut(categoryKey) { mutableListOf() }
|
||||
val existingUrls = items.map { it.url }.toSet()
|
||||
val newItems = result.items.filter { it.url !in existingUrls }
|
||||
|
||||
if (newItems.isNotEmpty()) {
|
||||
items.addAll(newItems)
|
||||
|
||||
val adapter = categoryRowAdapters[categoryKey]
|
||||
activity?.runOnUiThread {
|
||||
adapter?.let {
|
||||
newItems.forEach { item -> it.add(item) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
categoryPages[categoryKey] = page
|
||||
Log.w(TAG, "Preloaded page $page for $categoryKey, total items=${items.size}")
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Error preloading page $page for $categoryKey", e)
|
||||
} finally {
|
||||
categoryLoading[categoryKey] = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun findCategoryKeyForRow(rowIndex: Int): String? {
|
||||
for ((key, _) in categoryPages) {
|
||||
val catIndex = TvmonScraper.CATEGORIES.keys.toList().indexOf(key)
|
||||
if (catIndex == rowIndex) return key
|
||||
}
|
||||
|
||||
val row = rowsAdapter.get(rowIndex) as? ListRow ?: return null
|
||||
val headerName = row.headerItem?.name ?: return null
|
||||
|
||||
for ((key, cat) in TvmonScraper.CATEGORIES) {
|
||||
if (cat.name == headerName) return key
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
private fun loadNextPage(categoryKey: String, page: Int) {
|
||||
if (categoryLoading[categoryKey] == true) return
|
||||
categoryLoading[categoryKey] = true
|
||||
|
||||
lifecycleScope.launch {
|
||||
try {
|
||||
val result = scraper.getCategory(categoryKey, page)
|
||||
Log.w(TAG, "loadNextPage: $categoryKey page $page success=${result.success}, items=${result.items.size}")
|
||||
|
||||
if (result.success && result.items.isNotEmpty()) {
|
||||
val items = categoryItems.getOrPut(categoryKey) { mutableListOf() }
|
||||
val existingUrls = items.map { it.url }.toSet()
|
||||
val newItems = result.items.filter { it.url !in existingUrls }
|
||||
|
||||
if (newItems.isNotEmpty()) {
|
||||
items.addAll(newItems)
|
||||
|
||||
val adapter = categoryRowAdapters[categoryKey]
|
||||
activity?.runOnUiThread {
|
||||
adapter?.let {
|
||||
newItems.forEach { item -> it.add(item) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
categoryPages[categoryKey] = page
|
||||
Log.w(TAG, "Loaded page $page for $categoryKey, total items=${items.size}")
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Error loading page $page for $categoryKey", e)
|
||||
} finally {
|
||||
categoryLoading[categoryKey] = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun loadData() {
|
||||
Log.w(TAG, "=== loadData called ===")
|
||||
lifecycleScope.launch {
|
||||
@@ -213,42 +83,33 @@ private fun loadNextPage(categoryKey: String, page: Int) {
|
||||
|
||||
private suspend fun loadCategories() {
|
||||
Log.w(TAG, "=== loadCategories: Starting ===")
|
||||
Log.w(TAG, "Categories to load: ${TvmonScraper.CATEGORIES.keys}")
|
||||
|
||||
var successCount = 0
|
||||
var failCount = 0
|
||||
|
||||
TvmonScraper.CATEGORIES.values.forEach { category ->
|
||||
for (category in TvmonScraper.CATEGORIES.values) {
|
||||
Log.w(TAG, "Loading category: ${category.key} - ${category.name}")
|
||||
val success = loadCategoryRows(category)
|
||||
val success = loadCategoryWithCache(category)
|
||||
if (success) successCount++ else failCount++
|
||||
}
|
||||
|
||||
Log.w(TAG, "=== loadCategories COMPLETE: success=$successCount, fail=$failCount ===")
|
||||
Log.w(TAG, "rowsAdapter size: ${rowsAdapter.size()}")
|
||||
|
||||
isDataLoaded = true
|
||||
|
||||
activity?.runOnUiThread {
|
||||
if (rowsAdapter.size() == 0) {
|
||||
Toast.makeText(requireContext(), "카테고리를 불러오지 못했습니다", Toast.LENGTH_LONG).show()
|
||||
}
|
||||
if (rowsAdapter.size() == 0) {
|
||||
Toast.makeText(requireContext(), "카테고리를 불러오지 못했습니다", Toast.LENGTH_LONG).show()
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun loadCategoryRows(category: Category): Boolean {
|
||||
private suspend fun loadCategoryWithCache(category: com.example.tvmon.data.model.Category): Boolean {
|
||||
return try {
|
||||
Log.w(TAG, "Fetching: ${category.key} from ${TvmonScraper.BASE_URL}${category.path}")
|
||||
|
||||
val result1 = scraper.getCategory(category.key, page = 1)
|
||||
Log.w(TAG, "Page 1 for ${category.key}: success=${result1.success}, items=${result1.items.size}")
|
||||
|
||||
val maxPage = result1.pagination.maxPage
|
||||
|
||||
if (result1.success && result1.items.isNotEmpty()) {
|
||||
Log.w(TAG, "loadCategoryWithCache: ${category.key}")
|
||||
|
||||
val result = categoryCacheRepository.getCategoryWithCache(category.key, page = 1)
|
||||
|
||||
if (result.success && result.items.isNotEmpty()) {
|
||||
val listRowAdapter = ArrayObjectAdapter(ContentCardPresenter())
|
||||
result1.items.forEach { content ->
|
||||
Log.d(TAG, " Item: ${content.title} -> thumb: ${content.thumbnail}")
|
||||
result.items.forEach { content ->
|
||||
listRowAdapter.add(content)
|
||||
}
|
||||
|
||||
@@ -257,18 +118,10 @@ private fun loadNextPage(categoryKey: String, page: Int) {
|
||||
|
||||
activity?.runOnUiThread {
|
||||
rowsAdapter.add(row)
|
||||
Log.w(TAG, "ADDED ROW: ${category.name} with ${result1.items.size} items")
|
||||
Log.w(TAG, "ADDED ROW: ${category.name} with ${result.items.size} items (fromCache=${result.fromCache})")
|
||||
}
|
||||
|
||||
categoryPages[category.key] = 1
|
||||
categoryMaxPage[category.key] = maxPage
|
||||
categoryItems[category.key] = result1.items.toMutableList()
|
||||
|
||||
categoryRowAdapters[category.key] = listRowAdapter
|
||||
|
||||
if (maxPage > 1) {
|
||||
categoryLoading[category.key] = false
|
||||
}
|
||||
|
||||
true
|
||||
} else {
|
||||
Log.w(TAG, "No items for ${category.key}")
|
||||
@@ -286,19 +139,14 @@ private fun loadNextPage(categoryKey: String, page: Int) {
|
||||
rowViewHolder: RowPresenter.ViewHolder?,
|
||||
row: Row?
|
||||
) {
|
||||
Log.w(TAG, "=== onItemClicked: item=$item, itemViewHolder=$itemViewHolder ===")
|
||||
Log.w(TAG, "=== onItemClicked: item=$item ===")
|
||||
when (item) {
|
||||
is Content -> {
|
||||
Log.w(TAG, "Content clicked: ${item.title}, url=${item.url}")
|
||||
try {
|
||||
val intent = Intent(requireContext(), DetailsActivity::class.java).apply {
|
||||
putExtra(DetailsActivity.EXTRA_CONTENT, item)
|
||||
}
|
||||
startActivity(intent)
|
||||
Log.w(TAG, "Started DetailsActivity successfully")
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "ERROR starting DetailsActivity", e)
|
||||
val intent = Intent(requireContext(), DetailsActivity::class.java).apply {
|
||||
putExtra(DetailsActivity.EXTRA_CONTENT, item)
|
||||
}
|
||||
startActivity(intent)
|
||||
}
|
||||
else -> {
|
||||
Log.w(TAG, "Unknown item type: ${item?.javaClass?.simpleName}")
|
||||
@@ -316,21 +164,45 @@ private fun loadNextPage(categoryKey: String, page: Int) {
|
||||
val rowIndex = rowsAdapter.indexOf(row)
|
||||
if (rowIndex >= 0) {
|
||||
val categoryKey = findCategoryKeyForRow(rowIndex)
|
||||
currentCategoryKey = categoryKey
|
||||
handleRowSelection(rowIndex)
|
||||
if (categoryKey != null && item is Content) {
|
||||
checkAndLoadMore(categoryKey)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun checkAndPreload(categoryKey: String, position: Int) {
|
||||
val currentPage = categoryPages[categoryKey] ?: 1
|
||||
val maxPage = categoryMaxPage[categoryKey] ?: 1
|
||||
|
||||
val thresholdPage = (position / PRELOAD_THRESHOLD) + 1
|
||||
|
||||
if (thresholdPage > currentPage && thresholdPage <= maxPage) {
|
||||
Log.w(TAG, "checkAndPreload: $categoryKey at position $position, preloading page $thresholdPage")
|
||||
preloadNextPage(categoryKey, thresholdPage)
|
||||
private fun checkAndLoadMore(categoryKey: String) {
|
||||
val adapter = categoryRowAdapters[categoryKey] ?: return
|
||||
if (loadingStates[categoryKey] == true) return
|
||||
|
||||
val position = adapter.size() - 1
|
||||
if (position < 5) return
|
||||
|
||||
lifecycleScope.launch {
|
||||
try {
|
||||
loadingStates[categoryKey] = true
|
||||
val items = categoryCacheRepository.loadMoreForCategory(categoryKey)
|
||||
if (items.isNotEmpty()) {
|
||||
activity?.runOnUiThread {
|
||||
items.forEach { adapter.add(it) }
|
||||
Log.w(TAG, "checkAndLoadMore: $categoryKey added ${items.size} items, total=${adapter.size()}")
|
||||
}
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "checkAndLoadMore error: ${e.message}")
|
||||
} finally {
|
||||
loadingStates[categoryKey] = false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun findCategoryKeyForRow(rowIndex: Int): String? {
|
||||
val row = rowsAdapter.get(rowIndex) as? ListRow ?: return null
|
||||
val headerName = row.headerItem?.name ?: return null
|
||||
|
||||
for ((key, cat) in TvmonScraper.CATEGORIES) {
|
||||
if (cat.name == headerName) return key
|
||||
}
|
||||
return null
|
||||
}
|
||||
}
|
||||
@@ -159,6 +159,7 @@ class PlaybackActivity : AppCompatActivity() {
|
||||
|
||||
webSettings.javaScriptEnabled = true
|
||||
webSettings.domStorageEnabled = true
|
||||
webSettings.databaseEnabled = true
|
||||
webSettings.allowFileAccess = true
|
||||
webSettings.allowContentAccess = true
|
||||
webSettings.mediaPlaybackRequiresUserGesture = false
|
||||
@@ -167,7 +168,7 @@ class PlaybackActivity : AppCompatActivity() {
|
||||
webSettings.userAgentString = USER_AGENT
|
||||
webSettings.useWideViewPort = true
|
||||
webSettings.loadWithOverviewMode = true
|
||||
webSettings.cacheMode = WebSettings.LOAD_NO_CACHE
|
||||
webSettings.cacheMode = WebSettings.LOAD_DEFAULT
|
||||
webSettings.allowUniversalAccessFromFileURLs = true
|
||||
webSettings.allowFileAccessFromFileURLs = true
|
||||
|
||||
@@ -283,21 +284,16 @@ class PlaybackActivity : AppCompatActivity() {
|
||||
super.onPageFinished(view, url)
|
||||
android.util.Log.i("PlaybackActivity", "Page finished: $url")
|
||||
|
||||
handler.postDelayed({
|
||||
injectFullscreenScript()
|
||||
android.util.Log.i("PlaybackActivity", "Fullscreen script injected")
|
||||
}, 300)
|
||||
handler.postDelayed({
|
||||
injectFullscreenScript()
|
||||
injectEnhancedAutoPlayScript()
|
||||
}, 200)
|
||||
|
||||
handler.postDelayed({
|
||||
injectEnhancedAutoPlayScript()
|
||||
android.util.Log.i("PlaybackActivity", "Enhanced AutoPlay script injected")
|
||||
}, 800)
|
||||
|
||||
handler.postDelayed({
|
||||
runOnUiThread {
|
||||
loadingOverlay.visibility = View.GONE
|
||||
}
|
||||
}, 1500)
|
||||
handler.postDelayed({
|
||||
runOnUiThread {
|
||||
loadingOverlay.visibility = View.GONE
|
||||
}
|
||||
}, 1000)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user