iOS PWA and IT Job Checklist for IT Staff view
This commit is contained in:
@@ -0,0 +1,93 @@
|
||||
import 'package:flutter/foundation.dart' show kIsWeb;
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
|
||||
import 'ios_install_prompt_stub.dart'
|
||||
if (dart.library.html) 'ios_install_prompt_web.dart';
|
||||
|
||||
/// Wraps the app and shows a persistent banner on iOS Safari when the PWA has
|
||||
/// not been added to the Home Screen.
|
||||
///
|
||||
/// Adding TasQ to the Home Screen is required to:
|
||||
/// - Receive push notifications on iOS 16.4+
|
||||
/// - Run in standalone (full-screen) mode without Safari chrome
|
||||
///
|
||||
/// The banner is dismissed permanently once the user taps "Got it".
|
||||
class IosInstallPrompt extends StatefulWidget {
|
||||
const IosInstallPrompt({required this.child, super.key});
|
||||
|
||||
final Widget child;
|
||||
|
||||
@override
|
||||
State<IosInstallPrompt> createState() => _IosInstallPromptState();
|
||||
}
|
||||
|
||||
class _IosInstallPromptState extends State<IosInstallPrompt> {
|
||||
bool _showBanner = false;
|
||||
static const _prefKey = 'ios_a2hs_prompt_dismissed_v1';
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
if (kIsWeb) _checkShouldShow();
|
||||
}
|
||||
|
||||
Future<void> _checkShouldShow() async {
|
||||
if (!detectIosSafariNotInstalled()) return;
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
final dismissed = prefs.getBool(_prefKey) ?? false;
|
||||
if (!dismissed && mounted) {
|
||||
setState(() => _showBanner = true);
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _dismiss() async {
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
await prefs.setBool(_prefKey, true);
|
||||
if (mounted) setState(() => _showBanner = false);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
if (!_showBanner) return widget.child;
|
||||
|
||||
final cs = Theme.of(context).colorScheme;
|
||||
|
||||
return Column(
|
||||
children: [
|
||||
Material(
|
||||
color: cs.primaryContainer,
|
||||
child: SafeArea(
|
||||
bottom: false,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 10),
|
||||
child: Row(
|
||||
children: [
|
||||
Icon(Icons.ios_share, size: 20, color: cs.onPrimaryContainer),
|
||||
const SizedBox(width: 12),
|
||||
Expanded(
|
||||
child: Text(
|
||||
'Install TasQ: tap Share then "Add to Home Screen" '
|
||||
'to enable push notifications.',
|
||||
style: Theme.of(context).textTheme.bodySmall?.copyWith(
|
||||
color: cs.onPrimaryContainer,
|
||||
),
|
||||
),
|
||||
),
|
||||
TextButton(
|
||||
onPressed: _dismiss,
|
||||
child: Text(
|
||||
'Got it',
|
||||
style: TextStyle(color: cs.onPrimaryContainer),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
Expanded(child: widget.child),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
/// Stub for non-web platforms. Always returns false — no install prompt needed.
|
||||
bool detectIosSafariNotInstalled() => false;
|
||||
@@ -0,0 +1,27 @@
|
||||
import 'dart:js_interop';
|
||||
|
||||
@JS('navigator.userAgent')
|
||||
external String get _navigatorUserAgent;
|
||||
|
||||
@JS('window.matchMedia')
|
||||
external _MediaQueryList _matchMedia(String query);
|
||||
|
||||
extension type _MediaQueryList._(JSObject _) implements JSObject {
|
||||
external bool get matches;
|
||||
}
|
||||
|
||||
/// Returns true when the user is on iOS Safari and the app has NOT been added
|
||||
/// to the Home Screen (i.e., not running in standalone mode).
|
||||
///
|
||||
/// When this returns true, we show the "Add to Home Screen" install banner so
|
||||
/// that users can unlock iOS 16.4+ web push notifications.
|
||||
bool detectIosSafariNotInstalled() {
|
||||
final ua = _navigatorUserAgent.toLowerCase();
|
||||
final isIos =
|
||||
ua.contains('iphone') || ua.contains('ipad') || ua.contains('ipod');
|
||||
if (!isIos) return false;
|
||||
|
||||
// The PWA is "installed" if the display-mode: standalone media query matches.
|
||||
final isStandalone = _matchMedia('(display-mode: standalone)').matches;
|
||||
return !isStandalone;
|
||||
}
|
||||
Reference in New Issue
Block a user