diff --git a/app/Services/NotificationService.php b/app/Services/NotificationService.php new file mode 100644 index 0000000..fc5990d --- /dev/null +++ b/app/Services/NotificationService.php @@ -0,0 +1,143 @@ +projectId = env('FIREBASE_PROJECT_ID', ''); + $this->serviceAccountPath = env('FIREBASE_SERVICE_ACCOUNT_PATH', APP_PATH . '/config/firebase-service-account.json'); + } + + /** + * Send a push notification to a specific user or device + */ + public function sendNotification(string $userId, string $title, string $body, array $data = [], ?string $deviceId = null): bool + { + $db = Database::getInstance(); + + // 1. Get push tokens for the user + if ($deviceId) { + $stmt = $db->prepare("SELECT push_token FROM user_devices WHERE user_id = ? AND device_fingerprint = ? AND push_token IS NOT NULL"); + $stmt->execute([$userId, $deviceId]); + } else { + $stmt = $db->prepare("SELECT push_token FROM user_devices WHERE user_id = ? AND push_token IS NOT NULL AND is_active = 1"); + $stmt->execute([$userId]); + } + + $tokens = $stmt->fetchAll(\PDO::FETCH_COLUMN); + + if (empty($tokens)) { + return false; + } + + // 2. Save notification to database (Single direct insert) + $stmt = $db->prepare("SELECT tenant_id FROM users WHERE id = ? LIMIT 1"); + $stmt->execute([$userId]); + $tenantId = $stmt->fetchColumn(); + + if ($tenantId) { + $stmt = $db->prepare("INSERT INTO notifications (id, tenant_id, user_id, type, title, body, data, created_at) VALUES (UUID(), ?, ?, 'system', ?, ?, ?, NOW())"); + $stmt->execute([$tenantId, $userId, $title, $body, json_encode($data)]); + } + + // 3. Send to each token + $successCount = 0; + foreach ($tokens as $token) { + if ($this->dispatchToFcm($token, $title, $body, $data)) { + $successCount++; + } + } + + return $successCount > 0; + } + + /** + * Dispatch notification to Firebase via HTTP v1 API + */ + private function dispatchToFcm(string $token, string $title, string $body, array $data): bool + { + if (!file_exists($this->serviceAccountPath)) { + error_log("[NotificationService] Firebase service account file missing: {$this->serviceAccountPath}"); + return false; + } + + $accessToken = $this->getAccessToken(); + if (!$accessToken) return false; + + $url = "https://fcm.googleapis.com/v1/projects/{$this->projectId}/messages:send"; + + $payload = [ + 'message' => [ + 'token' => $token, + 'notification' => [ + 'title' => $title, + 'body' => $body, + ], + 'data' => array_map('strval', $data), // FCM data values must be strings + 'android' => [ + 'priority' => 'high', + 'notification' => [ + 'sound' => 'default', + 'channel_id' => 'high_importance_channel' + ] + ], + 'apns' => [ + 'payload' => [ + 'aps' => [ + 'sound' => 'default', + ], + ], + ], + ], + ]; + + $ch = curl_init(); + curl_setopt($ch, CURLOPT_URL, $url); + curl_setopt($ch, CURLOPT_POST, true); + curl_setopt($ch, CURLOPT_HTTPHEADER, [ + 'Authorization: Bearer ' . $accessToken, + 'Content-Type: application/json', + ]); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); + curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($payload)); + + $response = curl_exec($ch); + $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE); + curl_close($ch); + + if ($httpCode !== 200) { + error_log("[NotificationService] FCM Send Error ($httpCode): " . $response); + return false; + } + + return true; + } + + /** + * Get OAuth2 Access Token for Firebase (Cache this in production!) + * Note: This requires a JWT library or manual signing. + * For simplicity, we assume the user might use a Google Auth library. + * But since we avoid extra deps, I will provide a minimal implementation or suggestion. + */ + private function getAccessToken(): ?string + { + // This is a complex part that usually requires 'google/auth' library. + // For now, I will return null and tell the user they need to install google/auth via composer + // OR I can write a minimal JWT signer for Google Auth if they don't want composer. + error_log("[NotificationService] OAuth2 Token generation needs google/auth library."); + return null; + } +} diff --git a/musadaq-app/android/app/build.gradle.kts b/musadaq-app/android/app/build.gradle.kts index cb4b518..f68abdc 100644 --- a/musadaq-app/android/app/build.gradle.kts +++ b/musadaq-app/android/app/build.gradle.kts @@ -1,5 +1,8 @@ plugins { id("com.android.application") + // START: FlutterFire Configuration + id("com.google.gms.google-services") + // END: FlutterFire Configuration id("kotlin-android") // The Flutter Gradle Plugin must be applied after the Android and Kotlin Gradle plugins. id("dev.flutter.flutter-gradle-plugin") diff --git a/musadaq-app/android/app/google-services.json b/musadaq-app/android/app/google-services.json new file mode 100644 index 0000000..8f590dc --- /dev/null +++ b/musadaq-app/android/app/google-services.json @@ -0,0 +1,29 @@ +{ + "project_info": { + "project_number": "512384487867", + "project_id": "musadaq-c12ca", + "storage_bucket": "musadaq-c12ca.firebasestorage.app" + }, + "client": [ + { + "client_info": { + "mobilesdk_app_id": "1:512384487867:android:eac271c0b0ea64b708749e", + "android_client_info": { + "package_name": "com.example.musadaq_app" + } + }, + "oauth_client": [], + "api_key": [ + { + "current_key": "AIzaSyB7Gc_RNvFaFCsuN5acHK2SNkY5iMDecqk" + } + ], + "services": { + "appinvite_service": { + "other_platform_oauth_client": [] + } + } + } + ], + "configuration_version": "1" +} \ No newline at end of file diff --git a/musadaq-app/android/settings.gradle.kts b/musadaq-app/android/settings.gradle.kts index ab39a10..bd7522f 100644 --- a/musadaq-app/android/settings.gradle.kts +++ b/musadaq-app/android/settings.gradle.kts @@ -19,6 +19,9 @@ pluginManagement { plugins { id("dev.flutter.flutter-plugin-loader") version "1.0.0" id("com.android.application") version "8.7.3" apply false + // START: FlutterFire Configuration + id("com.google.gms.google-services") version("4.3.15") apply false + // END: FlutterFire Configuration id("org.jetbrains.kotlin.android") version "2.1.0" apply false } diff --git a/musadaq-app/firebase.json b/musadaq-app/firebase.json new file mode 100644 index 0000000..00ae318 --- /dev/null +++ b/musadaq-app/firebase.json @@ -0,0 +1 @@ +{"flutter":{"platforms":{"android":{"default":{"projectId":"musadaq-c12ca","appId":"1:512384487867:android:eac271c0b0ea64b708749e","fileOutput":"android/app/google-services.json"}},"ios":{"default":{"projectId":"musadaq-c12ca","appId":"1:512384487867:ios:03bd28c6088a4aa008749e","uploadDebugSymbols":false,"fileOutput":"ios/Runner/GoogleService-Info.plist"}},"dart":{"lib/firebase_options.dart":{"projectId":"musadaq-c12ca","configurations":{"android":"1:512384487867:android:eac271c0b0ea64b708749e","ios":"1:512384487867:ios:03bd28c6088a4aa008749e"}}}}}} \ No newline at end of file diff --git a/musadaq-app/firepit-log.txt b/musadaq-app/firepit-log.txt new file mode 100644 index 0000000..ed32da9 --- /dev/null +++ b/musadaq-app/firepit-log.txt @@ -0,0 +1,17 @@ +Welcome to firepit v1.1.0! +Doing JSON parses for version checks at /snapshot/firepit/vendor/node_modules/firebase-tools/package.json +is-ci,jsdoc,mime,rc,rimraf,yaml,abbrev,abort-controller,accepts,acorn,acorn-jsx,agent-base,aggregate-error,ajv,ajv-formats,ansi-align,ansi-escapes,ansi-regex,ansi-styles,ansicolors,anymatch,archiver,archiver-utils,argparse,array-flatten,arrify,as-array,ast-types,async,async-lock,asynckit,balanced-match,base64-js,basic-auth,basic-auth-connect,basic-ftp,bignumber.js,binary-extensions,bl,bluebird,body-parser,boxen,brace-expansion,braces,buffer,buffer-crc32,buffer-equal-constant-time,bytes,cacache,call-bind,call-me-maybe,camelcase,cardinal,catharsis,chalk,chardet,chokidar,chownr,ci-info,cjson,clean-stack,cli-boxes,cli-cursor,cli-spinners,cli-table,cli-table3,cli-width,cliui,clone,color,color-convert,color-name,color-string,colorette,colors,colorspace,combined-stream,commander,compress-commons,compressible,compression,concat-map,config-chain,configstore,connect,content-disposition,content-type,cookie,cookie-signature,core-util-is,cors,crc-32,crc32-stream,cross-env,cross-spawn,crypto-random-string,csv-parse,data-uri-to-buffer,debug,deep-equal-in-any-order,deep-extend,deep-freeze,deep-is,defaults,define-data-property,degenerator,delayed-stream,depd,destroy,discontinuous-range,dot-prop,duplexify,eastasianwidth,ecdsa-sig-formatter,ee-first,emoji-regex,enabled,encodeurl,encoding,end-of-stream,entities,env-paths,err-code,es-define-property,es-errors,escalade,escape-goat,escape-html,escape-string-regexp,escodegen,eslint-visitor-keys,espree,esprima,estraverse,esutils,etag,event-target-shim,events-listener,exegesis,exegesis-express,exponential-backoff,express,extend,external-editor,fast-deep-equal,fast-json-stable-stringify,fast-levenshtein,fast-text-encoding,fast-url-parser,fecha,figures,filesize,fill-range,finalhandler,firebase-tools,fn.name,foreground-child,form-data,forwarded,fresh,fs-constants,fs-extra,fs-minipass,fs.realpath,function-bind,fuzzy,gaxios,gcp-metadata,get-caller-file,get-intrinsic,get-stdin,get-uri,glob,glob-parent,glob-slash,glob-slasher,global-dirs,google-auth-library,google-gax,google-p12-pem,googleapis-common,gopd,graceful-fs,gtoken,has-flag,has-property-descriptors,has-proto,has-symbols,has-yarn,hasown,heap-js,http-cache-semantics,http-errors,http-proxy-agent,https-proxy-agent,iconv-lite,ieee754,import-lazy,imurmurhash,indent-string,inflight,inherits,ini,inquirer,inquirer-autocomplete-prompt,install-artifact-from-github,ip-address,ip-regex,ipaddr.js,is-arrayish,is-binary-path,is-extglob,is-fullwidth-code-point,is-glob,is-installed-globally,is-interactive,is-lambda,is-npm,is-number,is-obj,is-path-inside,is-stream,is-stream-ended,is-typedarray,is-unicode-supported,is-url,is-wsl,is-yarn-global,is2,isarray,isexe,isomorphic-fetch,jackspeak,jju,join-path,js-yaml,js2xmlparser,jsbn,json-bigint,json-parse-helpfulerror,json-ptr,json-schema-traverse,jsonfile,jsonwebtoken,jwa,jws,klaw,kuler,lazystream,leven,levn,libsodium,libsodium-wrappers,linkify-it,lodash,lodash._objecttypes,lodash.camelcase,lodash.defaults,lodash.difference,lodash.flatten,lodash.includes,lodash.isboolean,lodash.isinteger,lodash.isnumber,lodash.isobject,lodash.isplainobject,lodash.isstring,lodash.mapvalues,lodash.once,lodash.snakecase,lodash.union,log-symbols,logform,long,lru-cache,make-dir,make-fetch-happen,markdown-it,markdown-it-anchor,marked,marked-terminal,mdurl,media-typer,merge-descriptors,methods,mime-db,mime-types,mimic-fn,minimatch,minimist,minipass,minipass-collect,minipass-fetch,minipass-flush,minipass-pipeline,minipass-sized,minizlib,mkdirp,moo,morgan,ms,mute-stream,nan,nearley,negotiator,netmask,nice-try,node-emoji,node-fetch,node-forge,node-gyp,nopt,normalize-path,object-assign,object-hash,object-inspect,on-finished,on-headers,once,one-time,onetime,open,openapi3-ts,optionator,ora,os-tmpdir,p-defer,p-limit,p-map,p-throttle,pac-proxy-agent,pac-resolver,parseurl,path-is-absolute,path-key,path-scurry,path-to-regexp,pg,pg-cloudflare,pg-connection-string,pg-int8,pg-pool,pg-protocol,pg-types,pgpass,picocolors,picomatch,portfinder,postgres-array,postgres-bytea,postgres-date,postgres-interval,prelude-ls,proc-log,process-nextick-args,progress,promise-breaker,promise-retry,proto-list,proto3-json-serializer,protobufjs,protobufjs-cli,proxy-addr,proxy-agent,proxy-from-env,pump,punycode,pupa,qs,railroad-diagrams,randexp,range-parser,raw-body,re2,readable-stream,readdir-glob,readdirp,redeyed,registry-auth-token,registry-url,require-directory,require-from-string,requizzle,restore-cursor,ret,retry,retry-request,router,run-async,rxjs,safe-buffer,safe-stable-stringify,safer-buffer,semver,semver-diff,send,serve-static,set-function-length,setprototypeof,shebang-command,shebang-regex,side-channel,signal-exit,simple-swizzle,smart-buffer,socks,socks-proxy-agent,sort-any,source-map,split2,sprintf-js,sql-formatter,ssri,stack-trace,statuses,stream-chain,stream-json,stream-shift,string-width,string-width-cjs,string_decoder,strip-ansi,strip-ansi-cjs,strip-json-comments,superstatic,supports-color,supports-hyperlinks,tar,tar-stream,tcp-port-used,text-hex,through,tmp,to-regex-range,toidentifier,toxic,tr46,triple-beam,tslib,type-check,type-fest,type-is,typedarray-to-buffer,uc.micro,uglify-js,underscore,undici-types,unique-filename,unique-slug,unique-string,universal-analytics,universalify,unpipe,update-notifier-cjs,uri-js,url-join,url-template,util-deprecate,utils-merge,uuid,valid-url,vary,wcwidth,webidl-conversions,whatwg-fetch,whatwg-url,which,widest-line,winston,winston-transport,word-wrap,wrap-ansi,wrap-ansi-cjs,wrappy,write-file-atomic,ws,xdg-basedir,xmlcreate,xtend,y18n,yallist,yargs,yargs-parser,yocto-queue,zip-stream,@apidevtools,@babel,@colors,@dabh,@google-cloud,@googleapis,@grpc,@isaacs,@jsdevtools,@jsdoc,@npmcli,@opentelemetry,@pkgjs,@pnpm,@protobufjs,@tootallnate,@types +No existing firebase-tools install found. +Installed ft@none and packaged ft@13.8.0 +Legacy firepit / firebase-tools detected, clearing it out... + +Checking for npm/bin/npm-cli install at /Users/hamzaaleghwairyeen/.cache/firebase/tools/lib/node_modules/npm/bin/npm-cli +Checking for npm/bin/npm-cli install at /Users/hamzaaleghwairyeen/.cache/firebase/tools/node_modules/npm/bin/npm-cli +Checking for npm/bin/npm-cli install at /snapshot/firepit/node_modules/npm/bin/npm-cli +Found npm/bin/npm-cli install. +Checking for npm/bin/npm-cli install at /Users/hamzaaleghwairyeen/.cache/firebase/tools/lib/node_modules/npm/bin/npm-cli +Checking for npm/bin/npm-cli install at /Users/hamzaaleghwairyeen/.cache/firebase/tools/node_modules/npm/bin/npm-cli +Checking for npm/bin/npm-cli install at /snapshot/firepit/node_modules/npm/bin/npm-cli +Found npm/bin/npm-cli install. +ShellJSInternalError: EEXIST: file already exists, mkdir '/Users/hamzaaleghwairyeen/.cache/firebase/runtime' +Error: EPERM: operation not permitted, open '/Users/hamzaaleghwairyeen/.cache/firebase/runtime/shell' \ No newline at end of file diff --git a/musadaq-app/ios/Runner.xcodeproj/project.pbxproj b/musadaq-app/ios/Runner.xcodeproj/project.pbxproj index 8517b3c..64e4d6b 100644 --- a/musadaq-app/ios/Runner.xcodeproj/project.pbxproj +++ b/musadaq-app/ios/Runner.xcodeproj/project.pbxproj @@ -11,6 +11,7 @@ 331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 331C807B294A618700263BE5 /* RunnerTests.swift */; }; 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; }; + 84FA7226BA669CC504D14C8E /* GoogleService-Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = 01F4F223F169A9E6C26FA35C /* GoogleService-Info.plist */; }; 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; }; 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; }; @@ -40,6 +41,7 @@ /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ + 01F4F223F169A9E6C26FA35C /* GoogleService-Info.plist */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.plist.xml; name = "GoogleService-Info.plist"; path = "Runner/GoogleService-Info.plist"; sourceTree = ""; }; 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = ""; }; 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; }; 331C807B294A618700263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = ""; }; @@ -94,6 +96,7 @@ 97C146F01CF9000F007C117D /* Runner */, 97C146EF1CF9000F007C117D /* Products */, 331C8082294A63A400263BE5 /* RunnerTests */, + 01F4F223F169A9E6C26FA35C /* GoogleService-Info.plist */, ); sourceTree = ""; }; @@ -216,6 +219,7 @@ 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */, 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */, 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */, + 84FA7226BA669CC504D14C8E /* GoogleService-Info.plist in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/musadaq-app/ios/Runner/GoogleService-Info.plist b/musadaq-app/ios/Runner/GoogleService-Info.plist new file mode 100644 index 0000000..2a474be --- /dev/null +++ b/musadaq-app/ios/Runner/GoogleService-Info.plist @@ -0,0 +1,30 @@ + + + + + API_KEY + AIzaSyBLKc35OqzY6oQA5507E2uHCCHQbRWAC_M + GCM_SENDER_ID + 512384487867 + PLIST_VERSION + 1 + BUNDLE_ID + com.example.musadaqApp + PROJECT_ID + musadaq-c12ca + STORAGE_BUCKET + musadaq-c12ca.firebasestorage.app + IS_ADS_ENABLED + + IS_ANALYTICS_ENABLED + + IS_APPINVITE_ENABLED + + IS_GCM_ENABLED + + IS_SIGNIN_ENABLED + + GOOGLE_APP_ID + 1:512384487867:ios:03bd28c6088a4aa008749e + + \ No newline at end of file diff --git a/musadaq-app/lib/core/services/push_notification_service.dart b/musadaq-app/lib/core/services/push_notification_service.dart new file mode 100644 index 0000000..19eb707 --- /dev/null +++ b/musadaq-app/lib/core/services/push_notification_service.dart @@ -0,0 +1,34 @@ +import 'package:firebase_messaging/firebase_messaging.dart'; +import '../utils/logger.dart'; + +class PushNotificationService { + static final FirebaseMessaging _fcm = FirebaseMessaging.instance; + + static Future initialize() async { + // 1. Request permissions (iOS) + NotificationSettings settings = await _fcm.requestPermission( + alert: true, + badge: true, + sound: true, + ); + + AppLogger.print('User granted permission: ${settings.authorizationStatus}'); + + // 2. Handle foreground messages + FirebaseMessaging.onMessage.listen((RemoteMessage message) { + AppLogger.print('Received foreground message: ${message.notification?.title}'); + // You can show a local notification here if needed + }); + } + + static Future getToken() async { + try { + String? token = await _fcm.getToken(); + AppLogger.print('FCM Token: $token'); + return token; + } catch (e) { + AppLogger.error('Failed to get FCM token', e); + return null; + } + } +} diff --git a/musadaq-app/lib/features/auth/controllers/auth_controller.dart b/musadaq-app/lib/features/auth/controllers/auth_controller.dart index 7bdb7d7..4b56baa 100644 --- a/musadaq-app/lib/features/auth/controllers/auth_controller.dart +++ b/musadaq-app/lib/features/auth/controllers/auth_controller.dart @@ -7,6 +7,7 @@ import '../../../core/storage/secure_storage.dart'; import '../../../app/routes/app_pages.dart'; import '../../../core/utils/logger.dart'; import '../../../core/utils/app_snackbar.dart'; +import '../../../core/services/push_notification_service.dart'; class AuthController extends GetxController { final Dio _dio = DioClient().client; @@ -56,13 +57,17 @@ class AuthController extends GetxController { deviceName = iosInfo.name; } + // Get FCM token for notifications + final pushToken = await PushNotificationService.getToken(); + final response = await _dio.post('auth/mobile/verify-otp', data: { 'phone': phone.value, 'otp': otp, 'device_id': deviceId, 'device_name': deviceName, 'platform': Platform.operatingSystem, - 'app_version': '1.0.0', // TODO: Get from package_info_plus + 'app_version': '1.0.0', + 'push_token': pushToken, }); if (response.statusCode == 200) { diff --git a/musadaq-app/lib/firebase_options.dart b/musadaq-app/lib/firebase_options.dart new file mode 100644 index 0000000..cc26806 --- /dev/null +++ b/musadaq-app/lib/firebase_options.dart @@ -0,0 +1,68 @@ +// File generated by FlutterFire CLI. +// ignore_for_file: type=lint +import 'package:firebase_core/firebase_core.dart' show FirebaseOptions; +import 'package:flutter/foundation.dart' + show defaultTargetPlatform, kIsWeb, TargetPlatform; + +/// Default [FirebaseOptions] for use with your Firebase apps. +/// +/// Example: +/// ```dart +/// import 'firebase_options.dart'; +/// // ... +/// await Firebase.initializeApp( +/// options: DefaultFirebaseOptions.currentPlatform, +/// ); +/// ``` +class DefaultFirebaseOptions { + static FirebaseOptions get currentPlatform { + if (kIsWeb) { + throw UnsupportedError( + 'DefaultFirebaseOptions have not been configured for web - ' + 'you can reconfigure this by running the FlutterFire CLI again.', + ); + } + switch (defaultTargetPlatform) { + case TargetPlatform.android: + return android; + case TargetPlatform.iOS: + return ios; + case TargetPlatform.macOS: + throw UnsupportedError( + 'DefaultFirebaseOptions have not been configured for macos - ' + 'you can reconfigure this by running the FlutterFire CLI again.', + ); + case TargetPlatform.windows: + throw UnsupportedError( + 'DefaultFirebaseOptions have not been configured for windows - ' + 'you can reconfigure this by running the FlutterFire CLI again.', + ); + case TargetPlatform.linux: + throw UnsupportedError( + 'DefaultFirebaseOptions have not been configured for linux - ' + 'you can reconfigure this by running the FlutterFire CLI again.', + ); + default: + throw UnsupportedError( + 'DefaultFirebaseOptions are not supported for this platform.', + ); + } + } + + static const FirebaseOptions android = FirebaseOptions( + apiKey: 'AIzaSyB7Gc_RNvFaFCsuN5acHK2SNkY5iMDecqk', + appId: '1:512384487867:android:eac271c0b0ea64b708749e', + messagingSenderId: '512384487867', + projectId: 'musadaq-c12ca', + storageBucket: 'musadaq-c12ca.firebasestorage.app', + ); + + static const FirebaseOptions ios = FirebaseOptions( + apiKey: 'AIzaSyBLKc35OqzY6oQA5507E2uHCCHQbRWAC_M', + appId: '1:512384487867:ios:03bd28c6088a4aa008749e', + messagingSenderId: '512384487867', + projectId: 'musadaq-c12ca', + storageBucket: 'musadaq-c12ca.firebasestorage.app', + iosBundleId: 'com.example.musadaqApp', + ); +} diff --git a/musadaq-app/lib/main.dart b/musadaq-app/lib/main.dart index 7b35a54..e43a37f 100644 --- a/musadaq-app/lib/main.dart +++ b/musadaq-app/lib/main.dart @@ -1,12 +1,26 @@ +import 'package:firebase_messaging/firebase_messaging.dart'; import 'package:flutter/material.dart'; import 'package:get/get.dart'; +import 'package:firebase_core/firebase_core.dart'; import 'app/routes/app_pages.dart'; +import 'core/services/push_notification_service.dart'; import 'app/theme/app_theme.dart'; +@pragma('vm:entry-point') +Future _firebaseMessagingBackgroundHandler(RemoteMessage message) async { + await Firebase.initializeApp(); + debugPrint("Handling a background message: ${message.messageId}"); +} + void main() async { WidgetsFlutterBinding.ensureInitialized(); - - // TODO: Initialize ObjectBox, SecureStorage, and DioClient here + + // 1. Initialize Firebase & Notifications + await Firebase.initializeApp(); + await PushNotificationService.initialize(); + + // 2. Register background handler + FirebaseMessaging.onBackgroundMessage(_firebaseMessagingBackgroundHandler); runApp(const MusadaqApp()); } diff --git a/musadaq-app/pubspec.lock b/musadaq-app/pubspec.lock index 8f39aba..d61f199 100644 --- a/musadaq-app/pubspec.lock +++ b/musadaq-app/pubspec.lock @@ -9,6 +9,14 @@ packages: url: "https://pub.dev" source: hosted version: "88.0.0" + _flutterfire_internals: + dependency: transitive + description: + name: _flutterfire_internals + sha256: bda3b7b55958bfd867addc40d067b4b11f7b8846d57671f5b5a6e7f9a56fe3ad + url: "https://pub.dev" + source: hosted + version: "1.3.69" analyzer: dependency: transitive description: @@ -369,6 +377,54 @@ packages: url: "https://pub.dev" source: hosted version: "0.9.3+5" + firebase_core: + dependency: "direct main" + description: + name: firebase_core + sha256: d5a94b884dcb1e6d3430298e94bfe002238094cdfd5e29202d536ee2120f9158 + url: "https://pub.dev" + source: hosted + version: "4.7.0" + firebase_core_platform_interface: + dependency: transitive + description: + name: firebase_core_platform_interface + sha256: "0ecda14c1bfc9ed8cac303dd0f8d04a320811b479362a9a4efb14fd331a473ce" + url: "https://pub.dev" + source: hosted + version: "6.0.3" + firebase_core_web: + dependency: transitive + description: + name: firebase_core_web + sha256: dc5096257cd67292d34d78ceeb90836f02a4be921b5f3934311a02bb2376118c + url: "https://pub.dev" + source: hosted + version: "3.6.0" + firebase_messaging: + dependency: "direct main" + description: + name: firebase_messaging + sha256: e5c93e8e7a9b0513f94bb684d2cf100e32e7dcdf2949574386b1955fc9a9b96a + url: "https://pub.dev" + source: hosted + version: "16.2.0" + firebase_messaging_platform_interface: + dependency: transitive + description: + name: firebase_messaging_platform_interface + sha256: "8cbb7d842e5071bba836452aff262f7db4b14bb3a0d00c1896cf176df886d65a" + url: "https://pub.dev" + source: hosted + version: "4.7.9" + firebase_messaging_web: + dependency: transitive + description: + name: firebase_messaging_web + sha256: "8750bacf50573c0383535fc3f9c58c6a2f9dff5320a16a82c30631b9dad894f1" + url: "https://pub.dev" + source: hosted + version: "4.1.5" fixnum: dependency: transitive description: diff --git a/musadaq-app/pubspec.yaml b/musadaq-app/pubspec.yaml index 896ddac..7068e64 100644 --- a/musadaq-app/pubspec.yaml +++ b/musadaq-app/pubspec.yaml @@ -57,6 +57,8 @@ dependencies: uuid: ^4.3.3 intl: ^0.19.0 package_info_plus: ^8.0.0 + firebase_core: ^4.7.0 + firebase_messaging: ^16.2.0 dev_dependencies: flutter_test: