update + Fix

This commit is contained in:
Johnny
2026-07-03 08:48:21 +02:00
parent fe672f753b
commit 84b972a0f7
6 changed files with 42 additions and 47 deletions

Binary file not shown.

View File

@@ -4,6 +4,7 @@ import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.activity.enableEdgeToEdge
import com.jttools.datacontrol.service.DataMonitorService
import com.jttools.datacontrol.ui.SystemActions
import com.jttools.datacontrol.ui.navigation.DataControlNavHost
import com.jttools.datacontrol.ui.theme.DataControlTheme
@@ -18,6 +19,10 @@ class MainActivity : ComponentActivity() {
super.onCreate(savedInstanceState)
systemActions = SystemActions(this)
enableEdgeToEdge()
// Démarre le suivi de consommation dès l'ouverture de l'app, indépendamment du contrôle
// (pare-feu) : sans ça, le tableau de bord/les réglages restent à 0 tant que le contrôle
// n'a jamais été activé, car c'est ce service qui alimente MonitorStateHolder.
DataMonitorService.start(this)
setContent {
DataControlTheme {
DataControlNavHost(systemActions = systemActions)

View File

@@ -53,9 +53,8 @@ data class UsageState(
}
enum class DataState {
OK, // sous le forfait
WARNING, // proche du forfait (>= 80%)
OVERRUN_FILTER, // dépassé, liste blanche seule active
OVERRUN_CUT, // dépassé, coupure totale cyclique en cours
WIFI // Wi-Fi actif, aucun filtrage appliqué
OK, // sous le seuil d'alerte, liste blanche active, pas de coupure en cours
WARNING, // proche ou au-delà du forfait (>= 80%), liste blanche active, pas de coupure en cours
CUT, // coupure totale cyclique en cours (indépendant du dépassement de forfait)
WIFI // Wi-Fi actif, aucun filtrage (sauf si filtrage Wi-Fi activé séparément)
}

View File

@@ -32,12 +32,11 @@ import kotlin.math.max
* pilote le pare-feu [FirewallVpnService] et met à jour la notification permanente.
*
* Règles appliquées (voir README) :
* - Sur Wi-Fi : aucun filtrage, jamais (le pare-feu est arrêté).
* - Sur données mobiles, sous le forfait : liste blanche déjà active en permanence
* (seules les apps cochées ont accès aux données mobiles).
* - Sur données mobiles, forfait dépassé : la liste blanche reste active, ET une coupure
* totale (même liste blanche) se déclenche cycliquement toutes les X minutes pendant
* une courte pulsation avant de revenir en mode "liste blanche seule".
* - Sur Wi-Fi : aucun filtrage par défaut (le pare-feu est arrêté), sauf si le filtrage Wi-Fi
* est explicitement activé (Liste blanche Wi-Fi).
* - Sur données mobiles : liste blanche active en permanence (seules les apps cochées ont accès),
* ET une coupure totale cyclique (même liste blanche) se déclenche automatiquement toutes les
* X minutes pendant une courte pulsation, que le forfait soit dépassé ou non.
*/
@AndroidEntryPoint
class DataMonitorService : LifecycleService() {
@@ -125,7 +124,6 @@ class DataMonitorService : LifecycleService() {
}
val consumedMo = UsageRepository.bytesToMo(consumedBytes)
val overageMo = max(0L, consumedMo - settings.planLimitMo)
val isOverPlan = consumedMo > settings.planLimitMo
val estimatedTotal = settings.planPriceEuro + overageMo * settings.overagePricePerMoEuro
val percent = if (settings.planLimitMo > 0) consumedMo.toFloat() / settings.planLimitMo else 0f
@@ -162,39 +160,35 @@ class DataMonitorService : LifecycleService() {
dataState = DataState.WIFI
}
latestTransport == NetworkTransport.CELLULAR && !vpnRevoked && FirewallVpnService.isPrepared(this) -> {
if (!isOverPlan) {
resetCutoffCycle()
applyVpnMode(VpnMode.FILTER, settings.whitelistedPackages)
dataState = if (percent >= WARNING_THRESHOLD) DataState.WARNING else DataState.OK
} else {
val now = System.currentTimeMillis()
val intervalMs = settings.cutoffIntervalMinutes * 60_000L
if (cutoffCycleAnchorMillis == null) {
// La coupure cyclique tourne en continu sur données mobiles, indépendamment du
// dépassement du forfait : elle ne doit pas attendre le hors-forfait pour se déclencher.
val now = System.currentTimeMillis()
val intervalMs = settings.cutoffIntervalMinutes * 60_000L
if (cutoffCycleAnchorMillis == null) {
cutoffCycleAnchorMillis = now
persistCutoffState()
}
val anchor = cutoffCycleAnchorMillis!!
if (inPulse) {
if (now - pulseStartMillis >= PULSE_DURATION_MS) {
inPulse = false
cutoffCycleAnchorMillis = now
persistCutoffState()
}
val anchor = cutoffCycleAnchorMillis!!
if (inPulse) {
if (now - pulseStartMillis >= PULSE_DURATION_MS) {
inPulse = false
cutoffCycleAnchorMillis = now
persistCutoffState()
applyVpnMode(VpnMode.FILTER, settings.whitelistedPackages)
} else {
applyVpnMode(VpnMode.BLOCK_ALL, emptySet())
}
} else if (now - anchor >= intervalMs) {
inPulse = true
pulseStartMillis = now
persistCutoffState()
applyVpnMode(VpnMode.BLOCK_ALL, emptySet())
} else {
applyVpnMode(VpnMode.FILTER, settings.whitelistedPackages)
} else {
applyVpnMode(VpnMode.BLOCK_ALL, emptySet())
}
nextCutoffAt = if (inPulse) pulseStartMillis + PULSE_DURATION_MS else anchor + intervalMs
dataState = if (inPulse) DataState.OVERRUN_CUT else DataState.OVERRUN_FILTER
} else if (now - anchor >= intervalMs) {
inPulse = true
pulseStartMillis = now
persistCutoffState()
applyVpnMode(VpnMode.BLOCK_ALL, emptySet())
} else {
applyVpnMode(VpnMode.FILTER, settings.whitelistedPackages)
}
nextCutoffAt = if (inPulse) pulseStartMillis + PULSE_DURATION_MS else anchor + intervalMs
dataState = if (inPulse) DataState.CUT else if (percent >= WARNING_THRESHOLD) DataState.WARNING else DataState.OK
}
else -> {
// Pas de réseau, transport inconnu, ou VPN non préparé/révoqué : rien à filtrer.
@@ -271,8 +265,7 @@ class DataMonitorService : LifecycleService() {
!snapshot.serviceRunning -> Triple("", getString(R.string.dashboard_control_disabled), Color.parseColor("#757575"))
snapshot.dataState == DataState.OK -> Triple("🟢", getString(R.string.dashboard_state_ok), Color.parseColor("#2E7D32"))
snapshot.dataState == DataState.WARNING -> Triple("🟠", getString(R.string.dashboard_state_warning), Color.parseColor("#EF6C00"))
snapshot.dataState == DataState.OVERRUN_FILTER -> Triple("🔴", getString(R.string.dashboard_state_overrun_filtered), Color.parseColor("#C62828"))
snapshot.dataState == DataState.OVERRUN_CUT -> Triple("🔴", getString(R.string.dashboard_state_overrun_cut), Color.parseColor("#C62828"))
snapshot.dataState == DataState.CUT -> Triple("🔴", getString(R.string.dashboard_state_cut), Color.parseColor("#C62828"))
else -> Triple("🔵", getString(R.string.dashboard_state_wifi), Color.parseColor("#1565C0"))
}

View File

@@ -72,8 +72,7 @@ fun DashboardScreen(systemActions: SystemActions) {
!snapshot.serviceRunning -> stringResource(R.string.dashboard_control_disabled)
snapshot.dataState == DataState.OK -> stringResource(R.string.dashboard_state_ok)
snapshot.dataState == DataState.WARNING -> stringResource(R.string.dashboard_state_warning)
snapshot.dataState == DataState.OVERRUN_FILTER -> stringResource(R.string.dashboard_state_overrun_filtered)
snapshot.dataState == DataState.OVERRUN_CUT -> stringResource(R.string.dashboard_state_overrun_cut)
snapshot.dataState == DataState.CUT -> stringResource(R.string.dashboard_state_cut)
else -> stringResource(R.string.dashboard_state_wifi)
}

View File

@@ -17,8 +17,7 @@
<string name="dashboard_state_label">État des données</string>
<string name="dashboard_state_ok">Sous le forfait</string>
<string name="dashboard_state_warning">Proche du forfait</string>
<string name="dashboard_state_overrun_filtered">Hors-forfait — liste blanche seule</string>
<string name="dashboard_state_overrun_cut">Hors-forfait — coupure en cours</string>
<string name="dashboard_state_cut">Coupure totale en cours</string>
<string name="dashboard_state_wifi">Wi-Fi actif — aucun filtrage</string>
<string name="dashboard_cost_title">Estimation du coût</string>
<string name="dashboard_cost_plan">Prix du forfait</string>
@@ -45,7 +44,7 @@
<string name="settings_auto_reset">Réinitialisation automatique mensuelle</string>
<string name="settings_section_cutoff">Coupure automatique</string>
<string name="settings_cutoff_interval">Intervalle de coupure (minutes)</string>
<string name="settings_cutoff_help">Une fois le forfait dépassé, une coupure totale (même pour la liste blanche) se déclenche brièvement à cet intervalle.</string>
<string name="settings_cutoff_help">Une coupure totale (même pour la liste blanche) se déclenche cycliquement à cet intervalle, que le forfait soit dépassé ou non.</string>
<string name="settings_section_permissions">Autorisations</string>
<string name="settings_perm_usage_access">Accès aux statistiques d\'utilisation</string>
<string name="settings_perm_usage_access_desc">Nécessaire pour mesurer la consommation de données mobiles</string>