Added Local Notification for Matches (Android Only)
parent
7b82c1cbb8
commit
9ddfb5cfd5
|
@ -27,10 +27,13 @@ if (flutterVersionName == null) {
|
|||
|
||||
android {
|
||||
namespace "com.example.cofounderella"
|
||||
compileSdk flutter.compileSdkVersion
|
||||
compileSdk 34 // 34 required by flutter_local_notification; old value: flutter.compileSdkVersion
|
||||
ndkVersion "26.1.10909125"
|
||||
|
||||
compileOptions {
|
||||
// Flag to enable support for the new language APIs
|
||||
coreLibraryDesugaringEnabled true
|
||||
// Sets Java compatibility to Java 8
|
||||
sourceCompatibility JavaVersion.VERSION_1_8
|
||||
targetCompatibility JavaVersion.VERSION_1_8
|
||||
}
|
||||
|
@ -67,4 +70,10 @@ flutter {
|
|||
source '../..'
|
||||
}
|
||||
|
||||
dependencies {}
|
||||
dependencies {
|
||||
coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:2.0.3'
|
||||
// There have been reports that enabling desugaring may result in a Flutter apps crashing on
|
||||
// Android 12L and above. https://pub.dev/packages/flutter_local_notifications#-android-setup
|
||||
implementation 'androidx.window:window:1.0.0'
|
||||
implementation 'androidx.window:window-java:1.0.0'
|
||||
}
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
import 'package:flutter_local_notifications/flutter_local_notifications.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
import '../constants.dart';
|
||||
import '../services/auth/auth_gate.dart';
|
||||
|
@ -10,6 +11,7 @@ import 'pages/conversations_page.dart';
|
|||
import 'pages/liked_users_page.dart';
|
||||
import 'pages/user_matching_page.dart';
|
||||
import 'pages/user_profile_page.dart';
|
||||
import 'services/swipe_stream_service.dart';
|
||||
|
||||
void main() async {
|
||||
// Firebase stuff
|
||||
|
@ -22,14 +24,35 @@ void main() async {
|
|||
final prefs = await SharedPreferences.getInstance();
|
||||
final isDarkMode = prefs.getBool(Constants.prefKeyThemeDarkMode) ?? false;
|
||||
|
||||
final FlutterLocalNotificationsPlugin flutterLocalNotificationsPlugin =
|
||||
FlutterLocalNotificationsPlugin();
|
||||
const AndroidInitializationSettings initializationSettingsAndroid =
|
||||
AndroidInitializationSettings('@mipmap/ic_launcher');
|
||||
const InitializationSettings initializationSettings =
|
||||
InitializationSettings(android: initializationSettingsAndroid);
|
||||
await flutterLocalNotificationsPlugin.initialize(initializationSettings);
|
||||
|
||||
// Initialize the singleton instance with a dummy userId.
|
||||
// The actual userId is set later in the HomePage.
|
||||
SwipeStreamService()
|
||||
.initialize('dummy_initial_user_id', flutterLocalNotificationsPlugin);
|
||||
|
||||
runApp(
|
||||
ChangeNotifierProvider(
|
||||
create: (context) => ThemeProvider(initialIsDarkMode: isDarkMode),
|
||||
MultiProvider(
|
||||
providers: [
|
||||
ChangeNotifierProvider(
|
||||
create: (context) => ThemeProvider(initialIsDarkMode: isDarkMode)),
|
||||
Provider<FlutterLocalNotificationsPlugin>.value(
|
||||
value: flutterLocalNotificationsPlugin),
|
||||
],
|
||||
child: const MyApp(),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
// Global key for navigation
|
||||
final GlobalKey<NavigatorState> navigatorKey = GlobalKey<NavigatorState>();
|
||||
|
||||
class MyApp extends StatelessWidget {
|
||||
const MyApp({super.key});
|
||||
|
||||
|
@ -39,6 +62,7 @@ class MyApp extends StatelessWidget {
|
|||
debugShowCheckedModeBanner: false,
|
||||
title: Constants.appTitle,
|
||||
theme: Provider.of<ThemeProvider>(context).themeData,
|
||||
navigatorKey: navigatorKey,
|
||||
home: const AuthGate(),
|
||||
routes: {
|
||||
'/discover': (context) => const UserMatchingPage(),
|
||||
|
|
|
@ -1,11 +1,23 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_local_notifications/flutter_local_notifications.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import '../components/my_drawer.dart';
|
||||
import '../services/auth/auth_service.dart';
|
||||
import '../services/swipe_stream_service.dart';
|
||||
|
||||
class HomePage extends StatelessWidget {
|
||||
const HomePage({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final currentUserId = AuthService().getCurrentUser()!.uid;
|
||||
final flutterLocalNotificationsPlugin =
|
||||
Provider.of<FlutterLocalNotificationsPlugin>(context);
|
||||
final swipeStreamService = SwipeStreamService();
|
||||
swipeStreamService.initialize(
|
||||
currentUserId, flutterLocalNotificationsPlugin);
|
||||
swipeStreamService.listenToSwipes();
|
||||
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: const Text('Home'),
|
||||
|
|
|
@ -12,6 +12,7 @@ import '../constants.dart';
|
|||
import '../forms/matched_screen.dart';
|
||||
import '../models/user_profile.dart';
|
||||
import '../services/auth/auth_service.dart';
|
||||
import '../services/swipe_stream_service.dart';
|
||||
import '../services/user_service.dart';
|
||||
import '../utils/helper.dart';
|
||||
import '../utils/list_utils.dart';
|
||||
|
@ -173,6 +174,9 @@ class UserMatchingPageState extends State<UserMatchingPage> {
|
|||
.doc(swipedUserId)
|
||||
.collection(Constants.dbCollectionMatches);
|
||||
|
||||
// current user swiped, so avoid local push notification
|
||||
SwipeStreamService().addUser(swipedUserId);
|
||||
|
||||
await matchesCurrentUser.doc(swipedUserId).set({
|
||||
'otherUserId': swipedUserId,
|
||||
'timestamp': FieldValue.serverTimestamp(),
|
||||
|
@ -183,9 +187,6 @@ class UserMatchingPageState extends State<UserMatchingPage> {
|
|||
'timestamp': FieldValue.serverTimestamp(),
|
||||
});
|
||||
|
||||
//
|
||||
// TODO Notify other user?
|
||||
//
|
||||
showMatchedScreen(currentUserId, swipedUserId);
|
||||
|
||||
// Remove matched user from the list of potential users
|
||||
|
@ -237,9 +238,9 @@ class UserMatchingPageState extends State<UserMatchingPage> {
|
|||
} else if (percentage >= 40) {
|
||||
return Colors.amber.shade200; // 54 - 40
|
||||
} else if (percentage >= 20) {
|
||||
return Colors.orange.shade200; // 39 - 20
|
||||
return Colors.orange; // 39 - 20
|
||||
} else {
|
||||
return Colors.orange.shade300; // 19 - 0
|
||||
return Colors.red.shade400; // 19 - 0
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
import 'package:firebase_auth/firebase_auth.dart';
|
||||
|
||||
import '../swipe_stream_service.dart';
|
||||
|
||||
class AuthService {
|
||||
// instance of auth
|
||||
final FirebaseAuth _auth = FirebaseAuth.instance;
|
||||
|
@ -45,6 +47,7 @@ class AuthService {
|
|||
|
||||
// sign out
|
||||
Future<void> signOut() async {
|
||||
SwipeStreamService().stopListening();
|
||||
await _auth.signOut();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,122 @@
|
|||
import 'dart:async';
|
||||
import 'package:flutter/foundation.dart' show debugPrint, kDebugMode;
|
||||
import 'package:cloud_firestore/cloud_firestore.dart';
|
||||
import 'package:flutter_local_notifications/flutter_local_notifications.dart';
|
||||
import '../constants.dart';
|
||||
import '../main.dart';
|
||||
import '../utils/helper_dialogs.dart';
|
||||
import 'user_service.dart';
|
||||
|
||||
class SwipeStreamService {
|
||||
final FirebaseFirestore _firestore = FirebaseFirestore.instance;
|
||||
final List<String> _matchesInDB = [];
|
||||
|
||||
StreamSubscription<List<DocumentSnapshot>>? _subscription;
|
||||
|
||||
late String userId;
|
||||
late FlutterLocalNotificationsPlugin flutterLocalNotificationsPlugin;
|
||||
late bool matchesRead = false;
|
||||
|
||||
// Singleton instance
|
||||
static final SwipeStreamService _instance = SwipeStreamService._internal();
|
||||
|
||||
// Private constructor
|
||||
SwipeStreamService._internal();
|
||||
|
||||
// Factory constructor
|
||||
factory SwipeStreamService() {
|
||||
return _instance;
|
||||
}
|
||||
|
||||
// Initialization method
|
||||
void initialize(String userId, FlutterLocalNotificationsPlugin plugin) {
|
||||
_instance.userId = userId;
|
||||
_instance.flutterLocalNotificationsPlugin = plugin;
|
||||
// Reset old data
|
||||
_matchesInDB.clear();
|
||||
matchesRead = false;
|
||||
}
|
||||
|
||||
Stream<List<DocumentSnapshot>> get swipesStream {
|
||||
return _firestore
|
||||
.collection(Constants.dbCollectionUsers)
|
||||
.doc(userId)
|
||||
.collection(Constants.dbCollectionMatches)
|
||||
.snapshots()
|
||||
.map((snapshot) => snapshot.docs);
|
||||
}
|
||||
|
||||
void _showNotification(String swipeId) async {
|
||||
String matchName = await UserService.getUserName(swipeId);
|
||||
const AndroidNotificationDetails androidPlatformChannelSpecifics =
|
||||
AndroidNotificationDetails(
|
||||
'my_match_channel_id', 'my_match_channel_name',
|
||||
channelShowBadge: true,
|
||||
visibility: NotificationVisibility.private,
|
||||
importance: Importance.max,
|
||||
priority: Priority.high,
|
||||
showWhen: true);
|
||||
const NotificationDetails platformChannelSpecifics =
|
||||
NotificationDetails(android: androidPlatformChannelSpecifics);
|
||||
await flutterLocalNotificationsPlugin.show(0, 'New Match',
|
||||
'You have a new match with $matchName', platformChannelSpecifics);
|
||||
}
|
||||
|
||||
void listenToSwipes() {
|
||||
_subscription = swipesStream.listen(
|
||||
(swipes) async {
|
||||
List<String> userNames = [];
|
||||
String tempUser = '';
|
||||
|
||||
if (!matchesRead) {
|
||||
for (var swipe in swipes) {
|
||||
debugPrint('Init Match Notify --> ${swipe.id}');
|
||||
_matchesInDB.add(swipe.id);
|
||||
tempUser = await UserService.getUserName(swipe.id);
|
||||
userNames.add('INIT $tempUser');
|
||||
}
|
||||
if (kDebugMode) {
|
||||
showErrorSnackBar(
|
||||
navigatorKey.currentContext!,
|
||||
userNames.join(', '),
|
||||
);
|
||||
}
|
||||
|
||||
matchesRead = true;
|
||||
} else {
|
||||
for (var swipe in swipes) {
|
||||
tempUser = await UserService.getUserName(swipe.id);
|
||||
|
||||
if (!_matchesInDB.contains(swipe.id)) {
|
||||
userNames.add('NEW! $tempUser');
|
||||
debugPrint('NEW Match Notify --> ${swipe.id}');
|
||||
_matchesInDB.add(swipe.id);
|
||||
_showNotification(swipe.id);
|
||||
} else {
|
||||
userNames.add('OLD $tempUser');
|
||||
debugPrint('Old Match Notify --> ${swipe.id}');
|
||||
}
|
||||
}
|
||||
if (kDebugMode) {
|
||||
showErrorSnackBar(
|
||||
navigatorKey.currentContext!,
|
||||
userNames.join(', '),
|
||||
);
|
||||
}
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
void stopListening() {
|
||||
_subscription?.cancel();
|
||||
_subscription = null;
|
||||
}
|
||||
|
||||
void addUser(String userId) {
|
||||
if (!_matchesInDB.contains(userId)) {
|
||||
debugPrint('Notify: SKIP Match Notify for --> $userId');
|
||||
_matchesInDB.add(userId);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -346,4 +346,21 @@ class UserService {
|
|||
throw Exception('Error fetching sectors from Firebase: $e');
|
||||
}
|
||||
}
|
||||
|
||||
static Future<String> getUserName(String userId) async {
|
||||
try {
|
||||
DocumentSnapshot userDoc = await FirebaseFirestore.instance
|
||||
.collection(Constants.dbCollectionUsers)
|
||||
.doc(userId)
|
||||
.get();
|
||||
|
||||
if (userDoc.exists) {
|
||||
return userDoc[Constants.dbFieldUsersName] ?? 'Unknown User';
|
||||
} else {
|
||||
return 'User not found';
|
||||
}
|
||||
} catch (e) {
|
||||
return e.toString();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
40
pubspec.lock
40
pubspec.lock
|
@ -129,6 +129,14 @@ packages:
|
|||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.0.8"
|
||||
dbus:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: dbus
|
||||
sha256: "365c771ac3b0e58845f39ec6deebc76e3276aa9922b0cc60840712094d9047ac"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.7.10"
|
||||
expandable_text:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
|
@ -294,6 +302,30 @@ packages:
|
|||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.0.2"
|
||||
flutter_local_notifications:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: flutter_local_notifications
|
||||
sha256: "40e6fbd2da7dcc7ed78432c5cdab1559674b4af035fddbfb2f9a8f9c2112fcef"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "17.1.2"
|
||||
flutter_local_notifications_linux:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: flutter_local_notifications_linux
|
||||
sha256: "33f741ef47b5f63cc7f78fe75eeeac7e19f171ff3c3df054d84c1e38bedb6a03"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "4.0.0+1"
|
||||
flutter_local_notifications_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: flutter_local_notifications_platform_interface
|
||||
sha256: "340abf67df238f7f0ef58f4a26d2a83e1ab74c77ab03cd2b2d5018ac64db30b7"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "7.1.0"
|
||||
flutter_plugin_android_lifecycle:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -821,6 +853,14 @@ packages:
|
|||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.7.0"
|
||||
timezone:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: timezone
|
||||
sha256: a6ccda4a69a442098b602c44e61a1e2b4bf6f5516e875bbf0f427d5df14745d5
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.9.3"
|
||||
typed_data:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
|
|
@ -31,6 +31,7 @@ dependencies:
|
|||
shared_preferences: ^2.2.3
|
||||
flutter_launcher_icons: ^0.13.1
|
||||
url_launcher: ^6.3.0
|
||||
flutter_local_notifications: ^17.1.2
|
||||
|
||||
dev_dependencies:
|
||||
flutter_test:
|
||||
|
|
Loading…
Reference in New Issue