Aggiunge i18n IT/EN/FR/DE/ES, pagine pubbliche squadre e UX archivio.
Il selettore lingua funziona sul web e sulle app native; su Android la preferenza è persistita e applicata al riavvio. Incluse anche eliminazione replay a scope e campi pagina pubblica squadra. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -21,6 +21,7 @@
|
||||
android:label="@string/app_name"
|
||||
android:supportsRtl="true"
|
||||
android:theme="@style/Theme.MatchLiveTv"
|
||||
android:localeConfig="@xml/locales_config"
|
||||
android:usesCleartextTraffic="true"
|
||||
android:networkSecurityConfig="@xml/network_security_config">
|
||||
|
||||
@@ -29,7 +30,7 @@
|
||||
android:exported="true"
|
||||
android:launchMode="singleTop"
|
||||
android:screenOrientation="portrait"
|
||||
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
|
||||
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|fontScale|screenLayout|density|uiMode"
|
||||
android:hardwareAccelerated="true"
|
||||
android:windowSoftInputMode="adjustResize">
|
||||
<intent-filter>
|
||||
@@ -57,5 +58,15 @@
|
||||
android:name=".streaming.LiveBroadcastService"
|
||||
android:exported="false"
|
||||
android:foregroundServiceType="camera|microphone" />
|
||||
|
||||
<!-- Persistenza lingue AppCompat (API < 33) -->
|
||||
<service
|
||||
android:name="androidx.appcompat.app.AppLocalesMetadataHolderService"
|
||||
android:enabled="false"
|
||||
android:exported="false">
|
||||
<meta-data
|
||||
android:name="autoStoreLocales"
|
||||
android:value="true" />
|
||||
</service>
|
||||
</application>
|
||||
</manifest>
|
||||
|
||||
@@ -1,10 +1,13 @@
|
||||
package com.matchlivetv.match_live_tv
|
||||
|
||||
import android.content.Context
|
||||
import android.content.res.Configuration
|
||||
import android.os.Bundle
|
||||
import androidx.activity.ComponentActivity
|
||||
import androidx.activity.compose.setContent
|
||||
import androidx.activity.enableEdgeToEdge
|
||||
import androidx.compose.runtime.key
|
||||
import com.matchlivetv.match_live_tv.core.AppLocale
|
||||
import com.matchlivetv.match_live_tv.ui.navigation.AppNavHost
|
||||
import com.matchlivetv.match_live_tv.ui.theme.MatchLiveTheme
|
||||
|
||||
@@ -12,6 +15,10 @@ class MainActivity : ComponentActivity() {
|
||||
|
||||
private val container by lazy { (application as MatchLiveTvApplication).container }
|
||||
|
||||
override fun attachBaseContext(newBase: Context) {
|
||||
super.attachBaseContext(AppLocale.wrap(newBase))
|
||||
}
|
||||
|
||||
override fun onConfigurationChanged(newConfig: Configuration) {
|
||||
container.broadcastCoordinator.pauseForConfigurationChange()
|
||||
super.onConfigurationChanged(newConfig)
|
||||
@@ -21,9 +28,12 @@ class MainActivity : ComponentActivity() {
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
enableEdgeToEdge()
|
||||
val localeTag = AppLocale.currentTag(this)
|
||||
setContent {
|
||||
MatchLiveTheme {
|
||||
AppNavHost(container)
|
||||
key(localeTag) {
|
||||
MatchLiveTheme {
|
||||
AppNavHost(container)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,89 @@
|
||||
package com.matchlivetv.match_live_tv.core
|
||||
|
||||
import android.app.Activity
|
||||
import android.content.Context
|
||||
import android.content.ContextWrapper
|
||||
import android.content.res.Configuration
|
||||
import android.os.Handler
|
||||
import android.os.LocaleList
|
||||
import android.os.Looper
|
||||
import androidx.appcompat.app.AppCompatDelegate
|
||||
import androidx.core.os.LocaleListCompat
|
||||
import java.util.Locale
|
||||
|
||||
/**
|
||||
* Lingua app persistita in SharedPreferences + context wrapping.
|
||||
* Più affidabile di soli AppCompat locales su ComponentActivity/Compose.
|
||||
*/
|
||||
object AppLocale {
|
||||
const val SYSTEM = ""
|
||||
|
||||
private const val PREFS = "mltv_locale"
|
||||
private const val KEY_TAG = "tag"
|
||||
|
||||
data class Option(val tag: String, val labelRes: Int)
|
||||
|
||||
val options = listOf(
|
||||
Option(SYSTEM, com.matchlivetv.match_live_tv.R.string.language_system),
|
||||
Option("it", com.matchlivetv.match_live_tv.R.string.language_italian),
|
||||
Option("en", com.matchlivetv.match_live_tv.R.string.language_english),
|
||||
Option("fr", com.matchlivetv.match_live_tv.R.string.language_french),
|
||||
Option("de", com.matchlivetv.match_live_tv.R.string.language_german),
|
||||
Option("es", com.matchlivetv.match_live_tv.R.string.language_spanish),
|
||||
)
|
||||
|
||||
fun normalize(tag: String): String {
|
||||
if (tag.isBlank()) return SYSTEM
|
||||
return tag.substringBefore(",").substringBefore("-").substringBefore("_").lowercase()
|
||||
}
|
||||
|
||||
fun currentTag(context: Context): String {
|
||||
val stored = context.applicationContext
|
||||
.getSharedPreferences(PREFS, Context.MODE_PRIVATE)
|
||||
.getString(KEY_TAG, SYSTEM)
|
||||
.orEmpty()
|
||||
return normalize(stored)
|
||||
}
|
||||
|
||||
fun wrap(context: Context): Context {
|
||||
val tag = currentTag(context)
|
||||
if (tag.isBlank()) return context
|
||||
|
||||
val locale = Locale.forLanguageTag(tag)
|
||||
Locale.setDefault(locale)
|
||||
val config = Configuration(context.resources.configuration)
|
||||
config.setLocales(LocaleList(locale))
|
||||
return context.createConfigurationContext(config)
|
||||
}
|
||||
|
||||
fun apply(activity: Activity, tag: String) {
|
||||
val normalized = normalize(tag)
|
||||
val prefs = activity.applicationContext.getSharedPreferences(PREFS, Context.MODE_PRIVATE)
|
||||
if (normalized == currentTag(activity)) return
|
||||
|
||||
// commit sincronizzato: deve essere scritto prima del recreate
|
||||
prefs.edit().putString(KEY_TAG, normalized).commit()
|
||||
|
||||
val locales = if (normalized.isBlank()) {
|
||||
LocaleListCompat.getEmptyLocaleList()
|
||||
} else {
|
||||
LocaleListCompat.forLanguageTags(normalized)
|
||||
}
|
||||
AppCompatDelegate.setApplicationLocales(locales)
|
||||
activity.recreate()
|
||||
}
|
||||
|
||||
fun applyAfterDismiss(activity: Activity?, tag: String, dismiss: () -> Unit) {
|
||||
dismiss()
|
||||
Handler(Looper.getMainLooper()).post {
|
||||
if (activity == null || activity.isFinishing || activity.isDestroyed) return@post
|
||||
apply(activity, tag)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
tailrec fun Context.findActivity(): Activity? = when (this) {
|
||||
is Activity -> this
|
||||
is ContextWrapper -> baseContext.findActivity()
|
||||
else -> null
|
||||
}
|
||||
+47
@@ -0,0 +1,47 @@
|
||||
package com.matchlivetv.match_live_tv.core
|
||||
|
||||
import java.net.URI
|
||||
|
||||
/**
|
||||
* Normalizza l'URL RTMP restituito dall'API per il dispositivo corrente.
|
||||
* In locale Docker espone spesso 127.0.0.1 / mediamtx: dall'emulatore Android
|
||||
* vanno riscritti sull'host dell'API (es. 10.0.2.2).
|
||||
*/
|
||||
object RtmpIngestUrl {
|
||||
private val rewriteHosts = setOf(
|
||||
"localhost",
|
||||
"127.0.0.1",
|
||||
"mediamtx",
|
||||
"host.docker.internal",
|
||||
)
|
||||
|
||||
fun resolveForDevice(urlString: String): String =
|
||||
resolve(urlString, apiBaseUrl = AppConfig.apiBaseUrl)
|
||||
|
||||
fun resolve(urlString: String, apiBaseUrl: String): String {
|
||||
if (urlString.isBlank()) return urlString
|
||||
val uri = runCatching { URI(urlString) }.getOrNull() ?: return urlString
|
||||
val host = uri.host?.lowercase() ?: return urlString
|
||||
if (host !in rewriteHosts) return urlString
|
||||
|
||||
val apiHost = runCatching { URI(apiBaseUrl).host }.getOrNull()
|
||||
?.takeIf { it.isNotBlank() }
|
||||
?: return urlString
|
||||
|
||||
val port = if (uri.port > 0) uri.port else 1935
|
||||
val path = uri.rawPath.orEmpty()
|
||||
val query = uri.rawQuery
|
||||
|
||||
return buildString {
|
||||
append("rtmp://")
|
||||
append(apiHost)
|
||||
append(':')
|
||||
append(port)
|
||||
append(path)
|
||||
if (!query.isNullOrBlank()) {
|
||||
append('?')
|
||||
append(query)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+5
-1
@@ -32,6 +32,7 @@ import androidx.compose.ui.viewinterop.AndroidView
|
||||
import com.matchlivetv.match_live_tv.core.DeviceTelemetry
|
||||
import com.matchlivetv.match_live_tv.core.parseColorHex
|
||||
import com.matchlivetv.match_live_tv.core.resolveMediaUrl
|
||||
import com.matchlivetv.match_live_tv.core.RtmpIngestUrl
|
||||
import com.matchlivetv.match_live_tv.data.AppContainer
|
||||
import com.matchlivetv.match_live_tv.domain.Match
|
||||
import com.matchlivetv.match_live_tv.domain.MatchScoringRules
|
||||
@@ -85,7 +86,10 @@ fun BroadcastScreen(
|
||||
}
|
||||
|
||||
fun broadcastConfig(loaded: StreamSession): BroadcastConfig =
|
||||
StreamVideoPreset.broadcastConfig(loaded, loaded.rtmpIngestUrl.orEmpty())
|
||||
StreamVideoPreset.broadcastConfig(
|
||||
loaded,
|
||||
RtmpIngestUrl.resolveForDevice(loaded.rtmpIngestUrl.orEmpty()),
|
||||
)
|
||||
|
||||
suspend fun stopStreamPermanently() {
|
||||
runCatching { container.sessionRepository.stopSession(sessionId) }
|
||||
|
||||
+69
@@ -0,0 +1,69 @@
|
||||
package com.matchlivetv.match_live_tv.ui.components
|
||||
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.material3.AlertDialog
|
||||
import androidx.compose.material3.RadioButton
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TextButton
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.matchlivetv.match_live_tv.R
|
||||
import com.matchlivetv.match_live_tv.core.AppLocale
|
||||
import com.matchlivetv.match_live_tv.core.findActivity
|
||||
import com.matchlivetv.match_live_tv.ui.theme.MatchColors
|
||||
|
||||
@Composable
|
||||
fun LanguagePickerDialog(
|
||||
onDismiss: () -> Unit,
|
||||
) {
|
||||
val context = LocalContext.current
|
||||
val activity = context.findActivity()
|
||||
val selected = AppLocale.currentTag(context)
|
||||
AlertDialog(
|
||||
onDismissRequest = onDismiss,
|
||||
title = { Text(stringResource(R.string.language_label)) },
|
||||
text = {
|
||||
Column {
|
||||
AppLocale.options.forEach { option ->
|
||||
val optionTag = AppLocale.normalize(option.tag)
|
||||
val isSelected = selected == optionTag
|
||||
fun choose() {
|
||||
AppLocale.applyAfterDismiss(activity, option.tag, onDismiss)
|
||||
}
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.clickable(onClick = ::choose)
|
||||
.padding(vertical = 4.dp),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.Start,
|
||||
) {
|
||||
RadioButton(
|
||||
selected = isSelected,
|
||||
onClick = ::choose,
|
||||
)
|
||||
Text(
|
||||
text = stringResource(option.labelRes),
|
||||
color = MatchColors.TextSecondary,
|
||||
modifier = Modifier.padding(start = 4.dp),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
confirmButton = {
|
||||
TextButton(onClick = onDismiss) {
|
||||
Text(stringResource(R.string.action_cancel))
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
+24
-16
@@ -17,6 +17,7 @@ import androidx.compose.material3.IconButton
|
||||
import androidx.compose.material3.OutlinedTextField
|
||||
import androidx.compose.material3.OutlinedTextFieldDefaults
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TextButton
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
@@ -27,13 +28,16 @@ import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.focus.FocusDirection
|
||||
import androidx.compose.ui.platform.LocalFocusManager
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.input.ImeAction
|
||||
import androidx.compose.ui.text.input.KeyboardType
|
||||
import androidx.compose.ui.text.input.PasswordVisualTransformation
|
||||
import androidx.compose.ui.text.input.VisualTransformation
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.matchlivetv.match_live_tv.R
|
||||
import com.matchlivetv.match_live_tv.data.AppContainer
|
||||
import com.matchlivetv.match_live_tv.ui.components.LanguagePickerDialog
|
||||
import com.matchlivetv.match_live_tv.ui.components.MatchLiveWordmark
|
||||
import com.matchlivetv.match_live_tv.ui.components.MatchPrimaryButton
|
||||
import com.matchlivetv.match_live_tv.ui.components.MatchScreenScaffold
|
||||
@@ -50,8 +54,12 @@ fun LoginScreen(
|
||||
var error by remember { mutableStateOf<String?>(null) }
|
||||
var loading by remember { mutableStateOf(false) }
|
||||
var passwordVisible by remember { mutableStateOf(false) }
|
||||
var showLanguagePicker by remember { mutableStateOf(false) }
|
||||
val scope = rememberCoroutineScope()
|
||||
val focusManager = LocalFocusManager.current
|
||||
val errCredentials = stringResource(R.string.login_error_credentials)
|
||||
val errUnreachable = stringResource(R.string.login_error_unreachable)
|
||||
val errGeneric = stringResource(R.string.login_error_generic)
|
||||
|
||||
fun submitLogin() {
|
||||
if (loading || email.isBlank() || password.isBlank()) return
|
||||
@@ -64,17 +72,19 @@ fun LoginScreen(
|
||||
onLoggedIn()
|
||||
}.onFailure {
|
||||
error = when {
|
||||
it.message?.contains("401") == true -> "Email o password non corretti"
|
||||
it.message?.contains("timeout", ignoreCase = true) == true ->
|
||||
"Server non raggiungibile. Verifica la connessione."
|
||||
else -> it.message ?: "Login fallito"
|
||||
it.message?.contains("401") == true -> errCredentials
|
||||
it.message?.contains("timeout", ignoreCase = true) == true -> errUnreachable
|
||||
else -> it.message ?: errGeneric
|
||||
}
|
||||
}
|
||||
loading = false
|
||||
}
|
||||
}
|
||||
|
||||
MatchScreenScaffold {
|
||||
MatchScreenScaffold {
|
||||
if (showLanguagePicker) {
|
||||
LanguagePickerDialog(onDismiss = { showLanguagePicker = false })
|
||||
}
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.verticalScroll(rememberScrollState())
|
||||
@@ -82,22 +92,20 @@ fun LoginScreen(
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
) {
|
||||
MatchLiveWordmark(showSlogan = true)
|
||||
Spacer(Modifier.height(48.dp))
|
||||
Spacer(Modifier.height(16.dp))
|
||||
TextButton(onClick = { showLanguagePicker = true }) {
|
||||
Text(stringResource(R.string.language_label), color = MatchColors.TextSecondary)
|
||||
}
|
||||
Spacer(Modifier.height(32.dp))
|
||||
Text(
|
||||
text = "ACCEDI",
|
||||
text = stringResource(R.string.login_submit).uppercase(),
|
||||
style = androidx.compose.material3.MaterialTheme.typography.headlineMedium,
|
||||
)
|
||||
Spacer(Modifier.height(8.dp))
|
||||
Text(
|
||||
text = "Gestisci le dirette della tua squadra",
|
||||
style = androidx.compose.material3.MaterialTheme.typography.bodyMedium,
|
||||
textAlign = TextAlign.Center,
|
||||
)
|
||||
Spacer(Modifier.height(32.dp))
|
||||
OutlinedTextField(
|
||||
value = email,
|
||||
onValueChange = { email = it },
|
||||
label = { Text("Email") },
|
||||
label = { Text(stringResource(R.string.login_email)) },
|
||||
placeholder = { Text("coach@squadra.it") },
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
singleLine = true,
|
||||
@@ -114,7 +122,7 @@ fun LoginScreen(
|
||||
OutlinedTextField(
|
||||
value = password,
|
||||
onValueChange = { password = it },
|
||||
label = { Text("Password") },
|
||||
label = { Text(stringResource(R.string.login_password)) },
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
singleLine = true,
|
||||
visualTransformation = if (passwordVisible) {
|
||||
@@ -153,7 +161,7 @@ fun LoginScreen(
|
||||
}
|
||||
Spacer(Modifier.height(32.dp))
|
||||
MatchPrimaryButton(
|
||||
label = "ACCEDI",
|
||||
label = stringResource(R.string.login_submit).uppercase(),
|
||||
loading = loading,
|
||||
enabled = email.isNotBlank() && password.isNotBlank(),
|
||||
onClick = { submitLogin() },
|
||||
|
||||
+69
-6
@@ -2,24 +2,30 @@ package com.matchlivetv.match_live_tv.ui.matches
|
||||
|
||||
import android.app.DatePickerDialog
|
||||
import android.app.TimePickerDialog
|
||||
import androidx.compose.foundation.BorderStroke
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.border
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.items
|
||||
import androidx.compose.foundation.shape.CircleShape
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.CalendarToday
|
||||
import androidx.compose.material.icons.filled.CheckCircle
|
||||
import androidx.compose.material.icons.filled.ChevronRight
|
||||
import androidx.compose.material.icons.filled.EventAvailable
|
||||
import androidx.compose.material.icons.filled.ExpandMore
|
||||
import androidx.compose.material.icons.filled.Groups
|
||||
import androidx.compose.material.icons.filled.PlayCircleOutline
|
||||
import androidx.compose.material.icons.filled.RadioButtonUnchecked
|
||||
@@ -41,10 +47,15 @@ import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import com.matchlivetv.match_live_tv.R
|
||||
import com.matchlivetv.match_live_tv.core.isScheduledFuture
|
||||
import com.matchlivetv.match_live_tv.core.parseApiInstant
|
||||
import com.matchlivetv.match_live_tv.domain.Match
|
||||
@@ -473,26 +484,78 @@ fun TeamPickerBar(
|
||||
team: Team,
|
||||
showPicker: Boolean,
|
||||
onClick: () -> Unit,
|
||||
expandable: Boolean = true,
|
||||
) {
|
||||
if (!showPicker) return
|
||||
val shape = RoundedCornerShape(14.dp)
|
||||
Row(
|
||||
Modifier
|
||||
.fillMaxWidth()
|
||||
.background(MatchColors.Surface, RoundedCornerShape(12.dp))
|
||||
.clickable(onClick = onClick)
|
||||
.padding(horizontal = 14.dp, vertical = 12.dp),
|
||||
.clip(shape)
|
||||
.background(MatchColors.SurfaceElevated, shape)
|
||||
.border(
|
||||
BorderStroke(
|
||||
width = if (expandable) 1.5.dp else 1.dp,
|
||||
color = if (expandable) MatchColors.PrimaryRed.copy(alpha = 0.55f) else MatchColors.Outline,
|
||||
),
|
||||
shape,
|
||||
)
|
||||
.then(if (expandable) Modifier.clickable(onClick = onClick) else Modifier)
|
||||
.padding(horizontal = 14.dp, vertical = 14.dp),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
Icon(Icons.Default.Groups, null, tint = MatchColors.PrimaryRed)
|
||||
Box(
|
||||
Modifier
|
||||
.size(40.dp)
|
||||
.background(MatchColors.PrimaryRed.copy(alpha = 0.16f), CircleShape),
|
||||
contentAlignment = Alignment.Center,
|
||||
) {
|
||||
Icon(
|
||||
Icons.Default.Groups,
|
||||
contentDescription = null,
|
||||
tint = MatchColors.PrimaryRed,
|
||||
modifier = Modifier.size(22.dp),
|
||||
)
|
||||
}
|
||||
Spacer(Modifier.width(12.dp))
|
||||
Column(Modifier.weight(1f)) {
|
||||
Text("Squadra per la diretta", style = MaterialTheme.typography.bodyMedium)
|
||||
Text(
|
||||
if (expandable) {
|
||||
stringResource(R.string.matches_tap_change_team)
|
||||
} else {
|
||||
stringResource(R.string.matches_team_for_live)
|
||||
},
|
||||
color = if (expandable) MatchColors.PrimaryRed else MatchColors.TextSecondary,
|
||||
fontSize = 12.sp,
|
||||
fontWeight = FontWeight.SemiBold,
|
||||
)
|
||||
Text(team.name, style = MaterialTheme.typography.titleMedium)
|
||||
team.clubName?.takeIf { it.isNotBlank() }?.let {
|
||||
Text(it, style = MaterialTheme.typography.bodyMedium)
|
||||
}
|
||||
}
|
||||
Icon(Icons.Default.ChevronRight, null, tint = MatchColors.TextSecondary)
|
||||
if (expandable) {
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.spacedBy(4.dp),
|
||||
modifier = Modifier
|
||||
.background(MatchColors.PrimaryRed.copy(alpha = 0.14f), RoundedCornerShape(999.dp))
|
||||
.padding(horizontal = 10.dp, vertical = 6.dp),
|
||||
) {
|
||||
Text(
|
||||
stringResource(R.string.matches_change),
|
||||
color = MatchColors.PrimaryRed,
|
||||
fontSize = 12.sp,
|
||||
fontWeight = FontWeight.Bold,
|
||||
)
|
||||
Icon(
|
||||
Icons.Default.ExpandMore,
|
||||
contentDescription = stringResource(R.string.matches_change),
|
||||
tint = MatchColors.PrimaryRed,
|
||||
modifier = Modifier.size(18.dp),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+58
-21
@@ -17,7 +17,9 @@ import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.items
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.automirrored.filled.Logout
|
||||
import androidx.compose.material.icons.filled.DeleteOutline
|
||||
import androidx.compose.material.icons.filled.Language
|
||||
import androidx.compose.material3.CircularProgressIndicator
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.Icon
|
||||
@@ -41,14 +43,17 @@ import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.matchlivetv.match_live_tv.R
|
||||
import com.matchlivetv.match_live_tv.core.isScheduledFuture
|
||||
import com.matchlivetv.match_live_tv.core.parseApiInstant
|
||||
import com.matchlivetv.match_live_tv.data.AppContainer
|
||||
import com.matchlivetv.match_live_tv.data.repository.MatchSessionLauncher
|
||||
import com.matchlivetv.match_live_tv.domain.Match
|
||||
import com.matchlivetv.match_live_tv.domain.Team
|
||||
import com.matchlivetv.match_live_tv.ui.components.LanguagePickerDialog
|
||||
import com.matchlivetv.match_live_tv.ui.components.MatchLiveWordmark
|
||||
import com.matchlivetv.match_live_tv.ui.components.MatchPrimaryButton
|
||||
import com.matchlivetv.match_live_tv.ui.components.MatchScreenScaffold
|
||||
@@ -82,6 +87,7 @@ fun MatchesScreen(
|
||||
var resumeMatch by remember { mutableStateOf<Match?>(null) }
|
||||
var configureMatch by remember { mutableStateOf<Match?>(null) }
|
||||
var deleteMatch by remember { mutableStateOf<Match?>(null) }
|
||||
var showLanguagePicker by remember { mutableStateOf(false) }
|
||||
|
||||
suspend fun showMessage(message: String) {
|
||||
snackbarHostState.showSnackbar(message)
|
||||
@@ -159,18 +165,32 @@ fun MatchesScreen(
|
||||
titleContentColor = Color.White,
|
||||
),
|
||||
actions = {
|
||||
IconButton(onClick = { showLanguagePicker = true }) {
|
||||
Icon(
|
||||
imageVector = Icons.Filled.Language,
|
||||
contentDescription = stringResource(R.string.language_label),
|
||||
tint = MatchColors.TextSecondary,
|
||||
)
|
||||
}
|
||||
IconButton(onClick = {
|
||||
scope.launch {
|
||||
container.authRepository.logout()
|
||||
onLogout()
|
||||
}
|
||||
}) {
|
||||
Text("Esci", color = MatchColors.TextSecondary)
|
||||
Icon(
|
||||
imageVector = Icons.AutoMirrored.Filled.Logout,
|
||||
contentDescription = stringResource(R.string.action_logout),
|
||||
tint = MatchColors.TextSecondary,
|
||||
)
|
||||
}
|
||||
},
|
||||
)
|
||||
},
|
||||
) {
|
||||
if (showLanguagePicker) {
|
||||
LanguagePickerDialog(onDismiss = { showLanguagePicker = false })
|
||||
}
|
||||
Box(Modifier.fillMaxSize()) {
|
||||
PullToRefreshBox(
|
||||
isRefreshing = refreshing,
|
||||
@@ -192,7 +212,10 @@ fun MatchesScreen(
|
||||
) {
|
||||
Text(error!!, color = MatchColors.PrimaryRed, textAlign = TextAlign.Center)
|
||||
Spacer(Modifier.height(16.dp))
|
||||
MatchPrimaryButton(label = "RIPROVA", onClick = { reload(showInitialSpinner = false) })
|
||||
MatchPrimaryButton(
|
||||
label = stringResource(R.string.matches_retry).uppercase(),
|
||||
onClick = { reload(showInitialSpinner = false) },
|
||||
)
|
||||
}
|
||||
else -> LazyColumn(
|
||||
contentPadding = PaddingValues(bottom = 24.dp),
|
||||
@@ -200,23 +223,26 @@ fun MatchesScreen(
|
||||
item {
|
||||
Column(Modifier.padding(horizontal = 20.dp, vertical = 8.dp)) {
|
||||
Text(
|
||||
"Ciao, ${session?.user?.name.orEmpty()}",
|
||||
stringResource(
|
||||
R.string.matches_hello,
|
||||
session?.user?.name.orEmpty(),
|
||||
),
|
||||
style = MaterialTheme.typography.headlineMedium,
|
||||
)
|
||||
Spacer(Modifier.height(4.dp))
|
||||
Text(
|
||||
"Riprendi una diretta in corso o avvia una partita programmata.",
|
||||
stringResource(R.string.matches_subtitle),
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
)
|
||||
Spacer(Modifier.height(16.dp))
|
||||
Row(horizontalArrangement = Arrangement.spacedBy(12.dp)) {
|
||||
MatchSecondaryButton(
|
||||
label = "PARTITA PROGRAMMATA",
|
||||
label = stringResource(R.string.matches_schedule).uppercase(),
|
||||
onClick = { showScheduleSheet = true },
|
||||
modifier = Modifier.weight(1f),
|
||||
)
|
||||
MatchPrimaryButton(
|
||||
label = "NUOVA PARTITA",
|
||||
label = stringResource(R.string.matches_new).uppercase(),
|
||||
onClick = { showNewMatchSheet = true },
|
||||
modifier = Modifier.weight(1f),
|
||||
)
|
||||
@@ -226,6 +252,7 @@ fun MatchesScreen(
|
||||
TeamPickerBar(
|
||||
team = team,
|
||||
showPicker = true,
|
||||
expandable = teams.size > 1,
|
||||
onClick = { if (teams.size > 1) showTeamSheet = true },
|
||||
)
|
||||
}
|
||||
@@ -238,9 +265,11 @@ fun MatchesScreen(
|
||||
item {
|
||||
Text(
|
||||
when {
|
||||
calendarMatches.isEmpty() && activeMatch == null -> "Nessuna partita in calendario"
|
||||
scheduledMatches.isNotEmpty() -> "Partite programmate"
|
||||
else -> "Pronte da avviare"
|
||||
calendarMatches.isEmpty() && activeMatch == null ->
|
||||
stringResource(R.string.matches_empty_title)
|
||||
scheduledMatches.isNotEmpty() ->
|
||||
stringResource(R.string.matches_scheduled_title)
|
||||
else -> stringResource(R.string.matches_ready_title)
|
||||
},
|
||||
style = MaterialTheme.typography.labelLarge,
|
||||
color = MatchColors.TextSecondary,
|
||||
@@ -249,16 +278,25 @@ fun MatchesScreen(
|
||||
}
|
||||
if (calendarMatches.isEmpty() && activeMatch == null) {
|
||||
item {
|
||||
val emptyHint = stringResource(R.string.matches_empty_hint)
|
||||
val activeTeamLine = activeTeam?.name?.let {
|
||||
stringResource(R.string.matches_active_team, it)
|
||||
}
|
||||
val multiTeamHint = if (teams.size > 1) {
|
||||
stringResource(R.string.matches_multi_team_hint)
|
||||
} else {
|
||||
null
|
||||
}
|
||||
Text(
|
||||
buildString {
|
||||
append("Programma una partita o avviane una nuova con «Nuova partita».")
|
||||
activeTeam?.name?.let { teamName ->
|
||||
append("\n\nSquadra attiva: ")
|
||||
append(teamName)
|
||||
append('.')
|
||||
append(emptyHint)
|
||||
activeTeamLine?.let {
|
||||
append("\n\n")
|
||||
append(it)
|
||||
}
|
||||
if (teams.size > 1) {
|
||||
append("\nHai più squadre: verifica quella selezionata sopra.")
|
||||
multiTeamHint?.let {
|
||||
append("\n")
|
||||
append(it)
|
||||
}
|
||||
},
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
@@ -269,7 +307,7 @@ fun MatchesScreen(
|
||||
} else if (calendarMatches.isEmpty()) {
|
||||
item {
|
||||
Text(
|
||||
"Nessuna altra partita in calendario.",
|
||||
stringResource(R.string.matches_no_other),
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
textAlign = TextAlign.Center,
|
||||
modifier = Modifier.padding(horizontal = 24.dp, vertical = 12.dp),
|
||||
@@ -437,19 +475,18 @@ private fun NoTeamContent(onRetry: () -> Unit) {
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
) {
|
||||
Text(
|
||||
"Nessuna squadra assegnata",
|
||||
stringResource(R.string.matches_no_team_title),
|
||||
style = MaterialTheme.typography.headlineMedium,
|
||||
textAlign = TextAlign.Center,
|
||||
)
|
||||
Spacer(Modifier.height(8.dp))
|
||||
Text(
|
||||
"Per trasmettere devi essere responsabile della trasmissione di almeno una squadra. " +
|
||||
"Chiedi al tuo club di aggiungerti come staff trasmissione.",
|
||||
stringResource(R.string.matches_no_team_body),
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
textAlign = TextAlign.Center,
|
||||
)
|
||||
Spacer(Modifier.height(16.dp))
|
||||
MatchPrimaryButton(label = "RIPROVA", onClick = onRetry)
|
||||
MatchPrimaryButton(label = stringResource(R.string.matches_retry).uppercase(), onClick = onRetry)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="app_name">Match Live TV</string>
|
||||
<string name="app_slogan">Jedes Spiel, jedes Event, für eure Fans.</string>
|
||||
<string name="language_label">Sprache</string>
|
||||
<string name="language_system">System</string>
|
||||
<string name="language_italian">Italiano</string>
|
||||
<string name="language_english">English</string>
|
||||
<string name="language_french">Français</string>
|
||||
<string name="language_german">Deutsch</string>
|
||||
<string name="language_spanish">Español</string>
|
||||
<string name="action_logout">Abmelden</string>
|
||||
<string name="action_login">Anmelden</string>
|
||||
<string name="action_cancel">Abbrechen</string>
|
||||
<string name="action_save">Speichern</string>
|
||||
<string name="login_email">E-Mail</string>
|
||||
<string name="login_password">Passwort</string>
|
||||
<string name="login_submit">Anmelden</string>
|
||||
<string name="login_error_credentials">Falsche E-Mail oder Passwort</string>
|
||||
<string name="login_error_unreachable">Server nicht erreichbar. Verbindung prüfen.</string>
|
||||
<string name="login_error_generic">Anmeldung fehlgeschlagen</string>
|
||||
<string name="matches_title">Spiele</string>
|
||||
<string name="matches_hello">Hallo, %1$s</string>
|
||||
<string name="matches_subtitle">Nimm einen laufenden Livestream wieder auf oder starte ein geplantes Spiel.</string>
|
||||
<string name="matches_schedule">Geplantes Spiel</string>
|
||||
<string name="matches_new">Neues Spiel</string>
|
||||
<string name="matches_empty_title">Keine Spiele im Kalender</string>
|
||||
<string name="matches_scheduled_title">Geplante Spiele</string>
|
||||
<string name="matches_ready_title">Bereit zum Start</string>
|
||||
<string name="matches_empty_hint">Plane ein Spiel oder starte ein neues mit «Neues Spiel».</string>
|
||||
<string name="matches_active_team">Aktives Team: %1$s.</string>
|
||||
<string name="matches_multi_team_hint">Du hast mehrere Teams: prüfe die oben ausgewählte.</string>
|
||||
<string name="matches_no_other">Keine weiteren Spiele im Kalender.</string>
|
||||
<string name="matches_retry">Erneut versuchen</string>
|
||||
<string name="matches_load_error">Ladefehler</string>
|
||||
<string name="matches_no_team_title">Kein Team zugewiesen</string>
|
||||
<string name="matches_no_team_body">Zum Streamen musst du Übertragungs-Staff für mindestens ein Team sein. Bitte deinen Verein, dich als Streaming-Staff hinzuzufügen.</string>
|
||||
<string name="matches_tap_change_team">Tippen zum Teamwechsel</string>
|
||||
<string name="matches_team_for_live">Team für den Livestream</string>
|
||||
<string name="matches_change">Wechseln</string>
|
||||
<string name="streaming_notification_channel">Livestream</string>
|
||||
<string name="streaming_notification_channel_desc">Benachrichtigung während des Livestreams</string>
|
||||
<string name="streaming_notification_title">Match Live TV</string>
|
||||
<string name="streaming_notification_active">Stream wird vorbereitet…</string>
|
||||
<string name="streaming_notification_connecting">RTMP-Verbindung…</string>
|
||||
<string name="streaming_notification_streaming">Live</string>
|
||||
<string name="streaming_notification_reconnecting">Erneute Verbindung…</string>
|
||||
<string name="streaming_notification_error">Streaming-Fehler</string>
|
||||
<string name="streaming_notification_stop">Beenden</string>
|
||||
</resources>
|
||||
@@ -0,0 +1,50 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="app_name">Match Live TV</string>
|
||||
<string name="app_slogan">Every match, every event, for your fans.</string>
|
||||
<string name="language_label">Language</string>
|
||||
<string name="language_system">System</string>
|
||||
<string name="language_italian">Italiano</string>
|
||||
<string name="language_english">English</string>
|
||||
<string name="language_french">Français</string>
|
||||
<string name="language_german">Deutsch</string>
|
||||
<string name="language_spanish">Español</string>
|
||||
<string name="action_logout">Log out</string>
|
||||
<string name="action_login">Log in</string>
|
||||
<string name="action_cancel">Cancel</string>
|
||||
<string name="action_save">Save</string>
|
||||
<string name="login_email">Email</string>
|
||||
<string name="login_password">Password</string>
|
||||
<string name="login_submit">Log in</string>
|
||||
<string name="login_error_credentials">Incorrect email or password</string>
|
||||
<string name="login_error_unreachable">Server unreachable. Check your connection.</string>
|
||||
<string name="login_error_generic">Login failed</string>
|
||||
<string name="matches_title">Matches</string>
|
||||
<string name="matches_hello">Hi, %1$s</string>
|
||||
<string name="matches_subtitle">Resume a live stream or start a scheduled match.</string>
|
||||
<string name="matches_schedule">Scheduled match</string>
|
||||
<string name="matches_new">New match</string>
|
||||
<string name="matches_empty_title">No matches on the calendar</string>
|
||||
<string name="matches_scheduled_title">Scheduled matches</string>
|
||||
<string name="matches_ready_title">Ready to start</string>
|
||||
<string name="matches_empty_hint">Schedule a match or start a new one with «New match».</string>
|
||||
<string name="matches_active_team">Active team: %1$s.</string>
|
||||
<string name="matches_multi_team_hint">You have multiple teams: check the one selected above.</string>
|
||||
<string name="matches_no_other">No other matches on the calendar.</string>
|
||||
<string name="matches_retry">Retry</string>
|
||||
<string name="matches_load_error">Loading error</string>
|
||||
<string name="matches_no_team_title">No team assigned</string>
|
||||
<string name="matches_no_team_body">To stream you must be transmission staff for at least one team. Ask your club to add you as streaming staff.</string>
|
||||
<string name="matches_tap_change_team">Tap to change team</string>
|
||||
<string name="matches_team_for_live">Team for the live stream</string>
|
||||
<string name="matches_change">Change</string>
|
||||
<string name="streaming_notification_channel">Live stream</string>
|
||||
<string name="streaming_notification_channel_desc">Notification while live streaming</string>
|
||||
<string name="streaming_notification_title">Match Live TV</string>
|
||||
<string name="streaming_notification_active">Preparing stream…</string>
|
||||
<string name="streaming_notification_connecting">Connecting RTMP…</string>
|
||||
<string name="streaming_notification_streaming">Live</string>
|
||||
<string name="streaming_notification_reconnecting">Reconnecting…</string>
|
||||
<string name="streaming_notification_error">Streaming error</string>
|
||||
<string name="streaming_notification_stop">Stop</string>
|
||||
</resources>
|
||||
@@ -0,0 +1,50 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="app_name">Match Live TV</string>
|
||||
<string name="app_slogan">Cada partido, cada evento, para tus aficionados.</string>
|
||||
<string name="language_label">Idioma</string>
|
||||
<string name="language_system">Sistema</string>
|
||||
<string name="language_italian">Italiano</string>
|
||||
<string name="language_english">English</string>
|
||||
<string name="language_french">Français</string>
|
||||
<string name="language_german">Deutsch</string>
|
||||
<string name="language_spanish">Español</string>
|
||||
<string name="action_logout">Salir</string>
|
||||
<string name="action_login">Acceder</string>
|
||||
<string name="action_cancel">Cancelar</string>
|
||||
<string name="action_save">Guardar</string>
|
||||
<string name="login_email">Email</string>
|
||||
<string name="login_password">Contraseña</string>
|
||||
<string name="login_submit">Acceder</string>
|
||||
<string name="login_error_credentials">Email o contraseña incorrectos</string>
|
||||
<string name="login_error_unreachable">Servidor no disponible. Comprueba la conexión.</string>
|
||||
<string name="login_error_generic">Error de acceso</string>
|
||||
<string name="matches_title">Partidos</string>
|
||||
<string name="matches_hello">Hola, %1$s</string>
|
||||
<string name="matches_subtitle">Reanuda un directo en curso o inicia un partido programado.</string>
|
||||
<string name="matches_schedule">Partido programado</string>
|
||||
<string name="matches_new">Nuevo partido</string>
|
||||
<string name="matches_empty_title">Ningún partido en el calendario</string>
|
||||
<string name="matches_scheduled_title">Partidos programados</string>
|
||||
<string name="matches_ready_title">Listos para empezar</string>
|
||||
<string name="matches_empty_hint">Programa un partido o inicia uno nuevo con «Nuevo partido».</string>
|
||||
<string name="matches_active_team">Equipo activo: %1$s.</string>
|
||||
<string name="matches_multi_team_hint">Tienes varios equipos: comprueba el seleccionado arriba.</string>
|
||||
<string name="matches_no_other">Ningún otro partido en el calendario.</string>
|
||||
<string name="matches_retry">Reintentar</string>
|
||||
<string name="matches_load_error">Error de carga</string>
|
||||
<string name="matches_no_team_title">Ningún equipo asignado</string>
|
||||
<string name="matches_no_team_body">Para emitir debes ser responsable de transmisión de al menos un equipo. Pide a tu club que te añada como staff de streaming.</string>
|
||||
<string name="matches_tap_change_team">Toca para cambiar de equipo</string>
|
||||
<string name="matches_team_for_live">Equipo para el directo</string>
|
||||
<string name="matches_change">Cambiar</string>
|
||||
<string name="streaming_notification_channel">Directo en curso</string>
|
||||
<string name="streaming_notification_channel_desc">Notificación durante el streaming en vivo</string>
|
||||
<string name="streaming_notification_title">Match Live TV</string>
|
||||
<string name="streaming_notification_active">Preparando el directo…</string>
|
||||
<string name="streaming_notification_connecting">Conectando RTMP…</string>
|
||||
<string name="streaming_notification_streaming">En directo</string>
|
||||
<string name="streaming_notification_reconnecting">Reconectando…</string>
|
||||
<string name="streaming_notification_error">Error de streaming</string>
|
||||
<string name="streaming_notification_stop">Terminar</string>
|
||||
</resources>
|
||||
@@ -0,0 +1,50 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="app_name">Match Live TV</string>
|
||||
<string name="app_slogan">Chaque match, chaque événement, pour vos fans.</string>
|
||||
<string name="language_label">Langue</string>
|
||||
<string name="language_system">Système</string>
|
||||
<string name="language_italian">Italiano</string>
|
||||
<string name="language_english">English</string>
|
||||
<string name="language_french">Français</string>
|
||||
<string name="language_german">Deutsch</string>
|
||||
<string name="language_spanish">Español</string>
|
||||
<string name="action_logout">Déconnexion</string>
|
||||
<string name="action_login">Connexion</string>
|
||||
<string name="action_cancel">Annuler</string>
|
||||
<string name="action_save">Enregistrer</string>
|
||||
<string name="login_email">E-mail</string>
|
||||
<string name="login_password">Mot de passe</string>
|
||||
<string name="login_submit">Connexion</string>
|
||||
<string name="login_error_credentials">E-mail ou mot de passe incorrect</string>
|
||||
<string name="login_error_unreachable">Serveur inaccessible. Vérifiez la connexion.</string>
|
||||
<string name="login_error_generic">Échec de la connexion</string>
|
||||
<string name="matches_title">Matchs</string>
|
||||
<string name="matches_hello">Bonjour, %1$s</string>
|
||||
<string name="matches_subtitle">Reprenez un direct en cours ou démarrez un match programmé.</string>
|
||||
<string name="matches_schedule">Match programmé</string>
|
||||
<string name="matches_new">Nouveau match</string>
|
||||
<string name="matches_empty_title">Aucun match au calendrier</string>
|
||||
<string name="matches_scheduled_title">Matchs programmés</string>
|
||||
<string name="matches_ready_title">Prêts à démarrer</string>
|
||||
<string name="matches_empty_hint">Programmez un match ou démarrez-en un avec « Nouveau match ».</string>
|
||||
<string name="matches_active_team">Équipe active : %1$s.</string>
|
||||
<string name="matches_multi_team_hint">Vous avez plusieurs équipes : vérifiez celle sélectionnée ci-dessus.</string>
|
||||
<string name="matches_no_other">Aucun autre match au calendrier.</string>
|
||||
<string name="matches_retry">Réessayer</string>
|
||||
<string name="matches_load_error">Erreur de chargement</string>
|
||||
<string name="matches_no_team_title">Aucune équipe assignée</string>
|
||||
<string name="matches_no_team_body">Pour diffuser, vous devez être responsable de la transmission d\'au moins une équipe. Demandez à votre club de vous ajouter comme staff diffusion.</string>
|
||||
<string name="matches_tap_change_team">Appuyez pour changer d\'équipe</string>
|
||||
<string name="matches_team_for_live">Équipe pour le direct</string>
|
||||
<string name="matches_change">Changer</string>
|
||||
<string name="streaming_notification_channel">Direct en cours</string>
|
||||
<string name="streaming_notification_channel_desc">Notification pendant le streaming live</string>
|
||||
<string name="streaming_notification_title">Match Live TV</string>
|
||||
<string name="streaming_notification_active">Préparation du direct…</string>
|
||||
<string name="streaming_notification_connecting">Connexion RTMP…</string>
|
||||
<string name="streaming_notification_streaming">En direct</string>
|
||||
<string name="streaming_notification_reconnecting">Reconnexion…</string>
|
||||
<string name="streaming_notification_error">Erreur de streaming</string>
|
||||
<string name="streaming_notification_stop">Arrêter</string>
|
||||
</resources>
|
||||
@@ -2,6 +2,42 @@
|
||||
<resources>
|
||||
<string name="app_name">Match Live TV</string>
|
||||
<string name="app_slogan">Ogni partita, ogni evento, per i tuoi tifosi.</string>
|
||||
<string name="language_label">Lingua</string>
|
||||
<string name="language_system">Sistema</string>
|
||||
<string name="language_italian">Italiano</string>
|
||||
<string name="language_english">English</string>
|
||||
<string name="language_french">Français</string>
|
||||
<string name="language_german">Deutsch</string>
|
||||
<string name="language_spanish">Español</string>
|
||||
<string name="action_logout">Esci</string>
|
||||
<string name="action_login">Accedi</string>
|
||||
<string name="action_cancel">Annulla</string>
|
||||
<string name="action_save">Salva</string>
|
||||
<string name="login_email">Email</string>
|
||||
<string name="login_password">Password</string>
|
||||
<string name="login_submit">Accedi</string>
|
||||
<string name="login_error_credentials">Email o password non corretti</string>
|
||||
<string name="login_error_unreachable">Server non raggiungibile. Verifica la connessione.</string>
|
||||
<string name="login_error_generic">Login fallito</string>
|
||||
<string name="matches_title">Partite</string>
|
||||
<string name="matches_hello">Ciao, %1$s</string>
|
||||
<string name="matches_subtitle">Riprendi una diretta in corso o avvia una partita programmata.</string>
|
||||
<string name="matches_schedule">Partita programmata</string>
|
||||
<string name="matches_new">Nuova partita</string>
|
||||
<string name="matches_empty_title">Nessuna partita in calendario</string>
|
||||
<string name="matches_scheduled_title">Partite programmate</string>
|
||||
<string name="matches_ready_title">Pronte da avviare</string>
|
||||
<string name="matches_empty_hint">Programma una partita o avviane una nuova con «Nuova partita».</string>
|
||||
<string name="matches_active_team">Squadra attiva: %1$s.</string>
|
||||
<string name="matches_multi_team_hint">Hai più squadre: verifica quella selezionata sopra.</string>
|
||||
<string name="matches_no_other">Nessuna altra partita in calendario.</string>
|
||||
<string name="matches_retry">Riprova</string>
|
||||
<string name="matches_load_error">Errore caricamento</string>
|
||||
<string name="matches_no_team_title">Nessuna squadra assegnata</string>
|
||||
<string name="matches_no_team_body">Per trasmettere devi essere responsabile della trasmissione di almeno una squadra. Chiedi al tuo club di aggiungerti come staff trasmissione.</string>
|
||||
<string name="matches_tap_change_team">Tocca per cambiare squadra</string>
|
||||
<string name="matches_team_for_live">Squadra per la diretta</string>
|
||||
<string name="matches_change">Cambia</string>
|
||||
<string name="streaming_notification_channel">Diretta in corso</string>
|
||||
<string name="streaming_notification_channel_desc">Notifica durante lo streaming live</string>
|
||||
<string name="streaming_notification_title">Match Live TV</string>
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<locale-config xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<locale android:name="it" />
|
||||
<locale android:name="en" />
|
||||
<locale android:name="fr" />
|
||||
<locale android:name="de" />
|
||||
<locale android:name="es" />
|
||||
</locale-config>
|
||||
+33
@@ -0,0 +1,33 @@
|
||||
package com.matchlivetv.match_live_tv.core
|
||||
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Test
|
||||
|
||||
class RtmpIngestUrlTest {
|
||||
@Test
|
||||
fun rewritesLoopbackToApiHost() {
|
||||
val resolved = RtmpIngestUrl.resolve(
|
||||
"rtmp://127.0.0.1:1935/live/match_abc",
|
||||
apiBaseUrl = "http://10.0.2.2:3000",
|
||||
)
|
||||
assertEquals("rtmp://10.0.2.2:1935/live/match_abc", resolved)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun rewritesDockerInternalHost() {
|
||||
val resolved = RtmpIngestUrl.resolve(
|
||||
"rtmp://mediamtx:1935/live/match_abc",
|
||||
apiBaseUrl = "http://10.0.2.2:3000",
|
||||
)
|
||||
assertEquals("rtmp://10.0.2.2:1935/live/match_abc", resolved)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun keepsProductionHost() {
|
||||
val input = "rtmp://stream.matchlivetv.it:1935/live/match_abc"
|
||||
assertEquals(
|
||||
input,
|
||||
RtmpIngestUrl.resolve(input, apiBaseUrl = "https://www.matchlivetv.it"),
|
||||
)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user