update + Fix
This commit is contained in:
Binary file not shown.
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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"))
|
||||
}
|
||||
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user