diff --git a/tvmon-app/app/build.gradle b/tvmon-app/app/build.gradle index c47f99a..2a79548 100644 --- a/tvmon-app/app/build.gradle +++ b/tvmon-app/app/build.gradle @@ -19,10 +19,21 @@ android { testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" } + signingConfigs { + release { + storeFile file('tvmon-release.keystore') + storePassword 'tvmon1234' + keyAlias 'tvmon' + keyPassword 'tvmon1234' + } + } + buildTypes { release { minifyEnabled true + shrinkResources true proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' + signingConfig signingConfigs.release } debug { minifyEnabled false diff --git a/tvmon-app/app/src/main/java/com/example/tvmon/data/scraper/TvmonScraper.kt b/tvmon-app/app/src/main/java/com/example/tvmon/data/scraper/TvmonScraper.kt index d546349..f8a8ddd 100644 --- a/tvmon-app/app/src/main/java/com/example/tvmon/data/scraper/TvmonScraper.kt +++ b/tvmon-app/app/src/main/java/com/example/tvmon/data/scraper/TvmonScraper.kt @@ -407,28 +407,30 @@ class TvmonScraper { } } - val episodes = mutableListOf() +val episodes = mutableListOf() val videoLinks = mutableListOf() val seenEpisodeIds = mutableSetOf() - - val episodeLinks = doc.select(".next-ep-list-scroll .ep-item, .ep-item") - - for (link in episodeLinks) { + + val seriesId = seriesUrl.substringAfterLast("/").substringBefore("?") + val allEpisodeLinks = doc.select(".next-ep-list-scroll .ep-item, .ep-item, .bo_v_list li a, #bo_v_list li a, .list_body .item a, .ep-list a") + + for (link in allEpisodeLinks) { val href = link.attr("href") if (href.isBlank()) continue + if (!href.contains("/$seriesId/")) continue val fullUrl = resolveUrl(href) - + val episodeIdMatch = Pattern.compile("/(\\d+)/(\\d+)$").matcher(href) if (!episodeIdMatch.find()) continue - + val episodeId = episodeIdMatch.group(2) ?: "" if (episodeId in seenEpisodeIds) continue seenEpisodeIds.add(episodeId) - val titleEl = link.selectFirst(".ep-item-title") + val titleEl = link.selectFirst(".ep-item-title, .item-title, strong, span") val linkText = titleEl?.text()?.trim() ?: link.text().trim() - + val episodeNumMatch = Pattern.compile("(\\d+ 화|\\d+ 회|EP?\\d+|제?\\d+부?|\\d+)").matcher(linkText) val episodeTitle = if (episodeNumMatch.find()) { episodeNumMatch.group(1) diff --git a/tvmon-app/app/src/main/java/com/example/tvmon/ui/detail/DetailsFragment.kt b/tvmon-app/app/src/main/java/com/example/tvmon/ui/detail/DetailsFragment.kt index e45e799..311eb89 100644 --- a/tvmon-app/app/src/main/java/com/example/tvmon/ui/detail/DetailsFragment.kt +++ b/tvmon-app/app/src/main/java/com/example/tvmon/ui/detail/DetailsFragment.kt @@ -77,6 +77,10 @@ class DetailsFragment : DetailsSupportFragment() { } } + override fun onDestroy() { + super.onDestroy() + } + private fun setupDetails() { Log.w(TAG, "setupDetails: Setting up details UI") val presenterSelector = ClassPresenterSelector() @@ -236,6 +240,11 @@ class DetailsFragment : DetailsSupportFragment() { } private fun addEpisodesRow(episodes: List) { + val existingRowIndex = findRowIndexByHeader(getString(R.string.episodes)) + if (existingRowIndex >= 0) { + rowsAdapter.removeItems(existingRowIndex, 1) + } + val episodesRowAdapter = ArrayObjectAdapter(EpisodePresenter()) episodes.forEachIndexed { index, episode -> Log.d(TAG, " Episode $index: ${episode.number} - ${episode.title}") @@ -279,9 +288,14 @@ class DetailsFragment : DetailsSupportFragment() { } } - private fun addCastRow(cast: List) { +private fun addCastRow(cast: List) { if (cast.isEmpty()) return - + + val existingRowIndex = findRowIndexByHeader("출연진") + if (existingRowIndex >= 0) { + rowsAdapter.removeItems(existingRowIndex, 1) + } + val castAdapter = ArrayObjectAdapter(CastPresenter()) cast.forEach { member -> castAdapter.add(member) 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 9551900..0a7c595 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 @@ -56,6 +56,11 @@ class MainFragment : BrowseSupportFragment(), OnItemViewClickedListener, OnItemV loadData() } + override fun onDestroy() { + handler.removeCallbacksAndMessages(null) + super.onDestroy() + } + private fun setupUI() { Log.w(TAG, "setupUI: Setting up UI") headersState = HEADERS_ENABLED @@ -77,27 +82,62 @@ class MainFragment : BrowseSupportFragment(), OnItemViewClickedListener, OnItemV onItemViewClickedListener = this } - private fun handleRowSelection(position: Int) { +private fun handleRowSelection(position: Int) { if (position == currentSelectedRowIndex) return currentSelectedRowIndex = position - + if (position >= 0 && position < rowsAdapter.size()) { - val row = rowsAdapter.get(position) as? ListRow ?: return 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) { - loadNextPage(categoryKey, currentPage + 1) + preloadNextPage(categoryKey, currentPage + 1) } } } } + private fun preloadNextPage(categoryKey: String, page: Int) { + if (categoryLoading[categoryKey] == true) 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) @@ -113,34 +153,33 @@ class MainFragment : BrowseSupportFragment(), OnItemViewClickedListener, OnItemV return null } - private fun loadNextPage(categoryKey: String, page: Int) { +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() } - items.addAll(result.items) + val existingUrls = items.map { it.url }.toSet() + val newItems = result.items.filter { it.url !in existingUrls } - val adapter = categoryRowAdapters[categoryKey] - adapter?.let { - result.items.forEach { item -> it.add(item) } + if (newItems.isNotEmpty()) { + items.addAll(newItems) + + val adapter = categoryRowAdapters[categoryKey] + activity?.runOnUiThread { + adapter?.let { + newItems.forEach { item -> it.add(item) } + } + } } - - val loadedPage = page - categoryPages[categoryKey] = loadedPage + + categoryPages[categoryKey] = page Log.w(TAG, "Loaded page $page for $categoryKey, total items=${items.size}") - - val currentMaxPage = categoryMaxPage[categoryKey] ?: 1 - if (loadedPage < currentMaxPage) { - handler.postDelayed({ - loadNextPage(categoryKey, loadedPage + 1) - }, 500) - } } } catch (e: Exception) { Log.e(TAG, "Error loading page $page for $categoryKey", e) diff --git a/tvmon-app/app/tvmon-release.keystore b/tvmon-app/app/tvmon-release.keystore new file mode 100644 index 0000000..eaf8699 Binary files /dev/null and b/tvmon-app/app/tvmon-release.keystore differ diff --git a/version.json b/version.json new file mode 100644 index 0000000..7244dcc --- /dev/null +++ b/version.json @@ -0,0 +1,6 @@ +{ + "versionCode": 1, + "versionName": "1.0.0", + "apkUrl": "https://git.webpluss.net/sanjeok77/NeFLIX_release/releases/download/v1.0.0/app-release.apk", + "updateMessage": "tvmon v1.0.0 - Android TV app with pagination fix and cast display improvements" +} \ No newline at end of file