mirror of
https://github.com/tapframe/NuvioStreaming.git
synced 2026-05-17 23:42:04 +00:00
Merge d8b7497e44 into 37203d1fc1
This commit is contained in:
commit
09cfb1bc2b
11 changed files with 746 additions and 312 deletions
File diff suppressed because it is too large
Load diff
|
|
@ -163,18 +163,27 @@
|
|||
<string name="collections_editor_tmdb_quick_networks">Quick networks</string>
|
||||
<string name="collections_editor_tmdb_genres">Genre IDs</string>
|
||||
<string name="collections_editor_tmdb_genres_helper">Use TMDB genre numbers. Separate multiple with commas for AND, or pipes for OR.</string>
|
||||
<string name="collections_editor_tmdb_genres_movie_placeholder">28,12</string>
|
||||
<string name="collections_editor_tmdb_genres_series_placeholder">18,35</string>
|
||||
<string name="collections_editor_tmdb_date_from">Release or air date from</string>
|
||||
<string name="collections_editor_tmdb_date_to">Release or air date to</string>
|
||||
<string name="collections_editor_tmdb_date_helper">Use YYYY-MM-DD, for example 2024-01-01.</string>
|
||||
<string name="collections_editor_tmdb_date_from_placeholder">2020-01-01</string>
|
||||
<string name="collections_editor_tmdb_date_to_placeholder">2024-12-31</string>
|
||||
<string name="collections_editor_tmdb_rating_min">Minimum rating</string>
|
||||
<string name="collections_editor_tmdb_rating_max">Maximum rating</string>
|
||||
<string name="collections_editor_tmdb_rating_helper">TMDB rating from 0 to 10. Example: 7.0.</string>
|
||||
<string name="collections_editor_tmdb_rating_min_placeholder">7.0</string>
|
||||
<string name="collections_editor_tmdb_rating_max_placeholder">10</string>
|
||||
<string name="collections_editor_tmdb_votes_min">Minimum votes</string>
|
||||
<string name="collections_editor_tmdb_votes_helper">Use this to avoid obscure low-vote titles. Example: 100.</string>
|
||||
<string name="collections_editor_tmdb_votes_min_placeholder">100</string>
|
||||
<string name="collections_editor_tmdb_language">Original language</string>
|
||||
<string name="collections_editor_tmdb_language_helper">Use two-letter language codes, for example en, ko, ja, hi.</string>
|
||||
<string name="collections_editor_tmdb_language_placeholder">en, ko, ja, hi</string>
|
||||
<string name="collections_editor_tmdb_country">Origin country</string>
|
||||
<string name="collections_editor_tmdb_country_helper">Use two-letter country codes, for example US, KR, JP, IN.</string>
|
||||
<string name="collections_editor_tmdb_country_placeholder">US, KR, JP, IN</string>
|
||||
<string name="collections_editor_tmdb_keywords">Keyword IDs</string>
|
||||
<string name="collections_editor_tmdb_keywords_helper">Use TMDB keyword numbers. Quick chips fill common examples.</string>
|
||||
<string name="collections_editor_tmdb_keywords_placeholder">9715 for superhero</string>
|
||||
|
|
@ -186,6 +195,7 @@
|
|||
<string name="collections_editor_tmdb_networks_placeholder">213 for Netflix</string>
|
||||
<string name="collections_editor_tmdb_year">Year</string>
|
||||
<string name="collections_editor_tmdb_year_helper">Use a four-digit year, for example 2024.</string>
|
||||
<string name="collections_editor_tmdb_year_placeholder">2024</string>
|
||||
<string name="collections_editor_tmdb_presets">Presets</string>
|
||||
<string name="collections_editor_tmdb_search">Search</string>
|
||||
<string name="collections_editor_add_source">Add Source</string>
|
||||
|
|
@ -210,6 +220,12 @@
|
|||
<string name="collections_editor_trakt_sort_popular">Popular</string>
|
||||
<string name="collections_editor_trakt_sort_percentage">Percentage</string>
|
||||
<string name="collections_editor_trakt_sort_votes">Votes</string>
|
||||
<string name="collections_editor_trakt_enter_name_url_or_id">Enter a Trakt list name, URL, or ID</string>
|
||||
<string name="collections_editor_trakt_enter_id_or_url">Enter a Trakt list ID or URL</string>
|
||||
<string name="collections_editor_trakt_load_failed">Could not load Trakt list</string>
|
||||
<string name="collections_editor_trakt_no_lists_found">No Trakt lists found</string>
|
||||
<string name="collections_editor_trakt_resolved_subtitle">Resolved Trakt list</string>
|
||||
<string name="collections_editor_trakt_fallback_title">Trakt List %1$d</string>
|
||||
<string name="collections_editor_tmdb_genre_action">Action</string>
|
||||
<string name="collections_editor_tmdb_genre_adventure">Adventure</string>
|
||||
<string name="collections_editor_tmdb_genre_animation">Animation</string>
|
||||
|
|
@ -1028,6 +1044,8 @@
|
|||
<string name="meta_section_trailers_description">Trailer rail and playback shortcuts.</string>
|
||||
<string name="network_back_online">Back online</string>
|
||||
<string name="network_cannot_reach_servers">Cannot reach servers</string>
|
||||
<string name="network_error_empty_response_body">Empty response body</string>
|
||||
<string name="network_error_request_failed_http">Request failed with HTTP %1$d</string>
|
||||
<string name="network_no_internet_connection">No internet connection</string>
|
||||
<string name="person_age">(age %1$d)</string>
|
||||
<string name="person_born">Born %1$s%2$s</string>
|
||||
|
|
@ -1118,6 +1136,8 @@
|
|||
<string name="updates_asset_line">%1$s • %2$s</string>
|
||||
<string name="updates_check_failed">Update check failed</string>
|
||||
<string name="updates_download_failed">Download failed</string>
|
||||
<string name="updates_download_empty_body">Empty download body</string>
|
||||
<string name="updates_download_file_missing">Downloaded update file is missing.</string>
|
||||
<string name="updates_downloading_progress">Downloading %1$d%</string>
|
||||
<string name="updates_install_failed">Unable to start installation</string>
|
||||
<string name="updates_latest_version">You're using the latest version.</string>
|
||||
|
|
@ -1165,6 +1185,7 @@
|
|||
<string name="notifications_test_send_failed">Failed to send a test notification.</string>
|
||||
<string name="notifications_test_sent_for">Test notification sent for %1$s.</string>
|
||||
<string name="player_unable_to_play_stream">Unable to play this stream.</string>
|
||||
<string name="player_engine_unavailable_rebuild">MPV player engine not available. Please rebuild the app.</string>
|
||||
<string name="profile_pin_changed_requires_refresh">This profile PIN changed. Connect once to refresh the lock on this device.</string>
|
||||
<string name="profile_pin_clear_failed">Couldn't remove PIN lock. Try again.</string>
|
||||
<string name="profile_pin_clear_requires_internet">Connect to the internet to remove the PIN lock.</string>
|
||||
|
|
@ -1177,6 +1198,8 @@
|
|||
<string name="source_embedded">Embedded</string>
|
||||
<string name="trakt_authorization_denied">Authorization denied</string>
|
||||
<string name="trakt_complete_sign_in_browser">Complete Trakt sign in in your browser</string>
|
||||
<string name="trakt_connected">Connected to Trakt</string>
|
||||
<string name="trakt_disconnected">Disconnected from Trakt</string>
|
||||
<string name="trakt_invalid_callback">Invalid Trakt callback</string>
|
||||
<string name="trakt_invalid_callback_state">Invalid Trakt callback state</string>
|
||||
<string name="trakt_invalid_token_response">Invalid Trakt token response</string>
|
||||
|
|
@ -1185,9 +1208,39 @@
|
|||
<string name="trakt_missing_auth_code">Trakt did not return an authorization code</string>
|
||||
<string name="trakt_missing_credentials">Missing Trakt credentials</string>
|
||||
<string name="trakt_progress_load_failed">Failed to load Trakt progress</string>
|
||||
<string name="trakt_public_list">Trakt public list</string>
|
||||
<string name="trakt_public_list_enter_valid_id_or_url">Enter a valid Trakt list ID or URL</string>
|
||||
<string name="trakt_public_list_items_count">%1$d items</string>
|
||||
<string name="trakt_public_list_likes_count">%1$d likes</string>
|
||||
<string name="trakt_public_list_missing_credentials">Missing Trakt credentials in local.properties (TRAKT_CLIENT_ID).</string>
|
||||
<string name="trakt_public_list_missing_id">Missing Trakt list ID</string>
|
||||
<string name="trakt_public_list_missing_numeric_id">Trakt list did not include a numeric ID</string>
|
||||
<string name="trakt_public_list_not_found_or_not_public">Trakt list not found or not public</string>
|
||||
<string name="trakt_public_list_rate_limit_reached">Trakt rate limit reached</string>
|
||||
<string name="trakt_public_list_request_failed">Trakt request failed</string>
|
||||
<string name="trakt_sign_in_complete_failed">Failed to complete Trakt sign in</string>
|
||||
<string name="trakt_user_fallback">Trakt user</string>
|
||||
<string name="trakt_watchlist">Watchlist</string>
|
||||
<string name="tmdb_sources_api_key_required">Add a TMDB API key in Settings to use TMDB sources.</string>
|
||||
<string name="tmdb_sources_collection_fallback_title">TMDB Collection %1$d</string>
|
||||
<string name="tmdb_sources_collection_not_found">TMDB collection not found</string>
|
||||
<string name="tmdb_sources_company_fallback_title">TMDB Production %1$d</string>
|
||||
<string name="tmdb_sources_company_not_found">TMDB company not found</string>
|
||||
<string name="tmdb_sources_director_fallback_title">TMDB Director %1$d</string>
|
||||
<string name="tmdb_sources_discover_no_data">TMDB discover returned no data</string>
|
||||
<string name="tmdb_sources_discover_title">TMDB Discover</string>
|
||||
<string name="tmdb_sources_invalid_id_or_url">Enter a valid TMDB ID or URL.</string>
|
||||
<string name="tmdb_sources_list_fallback_title">TMDB List %1$d</string>
|
||||
<string name="tmdb_sources_list_not_found">TMDB list not found</string>
|
||||
<string name="tmdb_sources_load_failed">Could not load TMDB source</string>
|
||||
<string name="tmdb_sources_missing_collection_id">Missing TMDB collection ID</string>
|
||||
<string name="tmdb_sources_missing_list_id">Missing TMDB list ID</string>
|
||||
<string name="tmdb_sources_missing_person_id">Missing TMDB person ID</string>
|
||||
<string name="tmdb_sources_network_fallback_title">TMDB Network %1$d</string>
|
||||
<string name="tmdb_sources_network_not_found">TMDB network not found</string>
|
||||
<string name="tmdb_sources_person_credits_not_found">TMDB person credits not found</string>
|
||||
<string name="tmdb_sources_person_fallback_title">TMDB Person %1$d</string>
|
||||
<string name="tmdb_sources_person_not_found">TMDB person not found</string>
|
||||
<string name="generic_trailer">Trailer</string>
|
||||
<string name="generic_unknown">Unknown</string>
|
||||
<string name="generic_addon">Addon</string>
|
||||
|
|
@ -1203,6 +1256,8 @@
|
|||
<string name="collections_import_error_trakt_list_id">Source %1$d in folder '%2$s' is missing a Trakt list ID.</string>
|
||||
<string name="collections_import_error_invalid_json">Invalid JSON: %1$s</string>
|
||||
<string name="collections_folder_addon_not_found">Addon not found: %1$s</string>
|
||||
<string name="collections_folder_trakt_movie_list">Trakt Movie List</string>
|
||||
<string name="collections_folder_trakt_series_list">Trakt Series List</string>
|
||||
<string name="date_month_january">January</string>
|
||||
<string name="date_month_february">February</string>
|
||||
<string name="date_month_march">March</string>
|
||||
|
|
@ -1251,9 +1306,13 @@
|
|||
<string name="downloads_enqueue_started">Download started</string>
|
||||
<string name="downloads_enqueue_unsupported_format">Unsupported stream format for downloads</string>
|
||||
<string name="downloads_error_empty_body">Empty response body</string>
|
||||
<string name="downloads_error_finalize_failed">Failed to finalize download file</string>
|
||||
<string name="downloads_error_http_failed">Request failed with HTTP %1$d</string>
|
||||
<string name="downloads_error_not_initialized">Download system is not initialized</string>
|
||||
<string name="downloads_error_open_partial_failed">Failed to open partial download file</string>
|
||||
<string name="downloads_error_partial_not_open">Partial download file is not open</string>
|
||||
<string name="downloads_error_request_failed">Download request failed</string>
|
||||
<string name="downloads_error_write_partial_failed">Failed to write partial download file</string>
|
||||
<string name="home_catalog_default_title">%1$s - %2$s</string>
|
||||
<string name="library_empty_message">Saved titles will appear here after you tap Save on a details screen.</string>
|
||||
<string name="library_empty_title">Your library is empty</string>
|
||||
|
|
@ -1284,4 +1343,64 @@
|
|||
<string name="unit_bytes_kb">KB</string>
|
||||
<string name="unit_bytes_mb">MB</string>
|
||||
<string name="unit_bytes_gb">GB</string>
|
||||
<string name="plugins_section_overview">OVERVIEW</string>
|
||||
<string name="plugins_badge_repos">%1$d repos</string>
|
||||
<string name="plugins_badge_providers">%1$d providers</string>
|
||||
<string name="plugins_badge_enabled">Plugins enabled</string>
|
||||
<string name="plugins_badge_disabled">Plugins disabled</string>
|
||||
<string name="plugins_badge_tmdb_key_set">TMDB API key set</string>
|
||||
<string name="plugins_badge_tmdb_key_missing">TMDB API key missing</string>
|
||||
<string name="plugins_tmdb_required_message">Plugin providers require a TMDB API key. Set it on the TMDB screen or plugin providers will not work correctly.</string>
|
||||
<string name="plugins_enable_globally_title">Enable plugin providers globally</string>
|
||||
<string name="plugins_enable_globally_desc">Use plugin providers during stream discovery.</string>
|
||||
<string name="plugins_group_by_repo_title">Group plugin providers by repository</string>
|
||||
<string name="plugins_group_by_repo_desc">In Streams, show one provider per repository instead of one per source.</string>
|
||||
<string name="plugins_section_add_repo">ADD REPOSITORY</string>
|
||||
<string name="plugins_input_manifest_placeholder">Plugin manifest URL</string>
|
||||
<string name="plugins_button_installing">Installing…</string>
|
||||
<string name="plugins_button_install_repo">Install Plugin Repository</string>
|
||||
<string name="plugins_error_enter_repo_url">Enter a plugin repository URL.</string>
|
||||
<string name="plugins_error_enter_valid_url">Enter a valid plugin URL.</string>
|
||||
<string name="plugins_error_already_installed">That plugin repository is already installed.</string>
|
||||
<string name="plugins_error_install_failed">Unable to install plugin repository</string>
|
||||
<string name="plugins_error_refresh_failed">Unable to refresh repository</string>
|
||||
<string name="plugins_error_provider_not_found">Provider not found</string>
|
||||
<string name="plugins_error_unavailable_build">Plugins are not available in this build.</string>
|
||||
<string name="plugins_manifest_error_name_missing">Manifest name is missing.</string>
|
||||
<string name="plugins_manifest_error_version_missing">Manifest version is missing.</string>
|
||||
<string name="plugins_manifest_error_no_providers">Manifest has no providers.</string>
|
||||
<string name="plugins_message_installed">Installed %1$s.</string>
|
||||
<string name="plugins_section_installed_repos">INSTALLED REPOSITORIES</string>
|
||||
<string name="plugins_empty_repos_title">No plugin repositories installed yet.</string>
|
||||
<string name="plugins_empty_repos_subtitle">Add a repository URL to install provider plugins for stream discovery.</string>
|
||||
<string name="plugins_repo_version">Version %1$s</string>
|
||||
<string name="plugins_cd_refresh_repo">Refresh plugin repository</string>
|
||||
<string name="plugins_cd_delete_repo">Delete plugin repository</string>
|
||||
<string name="plugins_badge_refreshing">Refreshing</string>
|
||||
<string name="plugins_section_providers">PROVIDERS</string>
|
||||
<string name="plugins_empty_providers">No providers available yet.</string>
|
||||
<string name="plugins_provider_no_description">No description</string>
|
||||
<string name="plugins_provider_version">v%1$s</string>
|
||||
<string name="plugins_provider_disabled_by_repo">Disabled by repo</string>
|
||||
<string name="plugins_button_testing">Testing…</string>
|
||||
<string name="plugins_button_test_provider">Test Provider</string>
|
||||
<string name="plugins_test_error_title">Error</string>
|
||||
<string name="plugins_test_failed">Provider test failed</string>
|
||||
<string name="plugins_test_results_count">Test results (%1$d)</string>
|
||||
<string name="plugins_repo_fallback_label">Plugin repository</string>
|
||||
<string name="submit_intro_action">Submit Intro</string>
|
||||
<string name="submit_intro_title">Submit Timestamps</string>
|
||||
<string name="submit_intro_segment_type_label">SEGMENT TYPE</string>
|
||||
<string name="submit_intro_segment_intro">Intro</string>
|
||||
<string name="submit_intro_segment_recap">Recap</string>
|
||||
<string name="submit_intro_segment_outro">Outro</string>
|
||||
<string name="submit_intro_start_time_label">START TIME (MM:SS)</string>
|
||||
<string name="submit_intro_end_time_label">END TIME (MM:SS)</string>
|
||||
<string name="submit_intro_button_submit">Submit</string>
|
||||
<string name="submit_intro_capture_button">Capture</string>
|
||||
<string name="settings_playback_introdb_invalid_key">Invalid API Key or connection failed</string>
|
||||
<string name="network_connection_issue">Connection issue</string>
|
||||
<string name="network_please_check_connection">Please check your connection and try again.</string>
|
||||
<string name="library_local_tab_title">Nuvio Library</string>
|
||||
<string name="streams_plugin_repository_fallback">Plugin repository</string>
|
||||
</resources>
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
package com.nuvio.app.core.network
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import com.nuvio.app.features.addons.httpRequestRaw
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
|
|
@ -9,6 +10,14 @@ import kotlinx.coroutines.flow.StateFlow
|
|||
import kotlinx.coroutines.flow.asStateFlow
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withTimeoutOrNull
|
||||
import nuvio.composeapp.generated.resources.Res
|
||||
import nuvio.composeapp.generated.resources.details_check_connection
|
||||
import nuvio.composeapp.generated.resources.details_servers_unreachable
|
||||
import nuvio.composeapp.generated.resources.network_cannot_reach_servers
|
||||
import nuvio.composeapp.generated.resources.network_connection_issue
|
||||
import nuvio.composeapp.generated.resources.network_no_internet_connection
|
||||
import nuvio.composeapp.generated.resources.network_please_check_connection
|
||||
import org.jetbrains.compose.resources.stringResource
|
||||
|
||||
enum class NetworkCondition {
|
||||
Unknown,
|
||||
|
|
@ -28,18 +37,20 @@ data class NetworkStatusUiState(
|
|||
get() = condition == NetworkCondition.NoInternet || condition == NetworkCondition.ServersUnreachable
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun NetworkCondition.titleForEmptyState(): String =
|
||||
when (this) {
|
||||
NetworkCondition.ServersUnreachable -> "Cannot reach servers"
|
||||
NetworkCondition.NoInternet -> "No internet connection"
|
||||
else -> "Connection issue"
|
||||
NetworkCondition.ServersUnreachable -> stringResource(Res.string.network_cannot_reach_servers)
|
||||
NetworkCondition.NoInternet -> stringResource(Res.string.network_no_internet_connection)
|
||||
else -> stringResource(Res.string.network_connection_issue)
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun NetworkCondition.messageForEmptyState(): String =
|
||||
when (this) {
|
||||
NetworkCondition.ServersUnreachable -> "Your device is online, but Nuvio could not reach required servers."
|
||||
NetworkCondition.NoInternet -> "Check your Wi-Fi or mobile data connection and try again."
|
||||
else -> "Please check your connection and try again."
|
||||
NetworkCondition.ServersUnreachable -> stringResource(Res.string.details_servers_unreachable)
|
||||
NetworkCondition.NoInternet -> stringResource(Res.string.details_check_connection)
|
||||
else -> stringResource(Res.string.network_please_check_connection)
|
||||
}
|
||||
|
||||
object NetworkStatusRepository {
|
||||
|
|
|
|||
|
|
@ -13,6 +13,11 @@ import com.nuvio.app.features.trakt.effectiveLibrarySourceMode as resolveEffecti
|
|||
import com.nuvio.app.features.trakt.shouldUseTraktLibrary
|
||||
import io.github.jan.supabase.postgrest.postgrest
|
||||
import io.github.jan.supabase.postgrest.rpc
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import nuvio.composeapp.generated.resources.Res
|
||||
import nuvio.composeapp.generated.resources.library_local_tab_title
|
||||
import nuvio.composeapp.generated.resources.library_other
|
||||
import org.jetbrains.compose.resources.getString
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.SupervisorJob
|
||||
|
|
@ -405,12 +410,11 @@ object LibraryRepository {
|
|||
}
|
||||
|
||||
internal const val LOCAL_LIBRARY_LIST_KEY = "local"
|
||||
internal const val LOCAL_LIBRARY_LIST_TITLE = "Nuvio Library"
|
||||
|
||||
internal fun localLibraryListTab(): TraktListTab =
|
||||
TraktListTab(
|
||||
key = LOCAL_LIBRARY_LIST_KEY,
|
||||
title = LOCAL_LIBRARY_LIST_TITLE,
|
||||
title = runBlocking { getString(Res.string.library_local_tab_title) },
|
||||
type = TraktListType.WATCHLIST,
|
||||
)
|
||||
|
||||
|
|
@ -461,7 +465,7 @@ private fun LibraryItem.toSyncItem(): LibrarySyncItem = LibrarySyncItem(
|
|||
|
||||
internal fun String.toLibraryDisplayTitle(): String {
|
||||
val normalized = trim()
|
||||
if (normalized.isBlank()) return "Other"
|
||||
if (normalized.isBlank()) return runBlocking { getString(Res.string.library_other) }
|
||||
|
||||
return normalized
|
||||
.split('-', '_', ' ')
|
||||
|
|
@ -469,5 +473,5 @@ internal fun String.toLibraryDisplayTitle(): String {
|
|||
.joinToString(" ") { token ->
|
||||
token.lowercase().replaceFirstChar { char -> char.uppercase() }
|
||||
}
|
||||
.ifBlank { "Other" }
|
||||
.ifBlank { runBlocking { getString(Res.string.library_other) } }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ import kotlinx.coroutines.flow.asStateFlow
|
|||
import kotlinx.coroutines.flow.collectLatest
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.sync.Mutex
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import kotlinx.coroutines.sync.Semaphore
|
||||
import kotlinx.coroutines.sync.withLock
|
||||
import kotlinx.coroutines.sync.withPermit
|
||||
|
|
@ -294,7 +295,7 @@ object EpisodeReleaseNotificationsRepository {
|
|||
permissionGranted = granted,
|
||||
testTargetTitle = currentTestTarget()?.name,
|
||||
errorMessage = when {
|
||||
_uiState.value.isEnabled && !granted -> "System notifications are currently disabled for Nuvio."
|
||||
_uiState.value.isEnabled && !granted -> runBlocking { getString(Res.string.settings_notifications_permission_disabled) }
|
||||
else -> _uiState.value.errorMessage
|
||||
},
|
||||
)
|
||||
|
|
@ -362,7 +363,7 @@ object EpisodeReleaseNotificationsRepository {
|
|||
scheduledCount = 0,
|
||||
testTargetTitle = currentTestTarget()?.name,
|
||||
errorMessage = if (_uiState.value.isEnabled && !permissionGranted) {
|
||||
"System notifications are currently disabled for Nuvio."
|
||||
runBlocking { getString(Res.string.settings_notifications_permission_disabled) }
|
||||
} else {
|
||||
null
|
||||
},
|
||||
|
|
|
|||
|
|
@ -268,7 +268,7 @@ private fun PlayerHeader(
|
|||
if (onSubmitIntroClick != null) {
|
||||
PlayerHeaderIconButton(
|
||||
icon = Icons.Rounded.Flag,
|
||||
contentDescription = "Submit Intro",
|
||||
contentDescription = stringResource(Res.string.submit_intro_action),
|
||||
buttonSize = metrics.headerIconSize + 16.dp,
|
||||
iconSize = metrics.headerIconSize,
|
||||
onClick = onSubmitIntroClick,
|
||||
|
|
|
|||
|
|
@ -49,6 +49,19 @@ import androidx.compose.ui.text.font.FontWeight
|
|||
import androidx.compose.ui.text.input.KeyboardType
|
||||
import androidx.compose.ui.unit.dp
|
||||
import kotlinx.coroutines.launch
|
||||
import nuvio.composeapp.generated.resources.Res
|
||||
import nuvio.composeapp.generated.resources.action_cancel
|
||||
import nuvio.composeapp.generated.resources.action_close
|
||||
import nuvio.composeapp.generated.resources.submit_intro_button_submit
|
||||
import nuvio.composeapp.generated.resources.submit_intro_capture_button
|
||||
import nuvio.composeapp.generated.resources.submit_intro_end_time_label
|
||||
import nuvio.composeapp.generated.resources.submit_intro_segment_intro
|
||||
import nuvio.composeapp.generated.resources.submit_intro_segment_outro
|
||||
import nuvio.composeapp.generated.resources.submit_intro_segment_recap
|
||||
import nuvio.composeapp.generated.resources.submit_intro_segment_type_label
|
||||
import nuvio.composeapp.generated.resources.submit_intro_start_time_label
|
||||
import nuvio.composeapp.generated.resources.submit_intro_title
|
||||
import org.jetbrains.compose.resources.stringResource
|
||||
import kotlin.math.floor
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
|
|
@ -91,20 +104,24 @@ fun SubmitIntroDialog(
|
|||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
Text(
|
||||
text = "Submit Timestamps",
|
||||
text = stringResource(Res.string.submit_intro_title),
|
||||
style = MaterialTheme.typography.titleLarge,
|
||||
color = MaterialTheme.colorScheme.onSurface,
|
||||
fontWeight = FontWeight.Bold,
|
||||
)
|
||||
IconButton(onClick = onDismiss) {
|
||||
Icon(Icons.Rounded.Close, contentDescription = "Close", tint = MaterialTheme.colorScheme.onSurfaceVariant)
|
||||
Icon(
|
||||
Icons.Rounded.Close,
|
||||
contentDescription = stringResource(Res.string.action_close),
|
||||
tint = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// Segment Type
|
||||
Column(verticalArrangement = Arrangement.spacedBy(8.dp)) {
|
||||
Text(
|
||||
text = "SEGMENT TYPE",
|
||||
text = stringResource(Res.string.submit_intro_segment_type_label),
|
||||
style = MaterialTheme.typography.labelSmall,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
fontWeight = FontWeight.SemiBold,
|
||||
|
|
@ -114,21 +131,21 @@ fun SubmitIntroDialog(
|
|||
horizontalArrangement = Arrangement.spacedBy(8.dp),
|
||||
) {
|
||||
SegmentTypeButton(
|
||||
label = "Intro",
|
||||
label = stringResource(Res.string.submit_intro_segment_intro),
|
||||
icon = Icons.Rounded.PlayCircleOutline,
|
||||
selected = segmentType == "intro",
|
||||
onClick = { onSegmentTypeChange("intro") },
|
||||
modifier = Modifier.weight(1f)
|
||||
)
|
||||
SegmentTypeButton(
|
||||
label = "Recap",
|
||||
label = stringResource(Res.string.submit_intro_segment_recap),
|
||||
icon = Icons.Rounded.Replay,
|
||||
selected = segmentType == "recap",
|
||||
onClick = { onSegmentTypeChange("recap") },
|
||||
modifier = Modifier.weight(1f)
|
||||
)
|
||||
SegmentTypeButton(
|
||||
label = "Outro",
|
||||
label = stringResource(Res.string.submit_intro_segment_outro),
|
||||
icon = Icons.Rounded.StopCircle,
|
||||
selected = segmentType == "outro",
|
||||
onClick = { onSegmentTypeChange("outro") },
|
||||
|
|
@ -139,7 +156,7 @@ fun SubmitIntroDialog(
|
|||
|
||||
// Start Time
|
||||
TimeInputRow(
|
||||
label = "START TIME (MM:SS)",
|
||||
label = stringResource(Res.string.submit_intro_start_time_label),
|
||||
value = startTimeStr,
|
||||
onValueChange = onStartTimeChange,
|
||||
onCapture = { onStartTimeChange(formatSecondsToMMSS(currentTimeSec)) }
|
||||
|
|
@ -147,7 +164,7 @@ fun SubmitIntroDialog(
|
|||
|
||||
// End Time
|
||||
TimeInputRow(
|
||||
label = "END TIME (MM:SS)",
|
||||
label = stringResource(Res.string.submit_intro_end_time_label),
|
||||
value = endTimeStr,
|
||||
onValueChange = onEndTimeChange,
|
||||
onCapture = { onEndTimeChange(formatSecondsToMMSS(currentTimeSec)) }
|
||||
|
|
@ -170,7 +187,7 @@ fun SubmitIntroDialog(
|
|||
contentAlignment = Alignment.Center
|
||||
) {
|
||||
Text(
|
||||
text = "Cancel",
|
||||
text = stringResource(Res.string.action_cancel),
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
fontWeight = FontWeight.SemiBold
|
||||
)
|
||||
|
|
@ -217,7 +234,7 @@ fun SubmitIntroDialog(
|
|||
) {
|
||||
Icon(Icons.Rounded.Send, contentDescription = null, tint = MaterialTheme.colorScheme.onPrimary, modifier = Modifier.size(18.dp))
|
||||
Text(
|
||||
text = "Submit",
|
||||
text = stringResource(Res.string.submit_intro_button_submit),
|
||||
color = MaterialTheme.colorScheme.onPrimary,
|
||||
fontWeight = FontWeight.Bold
|
||||
)
|
||||
|
|
@ -328,7 +345,7 @@ private fun TimeInputRow(
|
|||
modifier = Modifier.size(18.dp)
|
||||
)
|
||||
Text(
|
||||
text = "Capture",
|
||||
text = stringResource(Res.string.submit_intro_capture_button),
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
fontWeight = FontWeight.SemiBold
|
||||
|
|
|
|||
|
|
@ -2111,6 +2111,7 @@ private fun IntroDbApiKeyDialog(
|
|||
var value by remember { mutableStateOf(initialValue) }
|
||||
var isVerifying by remember { mutableStateOf(false) }
|
||||
var errorMessage by remember { mutableStateOf<String?>(null) }
|
||||
val invalidKeyMessage = stringResource(Res.string.settings_playback_introdb_invalid_key)
|
||||
|
||||
BasicAlertDialog(onDismissRequest = { if (!isVerifying) onDismiss() }) {
|
||||
Surface(
|
||||
|
|
@ -2179,7 +2180,7 @@ private fun IntroDbApiKeyDialog(
|
|||
if (isValid) {
|
||||
onSave(trimmed)
|
||||
} else {
|
||||
errorMessage = "Invalid API Key or connection failed"
|
||||
errorMessage = invalidKeyMessage
|
||||
}
|
||||
}
|
||||
},
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@ import kotlinx.coroutines.flow.MutableStateFlow
|
|||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.asStateFlow
|
||||
import kotlinx.coroutines.flow.update
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import nuvio.composeapp.generated.resources.*
|
||||
import org.jetbrains.compose.resources.getString
|
||||
import kotlinx.coroutines.launch
|
||||
|
|
@ -596,6 +597,8 @@ private fun String.fallbackRepositoryLabel(): String {
|
|||
val withoutManifest = withoutQuery.removeSuffix("/manifest.json")
|
||||
val host = withoutManifest.substringAfter("://", withoutManifest).substringBefore('/')
|
||||
return host.ifBlank {
|
||||
withoutManifest.substringAfterLast('/').ifBlank { "Plugin repository" }
|
||||
withoutManifest.substringAfterLast('/').ifBlank {
|
||||
runBlocking { getString(Res.string.streams_plugin_repository_fallback) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -22,6 +22,10 @@ import kotlinx.serialization.json.JsonPrimitive
|
|||
import kotlinx.serialization.json.contentOrNull
|
||||
import kotlinx.serialization.json.intOrNull
|
||||
import kotlinx.serialization.json.jsonPrimitive
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import nuvio.composeapp.generated.resources.Res
|
||||
import nuvio.composeapp.generated.resources.generic_unknown
|
||||
import org.jetbrains.compose.resources.getString
|
||||
import kotlin.random.Random
|
||||
|
||||
private const val PLUGIN_TIMEOUT_MS = 60_000L
|
||||
|
|
@ -438,7 +442,7 @@ internal object PluginRuntime {
|
|||
?.takeIf { it.isNotEmpty() }
|
||||
|
||||
PluginRuntimeResult(
|
||||
title = item.stringOrNull("title") ?: item.stringOrNull("name") ?: "Unknown",
|
||||
title = item.stringOrNull("title") ?: item.stringOrNull("name") ?: runBlocking { getString(Res.string.generic_unknown) },
|
||||
name = item.stringOrNull("name"),
|
||||
url = url,
|
||||
quality = item.stringOrNull("quality"),
|
||||
|
|
|
|||
|
|
@ -40,6 +40,44 @@ import com.nuvio.app.core.ui.NuvioSectionLabel
|
|||
import com.nuvio.app.core.ui.NuvioSurfaceCard
|
||||
import com.nuvio.app.features.tmdb.TmdbSettingsRepository
|
||||
import kotlinx.coroutines.launch
|
||||
import nuvio.composeapp.generated.resources.Res
|
||||
import nuvio.composeapp.generated.resources.plugins_badge_disabled
|
||||
import nuvio.composeapp.generated.resources.plugins_badge_enabled
|
||||
import nuvio.composeapp.generated.resources.plugins_badge_providers
|
||||
import nuvio.composeapp.generated.resources.plugins_badge_refreshing
|
||||
import nuvio.composeapp.generated.resources.plugins_badge_repos
|
||||
import nuvio.composeapp.generated.resources.plugins_badge_tmdb_key_missing
|
||||
import nuvio.composeapp.generated.resources.plugins_badge_tmdb_key_set
|
||||
import nuvio.composeapp.generated.resources.plugins_button_install_repo
|
||||
import nuvio.composeapp.generated.resources.plugins_button_installing
|
||||
import nuvio.composeapp.generated.resources.plugins_button_test_provider
|
||||
import nuvio.composeapp.generated.resources.plugins_button_testing
|
||||
import nuvio.composeapp.generated.resources.plugins_cd_delete_repo
|
||||
import nuvio.composeapp.generated.resources.plugins_cd_refresh_repo
|
||||
import nuvio.composeapp.generated.resources.plugins_empty_providers
|
||||
import nuvio.composeapp.generated.resources.plugins_empty_repos_subtitle
|
||||
import nuvio.composeapp.generated.resources.plugins_empty_repos_title
|
||||
import nuvio.composeapp.generated.resources.plugins_enable_globally_desc
|
||||
import nuvio.composeapp.generated.resources.plugins_enable_globally_title
|
||||
import nuvio.composeapp.generated.resources.plugins_error_enter_repo_url
|
||||
import nuvio.composeapp.generated.resources.plugins_group_by_repo_desc
|
||||
import nuvio.composeapp.generated.resources.plugins_group_by_repo_title
|
||||
import nuvio.composeapp.generated.resources.plugins_input_manifest_placeholder
|
||||
import nuvio.composeapp.generated.resources.plugins_message_installed
|
||||
import nuvio.composeapp.generated.resources.plugins_provider_disabled_by_repo
|
||||
import nuvio.composeapp.generated.resources.plugins_provider_no_description
|
||||
import nuvio.composeapp.generated.resources.plugins_provider_version
|
||||
import nuvio.composeapp.generated.resources.plugins_repo_fallback_label
|
||||
import nuvio.composeapp.generated.resources.plugins_repo_version
|
||||
import nuvio.composeapp.generated.resources.plugins_section_add_repo
|
||||
import nuvio.composeapp.generated.resources.plugins_section_installed_repos
|
||||
import nuvio.composeapp.generated.resources.plugins_section_overview
|
||||
import nuvio.composeapp.generated.resources.plugins_section_providers
|
||||
import nuvio.composeapp.generated.resources.plugins_test_error_title
|
||||
import nuvio.composeapp.generated.resources.plugins_test_failed
|
||||
import nuvio.composeapp.generated.resources.plugins_test_results_count
|
||||
import nuvio.composeapp.generated.resources.plugins_tmdb_required_message
|
||||
import org.jetbrains.compose.resources.stringResource
|
||||
|
||||
@Composable
|
||||
fun PluginsSettingsPageContent(
|
||||
|
|
@ -79,29 +117,43 @@ fun PluginsSettingsPageContent(
|
|||
)
|
||||
}
|
||||
|
||||
val repoFallbackLabel = stringResource(Res.string.plugins_repo_fallback_label)
|
||||
val testFailedDefault = stringResource(Res.string.plugins_test_failed)
|
||||
val testErrorTitle = stringResource(Res.string.plugins_test_error_title)
|
||||
val installedTemplate = stringResource(Res.string.plugins_message_installed)
|
||||
val enterRepoUrlError = stringResource(Res.string.plugins_error_enter_repo_url)
|
||||
|
||||
Column(
|
||||
modifier = modifier,
|
||||
verticalArrangement = Arrangement.spacedBy(12.dp),
|
||||
) {
|
||||
NuvioSectionLabel("OVERVIEW")
|
||||
NuvioSectionLabel(stringResource(Res.string.plugins_section_overview))
|
||||
NuvioSurfaceCard {
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
horizontalArrangement = Arrangement.spacedBy(10.dp),
|
||||
) {
|
||||
NuvioInfoBadge(text = "${sortedRepos.size} repos")
|
||||
NuvioInfoBadge(text = "${sortedScrapers.size} providers")
|
||||
NuvioInfoBadge(text = stringResource(Res.string.plugins_badge_repos, sortedRepos.size))
|
||||
NuvioInfoBadge(text = stringResource(Res.string.plugins_badge_providers, sortedScrapers.size))
|
||||
NuvioInfoBadge(
|
||||
text = if (uiState.pluginsEnabled) "Plugins enabled" else "Plugins disabled",
|
||||
text = if (uiState.pluginsEnabled) {
|
||||
stringResource(Res.string.plugins_badge_enabled)
|
||||
} else {
|
||||
stringResource(Res.string.plugins_badge_disabled)
|
||||
},
|
||||
)
|
||||
NuvioInfoBadge(
|
||||
text = if (hasTmdbApiKey) "TMDB API key set" else "TMDB API key missing",
|
||||
text = if (hasTmdbApiKey) {
|
||||
stringResource(Res.string.plugins_badge_tmdb_key_set)
|
||||
} else {
|
||||
stringResource(Res.string.plugins_badge_tmdb_key_missing)
|
||||
},
|
||||
)
|
||||
}
|
||||
if (!hasTmdbApiKey) {
|
||||
Spacer(modifier = Modifier.height(12.dp))
|
||||
Text(
|
||||
text = "Plugin providers require a TMDB API key. Set it on the TMDB screen or plugin providers will not work correctly.",
|
||||
text = stringResource(Res.string.plugins_tmdb_required_message),
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
color = MaterialTheme.colorScheme.error,
|
||||
)
|
||||
|
|
@ -114,13 +166,13 @@ fun PluginsSettingsPageContent(
|
|||
) {
|
||||
Column(modifier = Modifier.weight(1f)) {
|
||||
Text(
|
||||
text = "Enable plugin providers globally",
|
||||
text = stringResource(Res.string.plugins_enable_globally_title),
|
||||
style = MaterialTheme.typography.bodyLarge,
|
||||
color = MaterialTheme.colorScheme.onSurface,
|
||||
)
|
||||
Spacer(modifier = Modifier.height(2.dp))
|
||||
Text(
|
||||
text = "Use plugin providers during stream discovery.",
|
||||
text = stringResource(Res.string.plugins_enable_globally_desc),
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
)
|
||||
|
|
@ -143,13 +195,13 @@ fun PluginsSettingsPageContent(
|
|||
) {
|
||||
Column(modifier = Modifier.weight(1f)) {
|
||||
Text(
|
||||
text = "Group plugin providers by repository",
|
||||
text = stringResource(Res.string.plugins_group_by_repo_title),
|
||||
style = MaterialTheme.typography.bodyLarge,
|
||||
color = MaterialTheme.colorScheme.onSurface,
|
||||
)
|
||||
Spacer(modifier = Modifier.height(2.dp))
|
||||
Text(
|
||||
text = "In Streams, show one provider per repository instead of one per source.",
|
||||
text = stringResource(Res.string.plugins_group_by_repo_desc),
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
)
|
||||
|
|
@ -162,7 +214,7 @@ fun PluginsSettingsPageContent(
|
|||
}
|
||||
}
|
||||
|
||||
NuvioSectionLabel("ADD REPOSITORY")
|
||||
NuvioSectionLabel(stringResource(Res.string.plugins_section_add_repo))
|
||||
NuvioSurfaceCard {
|
||||
NuvioInputField(
|
||||
value = repositoryUrl,
|
||||
|
|
@ -170,16 +222,20 @@ fun PluginsSettingsPageContent(
|
|||
repositoryUrl = it
|
||||
message = null
|
||||
},
|
||||
placeholder = "Plugin manifest URL",
|
||||
placeholder = stringResource(Res.string.plugins_input_manifest_placeholder),
|
||||
)
|
||||
Spacer(modifier = Modifier.height(16.dp))
|
||||
NuvioPrimaryButton(
|
||||
text = if (isAdding) "Installing..." else "Install Plugin Repository",
|
||||
text = if (isAdding) {
|
||||
stringResource(Res.string.plugins_button_installing)
|
||||
} else {
|
||||
stringResource(Res.string.plugins_button_install_repo)
|
||||
},
|
||||
enabled = repositoryUrl.isNotBlank() && !isAdding,
|
||||
onClick = {
|
||||
val requested = repositoryUrl.trim()
|
||||
if (requested.isBlank()) {
|
||||
message = "Enter a plugin repository URL."
|
||||
message = enterRepoUrlError
|
||||
return@NuvioPrimaryButton
|
||||
}
|
||||
isAdding = true
|
||||
|
|
@ -188,7 +244,7 @@ fun PluginsSettingsPageContent(
|
|||
when (val result = PluginRepository.addRepository(requested)) {
|
||||
is AddPluginRepositoryResult.Success -> {
|
||||
repositoryUrl = ""
|
||||
message = "Installed ${result.repository.name}."
|
||||
message = installedTemplate.format(result.repository.name)
|
||||
}
|
||||
is AddPluginRepositoryResult.Error -> {
|
||||
message = result.message
|
||||
|
|
@ -208,17 +264,17 @@ fun PluginsSettingsPageContent(
|
|||
}
|
||||
}
|
||||
|
||||
NuvioSectionLabel("INSTALLED REPOSITORIES")
|
||||
NuvioSectionLabel(stringResource(Res.string.plugins_section_installed_repos))
|
||||
if (sortedRepos.isEmpty()) {
|
||||
NuvioSurfaceCard {
|
||||
Text(
|
||||
text = "No plugin repositories installed yet.",
|
||||
text = stringResource(Res.string.plugins_empty_repos_title),
|
||||
style = MaterialTheme.typography.titleLarge,
|
||||
color = MaterialTheme.colorScheme.onSurface,
|
||||
)
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
Text(
|
||||
text = "Add a repository URL to install provider plugins for stream discovery.",
|
||||
text = stringResource(Res.string.plugins_empty_repos_subtitle),
|
||||
style = MaterialTheme.typography.bodyLarge,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
)
|
||||
|
|
@ -242,7 +298,7 @@ fun PluginsSettingsPageContent(
|
|||
repo.version?.let { version ->
|
||||
Spacer(modifier = Modifier.height(6.dp))
|
||||
Text(
|
||||
text = "Version $version",
|
||||
text = stringResource(Res.string.plugins_repo_version, version),
|
||||
style = MaterialTheme.typography.bodyLarge,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
)
|
||||
|
|
@ -259,13 +315,13 @@ fun PluginsSettingsPageContent(
|
|||
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||
NuvioIconActionButton(
|
||||
icon = Icons.Rounded.Refresh,
|
||||
contentDescription = "Refresh plugin repository",
|
||||
contentDescription = stringResource(Res.string.plugins_cd_refresh_repo),
|
||||
tint = MaterialTheme.colorScheme.primary,
|
||||
onClick = { PluginRepository.refreshRepository(repo.manifestUrl, pushAfterRefresh = true) },
|
||||
)
|
||||
NuvioIconActionButton(
|
||||
icon = Icons.Rounded.Delete,
|
||||
contentDescription = "Delete plugin repository",
|
||||
contentDescription = stringResource(Res.string.plugins_cd_delete_repo),
|
||||
tint = MaterialTheme.colorScheme.error,
|
||||
onClick = { PluginRepository.removeRepository(repo.manifestUrl) },
|
||||
)
|
||||
|
|
@ -276,9 +332,9 @@ fun PluginsSettingsPageContent(
|
|||
modifier = Modifier.fillMaxWidth(),
|
||||
horizontalArrangement = Arrangement.spacedBy(10.dp),
|
||||
) {
|
||||
NuvioInfoBadge(text = "${repo.scraperCount} providers")
|
||||
NuvioInfoBadge(text = stringResource(Res.string.plugins_badge_providers, repo.scraperCount))
|
||||
if (repo.isRefreshing) {
|
||||
NuvioInfoBadge(text = "Refreshing")
|
||||
NuvioInfoBadge(text = stringResource(Res.string.plugins_badge_refreshing))
|
||||
}
|
||||
}
|
||||
repo.errorMessage?.let { errorMessage ->
|
||||
|
|
@ -293,11 +349,11 @@ fun PluginsSettingsPageContent(
|
|||
}
|
||||
}
|
||||
|
||||
NuvioSectionLabel("PROVIDERS")
|
||||
NuvioSectionLabel(stringResource(Res.string.plugins_section_providers))
|
||||
if (sortedScrapers.isEmpty()) {
|
||||
NuvioSurfaceCard {
|
||||
Text(
|
||||
text = "No providers available yet.",
|
||||
text = stringResource(Res.string.plugins_empty_providers),
|
||||
style = MaterialTheme.typography.bodyLarge,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
)
|
||||
|
|
@ -307,7 +363,7 @@ fun PluginsSettingsPageContent(
|
|||
val scraperResults = testResults[scraper.id]
|
||||
val isTestingThisScraper = testingScraperId == scraper.id
|
||||
val repositoryName = repositoryNameByUrl[scraper.repositoryUrl]
|
||||
?: scraper.repositoryUrl.fallbackRepositoryLabel()
|
||||
?: scraper.repositoryUrl.fallbackRepositoryLabel(repoFallbackLabel)
|
||||
|
||||
NuvioSurfaceCard {
|
||||
Row(
|
||||
|
|
@ -342,7 +398,9 @@ fun PluginsSettingsPageContent(
|
|||
overflow = TextOverflow.Ellipsis,
|
||||
)
|
||||
Text(
|
||||
text = scraper.description.ifBlank { "No description" },
|
||||
text = scraper.description.ifBlank {
|
||||
stringResource(Res.string.plugins_provider_no_description)
|
||||
},
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
maxLines = 2,
|
||||
|
|
@ -363,15 +421,19 @@ fun PluginsSettingsPageContent(
|
|||
horizontalArrangement = Arrangement.spacedBy(10.dp),
|
||||
) {
|
||||
NuvioInfoBadge(text = scraper.supportedTypes.joinToString(" | "))
|
||||
NuvioInfoBadge(text = "v${scraper.version}")
|
||||
NuvioInfoBadge(text = stringResource(Res.string.plugins_provider_version, scraper.version))
|
||||
if (!scraper.manifestEnabled) {
|
||||
NuvioInfoBadge(text = "Disabled by repo")
|
||||
NuvioInfoBadge(text = stringResource(Res.string.plugins_provider_disabled_by_repo))
|
||||
}
|
||||
}
|
||||
|
||||
Spacer(modifier = Modifier.height(12.dp))
|
||||
NuvioPrimaryButton(
|
||||
text = if (isTestingThisScraper) "Testing..." else "Test Provider",
|
||||
text = if (isTestingThisScraper) {
|
||||
stringResource(Res.string.plugins_button_testing)
|
||||
} else {
|
||||
stringResource(Res.string.plugins_button_test_provider)
|
||||
},
|
||||
enabled = hasTmdbApiKey && !isTestingThisScraper,
|
||||
onClick = {
|
||||
testingScraperId = scraper.id
|
||||
|
|
@ -383,8 +445,8 @@ fun PluginsSettingsPageContent(
|
|||
.onFailure { error ->
|
||||
testResults[scraper.id] = listOf(
|
||||
PluginRuntimeResult(
|
||||
title = "Error",
|
||||
name = error.message ?: "Provider test failed",
|
||||
title = testErrorTitle,
|
||||
name = error.message ?: testFailedDefault,
|
||||
url = "about:error",
|
||||
),
|
||||
)
|
||||
|
|
@ -399,7 +461,7 @@ fun PluginsSettingsPageContent(
|
|||
HorizontalDivider(color = MaterialTheme.colorScheme.outline)
|
||||
Spacer(modifier = Modifier.height(12.dp))
|
||||
Text(
|
||||
text = "Test results (${scraperResults.size})",
|
||||
text = stringResource(Res.string.plugins_test_results_count, scraperResults.size),
|
||||
style = MaterialTheme.typography.bodyLarge,
|
||||
color = MaterialTheme.colorScheme.onSurface,
|
||||
)
|
||||
|
|
@ -441,11 +503,11 @@ fun PluginsSettingsPageContent(
|
|||
}
|
||||
}
|
||||
|
||||
private fun String.fallbackRepositoryLabel(): String {
|
||||
private fun String.fallbackRepositoryLabel(fallback: String): String {
|
||||
val withoutQuery = substringBefore("?")
|
||||
val withoutManifest = withoutQuery.removeSuffix("/manifest.json")
|
||||
val host = withoutManifest.substringAfter("://", withoutManifest).substringBefore('/')
|
||||
return host.ifBlank {
|
||||
withoutManifest.substringAfterLast('/').ifBlank { "Plugin repository" }
|
||||
withoutManifest.substringAfterLast('/').ifBlank { fallback }
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue