feat: change icons & update invitations for testing
@ -1,36 +1,258 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import '../../services/notification_service.dart';
|
||||
|
||||
class InvitationsPage extends StatefulWidget {
|
||||
@override
|
||||
_InvitationsPageState createState() => _InvitationsPageState();
|
||||
}
|
||||
|
||||
class _InvitationsPageState extends State<InvitationsPage> {
|
||||
bool _isAccepted = false;
|
||||
bool _isLoading = false;
|
||||
|
||||
Future<void> _acceptCoffeeInvite() async {
|
||||
setState(() {
|
||||
_isLoading = true;
|
||||
});
|
||||
|
||||
try {
|
||||
final success = await NotificationService()
|
||||
.sendCoffeeInviteAcceptedNotification();
|
||||
|
||||
if (success) {
|
||||
setState(() {
|
||||
_isAccepted = true;
|
||||
_isLoading = false;
|
||||
});
|
||||
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
content: Text(
|
||||
'Coffee invite accepted! Notification sent to everyone.',
|
||||
),
|
||||
backgroundColor: Colors.green,
|
||||
duration: Duration(seconds: 3),
|
||||
),
|
||||
);
|
||||
} else {
|
||||
setState(() {
|
||||
_isLoading = false;
|
||||
});
|
||||
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
content: Text(
|
||||
'Failed to send notification. Check Firebase service account configuration.',
|
||||
),
|
||||
backgroundColor: Colors.orange,
|
||||
duration: Duration(seconds: 3),
|
||||
),
|
||||
);
|
||||
}
|
||||
} catch (e) {
|
||||
setState(() {
|
||||
_isLoading = false;
|
||||
});
|
||||
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
content: Text('Error: $e'),
|
||||
backgroundColor: Colors.red,
|
||||
duration: Duration(seconds: 3),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class InvitationsPage extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: Text(
|
||||
'Invitations',
|
||||
style: TextStyle(
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
style: TextStyle(fontWeight: FontWeight.w600),
|
||||
),
|
||||
backgroundColor: Colors.white,
|
||||
foregroundColor: Color(0xFF6A4C93),
|
||||
elevation: 0,
|
||||
bottom: PreferredSize(
|
||||
preferredSize: Size.fromHeight(1),
|
||||
child: Container(
|
||||
height: 1,
|
||||
color: Colors.grey[200],
|
||||
),
|
||||
child: Container(height: 1, color: Colors.grey[200]),
|
||||
),
|
||||
),
|
||||
body: Center(
|
||||
child: Text(
|
||||
'This is the Invitations Page',
|
||||
style: TextStyle(
|
||||
fontSize: 24,
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
body: Padding(
|
||||
padding: EdgeInsets.all(16),
|
||||
child: Column(
|
||||
children: [
|
||||
// Coffee Invitation Card
|
||||
Container(
|
||||
margin: EdgeInsets.only(bottom: 16),
|
||||
padding: EdgeInsets.all(20),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white,
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: Colors.black.withOpacity(0.08),
|
||||
blurRadius: 10,
|
||||
offset: Offset(0, 2),
|
||||
),
|
||||
],
|
||||
border: Border.all(
|
||||
color: Colors.grey.withOpacity(0.2),
|
||||
width: 1,
|
||||
),
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
// Header with icon and title
|
||||
Row(
|
||||
children: [
|
||||
Container(
|
||||
padding: EdgeInsets.all(8),
|
||||
decoration: BoxDecoration(
|
||||
color: Color(0xFF6A4C93).withOpacity(0.1),
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
child: Icon(
|
||||
Icons.coffee,
|
||||
color: Color(0xFF6A4C93),
|
||||
size: 24,
|
||||
),
|
||||
),
|
||||
SizedBox(width: 12),
|
||||
Expanded(
|
||||
child: Text(
|
||||
'Who\'s down for coffee?',
|
||||
style: TextStyle(
|
||||
fontSize: 18,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: Colors.black87,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
|
||||
SizedBox(height: 16),
|
||||
|
||||
// Description
|
||||
Text(
|
||||
'Quick coffee break at the campus café. Let\'s catch up!',
|
||||
style: TextStyle(
|
||||
fontSize: 14,
|
||||
color: Colors.grey[700],
|
||||
height: 1.4,
|
||||
),
|
||||
),
|
||||
|
||||
SizedBox(height: 16),
|
||||
|
||||
// Status indicator
|
||||
Row(
|
||||
children: [
|
||||
Container(
|
||||
padding: EdgeInsets.symmetric(
|
||||
horizontal: 8,
|
||||
vertical: 4,
|
||||
),
|
||||
decoration: BoxDecoration(
|
||||
color: _isAccepted
|
||||
? Colors.green.withOpacity(0.1)
|
||||
: Colors.orange.withOpacity(0.1),
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
child: Text(
|
||||
_isAccepted ? 'Accepted ✓' : '1 more person needed',
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: _isAccepted
|
||||
? Colors.green[700]
|
||||
: Colors.orange[700],
|
||||
),
|
||||
),
|
||||
),
|
||||
Spacer(),
|
||||
Text(
|
||||
'10 min ago',
|
||||
style: TextStyle(fontSize: 12, color: Colors.grey[500]),
|
||||
),
|
||||
],
|
||||
),
|
||||
|
||||
SizedBox(height: 16),
|
||||
|
||||
// Accept button
|
||||
SizedBox(
|
||||
width: double.infinity,
|
||||
height: 44,
|
||||
child: ElevatedButton(
|
||||
onPressed: _isAccepted
|
||||
? null
|
||||
: (_isLoading ? null : _acceptCoffeeInvite),
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: _isAccepted
|
||||
? Colors.grey[300]
|
||||
: Color(0xFF6A4C93),
|
||||
foregroundColor: _isAccepted
|
||||
? Colors.grey[600]
|
||||
: Colors.white,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
elevation: _isAccepted ? 0 : 2,
|
||||
),
|
||||
child: _isLoading
|
||||
? SizedBox(
|
||||
width: 20,
|
||||
height: 20,
|
||||
child: CircularProgressIndicator(
|
||||
valueColor: AlwaysStoppedAnimation<Color>(
|
||||
Colors.white,
|
||||
),
|
||||
strokeWidth: 2,
|
||||
),
|
||||
)
|
||||
: Text(
|
||||
_isAccepted ? 'Accepted' : 'Accept Invite',
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
// Info text
|
||||
if (!_isAccepted)
|
||||
Container(
|
||||
padding: EdgeInsets.all(16),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.blue.withOpacity(0.05),
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
border: Border.all(color: Colors.blue.withOpacity(0.2)),
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
Icon(Icons.info_outline, color: Colors.blue[600], size: 20),
|
||||
SizedBox(width: 8),
|
||||
Expanded(
|
||||
child: Text(
|
||||
'This is a test invitation :)',
|
||||
style: TextStyle(fontSize: 13, color: Colors.blue[700]),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,5 +1,8 @@
|
||||
import 'package:firebase_messaging/firebase_messaging.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'dart:convert';
|
||||
import 'package:http/http.dart' as http;
|
||||
import 'package:googleapis_auth/auth_io.dart';
|
||||
|
||||
class NotificationService {
|
||||
static final NotificationService _instance = NotificationService._internal();
|
||||
@ -7,9 +10,30 @@ class NotificationService {
|
||||
NotificationService._internal();
|
||||
|
||||
FirebaseMessaging? _messaging;
|
||||
|
||||
static const String vapidKey =
|
||||
'BKrFSFm2cb2DNtEpTNmEy3acpi2ziRA5DhzKSyjshqAWANaoydztUTa0Cn3jwh1v7KN6pHUQfsODFXUWrKG6aSU';
|
||||
|
||||
// Firebase project configuration
|
||||
static const String _projectId = 'wesalapp-bc676';
|
||||
|
||||
static const String vapidKey = 'BKrFSFm2cb2DNtEpTNmEy3acpi2ziRA5DhzKSyjshqAWANaoydztUTa0Cn3jwh1v7KN6pHUQfsODFXUWrKG6aSU';
|
||||
|
||||
// Service account credentials (JSON string)
|
||||
static const String _serviceAccountJson = '''
|
||||
{
|
||||
"type": "service_account",
|
||||
"project_id": "wesalapp-bc676",
|
||||
"private_key_id": "90f1ac73e8f8b59e5ad7b0caed638ffa57675d39",
|
||||
"private_key": "-----BEGIN PRIVATE KEY-----\\nMIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQCe/p4CkGeDft2F\\nLMDNyjbU0g1mLQ4bhaqy2WOHr8BX7kHt/WlpB3WeaSUItW2cXFtXhI66mW7Ejlg7\\n/Los4YLpVXner4P5Vj7sOVrhD1Jt9qmdsEjfsTaKs0t06tfUE9PdFqBl6F5HG9B1\\nkwdV2mQfiurpTb+zUXLpfvMbGT1ny7gJSXhYpRaC6UJUy7DNsxeChviXy1tPGr/r\\nmIaENt0KAZzRSSCp0bzN+gKoAm6qyOGYJ9++HlGXTDYmkGPkdc0lbSWjA2/+K7zR\\nWQNy4oOXCstdHL4lMp87UT8lL1ZnntROELyTBUslmYywxXtZinkOgBrWeUifqLW4\\nbcHy6jJ5AgMBAAECggEACSBwjZEggAneSXDCOI3tC9Zq8nyPnMDVhaK49eb+0Y1Z\\nt4GedWr6M3exqohPnHQowiNX1hpMo3fQVNEzFrRzQVWow0Gr/7oVrpW0Q8sPXkSU\\ng/rElCKmENwt7q40aXYh6UUNPAxUrRxJoRYpi6IXsT/WMEJISNDaGdExv1J5leWi\\no8Op2AhREV/ukFpTnNfzfWKjXN+i3psNCYqZAdAh+a4ZJH0vNpiaCq6uVFw7HzdR\\nF2mMha+MYtp2VupzDJ8DkL4ExQl1KCOCllahzqVrhqmEhhhTfDxPOj5q24Hnfn1p\\npzR+fC8Ex0dGB/j+9jKjQyilo/LzEJdrPxt/9QUdiQKBgQDQ2L7sQsEIihVUq3lW\\n3Od2GNjnloTo24rNLl8hxwgl9EA2VnG50gZPGAkcUA2eeA23T6C6gPbm0QBsfqSP\\nPNTbd6UYF508EE7PMaScFoJMcKDin8x4q5tfVjgao2r8LCOUXfU1ktreQR3bIMKk\\nsgsgBazfBT84ioxvDwoD+4EJqwKBgQDC5HEfouCTxvKwzmpxZ+kkHKPO5QjyxnOH\\nLnt/7jx5t7T1nWNUnusYj+uowaqKAmLz7kBhCbRGADdKuBAr0hY/aEOG3zhTH35K\\nc+8wJ3yDFkh8BhFsOYCxopIPAjEGxw5cnM4+r8QDqy61j4hsR9kSr40WwhRuSxu+\\nHqe38Vl4awKBgBYFJGxIxY2e8YzR36NW+1iqWgRhDHZ433Ou1fz7vVIzJKoWBzuu\\nd1fTkvJXRnhU9C1Fyg6gFmhT1RWbbMJliZPyU4fsxXlVxtl1xINopChnH6+FZcu7\\nXFB7CMNWQ6t/A+la1sXlTApvFzTJiXxQAXhI4OdK6FWP1irHjSjKVdqtAoGAcLQA\\ngyYCrxKux/YmcfyAQ2TYic3DNfnzVypXOuz/RfgpipwAlC/ujl60Dfwo7fRhWuTd\\nkAA3ov9++hOlLmIogXR/EGDHxrIAq3eNy5AaHghl1GsB6k76kD8OLeW7ikrUkFQR\\npQip1uFIerBNWSjXbEne0llbzUhb+77oiKPmdI8CgYBRBIUC4d/T3cgmF9uRqlpL\\nSsa8IxqohU1huAucf5UqNP9OXZ4TtZhM0PbR5SNHcGBXAl+XowCtUeCE9LlWWzpg\\ne/xTu4Mu1hwnRZ8ybujAyTPnN8KEfK8HDjORZnxzzdyPkO4BN+KOH6WZyKhKDTuR\\n6KCch1ooA8YlV43vchpKXg==\\n-----END PRIVATE KEY-----\\n",
|
||||
"client_email": "firebase-adminsdk-fbsvc@wesalapp-bc676.iam.gserviceaccount.com",
|
||||
"client_id": "112586481303824467416",
|
||||
"auth_uri": "https://accounts.google.com/o/oauth2/auth",
|
||||
"token_uri": "https://oauth2.googleapis.com/token",
|
||||
"auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs",
|
||||
"client_x509_cert_url": "https://www.googleapis.com/robot/v1/metadata/x509/firebase-adminsdk-fbsvc%40wesalapp-bc676.iam.gserviceaccount.com",
|
||||
"universe_domain": "googleapis.com"
|
||||
}
|
||||
''';
|
||||
|
||||
static const List<String> topics = [
|
||||
'all',
|
||||
'newposts',
|
||||
@ -53,7 +77,7 @@ class NotificationService {
|
||||
_messaging = FirebaseMessaging.instance;
|
||||
await _setupMessageHandlers();
|
||||
}
|
||||
|
||||
|
||||
await _requestPermission();
|
||||
await _subscribeToTopics();
|
||||
} catch (e) {
|
||||
@ -63,7 +87,7 @@ class NotificationService {
|
||||
|
||||
bool _isNotificationSupported() {
|
||||
if (!kIsWeb) return false;
|
||||
|
||||
|
||||
// Check if the browser supports notifications
|
||||
try {
|
||||
// This will throw an error if notifications are not supported
|
||||
@ -88,10 +112,11 @@ class NotificationService {
|
||||
);
|
||||
|
||||
print('User granted permission: ${settings.authorizationStatus}');
|
||||
|
||||
|
||||
if (settings.authorizationStatus == AuthorizationStatus.authorized) {
|
||||
print('User granted permission for notifications');
|
||||
} else if (settings.authorizationStatus == AuthorizationStatus.provisional) {
|
||||
} else if (settings.authorizationStatus ==
|
||||
AuthorizationStatus.provisional) {
|
||||
print('User granted provisional permission for notifications');
|
||||
} else {
|
||||
print('User declined or has not accepted permission for notifications');
|
||||
@ -160,7 +185,7 @@ class NotificationService {
|
||||
void _handleNotificationTap(RemoteMessage message) {
|
||||
// Handle notification tap actions here
|
||||
print('Notification tapped: ${message.data}');
|
||||
|
||||
|
||||
// You can navigate to specific screens based on the notification data
|
||||
// For example:
|
||||
// if (message.data['type'] == 'newpost') {
|
||||
@ -172,7 +197,7 @@ class NotificationService {
|
||||
|
||||
Future<void> unsubscribeFromTopic(String topic) async {
|
||||
if (_messaging == null) return;
|
||||
|
||||
|
||||
try {
|
||||
await _messaging!.unsubscribeFromTopic(topic);
|
||||
print('Unsubscribed from topic: $topic');
|
||||
@ -183,7 +208,7 @@ class NotificationService {
|
||||
|
||||
Future<void> subscribeToTopic(String topic) async {
|
||||
if (_messaging == null) return;
|
||||
|
||||
|
||||
try {
|
||||
await _messaging!.subscribeToTopic(topic);
|
||||
print('Subscribed to topic: $topic');
|
||||
@ -211,8 +236,9 @@ class NotificationService {
|
||||
}
|
||||
|
||||
try {
|
||||
NotificationSettings settings = await _messaging!.getNotificationSettings();
|
||||
|
||||
NotificationSettings settings = await _messaging!
|
||||
.getNotificationSettings();
|
||||
|
||||
switch (settings.authorizationStatus) {
|
||||
case AuthorizationStatus.authorized:
|
||||
return NotificationStatus.enabled;
|
||||
@ -230,6 +256,93 @@ class NotificationService {
|
||||
return NotificationStatus.error;
|
||||
}
|
||||
}
|
||||
|
||||
Future<String?> _getAccessToken() async {
|
||||
try {
|
||||
final credentials = ServiceAccountCredentials.fromJson(_serviceAccountJson);
|
||||
final client = await clientViaServiceAccount(
|
||||
credentials,
|
||||
['https://www.googleapis.com/auth/firebase.messaging'],
|
||||
);
|
||||
|
||||
final token = client.credentials.accessToken.data;
|
||||
client.close();
|
||||
|
||||
return token;
|
||||
} catch (e) {
|
||||
print('Error getting access token: $e');
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
Future<bool> sendNotificationToTopic(
|
||||
String topic,
|
||||
String title,
|
||||
String body, {
|
||||
Map<String, String>? data,
|
||||
}) async {
|
||||
try {
|
||||
final accessToken = await _getAccessToken();
|
||||
if (accessToken == null) {
|
||||
print('Failed to get access token. Cannot send notifications.');
|
||||
return false;
|
||||
}
|
||||
|
||||
final url = Uri.parse('https://fcm.googleapis.com/v1/projects/$_projectId/messages:send');
|
||||
final headers = {
|
||||
'Content-Type': 'application/json',
|
||||
'Authorization': 'Bearer $accessToken',
|
||||
};
|
||||
|
||||
final payload = {
|
||||
'message': {
|
||||
'topic': topic,
|
||||
'notification': {
|
||||
'title': title,
|
||||
'body': body,
|
||||
},
|
||||
'data': data ?? {},
|
||||
'webpush': {
|
||||
'notification': {
|
||||
'icon': 'icons/ios/192.png',
|
||||
'badge': 'icons/ios/192.png',
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
final response = await http.post(
|
||||
url,
|
||||
headers: headers,
|
||||
body: json.encode(payload),
|
||||
);
|
||||
|
||||
if (response.statusCode == 200) {
|
||||
print('Notification sent successfully to topic: $topic');
|
||||
print('Response: ${response.body}');
|
||||
return true;
|
||||
} else {
|
||||
print('Failed to send notification. Status: ${response.statusCode}');
|
||||
print('Response: ${response.body}');
|
||||
return false;
|
||||
}
|
||||
} catch (e) {
|
||||
print('Error sending notification: $e');
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
Future<bool> sendCoffeeInviteAcceptedNotification() async {
|
||||
return await sendNotificationToTopic(
|
||||
'all',
|
||||
'Coffee Time! ☕',
|
||||
'You\'re set for coffee! Your invitation has been accepted.',
|
||||
data: {
|
||||
'type': 'coffee_invite_accepted',
|
||||
'timestamp': DateTime.now().toIso8601String(),
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
enum NotificationStatus {
|
||||
@ -239,15 +352,17 @@ enum NotificationStatus {
|
||||
notDetermined,
|
||||
notSupported,
|
||||
notInitialized,
|
||||
error
|
||||
error,
|
||||
}
|
||||
|
||||
// Background message handler must be a top-level function
|
||||
Future<void> _firebaseMessagingBackgroundHandler(RemoteMessage message) async {
|
||||
print('Handling a background message: ${message.messageId}');
|
||||
print('Message data: ${message.data}');
|
||||
|
||||
|
||||
if (message.notification != null) {
|
||||
print('Background message contained a notification: ${message.notification}');
|
||||
print(
|
||||
'Background message contained a notification: ${message.notification}',
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
56
pubspec.lock
@ -9,6 +9,14 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.3.58"
|
||||
args:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: args
|
||||
sha256: d0481093c50b1da8910eb0bb301626d4d8eb7284aa739614d2b394ee09e3ea04
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.7.0"
|
||||
async:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -49,6 +57,14 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.19.1"
|
||||
crypto:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: crypto
|
||||
sha256: "1e445881f28f22d6140f181e07737b22f1e099a5e1ff94b0af2f9e4a463f4855"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.0.6"
|
||||
cupertino_icons:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
@ -136,6 +152,38 @@ packages:
|
||||
description: flutter
|
||||
source: sdk
|
||||
version: "0.0.0"
|
||||
google_identity_services_web:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: google_identity_services_web
|
||||
sha256: "5d187c46dc59e02646e10fe82665fc3884a9b71bc1c90c2b8b749316d33ee454"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.3.3+1"
|
||||
googleapis_auth:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: googleapis_auth
|
||||
sha256: befd71383a955535060acde8792e7efc11d2fccd03dd1d3ec434e85b68775938
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.6.0"
|
||||
http:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: http
|
||||
sha256: "2c11f3f94c687ee9bad77c171151672986360b2b001d109814ee7140b2cf261b"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.4.0"
|
||||
http_parser:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: http_parser
|
||||
sha256: "178d74305e7866013777bab2c3d8726205dc5a4dd935297175b19a23a2e66571"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "4.1.2"
|
||||
leak_tracker:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -261,6 +309,14 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.7.4"
|
||||
typed_data:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: typed_data
|
||||
sha256: f9049c039ebfeb4cf7a7104a675823cd72dba8297f264b6637062516699fa006
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.4.0"
|
||||
vector_math:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
||||
@ -12,6 +12,8 @@ dependencies:
|
||||
cupertino_icons: ^1.0.8
|
||||
firebase_core: ^3.15.1
|
||||
firebase_messaging: ^15.2.9
|
||||
http: ^1.1.0
|
||||
googleapis_auth: ^1.6.0
|
||||
|
||||
dev_dependencies:
|
||||
flutter_test:
|
||||
|
||||
BIN
web/favicon.png
|
Before Width: | Height: | Size: 917 B |
|
Before Width: | Height: | Size: 5.2 KiB |
|
Before Width: | Height: | Size: 8.1 KiB |
|
Before Width: | Height: | Size: 5.5 KiB |
|
Before Width: | Height: | Size: 20 KiB |
BIN
web/icons/android/android-launchericon-144-144.png
Normal file
|
After Width: | Height: | Size: 5.2 KiB |
BIN
web/icons/android/android-launchericon-192-192.png
Normal file
|
After Width: | Height: | Size: 7.2 KiB |
BIN
web/icons/android/android-launchericon-48-48.png
Normal file
|
After Width: | Height: | Size: 1.5 KiB |
BIN
web/icons/android/android-launchericon-512-512.png
Normal file
|
After Width: | Height: | Size: 30 KiB |
BIN
web/icons/android/android-launchericon-72-72.png
Normal file
|
After Width: | Height: | Size: 2.4 KiB |
BIN
web/icons/android/android-launchericon-96-96.png
Normal file
|
After Width: | Height: | Size: 3.3 KiB |
BIN
web/icons/ios/100.png
Normal file
|
After Width: | Height: | Size: 3.4 KiB |
BIN
web/icons/ios/1024.png
Normal file
|
After Width: | Height: | Size: 83 KiB |
BIN
web/icons/ios/114.png
Normal file
|
After Width: | Height: | Size: 4.0 KiB |
BIN
web/icons/ios/120.png
Normal file
|
After Width: | Height: | Size: 4.2 KiB |
BIN
web/icons/ios/128.png
Normal file
|
After Width: | Height: | Size: 4.5 KiB |
BIN
web/icons/ios/144.png
Normal file
|
After Width: | Height: | Size: 5.2 KiB |
BIN
web/icons/ios/152.png
Normal file
|
After Width: | Height: | Size: 5.5 KiB |
BIN
web/icons/ios/16.png
Normal file
|
After Width: | Height: | Size: 474 B |
BIN
web/icons/ios/167.png
Normal file
|
After Width: | Height: | Size: 6.2 KiB |
BIN
web/icons/ios/180.png
Normal file
|
After Width: | Height: | Size: 6.5 KiB |
BIN
web/icons/ios/192.png
Normal file
|
After Width: | Height: | Size: 7.2 KiB |
BIN
web/icons/ios/20.png
Normal file
|
After Width: | Height: | Size: 572 B |
BIN
web/icons/ios/256.png
Normal file
|
After Width: | Height: | Size: 10 KiB |
BIN
web/icons/ios/29.png
Normal file
|
After Width: | Height: | Size: 867 B |
BIN
web/icons/ios/32.png
Normal file
|
After Width: | Height: | Size: 978 B |
BIN
web/icons/ios/40.png
Normal file
|
After Width: | Height: | Size: 1.2 KiB |
BIN
web/icons/ios/50.png
Normal file
|
After Width: | Height: | Size: 1.5 KiB |
BIN
web/icons/ios/512.png
Normal file
|
After Width: | Height: | Size: 30 KiB |
BIN
web/icons/ios/57.png
Normal file
|
After Width: | Height: | Size: 1.8 KiB |
BIN
web/icons/ios/58.png
Normal file
|
After Width: | Height: | Size: 1.8 KiB |
BIN
web/icons/ios/60.png
Normal file
|
After Width: | Height: | Size: 1.9 KiB |
BIN
web/icons/ios/64.png
Normal file
|
After Width: | Height: | Size: 2.1 KiB |
BIN
web/icons/ios/72.png
Normal file
|
After Width: | Height: | Size: 2.4 KiB |
BIN
web/icons/ios/76.png
Normal file
|
After Width: | Height: | Size: 2.5 KiB |
BIN
web/icons/ios/80.png
Normal file
|
After Width: | Height: | Size: 2.7 KiB |
BIN
web/icons/ios/87.png
Normal file
|
After Width: | Height: | Size: 2.9 KiB |
BIN
web/icons/windows11/LargeTile.scale-100.png
Normal file
|
After Width: | Height: | Size: 14 KiB |
BIN
web/icons/windows11/LargeTile.scale-125.png
Normal file
|
After Width: | Height: | Size: 19 KiB |
BIN
web/icons/windows11/LargeTile.scale-150.png
Normal file
|
After Width: | Height: | Size: 26 KiB |
BIN
web/icons/windows11/LargeTile.scale-200.png
Normal file
|
After Width: | Height: | Size: 42 KiB |
BIN
web/icons/windows11/LargeTile.scale-400.png
Normal file
|
After Width: | Height: | Size: 104 KiB |
BIN
web/icons/windows11/SmallTile.scale-100.png
Normal file
|
After Width: | Height: | Size: 2.4 KiB |
BIN
web/icons/windows11/SmallTile.scale-125.png
Normal file
|
After Width: | Height: | Size: 3.0 KiB |
BIN
web/icons/windows11/SmallTile.scale-150.png
Normal file
|
After Width: | Height: | Size: 3.8 KiB |
BIN
web/icons/windows11/SmallTile.scale-200.png
Normal file
|
After Width: | Height: | Size: 5.2 KiB |
BIN
web/icons/windows11/SmallTile.scale-400.png
Normal file
|
After Width: | Height: | Size: 12 KiB |
BIN
web/icons/windows11/SplashScreen.scale-100.png
Normal file
|
After Width: | Height: | Size: 14 KiB |
BIN
web/icons/windows11/SplashScreen.scale-125.png
Normal file
|
After Width: | Height: | Size: 20 KiB |
BIN
web/icons/windows11/SplashScreen.scale-150.png
Normal file
|
After Width: | Height: | Size: 27 KiB |
BIN
web/icons/windows11/SplashScreen.scale-200.png
Normal file
|
After Width: | Height: | Size: 41 KiB |
BIN
web/icons/windows11/SplashScreen.scale-400.png
Normal file
|
After Width: | Height: | Size: 110 KiB |
BIN
web/icons/windows11/Square150x150Logo.scale-100.png
Normal file
|
After Width: | Height: | Size: 5.5 KiB |
BIN
web/icons/windows11/Square150x150Logo.scale-125.png
Normal file
|
After Width: | Height: | Size: 6.9 KiB |
BIN
web/icons/windows11/Square150x150Logo.scale-150.png
Normal file
|
After Width: | Height: | Size: 8.8 KiB |
BIN
web/icons/windows11/Square150x150Logo.scale-200.png
Normal file
|
After Width: | Height: | Size: 13 KiB |
BIN
web/icons/windows11/Square150x150Logo.scale-400.png
Normal file
|
After Width: | Height: | Size: 38 KiB |
|
After Width: | Height: | Size: 504 B |
|
After Width: | Height: | Size: 649 B |
|
After Width: | Height: | Size: 778 B |
|
After Width: | Height: | Size: 13 KiB |
|
After Width: | Height: | Size: 1015 B |
|
After Width: | Height: | Size: 1.1 KiB |
|
After Width: | Height: | Size: 1.2 KiB |
|
After Width: | Height: | Size: 1.4 KiB |
|
After Width: | Height: | Size: 1.5 KiB |
|
After Width: | Height: | Size: 1.7 KiB |
|
After Width: | Height: | Size: 2.2 KiB |
|
After Width: | Height: | Size: 2.4 KiB |
|
After Width: | Height: | Size: 2.7 KiB |
|
After Width: | Height: | Size: 3.1 KiB |
|
After Width: | Height: | Size: 3.8 KiB |
|
After Width: | Height: | Size: 504 B |
|
After Width: | Height: | Size: 649 B |
|
After Width: | Height: | Size: 778 B |
|
After Width: | Height: | Size: 13 KiB |
|
After Width: | Height: | Size: 1015 B |
|
After Width: | Height: | Size: 1.1 KiB |
|
After Width: | Height: | Size: 1.2 KiB |
|
After Width: | Height: | Size: 1.4 KiB |
|
After Width: | Height: | Size: 1.5 KiB |
|
After Width: | Height: | Size: 1.7 KiB |
|
After Width: | Height: | Size: 2.2 KiB |
|
After Width: | Height: | Size: 2.4 KiB |
|
After Width: | Height: | Size: 2.7 KiB |
|
After Width: | Height: | Size: 3.1 KiB |
|
After Width: | Height: | Size: 3.8 KiB |
BIN
web/icons/windows11/Square44x44Logo.scale-100.png
Normal file
|
After Width: | Height: | Size: 1.5 KiB |
BIN
web/icons/windows11/Square44x44Logo.scale-125.png
Normal file
|
After Width: | Height: | Size: 2.0 KiB |
BIN
web/icons/windows11/Square44x44Logo.scale-150.png
Normal file
|
After Width: | Height: | Size: 2.5 KiB |
BIN
web/icons/windows11/Square44x44Logo.scale-200.png
Normal file
|
After Width: | Height: | Size: 3.5 KiB |
BIN
web/icons/windows11/Square44x44Logo.scale-400.png
Normal file
|
After Width: | Height: | Size: 7.7 KiB |
BIN
web/icons/windows11/Square44x44Logo.targetsize-16.png
Normal file
|
After Width: | Height: | Size: 504 B |
BIN
web/icons/windows11/Square44x44Logo.targetsize-20.png
Normal file
|
After Width: | Height: | Size: 649 B |
BIN
web/icons/windows11/Square44x44Logo.targetsize-24.png
Normal file
|
After Width: | Height: | Size: 778 B |