@@ -4,7 +4,10 @@ import 'dart:io';
44import 'dart:ui' show ImageFilter;
55import 'package:flutter/foundation.dart' show kIsWeb;
66import 'package:flutter/material.dart' ;
7+ import 'package:http/http.dart' as http;
78import 'package:in_app_update/in_app_update.dart' ;
9+ import 'package:package_info_plus/package_info_plus.dart' ;
10+ import 'package:url_launcher/url_launcher.dart' ;
811import '../l10n/app_localizations.dart' ;
912import '../models/geofence.dart' ;
1013import '../services/socket_service.dart' ;
@@ -250,7 +253,15 @@ class _MainPageState extends State<MainPage> {
250253 }
251254
252255 Future <void > _checkForUpdate () async {
253- if (kIsWeb || ! Platform .isAndroid) return ;
256+ if (kIsWeb) return ;
257+ if (Platform .isAndroid) {
258+ await _checkForUpdateAndroid ();
259+ } else if (Platform .isIOS) {
260+ await _checkForUpdateIos ();
261+ }
262+ }
263+
264+ Future <void > _checkForUpdateAndroid () async {
254265 try {
255266 final info = await InAppUpdate .checkForUpdate ();
256267 if (! mounted) return ;
@@ -280,6 +291,40 @@ class _MainPageState extends State<MainPage> {
280291 }
281292 }
282293
294+ Future <void > _checkForUpdateIos () async {
295+ try {
296+ final packageInfo = await PackageInfo .fromPlatform ();
297+ final response = await http
298+ .get (Uri .parse (
299+ 'https://itunes.apple.com/lookup?bundleId=com.fleetmap.fleetmanager' ))
300+ .timeout (const Duration (seconds: 10 ));
301+ if (response.statusCode != 200 ) return ;
302+ final data = jsonDecode (response.body) as Map <String , dynamic >;
303+ final results = data['results' ] as List ? ;
304+ if (results == null || results.isEmpty) return ;
305+ final storeVersion = results[0 ]['version' ] as String ? ;
306+ final storeUrl = results[0 ]['trackViewUrl' ] as String ? ;
307+ if (storeVersion == null || storeUrl == null ) return ;
308+ if (_isNewerVersion (storeVersion, packageInfo.version)) {
309+ if (mounted) _showIosUpdateSnackbar (storeUrl);
310+ }
311+ } catch (_) {
312+ // Update check is non-critical, ignore errors
313+ }
314+ }
315+
316+ bool _isNewerVersion (String storeVersion, String currentVersion) {
317+ final storeParts = storeVersion.split ('.' ).map (int .tryParse).toList ();
318+ final currentParts = currentVersion.split ('.' ).map (int .tryParse).toList ();
319+ for (var i = 0 ; i < storeParts.length; i++ ) {
320+ final store = storeParts[i] ?? 0 ;
321+ final current = i < currentParts.length ? (currentParts[i] ?? 0 ) : 0 ;
322+ if (store > current) return true ;
323+ if (store < current) return false ;
324+ }
325+ return false ;
326+ }
327+
283328 void _showUpdateReadySnackbar () {
284329 final l10n = AppLocalizations .of (context)! ;
285330 ScaffoldMessenger .of (context).showSnackBar (
@@ -294,6 +339,23 @@ class _MainPageState extends State<MainPage> {
294339 );
295340 }
296341
342+ void _showIosUpdateSnackbar (String storeUrl) {
343+ final l10n = AppLocalizations .of (context)! ;
344+ ScaffoldMessenger .of (context).showSnackBar (
345+ SnackBar (
346+ content: Text (l10n.updateAvailable),
347+ duration: const Duration (seconds: 15 ),
348+ action: SnackBarAction (
349+ label: l10n.update,
350+ onPressed: () => launchUrl (
351+ Uri .parse (storeUrl),
352+ mode: LaunchMode .externalApplication,
353+ ),
354+ ),
355+ ),
356+ );
357+ }
358+
297359 Future <void > _connectSocket () async {
298360 _socketService.onStatusChanged = () {
299361 if (mounted) setState (() => _wsConnected = _socketService.isConnected);
0 commit comments