iOS PWA and IT Job Checklist for IT Staff view

This commit is contained in:
2026-04-11 07:40:12 +08:00
parent 5cb6561924
commit f223d1f958
18 changed files with 389 additions and 109 deletions
+60 -9
View File
@@ -9,19 +9,70 @@ importScripts('https://www.gstatic.com/firebasejs/9.22.2/firebase-messaging-comp
firebase.initializeApp({
// values must match `firebase_options.dart` generated by `flutterfire`
apiKey: 'AIzaSyBKGSaHYiqpZvbEgsvJJY45soiIkV6MV3M',
appId: '1:173359574734:web:f894a6b43a443e902baa9f',
messagingSenderId: '173359574734',
projectId: 'tasq-17fb3',
authDomain: 'tasq-17fb3.firebaseapp.com',
storageBucket: 'tasq-17fb3.firebasestorage.app',
appId: '1:173359574734:web:f894a6b43a443e902baa9f',
messagingSenderId: '173359574734',
projectId: 'tasq-17fb3',
authDomain: 'tasq-17fb3.firebaseapp.com',
storageBucket: 'tasq-17fb3.firebasestorage.app',
});
const messaging = firebase.messaging();
messaging.onBackgroundMessage(function(payload) {
// display a notification using data in the payload
self.registration.showNotification(payload.notification.title, {
body: payload.notification.body,
const notificationTitle = payload.notification?.title
|| payload.data?.title
|| 'TasQ Notification';
const notificationBody = payload.notification?.body
|| payload.data?.body
|| '';
const notificationOptions = {
body: notificationBody,
icon: '/icons/Icon-192.png',
badge: '/icons/Icon-192.png',
data: payload.data,
});
// tag deduplicates: same notification_id won't stack
tag: payload.data?.notification_id || payload.messageId || 'tasq',
renotify: true,
};
return self.registration.showNotification(notificationTitle, notificationOptions);
});
// Handle notification click — focus the PWA window and navigate to the
// relevant task / ticket route based on the data payload.
self.addEventListener('notificationclick', function(event) {
event.notification.close();
const data = event.notification.data || {};
const navigateTo = data.navigate_to || data.route;
const taskId = data.task_id || data.taskId;
const ticketId = data.ticket_id || data.ticketId;
let targetPath = '/';
if (navigateTo && navigateTo !== '/') {
targetPath = navigateTo;
} else if (taskId) {
targetPath = '/tasks/' + taskId;
} else if (ticketId) {
targetPath = '/tickets/' + ticketId;
}
event.waitUntil(
clients
.matchAll({ type: 'window', includeUncontrolled: true })
.then(function(clientList) {
// If the app is already open, navigate it and bring it to front
for (const client of clientList) {
if ('navigate' in client) {
client.navigate(targetPath);
return client.focus();
}
}
// Otherwise open a new window
if (clients.openWindow) {
return clients.openWindow(targetPath);
}
})
);
});
Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 53 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 56 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 43 KiB

+33 -7
View File
@@ -5,17 +5,43 @@
<meta charset="UTF-8">
<meta content="IE=Edge" http-equiv="X-UA-Compatible">
<meta name="description" content="A new Flutter project.">
<meta name="description" content="TasQ — Task and workforce management.">
<meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover">
<!-- PWA / Android -->
<meta name="mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-status-bar-style" content="black">
<meta name="apple-mobile-web-app-title" content="tasq">
<link rel="apple-touch-icon" href="icons/Icon-192.png">
<link rel="manifest" href="manifest.json">
<!-- iOS PWA meta tags -->
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">
<meta name="apple-mobile-web-app-title" content="TasQ">
<!-- Apple touch icons (iOS uses largest available that fits) -->
<link rel="apple-touch-icon" href="icons/apple-touch-icon-180.png">
<link rel="apple-touch-icon" sizes="152x152" href="icons/apple-touch-icon-152.png">
<link rel="apple-touch-icon" sizes="167x167" href="icons/apple-touch-icon-167.png">
<link rel="apple-touch-icon" sizes="180x180" href="icons/apple-touch-icon-180.png">
<!-- iOS Splash Screens -->
<!-- iPhone 16 Pro Max (1320x2868 @3x) -->
<link rel="apple-touch-startup-image"
media="screen and (device-width: 440px) and (device-height: 956px) and (-webkit-device-pixel-ratio: 3) and (orientation: portrait)"
href="icons/splash-1320x2868.png">
<!-- iPhone 14 / 15 / 16 (1179x2556 @3x) -->
<link rel="apple-touch-startup-image"
media="screen and (device-width: 393px) and (device-height: 852px) and (-webkit-device-pixel-ratio: 3) and (orientation: portrait)"
href="icons/splash-1179x2556.png">
<!-- iPhone SE 3rd gen (750x1334 @2x) -->
<link rel="apple-touch-startup-image"
media="screen and (device-width: 375px) and (device-height: 667px) and (-webkit-device-pixel-ratio: 2) and (orientation: portrait)"
href="icons/splash-750x1334.png">
<meta name="theme-color" content="#FFFFFF">
<link rel="icon" type="image/png" href="favicon.png"/>
<title>tasq</title>
<link rel="manifest" href="manifest.json">
<title>TasQ</title>
<script>
var dartPdfJsVersion = "3.2.146";
@@ -26,4 +52,4 @@
<body>
<script src="flutter_bootstrap.js" async></script>
</body>
</html>
</html>
+14 -5
View File
@@ -1,13 +1,16 @@
{
"name": "tasq",
"short_name": "tasq",
"start_url": ".",
"id": "/",
"name": "TasQ",
"short_name": "TasQ",
"description": "Task and workforce management for your organisation.",
"start_url": "./",
"display": "standalone",
"display_override": ["standalone", "minimal-ui"],
"background_color": "#FFFFFF",
"theme_color": "#FFFFFF",
"description": "A new Flutter project.",
"orientation": "portrait-primary",
"prefer_related_applications": false,
"categories": ["productivity", "business"],
"icons": [
{
"src": "icons/Icon-192.png",
@@ -30,6 +33,12 @@
"sizes": "512x512",
"type": "image/png",
"purpose": "maskable"
},
{
"src": "icons/apple-touch-icon-180.png",
"sizes": "180x180",
"type": "image/png",
"purpose": "any"
}
]
}
}