tk_ota_push 0.2.2
tk_ota_push: ^0.2.2 copied to clipboard
Sovereign OTA code push for Flutter with a 3-step Remote Config Gatekeeper, bank-grade security shield, and multiplatform deployment CLI.
example/lib/main.dart
import 'package:flutter/material.dart';
import 'package:tk_ota_push/tk_ota_push.dart';
void main() async {
WidgetsFlutterBinding.ensureInitialized();
// 1. Initialiser le SDK mobile avec les politiques de sécurité bancaires
TkOtaPush.init(
appReleaseVersion: '1.0.0+1',
requireWifiForSilentUpdate: false, // true pour forcer le Wi-Fi en mode éco-data
minBatteryPercentageForSilentUpdate: 20,
securityPolicy: const OtaSecurityPolicy(
blockRootedOrJailbrokenDevices: false, // Activer en production bancaire
expectedApkSignaturesSha256: {
// Empreinte SHA-256 du certificat officiel de la banque
'6b6929c8ff345cf008496328d7bff081a8ccfb89498bf9916ed668be3c3dedcf',
},
pinnedServerCertSha256Fingerprints: {
// Empreinte TLS Let's Encrypt / Serveur souverain
'42d889f8f4b6bf9d3abb767d3b5f215e0009e4d26cb7b958497e8b291e1b96dc',
},
),
onRollbackDetected: (event) async {
debugPrint('⚠️ ROLLBACK DÉTECTÉ : #${event.previousPatchNumber} -> #${event.restoredPatchNumber}');
// Nettoyer le cache local (SQLite / SharedPreferences / Hive)
},
onSecurityViolation: (verdict) async {
debugPrint('🚨 ALERTE SÉCURITÉ MOBILE : ${verdict.reason}');
},
);
runApp(const TkOtaPushExampleApp());
}
class TkOtaPushExampleApp extends StatelessWidget {
const TkOtaPushExampleApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'tk_ota_push Démo',
debugShowCheckedModeBanner: false,
theme: ThemeData(
useMaterial3: true,
colorScheme: ColorScheme.fromSeed(
seedColor: const Color(0xFF0F766E), // Teal FinTech
brightness: Brightness.light,
),
),
home: const OtaDashboardScreen(),
);
}
}
class OtaDashboardScreen extends StatefulWidget {
const OtaDashboardScreen({super.key});
@override
State<OtaDashboardScreen> createState() => _OtaDashboardScreenState();
}
class _OtaDashboardScreenState extends State<OtaDashboardScreen> {
final TkOtaPush _ota = TkOtaPush.instance;
int? _currentPatch;
String _statusMessage = 'Prêt';
bool _isLoading = false;
@override
void initState() {
super.initState();
_loadCurrentPatch();
}
Future<void> _loadCurrentPatch() async {
final patch = await _ota.currentPatchNumber();
setState(() => _currentPatch = patch);
}
/// Démonstration 1 : Simulation Gatekeeper Silencieux
Future<void> _testSilentGatekeeper() async {
setState(() {
_isLoading = true;
_statusMessage = 'Évaluation Gatekeeper (Mode Silencieux)...';
});
final config = OtaRemoteConfig(
targetReleaseVersion: '1.0.0+1',
patchAvailable: true,
latestPatchNumber: (_currentPatch ?? 0) + 1,
forceUpdate: false,
releaseNotes: 'Optimisation de la vitesse de transaction.',
);
final outcome = await _ota.syncWithRemoteConfig(config);
setState(() {
_isLoading = false;
_statusMessage = outcome.message ?? 'Synchronisation terminée.';
});
await _loadCurrentPatch();
}
/// Démonstration 2 : Simulation Gatekeeper Critique Forcé
Future<void> _testCriticalGatekeeper() async {
final config = OtaRemoteConfig(
targetReleaseVersion: '1.0.0+1',
patchAvailable: true,
latestPatchNumber: (_currentPatch ?? 0) + 1,
forceUpdate: true,
releaseNotes: 'Correctif de sécurité critique obligatoire.',
);
await _showCriticalUpdateDialog(config);
}
/// Affiche la boîte de dialogue bloquante de mise à jour critique
Future<void> _showCriticalUpdateDialog(OtaRemoteConfig config) async {
return showDialog<void>(
context: context,
barrierDismissible: false,
builder: (ctx) {
var downloading = false;
return StatefulBuilder(
builder: (context, setModalState) {
return AlertDialog(
icon: const Icon(Icons.security_update_warning, size: 48, color: Colors.amber),
title: const Text('Mise à Jour Critique Requise'),
content: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
config.releaseNotes ?? 'Un correctif critique de sécurité doit être installé.',
style: const TextStyle(fontSize: 14),
),
const SizedBox(height: 16),
if (downloading) ...[
const LinearProgressIndicator(),
const SizedBox(height: 8),
const Center(child: Text('Téléchargement du patch souverain...')),
],
],
),
actions: [
if (!downloading)
FilledButton.icon(
icon: const Icon(Icons.download),
label: const Text('Mettre à jour maintenant'),
onPressed: () async {
setModalState(() => downloading = true);
await _ota.checkAndUpdate();
if (context.mounted) {
Navigator.of(context).pop();
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('Patch installé ! Redémarrez l\'application.'),
backgroundColor: Colors.green,
),
);
_loadCurrentPatch();
}
},
),
],
);
},
);
},
);
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('tk_ota_push Démo'),
actions: [
IconButton(
tooltip: 'Canal QA / Staging',
icon: Icon(_ota.track == UpdateTrack.staging ? Icons.science : Icons.verified_user),
onPressed: () {
setState(() {
if (_ota.track == UpdateTrack.staging) {
_ota.disableQaMode();
} else {
_ota.enableQaMode();
}
});
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('Canal actif : ${_ota.track.name}')),
);
},
),
],
),
body: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
// Carte Statut Courant
Card(
elevation: 2,
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
children: [
const Text('STATUT DU MOTEUR OTA', style: TextStyle(fontWeight: FontWeight.bold, letterSpacing: 1.2)),
const Divider(),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
const Text('Version Installée :'),
Text(_ota.appReleaseVersion, style: const TextStyle(fontWeight: FontWeight.bold)),
],
),
const SizedBox(height: 8),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
const Text('Numéro de Patch Actif :'),
Chip(
avatar: const Icon(Icons.tag, size: 16),
label: Text(_currentPatch == null ? 'Binaire Base (Patch 0)' : 'Patch #$_currentPatch'),
backgroundColor: _currentPatch == null ? Colors.grey.shade200 : Colors.teal.shade100,
),
],
),
const SizedBox(height: 8),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
const Text('Canal Déployé :'),
Text(_ota.track.name.toUpperCase(), style: const TextStyle(color: Colors.teal, fontWeight: FontWeight.bold)),
],
),
],
),
),
),
const SizedBox(height: 24),
// Actions de Démonstration Gatekeeper
const Text('TESTS DU REMOTE CONFIG GATEKEEPER', style: TextStyle(fontWeight: FontWeight.bold)),
const SizedBox(height: 8),
ElevatedButton.icon(
icon: const Icon(Icons.cloud_sync),
label: const Text('Tester Mode Silencieux (Eco-Data)'),
onPressed: _isLoading ? null : _testSilentGatekeeper,
),
const SizedBox(height: 8),
FilledButton.tonalIcon(
icon: const Icon(Icons.warning_amber),
label: const Text('Tester Mode Critique Forcé (Modale Bloquante)'),
onPressed: _isLoading ? null : _testCriticalGatekeeper,
),
const SizedBox(height: 16),
// Boîte de log / résultat
Expanded(
child: Container(
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
color: const Color(0x0A000000),
borderRadius: BorderRadius.circular(8),
border: Border.all(color: Colors.black12),
),
child: SingleChildScrollView(
child: Text(
_statusMessage,
style: const TextStyle(fontFamily: 'monospace', fontSize: 13),
),
),
),
),
],
),
),
);
}
}