chore(notifications): fix conflig when show notification in android
This commit is contained in:
@@ -3,6 +3,8 @@ PODS:
|
|||||||
- Flutter
|
- Flutter
|
||||||
- app_settings (5.1.1):
|
- app_settings (5.1.1):
|
||||||
- Flutter
|
- Flutter
|
||||||
|
- connectivity_plus (0.0.1):
|
||||||
|
- Flutter
|
||||||
- Firebase/CoreOnly (11.10.0):
|
- Firebase/CoreOnly (11.10.0):
|
||||||
- FirebaseCore (~> 11.10.0)
|
- FirebaseCore (~> 11.10.0)
|
||||||
- Firebase/Messaging (11.10.0):
|
- Firebase/Messaging (11.10.0):
|
||||||
@@ -102,6 +104,7 @@ PODS:
|
|||||||
DEPENDENCIES:
|
DEPENDENCIES:
|
||||||
- alarm (from `.symlinks/plugins/alarm/ios`)
|
- alarm (from `.symlinks/plugins/alarm/ios`)
|
||||||
- app_settings (from `.symlinks/plugins/app_settings/ios`)
|
- app_settings (from `.symlinks/plugins/app_settings/ios`)
|
||||||
|
- connectivity_plus (from `.symlinks/plugins/connectivity_plus/ios`)
|
||||||
- firebase_core (from `.symlinks/plugins/firebase_core/ios`)
|
- firebase_core (from `.symlinks/plugins/firebase_core/ios`)
|
||||||
- firebase_messaging (from `.symlinks/plugins/firebase_messaging/ios`)
|
- firebase_messaging (from `.symlinks/plugins/firebase_messaging/ios`)
|
||||||
- Flutter (from `Flutter`)
|
- Flutter (from `Flutter`)
|
||||||
@@ -134,6 +137,8 @@ EXTERNAL SOURCES:
|
|||||||
:path: ".symlinks/plugins/alarm/ios"
|
:path: ".symlinks/plugins/alarm/ios"
|
||||||
app_settings:
|
app_settings:
|
||||||
:path: ".symlinks/plugins/app_settings/ios"
|
:path: ".symlinks/plugins/app_settings/ios"
|
||||||
|
connectivity_plus:
|
||||||
|
:path: ".symlinks/plugins/connectivity_plus/ios"
|
||||||
firebase_core:
|
firebase_core:
|
||||||
:path: ".symlinks/plugins/firebase_core/ios"
|
:path: ".symlinks/plugins/firebase_core/ios"
|
||||||
firebase_messaging:
|
firebase_messaging:
|
||||||
@@ -162,6 +167,7 @@ EXTERNAL SOURCES:
|
|||||||
SPEC CHECKSUMS:
|
SPEC CHECKSUMS:
|
||||||
alarm: 9ff6d2dae9bd69c4022622f7e0a1e9c1dd70064e
|
alarm: 9ff6d2dae9bd69c4022622f7e0a1e9c1dd70064e
|
||||||
app_settings: 5127ae0678de1dcc19f2293271c51d37c89428b2
|
app_settings: 5127ae0678de1dcc19f2293271c51d37c89428b2
|
||||||
|
connectivity_plus: cb623214f4e1f6ef8fe7403d580fdad517d2f7dd
|
||||||
Firebase: 1fe1c0a7d9aaea32efe01fbea5f0ebd8d70e53a2
|
Firebase: 1fe1c0a7d9aaea32efe01fbea5f0ebd8d70e53a2
|
||||||
firebase_core: 2d4534e7b489907dcede540c835b48981d890943
|
firebase_core: 2d4534e7b489907dcede540c835b48981d890943
|
||||||
firebase_messaging: 75bc93a4df25faccad67f6662ae872ac9ae69b64
|
firebase_messaging: 75bc93a4df25faccad67f6662ae872ac9ae69b64
|
||||||
|
|||||||
@@ -17,6 +17,7 @@
|
|||||||
97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; };
|
97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; };
|
||||||
97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; };
|
97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; };
|
||||||
97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; };
|
97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; };
|
||||||
|
A241E2BF2EAA2F1C00664284 /* warning_alarm.caf in Resources */ = {isa = PBXBuildFile; fileRef = A241E2BE2EAA2F1C00664284 /* warning_alarm.caf */; };
|
||||||
/* End PBXBuildFile section */
|
/* End PBXBuildFile section */
|
||||||
|
|
||||||
/* Begin PBXContainerItemProxy section */
|
/* Begin PBXContainerItemProxy section */
|
||||||
@@ -64,6 +65,7 @@
|
|||||||
97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
|
97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
|
||||||
97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = "<group>"; };
|
97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = "<group>"; };
|
||||||
97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||||
|
A241E2BE2EAA2F1C00664284 /* warning_alarm.caf */ = {isa = PBXFileReference; lastKnownFileType = file; path = warning_alarm.caf; sourceTree = "<group>"; };
|
||||||
/* End PBXFileReference section */
|
/* End PBXFileReference section */
|
||||||
|
|
||||||
/* Begin PBXFrameworksBuildPhase section */
|
/* Begin PBXFrameworksBuildPhase section */
|
||||||
@@ -109,6 +111,7 @@
|
|||||||
97C146E51CF9000F007C117D = {
|
97C146E51CF9000F007C117D = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
|
A241E2BE2EAA2F1C00664284 /* warning_alarm.caf */,
|
||||||
9740EEB11CF90186004384FC /* Flutter */,
|
9740EEB11CF90186004384FC /* Flutter */,
|
||||||
97C146F01CF9000F007C117D /* Runner */,
|
97C146F01CF9000F007C117D /* Runner */,
|
||||||
97C146EF1CF9000F007C117D /* Products */,
|
97C146EF1CF9000F007C117D /* Products */,
|
||||||
@@ -256,6 +259,7 @@
|
|||||||
97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */,
|
97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */,
|
||||||
3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */,
|
3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */,
|
||||||
5EFD34A62DA7D89800351DB2 /* GoogleService-Info.plist in Resources */,
|
5EFD34A62DA7D89800351DB2 /* GoogleService-Info.plist in Resources */,
|
||||||
|
A241E2BF2EAA2F1C00664284 /* warning_alarm.caf in Resources */,
|
||||||
97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */,
|
97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */,
|
||||||
97C146FC1CF9000F007C117D /* Main.storyboard in Resources */,
|
97C146FC1CF9000F007C117D /* Main.storyboard in Resources */,
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,8 +1,10 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="10117" systemVersion="15F34" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" initialViewController="BYZ-38-t0r">
|
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="23727" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" colorMatched="YES" initialViewController="BYZ-38-t0r">
|
||||||
|
<device id="retina6_12" orientation="portrait" appearance="light"/>
|
||||||
<dependencies>
|
<dependencies>
|
||||||
<deployment identifier="iOS"/>
|
<deployment identifier="iOS"/>
|
||||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="10085"/>
|
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="23721"/>
|
||||||
|
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
<scenes>
|
<scenes>
|
||||||
<!--Flutter View Controller-->
|
<!--Flutter View Controller-->
|
||||||
@@ -14,13 +16,14 @@
|
|||||||
<viewControllerLayoutGuide type="bottom" id="wfy-db-euE"/>
|
<viewControllerLayoutGuide type="bottom" id="wfy-db-euE"/>
|
||||||
</layoutGuides>
|
</layoutGuides>
|
||||||
<view key="view" contentMode="scaleToFill" id="8bC-Xf-vdC">
|
<view key="view" contentMode="scaleToFill" id="8bC-Xf-vdC">
|
||||||
<rect key="frame" x="0.0" y="0.0" width="600" height="600"/>
|
<rect key="frame" x="0.0" y="0.0" width="393" height="852"/>
|
||||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||||
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="calibratedWhite"/>
|
<color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||||
</view>
|
</view>
|
||||||
</viewController>
|
</viewController>
|
||||||
<placeholder placeholderIdentifier="IBFirstResponder" id="dkx-z0-nzr" sceneMemberID="firstResponder"/>
|
<placeholder placeholderIdentifier="IBFirstResponder" id="dkx-z0-nzr" sceneMemberID="firstResponder"/>
|
||||||
</objects>
|
</objects>
|
||||||
|
<point key="canvasLocation" x="121" y="-34"/>
|
||||||
</scene>
|
</scene>
|
||||||
</scenes>
|
</scenes>
|
||||||
</document>
|
</document>
|
||||||
|
|||||||
BIN
ios/Runner/Sounds/warning_alarm.caf
Normal file
BIN
ios/Runner/Sounds/warning_alarm.caf
Normal file
Binary file not shown.
BIN
ios/warning_alarm.caf
Normal file
BIN
ios/warning_alarm.caf
Normal file
Binary file not shown.
@@ -1,5 +1,6 @@
|
|||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
import 'dart:developer';
|
import 'dart:developer';
|
||||||
|
import 'package:alarm/alarm.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
import '../../product/shared/shared_loading_animation.dart';
|
import '../../product/shared/shared_loading_animation.dart';
|
||||||
@@ -27,7 +28,6 @@ class _HomeScreenState extends State<HomeScreen> {
|
|||||||
bool isFunctionCall = false;
|
bool isFunctionCall = false;
|
||||||
Timer? getAllDevicesTimer;
|
Timer? getAllDevicesTimer;
|
||||||
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
@@ -35,14 +35,16 @@ class _HomeScreenState extends State<HomeScreen> {
|
|||||||
const duration = Duration(seconds: 10);
|
const duration = Duration(seconds: 10);
|
||||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||||
// Code ở đây chạy sau khi giao diện render xong
|
// Code ở đây chạy sau khi giao diện render xong
|
||||||
getAllDevicesTimer =
|
getAllDevicesTimer = Timer.periodic(
|
||||||
Timer.periodic(duration, (Timer t) => homeBloc.getOwnerAndJoinedDevices(context));
|
duration, (Timer t) => homeBloc.getOwnerAndJoinedDevices(context));
|
||||||
// Ví dụ: gọi API, scroll tới vị trí nào đó, v.v.
|
// Ví dụ: gọi API, scroll tới vị trí nào đó, v.v.
|
||||||
});
|
});
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<void> loadAlarms() async {
|
||||||
|
final alarms = await Alarm.getAlarms();
|
||||||
|
log("Alarms: $alarms");
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void dispose() {
|
void dispose() {
|
||||||
@@ -54,196 +56,229 @@ class _HomeScreenState extends State<HomeScreen> {
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return StreamBuilder<List<DeviceWithAlias>?>(
|
return StreamBuilder<List<DeviceWithAlias>?>(
|
||||||
stream: homeBloc.streamAliasDevices,
|
stream: homeBloc.streamAliasDevices,
|
||||||
builder: (context, aliasDevicesSnapshot) {
|
builder: (context, aliasDevicesSnapshot) {
|
||||||
if(aliasDevicesSnapshot.data == null){
|
if (aliasDevicesSnapshot.data == null) {
|
||||||
homeBloc.getOwnerAndJoinedDevices(context);
|
homeBloc.getOwnerAndJoinedDevices(context);
|
||||||
return const SharedLoadingAnimation();
|
return const SharedLoadingAnimation();
|
||||||
}else{
|
} else {
|
||||||
homeBloc.getOwnerDeviceState(context, aliasDevicesSnapshot.data ?? []);
|
homeBloc.getOwnerDeviceState(
|
||||||
homeBloc.getDeviceStatusAliasMap(aliasDevicesSnapshot.data ?? []);
|
context, aliasDevicesSnapshot.data ?? []);
|
||||||
checkSettingDevice(aliasDevicesSnapshot.data ?? []);
|
homeBloc.getDeviceStatusAliasMap(aliasDevicesSnapshot.data ?? []);
|
||||||
return Scaffold(
|
checkSettingDevice(aliasDevicesSnapshot.data ?? []);
|
||||||
body: Padding(
|
return Scaffold(
|
||||||
padding: context.paddingLow,
|
floatingActionButton: FloatingActionButton(
|
||||||
child: SingleChildScrollView(
|
onPressed: () {
|
||||||
child: Column(
|
loadAlarms();
|
||||||
children: <Widget>[
|
},
|
||||||
Row(
|
child: const Icon(Icons.alarm),
|
||||||
children: [
|
),
|
||||||
Text(
|
body: Padding(
|
||||||
appLocalization(context).notification,
|
padding: context.paddingLow,
|
||||||
style: context.titleMediumTextStyle,
|
child: SingleChildScrollView(
|
||||||
),
|
child: Column(
|
||||||
SizedBox(width: context.lowValue),
|
children: <Widget>[
|
||||||
StreamBuilder<int>(
|
Row(
|
||||||
stream: homeBloc.streamCountNotification,
|
children: [
|
||||||
builder: (context, countSnapshot) {
|
Text(
|
||||||
if(countSnapshot.data == null){
|
appLocalization(context).notification,
|
||||||
homeBloc.getOwnerDeviceState(context, aliasDevicesSnapshot.data ?? []);
|
style: context.titleMediumTextStyle,
|
||||||
return const Text("0");
|
),
|
||||||
} else{
|
SizedBox(width: context.lowValue),
|
||||||
return Text(
|
StreamBuilder<int>(
|
||||||
"(${countSnapshot.data ?? 0})",
|
stream: homeBloc.streamCountNotification,
|
||||||
style: context.titleMediumTextStyle,
|
builder: (context, countSnapshot) {
|
||||||
);
|
if (countSnapshot.data == null) {
|
||||||
}
|
homeBloc.getOwnerDeviceState(
|
||||||
},
|
context, aliasDevicesSnapshot.data ?? []);
|
||||||
)
|
return const Text("0");
|
||||||
],
|
} else {
|
||||||
),
|
return Text(
|
||||||
SizedBox(
|
"(${countSnapshot.data ?? 0})",
|
||||||
child: SingleChildScrollView(
|
style: context.titleMediumTextStyle,
|
||||||
scrollDirection: Axis.horizontal,
|
);
|
||||||
child: StreamBuilder<Map<String, List<DeviceWithAlias>>>(
|
}
|
||||||
stream: homeBloc.streamOwnerDevicesStatus,
|
},
|
||||||
builder: (context, ownerDevicesStatusSnapshot) {
|
)
|
||||||
if(ownerDevicesStatusSnapshot.data == null){
|
],
|
||||||
homeBloc.getOwnerDeviceState(context, aliasDevicesSnapshot.data ?? []);
|
),
|
||||||
return const SharedComponentLoadingAnimation();
|
SizedBox(
|
||||||
}else{
|
child: SingleChildScrollView(
|
||||||
return AnimatedSwitcher(
|
scrollDirection: Axis.horizontal,
|
||||||
duration: context.lowDuration,
|
child:
|
||||||
transitionBuilder:
|
StreamBuilder<Map<String, List<DeviceWithAlias>>>(
|
||||||
(Widget child, Animation<double> animation) {
|
stream: homeBloc.streamOwnerDevicesStatus,
|
||||||
final offsetAnimation = Tween<Offset>(
|
builder: (context, ownerDevicesStatusSnapshot) {
|
||||||
begin: const Offset(0.0, 0.2),
|
if (ownerDevicesStatusSnapshot.data == null) {
|
||||||
end: Offset.zero,
|
homeBloc.getOwnerDeviceState(
|
||||||
).animate(CurvedAnimation(
|
context, aliasDevicesSnapshot.data ?? []);
|
||||||
parent: animation,
|
return const SharedComponentLoadingAnimation();
|
||||||
curve: Curves.easeInOut,
|
} else {
|
||||||
));
|
return AnimatedSwitcher(
|
||||||
return FadeTransition(
|
duration: context.lowDuration,
|
||||||
opacity: animation,
|
transitionBuilder: (Widget child,
|
||||||
child: SlideTransition(
|
Animation<double> animation) {
|
||||||
position: offsetAnimation,
|
final offsetAnimation = Tween<Offset>(
|
||||||
child: child,
|
begin: const Offset(0.0, 0.2),
|
||||||
),
|
end: Offset.zero,
|
||||||
);
|
).animate(CurvedAnimation(
|
||||||
},
|
parent: animation,
|
||||||
child: ownerDevicesStatusSnapshot.data?['state'] != null ||
|
curve: Curves.easeInOut,
|
||||||
ownerDevicesStatusSnapshot.data?['battery'] != null
|
));
|
||||||
? ConstrainedBox(
|
return FadeTransition(
|
||||||
key: const ValueKey('data'),
|
opacity: animation,
|
||||||
constraints: BoxConstraints(
|
child: SlideTransition(
|
||||||
minWidth:
|
position: offsetAnimation,
|
||||||
MediaQuery.of(context).size.width),
|
child: child,
|
||||||
child: Row(
|
),
|
||||||
mainAxisAlignment: MainAxisAlignment.start,
|
);
|
||||||
children: [
|
},
|
||||||
if (ownerDevicesStatusSnapshot.data?['state'] != null)
|
child: ownerDevicesStatusSnapshot
|
||||||
...ownerDevicesStatusSnapshot.data!['state']!
|
.data?['state'] !=
|
||||||
.map(
|
null ||
|
||||||
(item) => SizedBox(
|
ownerDevicesStatusSnapshot
|
||||||
width: context.dynamicWidth(0.95),
|
.data?['battery'] !=
|
||||||
child: FutureBuilder<Widget>(
|
null
|
||||||
future: warningCard(
|
? ConstrainedBox(
|
||||||
context, apiServices, item),
|
key: const ValueKey('data'),
|
||||||
builder: (context,
|
constraints: BoxConstraints(
|
||||||
warningCardSnapshot) {
|
minWidth: MediaQuery.of(context)
|
||||||
if (warningCardSnapshot
|
.size
|
||||||
.hasData) {
|
.width),
|
||||||
return warningCardSnapshot
|
child: Row(
|
||||||
.data!;
|
mainAxisAlignment:
|
||||||
} else {
|
MainAxisAlignment.start,
|
||||||
return const SizedBox
|
children: [
|
||||||
.shrink();
|
if (ownerDevicesStatusSnapshot
|
||||||
}
|
.data?['state'] !=
|
||||||
},
|
null)
|
||||||
),
|
...ownerDevicesStatusSnapshot
|
||||||
|
.data!['state']!
|
||||||
|
.map(
|
||||||
|
(item) => SizedBox(
|
||||||
|
width: context
|
||||||
|
.dynamicWidth(0.95),
|
||||||
|
child: FutureBuilder<
|
||||||
|
Widget>(
|
||||||
|
future: warningCard(
|
||||||
|
context,
|
||||||
|
apiServices,
|
||||||
|
item),
|
||||||
|
builder: (context,
|
||||||
|
warningCardSnapshot) {
|
||||||
|
if (warningCardSnapshot
|
||||||
|
.hasData) {
|
||||||
|
return warningCardSnapshot
|
||||||
|
.data!;
|
||||||
|
} else {
|
||||||
|
return const SizedBox
|
||||||
|
.shrink();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.toList(),
|
||||||
|
if (ownerDevicesStatusSnapshot
|
||||||
|
.data?['battery'] !=
|
||||||
|
null)
|
||||||
|
...ownerDevicesStatusSnapshot
|
||||||
|
.data!['battery']!
|
||||||
|
.map(
|
||||||
|
(batteryItem) => SizedBox(
|
||||||
|
width: context
|
||||||
|
.dynamicWidth(0.95),
|
||||||
|
child: FutureBuilder<
|
||||||
|
Widget>(
|
||||||
|
future:
|
||||||
|
notificationCard(
|
||||||
|
context,
|
||||||
|
"lowBattery",
|
||||||
|
appLocalization(
|
||||||
|
context)
|
||||||
|
.low_battery_message,
|
||||||
|
batteryItem,
|
||||||
|
),
|
||||||
|
builder: (context,
|
||||||
|
warningCardSnapshot) {
|
||||||
|
if (warningCardSnapshot
|
||||||
|
.hasData) {
|
||||||
|
return warningCardSnapshot
|
||||||
|
.data!;
|
||||||
|
} else {
|
||||||
|
return const SizedBox
|
||||||
|
.shrink();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.toList(),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
.toList(),
|
: Padding(
|
||||||
if (ownerDevicesStatusSnapshot.data?['battery'] != null)
|
key: const ValueKey('no_data'),
|
||||||
...ownerDevicesStatusSnapshot.data!['battery']!
|
padding: context.paddingMedium,
|
||||||
.map(
|
child: Center(
|
||||||
(batteryItem) => SizedBox(
|
child: Row(
|
||||||
width: context.dynamicWidth(0.95),
|
mainAxisSize: MainAxisSize.min,
|
||||||
child: FutureBuilder<Widget>(
|
children: [
|
||||||
future: notificationCard(
|
const Icon(
|
||||||
context,
|
Icons
|
||||||
"lowBattery",
|
.check_circle_outline_rounded,
|
||||||
appLocalization(context)
|
size: 40,
|
||||||
.low_battery_message,
|
color: Colors.green,
|
||||||
batteryItem,
|
),
|
||||||
),
|
SizedBox(
|
||||||
builder: (context,
|
width: context.lowValue),
|
||||||
warningCardSnapshot) {
|
Text(
|
||||||
if (warningCardSnapshot
|
appLocalization(context)
|
||||||
.hasData) {
|
.notification_description,
|
||||||
return warningCardSnapshot
|
maxLines: 2,
|
||||||
.data!;
|
overflow:
|
||||||
} else {
|
TextOverflow.ellipsis,
|
||||||
return const SizedBox
|
softWrap: true,
|
||||||
.shrink();
|
textAlign: TextAlign.start,
|
||||||
}
|
),
|
||||||
},
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
)
|
|
||||||
.toList(),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
)
|
|
||||||
: Padding(
|
|
||||||
key: const ValueKey('no_data'),
|
|
||||||
padding: context.paddingMedium,
|
|
||||||
child: Center(
|
|
||||||
child: Row(
|
|
||||||
mainAxisSize: MainAxisSize.min,
|
|
||||||
children: [
|
|
||||||
const Icon(
|
|
||||||
Icons.check_circle_outline_rounded,
|
|
||||||
size: 40,
|
|
||||||
color: Colors.green,
|
|
||||||
),
|
),
|
||||||
SizedBox(width: context.lowValue),
|
);
|
||||||
Text(
|
}
|
||||||
appLocalization(context)
|
},
|
||||||
.notification_description,
|
),
|
||||||
maxLines: 2,
|
|
||||||
overflow: TextOverflow.ellipsis,
|
|
||||||
softWrap: true,
|
|
||||||
textAlign: TextAlign.start,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
StreamBuilder<Map<String, List<DeviceWithAlias>>?>(
|
||||||
StreamBuilder<Map<String, List<DeviceWithAlias>>?>(
|
stream: homeBloc.streamAllDevicesAliasMap,
|
||||||
stream: homeBloc.streamAllDevicesAliasMap,
|
builder: (context, allDevicesAliasMapSnapshot) {
|
||||||
builder: (context, allDevicesAliasMapSnapshot) {
|
if (allDevicesAliasMapSnapshot.data == null) {
|
||||||
if(allDevicesAliasMapSnapshot.data == null){
|
homeBloc.getDeviceStatusAliasMap(
|
||||||
homeBloc.getDeviceStatusAliasMap(aliasDevicesSnapshot.data ?? []);
|
aliasDevicesSnapshot.data ?? []);
|
||||||
return const SharedComponentLoadingAnimation();
|
return const SharedComponentLoadingAnimation();
|
||||||
}else{
|
} else {
|
||||||
final data = allDevicesAliasMapSnapshot.data!;
|
final data = allDevicesAliasMapSnapshot.data!;
|
||||||
return OverviewCard(
|
return OverviewCard(
|
||||||
isOwner: true,
|
isOwner: true,
|
||||||
total: data['all']?.length ?? 0,
|
total: data['all']?.length ?? 0,
|
||||||
active: data['online']?.length ?? 0,
|
active: data['online']?.length ?? 0,
|
||||||
inactive: data['offline']?.length ?? 0,
|
inactive: data['offline']?.length ?? 0,
|
||||||
warning: data['warn']?.length ?? 0,
|
warning: data['warn']?.length ?? 0,
|
||||||
unused: data['not-use']?.length ?? 0,
|
unused: data['not-use']?.length ?? 0,
|
||||||
showUnused: false,
|
showUnused: false,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
);
|
||||||
);
|
}
|
||||||
}
|
});
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void checkSettingDevice(List<DeviceWithAlias> devices) async {
|
void checkSettingDevice(List<DeviceWithAlias> devices) async {
|
||||||
|
|||||||
@@ -76,11 +76,11 @@ class _MainScreenState extends State<MainScreen> with WidgetsBindingObserver {
|
|||||||
mainBloc.sinkThemeMode.add(isLight);
|
mainBloc.sinkThemeMode.add(isLight);
|
||||||
checkAndRequestPermission();
|
checkAndRequestPermission();
|
||||||
NotificationServices.requestNotificationPermission();
|
NotificationServices.requestNotificationPermission();
|
||||||
NotificationPermission.instance.checkNotificationPermission(context);
|
// NotificationPermission.instance.checkNotificationPermission(context);
|
||||||
bool? notificationStatus = await NotificationPermission.instance.requestNotificationPermission();
|
// bool? notificationStatus = await NotificationPermission.instance.requestNotificationPermission();
|
||||||
if(!notificationStatus!){
|
// if(notificationStatus == false){
|
||||||
showNoIconTopSnackBar(context, "Yêu cầu quyền thông báo không thành công", Colors.orange, Colors.white12);
|
// showNoIconTopSnackBar(context, "Yêu cầu quyền thông báo không thành công", Colors.orange, Colors.white12);
|
||||||
}
|
// }
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
|
|
||||||
import 'package:alarm/alarm.dart';
|
import 'package:alarm/alarm.dart';
|
||||||
import 'package:firebase_core/firebase_core.dart';
|
import 'package:firebase_core/firebase_core.dart';
|
||||||
import 'package:firebase_messaging/firebase_messaging.dart';
|
import 'package:firebase_messaging/firebase_messaging.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:persistent_bottom_nav_bar_v2/persistent_bottom_nav_bar_v2.dart' show PersistentTabController;
|
import 'package:persistent_bottom_nav_bar_v2/persistent_bottom_nav_bar_v2.dart'
|
||||||
|
show PersistentTabController;
|
||||||
|
|
||||||
import 'firebase_options.dart';
|
import 'firebase_options.dart';
|
||||||
import 'product/lang/l10n/app_localizations.dart';
|
import 'product/lang/l10n/app_localizations.dart';
|
||||||
@@ -15,16 +15,17 @@ import 'bloc/main_bloc.dart';
|
|||||||
import 'product/base/bloc/base_bloc.dart';
|
import 'product/base/bloc/base_bloc.dart';
|
||||||
import 'product/constant/navigation/navigation_router.dart';
|
import 'product/constant/navigation/navigation_router.dart';
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
PersistentTabController controller = PersistentTabController(initialIndex: 0);
|
PersistentTabController controller = PersistentTabController(initialIndex: 0);
|
||||||
|
|
||||||
|
|
||||||
void main() async {
|
void main() async {
|
||||||
WidgetsFlutterBinding.ensureInitialized();
|
WidgetsFlutterBinding.ensureInitialized();
|
||||||
await Firebase.initializeApp(options: DefaultFirebaseOptions.currentPlatform, name: "sfm-notification");
|
await Firebase.initializeApp(
|
||||||
FirebaseMessaging.onBackgroundMessage(firebaseMessagingBackgroundHandler);
|
options: DefaultFirebaseOptions.currentPlatform,
|
||||||
|
name: "sfm-notification");
|
||||||
|
FirebaseMessaging.onBackgroundMessage(
|
||||||
|
NotificationServices.firebaseMessagingBackgroundHandler);
|
||||||
await Alarm.init();
|
await Alarm.init();
|
||||||
|
await Alarm.stopAll();
|
||||||
runApp(
|
runApp(
|
||||||
BlocProvider(
|
BlocProvider(
|
||||||
child: const MyApp(),
|
child: const MyApp(),
|
||||||
@@ -33,7 +34,6 @@ void main() async {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
class MyApp extends StatefulWidget {
|
class MyApp extends StatefulWidget {
|
||||||
const MyApp({super.key});
|
const MyApp({super.key});
|
||||||
|
|
||||||
@@ -92,19 +92,18 @@ class _MyAppState extends State<MyApp> {
|
|||||||
initialData: _locale,
|
initialData: _locale,
|
||||||
builder: (context, languageSnapshot) {
|
builder: (context, languageSnapshot) {
|
||||||
return StreamBuilder<ThemeData?>(
|
return StreamBuilder<ThemeData?>(
|
||||||
stream: mainBloc.streamTheme,
|
stream: mainBloc.streamTheme,
|
||||||
initialData: _themeData,
|
initialData: _themeData,
|
||||||
builder: (context, themeSnapshot) {
|
builder: (context, themeSnapshot) {
|
||||||
return MaterialApp.router(
|
return MaterialApp.router(
|
||||||
theme: themeSnapshot.data,
|
theme: themeSnapshot.data,
|
||||||
routerConfig: router,
|
routerConfig: router,
|
||||||
debugShowCheckedModeBanner: false,
|
debugShowCheckedModeBanner: false,
|
||||||
localizationsDelegates: AppLocalizations.localizationsDelegates,
|
localizationsDelegates: AppLocalizations.localizationsDelegates,
|
||||||
supportedLocales: AppLocalizations.supportedLocales,
|
supportedLocales: AppLocalizations.supportedLocales,
|
||||||
locale: languageSnapshot.data,
|
locale: languageSnapshot.data,
|
||||||
);
|
);
|
||||||
}
|
});
|
||||||
);
|
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
import 'package:alarm/alarm.dart';
|
import 'package:alarm/alarm.dart';
|
||||||
|
|
||||||
|
@pragma('vm:entry-point')
|
||||||
class AlarmServices {
|
class AlarmServices {
|
||||||
Future<void> showAlarm(String title, String body) async {
|
Future<void> showAlarm(String title, String body) async {
|
||||||
final DateTime now = DateTime.now();
|
final DateTime now = DateTime.now();
|
||||||
|
|||||||
@@ -2,6 +2,8 @@
|
|||||||
|
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
|
import 'dart:io';
|
||||||
|
import 'package:connectivity_plus/connectivity_plus.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:go_router/go_router.dart';
|
import 'package:go_router/go_router.dart';
|
||||||
import 'package:http/http.dart' as http;
|
import 'package:http/http.dart' as http;
|
||||||
@@ -27,8 +29,6 @@ import '../constant/enums/local_keys_enums.dart';
|
|||||||
import '../network/network_manager.dart';
|
import '../network/network_manager.dart';
|
||||||
|
|
||||||
class APIServices {
|
class APIServices {
|
||||||
|
|
||||||
|
|
||||||
Map<String, String> headers = {
|
Map<String, String> headers = {
|
||||||
'Accept': 'application/json',
|
'Accept': 'application/json',
|
||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
@@ -104,12 +104,33 @@ class APIServices {
|
|||||||
bool checkMounted = true,
|
bool checkMounted = true,
|
||||||
}) async {
|
}) async {
|
||||||
try {
|
try {
|
||||||
|
// Quick connectivity check before attempting network calls.
|
||||||
|
final conn = await Connectivity().checkConnectivity();
|
||||||
|
if (conn == ConnectivityResult.none) {
|
||||||
|
AppLoggerUtils.warning('No network connectivity');
|
||||||
|
if (checkMounted && context.mounted) {
|
||||||
|
showErrorTopSnackBarCustom(context, "Không có kết nối mạng");
|
||||||
|
}
|
||||||
|
return Future.error(const SocketException('No network connectivity'));
|
||||||
|
}
|
||||||
|
|
||||||
return await apiCall();
|
return await apiCall();
|
||||||
} catch (e) {
|
} on SocketException catch (e, stackTrace) {
|
||||||
|
// Network-related errors (DNS, timeout, host lookup...)
|
||||||
|
AppLoggerUtils.warning('Network error when calling API: $e');
|
||||||
|
if (checkMounted && context.mounted) {
|
||||||
|
AppLoggerUtils.error("Không có kết nối mạng");
|
||||||
|
}
|
||||||
|
return Future.error(e);
|
||||||
|
} catch (e, stackTrace) {
|
||||||
|
// If widget was unmounted (e.g. background isolate), preserve previous behavior
|
||||||
if (checkMounted && !context.mounted) {
|
if (checkMounted && !context.mounted) {
|
||||||
return Future.error('Widget not mounted');
|
return Future.error('Widget not mounted');
|
||||||
}
|
}
|
||||||
showErrorTopSnackBarCustom(context, "Lỗi hệ thống");
|
AppLoggerUtils.error("Lỗi hệ thống khi gọi API", e, stackTrace);
|
||||||
|
if (checkMounted && context.mounted) {
|
||||||
|
showErrorTopSnackBarCustom(context, "Lỗi hệ thống");
|
||||||
|
}
|
||||||
return Future.error(e);
|
return Future.error(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -308,7 +329,6 @@ class APIServices {
|
|||||||
.getDataFromServer(APIPathConstants.PROVINCES_PATH),
|
.getDataFromServer(APIPathConstants.PROVINCES_PATH),
|
||||||
parser: (json) => Province.fromJsonDynamicList(json['items']),
|
parser: (json) => Province.fromJsonDynamicList(json['items']),
|
||||||
errorMessage: 'Lỗi khi GET /${APIPathConstants.PROVINCES_PATH}');
|
errorMessage: 'Lỗi khi GET /${APIPathConstants.PROVINCES_PATH}');
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<List<Province>> getProvincesByName(String name) async {
|
Future<List<Province>> getProvincesByName(String name) async {
|
||||||
@@ -319,7 +339,6 @@ class APIServices {
|
|||||||
parser: (json) => Province.fromJsonDynamicList(json['items']),
|
parser: (json) => Province.fromJsonDynamicList(json['items']),
|
||||||
errorMessage: 'Lỗi khi GET /${APIPathConstants.PROVINCES_PATH}/$name',
|
errorMessage: 'Lỗi khi GET /${APIPathConstants.PROVINCES_PATH}/$name',
|
||||||
);
|
);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<Province> getProvinceByID(String provinceID) async {
|
Future<Province> getProvinceByID(String provinceID) async {
|
||||||
|
|||||||
@@ -4,20 +4,21 @@ import 'dart:math' as math;
|
|||||||
import 'package:alarm/alarm.dart';
|
import 'package:alarm/alarm.dart';
|
||||||
import 'package:firebase_core/firebase_core.dart';
|
import 'package:firebase_core/firebase_core.dart';
|
||||||
import 'package:firebase_messaging/firebase_messaging.dart'
|
import 'package:firebase_messaging/firebase_messaging.dart'
|
||||||
hide NotificationSettings;
|
as firebase_messaging;
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:persistent_bottom_nav_bar_v2/persistent_bottom_nav_bar_v2.dart';
|
import 'package:persistent_bottom_nav_bar_v2/persistent_bottom_nav_bar_v2.dart';
|
||||||
import 'package:flutter_local_notifications/flutter_local_notifications.dart';
|
import 'package:flutter_local_notifications/flutter_local_notifications.dart';
|
||||||
|
|
||||||
import '../utils/app_logger_utils.dart';
|
import '../utils/app_logger_utils.dart';
|
||||||
import '../../firebase_options.dart';
|
import '../../firebase_options.dart';
|
||||||
import '../../main.dart';
|
|
||||||
import 'alarm_services.dart';
|
import 'alarm_services.dart';
|
||||||
|
|
||||||
|
@pragma('vm:entry-point')
|
||||||
class NotificationServices {
|
class NotificationServices {
|
||||||
static final FlutterLocalNotificationsPlugin _notificationsPlugin =
|
static final FlutterLocalNotificationsPlugin _notificationsPlugin =
|
||||||
FlutterLocalNotificationsPlugin();
|
FlutterLocalNotificationsPlugin();
|
||||||
final FirebaseMessaging _messaging = FirebaseMessaging.instance;
|
final firebase_messaging.FirebaseMessaging _messaging =
|
||||||
|
firebase_messaging.FirebaseMessaging.instance;
|
||||||
AlarmServices alarmServices = AlarmServices();
|
AlarmServices alarmServices = AlarmServices();
|
||||||
|
|
||||||
Future<void> initialize() async {
|
Future<void> initialize() async {
|
||||||
@@ -25,36 +26,51 @@ class NotificationServices {
|
|||||||
dev.log("NotificationService initialized");
|
dev.log("NotificationService initialized");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@pragma('vm:entry-point')
|
||||||
Future<void> initializeLocalNotifications() async {
|
Future<void> initializeLocalNotifications() async {
|
||||||
try {
|
try {
|
||||||
const AndroidInitializationSettings androidInitializationSettings =
|
const androidInitSettings =
|
||||||
AndroidInitializationSettings('@mipmap/ic_launcher');
|
AndroidInitializationSettings('@mipmap/ic_launcher');
|
||||||
const DarwinInitializationSettings iosInitializationSettings =
|
final darwinInitSettings = DarwinInitializationSettings(
|
||||||
DarwinInitializationSettings();
|
onDidReceiveLocalNotification: _onDidReceiveLocalNotification,
|
||||||
const InitializationSettings initializationSettings =
|
);
|
||||||
InitializationSettings(
|
final initSettings = InitializationSettings(
|
||||||
android: androidInitializationSettings,
|
android: androidInitSettings,
|
||||||
iOS: iosInitializationSettings,
|
iOS: darwinInitSettings,
|
||||||
);
|
);
|
||||||
|
|
||||||
await _notificationsPlugin.initialize(
|
await _notificationsPlugin.initialize(
|
||||||
initializationSettings,
|
initSettings,
|
||||||
onDidReceiveNotificationResponse: (NotificationResponse response) {
|
onDidReceiveNotificationResponse: _onDidReceiveNotificationResponse,
|
||||||
dev.log(
|
|
||||||
"Người dùng click thông báo ở foreground với payload: ${response.payload}");
|
|
||||||
handleMessage(response.payload, controller);
|
|
||||||
},
|
|
||||||
);
|
);
|
||||||
|
|
||||||
dev.log("Local notifications initialized");
|
dev.log("Local notifications initialized");
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
dev.log("Error initializing local notifications: $e");
|
AppLoggerUtils.error("Failed to initialize local notifications", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _onDidReceiveLocalNotification(
|
||||||
|
int id, String? title, String? body, String? payload) async {
|
||||||
|
// Handle local notification tap when app is foreground (iOS)
|
||||||
|
dev.log("Local notification tapped (foreground): payload=$payload");
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _onDidReceiveNotificationResponse(
|
||||||
|
NotificationResponse response) async {
|
||||||
|
// Handle local notification tap (both foreground & background)
|
||||||
|
final payload = response.payload;
|
||||||
|
dev.log("Notification tapped: payload=$payload");
|
||||||
|
|
||||||
|
if (payload != null && payload.isNotEmpty) {
|
||||||
|
final controller = PersistentTabController(initialIndex: 0);
|
||||||
|
handleMessage(payload, controller);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void firebaseInit(BuildContext context) {
|
void firebaseInit(BuildContext context) {
|
||||||
FirebaseMessaging.onMessage.listen((message) {
|
firebase_messaging.FirebaseMessaging.onMessage.listen((message) {
|
||||||
dev.log("Foreground message payload: ${message.toMap()}");
|
_handleForegroundMessage(message);
|
||||||
_showNotification(message);
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -66,75 +82,156 @@ class NotificationServices {
|
|||||||
|
|
||||||
static Future<bool?> requestNotificationPermission() async {
|
static Future<bool?> requestNotificationPermission() async {
|
||||||
try {
|
try {
|
||||||
if (Platform.isAndroid) {
|
final messaging = firebase_messaging.FirebaseMessaging.instance;
|
||||||
return await _notificationsPlugin
|
final settings = await messaging.requestPermission(
|
||||||
.resolvePlatformSpecificImplementation<
|
alert: true,
|
||||||
AndroidFlutterLocalNotificationsPlugin>()
|
announcement: false,
|
||||||
?.requestNotificationsPermission();
|
badge: true,
|
||||||
} else if (Platform.isIOS) {
|
provisional: false,
|
||||||
return await _notificationsPlugin
|
sound: true,
|
||||||
.resolvePlatformSpecificImplementation<
|
);
|
||||||
IOSFlutterLocalNotificationsPlugin>()
|
return settings.authorizationStatus ==
|
||||||
?.requestPermissions(alert: true, sound: true, badge: true);
|
firebase_messaging.AuthorizationStatus.authorized;
|
||||||
}
|
|
||||||
return null;
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
dev.log("Error requesting notification permission: $e");
|
AppLoggerUtils.error("Failed to request notification permission", e);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _showNotification(RemoteMessage message) async {
|
/// Xử lý message khi app foreground
|
||||||
|
Future<void> _handleForegroundMessage(
|
||||||
|
firebase_messaging.RemoteMessage message) async {
|
||||||
try {
|
try {
|
||||||
// Early validation of notification data
|
|
||||||
final title = message.notification?.title ?? "Title" as String?;
|
|
||||||
final body = message.notification?.body ?? "Body" as String?;
|
|
||||||
final type = message.data['type'] as String? ?? 'normal';
|
final type = message.data['type'] as String? ?? 'normal';
|
||||||
|
final title =
|
||||||
|
message.notification?.title ?? message.data['title'] ?? 'SFM';
|
||||||
|
final body = message.notification?.body ?? message.data['body'] ?? '';
|
||||||
|
|
||||||
if (title == null || body == null) {
|
dev.log('Foreground message: type=$type, title=$title, body=$body');
|
||||||
dev.log('Skipping notification: missing title or body',
|
|
||||||
name: 'Notification');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handle smoke warning notifications
|
|
||||||
if (type == 'smoke_warning') {
|
if (type == 'smoke_warning') {
|
||||||
await alarmServices.showAlarm(title, body);
|
// Hiển thị alarm, không hiển thị notification
|
||||||
dev.log('Displayed smoke warning notification', name: 'Notification');
|
await _showAlarm(title, body);
|
||||||
return;
|
} else {
|
||||||
|
// Hiển thị notification local
|
||||||
|
await _showForegroundNotification(title, body, type);
|
||||||
}
|
}
|
||||||
|
} catch (e, stackTrace) {
|
||||||
|
AppLoggerUtils.error('Error handling foreground message', e, stackTrace);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Create notification channel
|
/// Hiển thị alarm (smoke warning)
|
||||||
final channelId = math.Random.secure().nextInt(1000000).toString();
|
Future<void> _showAlarm(String title, String body) async {
|
||||||
const channelName = 'High Importance Notification';
|
try {
|
||||||
const channelDescription = 'Channel description';
|
await Alarm.init();
|
||||||
|
|
||||||
// final androidChannel = AndroidNotificationChannel(
|
final alarmSettings = AlarmSettings(
|
||||||
// channelId,
|
id: 42,
|
||||||
// channelName,
|
dateTime: DateTime.now(),
|
||||||
// importance: Importance.max,
|
assetAudioPath: 'assets/sounds/warning_alarm.mp3',
|
||||||
// description: channelDescription,
|
loopAudio: true,
|
||||||
// );
|
vibrate: true,
|
||||||
|
warningNotificationOnKill: Platform.isIOS,
|
||||||
|
androidFullScreenIntent: true,
|
||||||
|
volumeSettings: VolumeSettings.fade(
|
||||||
|
volume: 0.8,
|
||||||
|
fadeDuration: const Duration(seconds: 5),
|
||||||
|
volumeEnforced: true,
|
||||||
|
),
|
||||||
|
notificationSettings: NotificationSettings(
|
||||||
|
title: title,
|
||||||
|
body: body,
|
||||||
|
stopButton: 'Dừng thông báo',
|
||||||
|
icon: "ic_launcher",
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
final androidPlugin =
|
await Alarm.set(alarmSettings: alarmSettings);
|
||||||
_notificationsPlugin.resolvePlatformSpecificImplementation<
|
dev.log('Alarm set successfully: $title');
|
||||||
AndroidFlutterLocalNotificationsPlugin>();
|
} catch (e, stackTrace) {
|
||||||
|
AppLoggerUtils.error('Failed to set alarm', e, stackTrace);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Delete existing channel to prevent conflicts
|
/// Hiển thị notification local khi app foreground
|
||||||
await androidPlugin?.deleteNotificationChannel(channelId);
|
Future<void> _showForegroundNotification(
|
||||||
|
String title, String body, String type) async {
|
||||||
// Configure notification details
|
try {
|
||||||
final androidDetails = AndroidNotificationDetails(
|
final androidDetails = AndroidNotificationDetails(
|
||||||
channelId,
|
'sfm_channel_${math.Random.secure().nextInt(1000000)}',
|
||||||
channelName,
|
'SFM Notifications',
|
||||||
channelDescription: channelDescription,
|
channelDescription: 'Notifications from SFM app',
|
||||||
sound: getSound(type),
|
sound: getSound(type),
|
||||||
importance: Importance.max,
|
importance: Importance.max,
|
||||||
priority: Priority.high,
|
priority: Priority.high,
|
||||||
ticker: 'ticker',
|
ticker: 'ticker',
|
||||||
);
|
);
|
||||||
|
|
||||||
const iosDetails = DarwinNotificationDetails(
|
final iosDetails = DarwinNotificationDetails(
|
||||||
|
sound: _getSoundNameForIOS(type),
|
||||||
|
presentAlert: true,
|
||||||
|
presentBadge: true,
|
||||||
|
presentBanner: true,
|
||||||
|
presentSound: true,
|
||||||
|
);
|
||||||
|
|
||||||
|
final notificationDetails = NotificationDetails(
|
||||||
|
android: androidDetails,
|
||||||
|
iOS: iosDetails,
|
||||||
|
);
|
||||||
|
|
||||||
|
await _notificationsPlugin.show(
|
||||||
|
math.Random.secure().nextInt(1000000),
|
||||||
|
title,
|
||||||
|
body,
|
||||||
|
notificationDetails,
|
||||||
|
payload: type,
|
||||||
|
);
|
||||||
|
|
||||||
|
dev.log('Foreground notification shown: title=$title, type=$type');
|
||||||
|
} catch (e, stackTrace) {
|
||||||
|
AppLoggerUtils.error(
|
||||||
|
'Failed to show foreground notification', e, stackTrace);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Hiển thị notification cho background/terminate
|
||||||
|
Future<void> showBackgroundOrTerminateNotification(
|
||||||
|
firebase_messaging.RemoteMessage message) async {
|
||||||
|
try {
|
||||||
|
final type = message.data['type'] as String? ?? 'normal';
|
||||||
|
final title =
|
||||||
|
message.notification?.title ?? message.data['title'] ?? 'SFM';
|
||||||
|
final body = message.notification?.body ?? message.data['body'] ?? '';
|
||||||
|
|
||||||
|
dev.log('Background/terminate notification: type=$type, title=$title');
|
||||||
|
|
||||||
|
// Cho Android: nếu type = smoke_warning, hiển thị alarm
|
||||||
|
// Cho iOS: chỉ hiển thị notification, APNs payload sẽ phát sound natively
|
||||||
|
if (type == 'smoke_warning' && Platform.isAndroid) {
|
||||||
|
dev.log('→ Showing alarm for Android background');
|
||||||
|
await _showAlarm(title, body);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Hiển thị notification (cho cả iOS lẫn Android, nhưng iOS không có alarm)
|
||||||
|
final channelId = math.Random.secure().nextInt(1000000).toString();
|
||||||
|
const channelName = 'SFM Notifications';
|
||||||
|
const channelDescription = 'Notifications from SFM app';
|
||||||
|
|
||||||
|
final androidDetails = AndroidNotificationDetails(
|
||||||
|
channelId,
|
||||||
|
channelName,
|
||||||
|
channelDescription: channelDescription,
|
||||||
|
sound: getSound(type),
|
||||||
|
importance: Importance.max,
|
||||||
|
priority: Priority.high,
|
||||||
|
ticker: 'ticker',
|
||||||
|
);
|
||||||
|
|
||||||
|
final iosDetails = DarwinNotificationDetails(
|
||||||
|
sound: _getSoundNameForIOS(type),
|
||||||
presentAlert: true,
|
presentAlert: true,
|
||||||
presentBadge: true,
|
presentBadge: true,
|
||||||
presentBanner: true,
|
presentBanner: true,
|
||||||
@@ -146,7 +243,6 @@ class NotificationServices {
|
|||||||
iOS: iosDetails,
|
iOS: iosDetails,
|
||||||
);
|
);
|
||||||
|
|
||||||
// Show notification
|
|
||||||
await _notificationsPlugin.show(
|
await _notificationsPlugin.show(
|
||||||
int.parse(channelId),
|
int.parse(channelId),
|
||||||
title,
|
title,
|
||||||
@@ -156,16 +252,10 @@ class NotificationServices {
|
|||||||
);
|
);
|
||||||
|
|
||||||
dev.log(
|
dev.log(
|
||||||
'Displayed notification - title: $title, body: $body, type: $type',
|
'Background/terminate notification shown: title=$title, type=$type');
|
||||||
name: 'Notification',
|
|
||||||
);
|
|
||||||
} catch (e, stackTrace) {
|
} catch (e, stackTrace) {
|
||||||
dev.log(
|
AppLoggerUtils.error(
|
||||||
'Failed to show notification: $e',
|
'Failed to show background/terminate notification', e, stackTrace);
|
||||||
name: 'Notification',
|
|
||||||
error: e,
|
|
||||||
stackTrace: stackTrace,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -179,153 +269,110 @@ class NotificationServices {
|
|||||||
} else if (type == "battery_warning") {
|
} else if (type == "battery_warning") {
|
||||||
return const RawResourceAndroidNotificationSound("new_alarm");
|
return const RawResourceAndroidNotificationSound("new_alarm");
|
||||||
} else {
|
} else {
|
||||||
return const RawResourceAndroidNotificationSound("normal");
|
return const RawResourceAndroidNotificationSound("warning_alarm");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
String _getSoundNameForIOS(String type) {
|
||||||
|
// iOS tìm file trong app bundle (không có đuôi)
|
||||||
|
if (type == "smoke_warning" || type == "battery_warning") {
|
||||||
|
return "warning_alarm"; // file: warning_alarm.caf trong bundle
|
||||||
|
}
|
||||||
|
return "default";
|
||||||
|
}
|
||||||
|
|
||||||
void handleMessage(String? payload, PersistentTabController controller) {
|
void handleMessage(String? payload, PersistentTabController controller) {
|
||||||
dev.log("Handling notification tap with payload: $payload");
|
AppLoggerUtils.info("Handling notification tap with payload: $payload");
|
||||||
controller.jumpToTab(1);
|
controller.jumpToTab(1);
|
||||||
|
|
||||||
|
if (payload == "smoke_warning") {
|
||||||
|
// TODO: Navigate to smoke warning screen nếu cần
|
||||||
|
// NavigationRouter.navigateToSmokeWarningScreen();
|
||||||
|
} else if (payload == "battery_warning") {
|
||||||
|
// TODO: Navigate to battery warning screen nếu cần
|
||||||
|
// NavigationRouter.navigateToBatteryWarningScreen();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> setupInteractMessage(PersistentTabController controller) async {
|
Future<void> setupInteractMessage(PersistentTabController controller) async {
|
||||||
// Khi app terminated
|
// Khi app terminated, được mở bằng notification
|
||||||
RemoteMessage? initialMessage =
|
firebase_messaging.RemoteMessage? initialMessage =
|
||||||
await FirebaseMessaging.instance.getInitialMessage();
|
await firebase_messaging.FirebaseMessaging.instance.getInitialMessage();
|
||||||
|
|
||||||
if (initialMessage != null) {
|
if (initialMessage != null) {
|
||||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||||
try {
|
final type = initialMessage.data['type'] as String?;
|
||||||
handleMessage(initialMessage.data['type'], controller);
|
handleMessage(type, controller);
|
||||||
} catch (e, stack) {
|
|
||||||
dev.log("Error handling initial message: $e\n$stack");
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
// Khi app ở background
|
|
||||||
FirebaseMessaging.onMessageOpenedApp.listen((message) {
|
// Khi app background, user tap notification
|
||||||
|
firebase_messaging.FirebaseMessaging.onMessageOpenedApp.listen((message) {
|
||||||
try {
|
try {
|
||||||
handleMessage(message.data['type'], controller);
|
final type = message.data['type'] as String?;
|
||||||
} catch (e, stack) {
|
handleMessage(type, controller);
|
||||||
dev.log("Error in onMessageOpenedApp: $e\n$stack");
|
} catch (e, stackTrace) {
|
||||||
|
AppLoggerUtils.error('Error in onMessageOpenedApp', e, stackTrace);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> showBackgroundOrTerminateNotification(
|
@pragma('vm:entry-point')
|
||||||
RemoteMessage message) async {
|
static Future<void> firebaseMessagingBackgroundHandler(
|
||||||
|
firebase_messaging.RemoteMessage message) async {
|
||||||
try {
|
try {
|
||||||
// Early validation of notification data
|
// Log to device storage for debugging background isolate
|
||||||
final title = message.data['title'] as String?;
|
dev.log('═══ BACKGROUND HANDLER STARTED ═══');
|
||||||
final body = message.data['body'] as String?;
|
dev.log('Message data: ${message.data}');
|
||||||
final type = message.data['type'] as String? ?? 'normal';
|
dev.log('Notification: ${message.notification}');
|
||||||
|
|
||||||
if (title == null || body == null) {
|
await Alarm.init();
|
||||||
dev.log('Skipping notification: missing title or body',
|
dev.log('✓ Alarm initialized');
|
||||||
name: 'Notification');
|
|
||||||
return;
|
if (Firebase.apps.isEmpty) {
|
||||||
|
await Firebase.initializeApp(
|
||||||
|
options: DefaultFirebaseOptions.currentPlatform,
|
||||||
|
name: "sfm-notification",
|
||||||
|
);
|
||||||
|
dev.log('✓ Firebase initialized in background');
|
||||||
|
} else {
|
||||||
|
dev.log('✓ Firebase already initialized');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create notification channel
|
final type = message.data['type'] as String? ?? 'normal';
|
||||||
final channelId = math.Random.secure().nextInt(1000000).toString();
|
final title = message.notification?.title ??
|
||||||
const channelName = 'High Importance Notification';
|
message.data['title'] ??
|
||||||
const channelDescription = 'Channel description';
|
'Thông báo từ SmartFM';
|
||||||
|
final body = message.notification?.body ??
|
||||||
|
message.data['body'] ??
|
||||||
|
'Bạn có một thông báo mới';
|
||||||
|
|
||||||
// Configure notification details
|
dev.log('Message type: $type');
|
||||||
final androidDetails = AndroidNotificationDetails(
|
dev.log('Title: $title');
|
||||||
channelId,
|
dev.log('Body: $body');
|
||||||
channelName,
|
|
||||||
channelDescription: channelDescription,
|
|
||||||
sound: getSound(type),
|
|
||||||
importance: Importance.max,
|
|
||||||
priority: Priority.high,
|
|
||||||
ticker: 'ticker',
|
|
||||||
);
|
|
||||||
|
|
||||||
const iosDetails = DarwinNotificationDetails(
|
// Logic: Hiển thị local notification cho background/terminate
|
||||||
presentAlert: true,
|
// iOS sẽ phát sound natively thông qua APNs payload (aps.sound)
|
||||||
presentBadge: true,
|
// Android sẽ phát sound thông qua local notification
|
||||||
presentBanner: true,
|
try {
|
||||||
presentSound: true,
|
final notificationService = NotificationServices();
|
||||||
);
|
await notificationService.initialize();
|
||||||
|
dev.log('✓ Notification service initialized');
|
||||||
|
|
||||||
final notificationDetails = NotificationDetails(
|
await notificationService
|
||||||
android: androidDetails,
|
.showBackgroundOrTerminateNotification(message);
|
||||||
iOS: iosDetails,
|
dev.log('✓ Background/terminate notification shown');
|
||||||
);
|
} catch (notifError, notifStackTrace) {
|
||||||
|
dev.log('✗ ERROR showing notification: $notifError');
|
||||||
|
dev.log('Stack trace: $notifStackTrace');
|
||||||
|
rethrow;
|
||||||
|
}
|
||||||
|
|
||||||
// Show notification
|
dev.log('═══ BACKGROUND HANDLER COMPLETED ═══');
|
||||||
await _notificationsPlugin.show(
|
|
||||||
int.parse(channelId),
|
|
||||||
title,
|
|
||||||
body,
|
|
||||||
notificationDetails,
|
|
||||||
payload: type,
|
|
||||||
);
|
|
||||||
|
|
||||||
dev.log(
|
|
||||||
'Displayed notification - title: $title, body: $body, type: $type',
|
|
||||||
name: 'Notification',
|
|
||||||
);
|
|
||||||
} catch (e, stackTrace) {
|
} catch (e, stackTrace) {
|
||||||
dev.log(
|
dev.log('✗ BACKGROUND HANDLER FAILED');
|
||||||
'Failed to show notification: $e',
|
dev.log('Error: $e');
|
||||||
name: 'Notification',
|
dev.log('Stack trace: $stackTrace');
|
||||||
error: e,
|
|
||||||
stackTrace: stackTrace,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@pragma('vm:entry-point')
|
|
||||||
Future<void> firebaseMessagingBackgroundHandler(RemoteMessage message) async {
|
|
||||||
try {
|
|
||||||
AppLoggerUtils.warning("Background handler started: ${message.data}");
|
|
||||||
await Alarm.init();
|
|
||||||
if (Firebase.apps.isEmpty) {
|
|
||||||
await Firebase.initializeApp(
|
|
||||||
options: DefaultFirebaseOptions.currentPlatform,
|
|
||||||
name: "sfm-notification");
|
|
||||||
AppLoggerUtils.warning(
|
|
||||||
"Firebase đã được khởi tạo trong background handler");
|
|
||||||
} else {
|
|
||||||
AppLoggerUtils.warning("Firebase đã được khởi tạo trước đó");
|
|
||||||
}
|
|
||||||
AppLoggerUtils.warning(message.toString());
|
|
||||||
AppLoggerUtils.warning(message.data.toString());
|
|
||||||
AppLoggerUtils.warning(message.data["notification"].toString());
|
|
||||||
String? title = message.data['title'];
|
|
||||||
String? body = message.data['body'];
|
|
||||||
String type = message.data['type'] ?? "normal";
|
|
||||||
final notificationService = NotificationServices();
|
|
||||||
await notificationService.initialize();
|
|
||||||
final alarmSettings = AlarmSettings(
|
|
||||||
id: 42,
|
|
||||||
dateTime: DateTime.now(),
|
|
||||||
assetAudioPath: 'assets/sounds/warning_alarm.mp3',
|
|
||||||
loopAudio: true,
|
|
||||||
vibrate: true,
|
|
||||||
warningNotificationOnKill: Platform.isIOS,
|
|
||||||
androidFullScreenIntent: true,
|
|
||||||
volumeSettings: VolumeSettings.fade(
|
|
||||||
volume: 0.8,
|
|
||||||
fadeDuration: const Duration(seconds: 5),
|
|
||||||
volumeEnforced: true,
|
|
||||||
),
|
|
||||||
notificationSettings: NotificationSettings(
|
|
||||||
title: title ?? "SFM",
|
|
||||||
body: body ?? "",
|
|
||||||
stopButton: 'Dừng thông báo',
|
|
||||||
icon: "ic_launcher",
|
|
||||||
),
|
|
||||||
);
|
|
||||||
if (type == "smoke_warning") {
|
|
||||||
await Alarm.set(alarmSettings: alarmSettings);
|
|
||||||
} else {
|
|
||||||
await notificationService.showBackgroundOrTerminateNotification(message);
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
AppLoggerUtils.warning("Error in background handler: $e");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import FlutterMacOS
|
|||||||
import Foundation
|
import Foundation
|
||||||
|
|
||||||
import app_settings
|
import app_settings
|
||||||
|
import connectivity_plus
|
||||||
import firebase_core
|
import firebase_core
|
||||||
import firebase_messaging
|
import firebase_messaging
|
||||||
import flutter_local_notifications
|
import flutter_local_notifications
|
||||||
@@ -16,6 +17,7 @@ import url_launcher_macos
|
|||||||
|
|
||||||
func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
|
func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
|
||||||
AppSettingsPlugin.register(with: registry.registrar(forPlugin: "AppSettingsPlugin"))
|
AppSettingsPlugin.register(with: registry.registrar(forPlugin: "AppSettingsPlugin"))
|
||||||
|
ConnectivityPlusPlugin.register(with: registry.registrar(forPlugin: "ConnectivityPlusPlugin"))
|
||||||
FLTFirebaseCorePlugin.register(with: registry.registrar(forPlugin: "FLTFirebaseCorePlugin"))
|
FLTFirebaseCorePlugin.register(with: registry.registrar(forPlugin: "FLTFirebaseCorePlugin"))
|
||||||
FLTFirebaseMessagingPlugin.register(with: registry.registrar(forPlugin: "FLTFirebaseMessagingPlugin"))
|
FLTFirebaseMessagingPlugin.register(with: registry.registrar(forPlugin: "FLTFirebaseMessagingPlugin"))
|
||||||
FlutterLocalNotificationsPlugin.register(with: registry.registrar(forPlugin: "FlutterLocalNotificationsPlugin"))
|
FlutterLocalNotificationsPlugin.register(with: registry.registrar(forPlugin: "FlutterLocalNotificationsPlugin"))
|
||||||
|
|||||||
24
pubspec.lock
24
pubspec.lock
@@ -113,6 +113,22 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.19.1"
|
version: "1.19.1"
|
||||||
|
connectivity_plus:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: connectivity_plus
|
||||||
|
sha256: "33bae12a398f841c6cda09d1064212957265869104c478e5ad51e2fb26c3973c"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "7.0.0"
|
||||||
|
connectivity_plus_platform_interface:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: connectivity_plus_platform_interface
|
||||||
|
sha256: "42657c1715d48b167930d5f34d00222ac100475f73d10162ddf43e714932f204"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.0.1"
|
||||||
crypto:
|
crypto:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -629,6 +645,14 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.16.0"
|
version: "1.16.0"
|
||||||
|
nm:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: nm
|
||||||
|
sha256: "2c9aae4127bdc8993206464fcc063611e0e36e72018696cd9631023a31b24254"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "0.5.0"
|
||||||
path:
|
path:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|||||||
@@ -49,6 +49,7 @@ dependencies:
|
|||||||
lottie: ^3.3.1
|
lottie: ^3.3.1
|
||||||
logger: ^2.5.0
|
logger: ^2.5.0
|
||||||
alarm: ^5.1.4
|
alarm: ^5.1.4
|
||||||
|
connectivity_plus: ^7.0.0
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
flutter_test:
|
flutter_test:
|
||||||
|
|||||||
@@ -6,6 +6,7 @@
|
|||||||
|
|
||||||
#include "generated_plugin_registrant.h"
|
#include "generated_plugin_registrant.h"
|
||||||
|
|
||||||
|
#include <connectivity_plus/connectivity_plus_windows_plugin.h>
|
||||||
#include <firebase_core/firebase_core_plugin_c_api.h>
|
#include <firebase_core/firebase_core_plugin_c_api.h>
|
||||||
#include <geolocator_windows/geolocator_windows.h>
|
#include <geolocator_windows/geolocator_windows.h>
|
||||||
#include <maps_launcher/maps_launcher_plugin.h>
|
#include <maps_launcher/maps_launcher_plugin.h>
|
||||||
@@ -13,6 +14,8 @@
|
|||||||
#include <url_launcher_windows/url_launcher_windows.h>
|
#include <url_launcher_windows/url_launcher_windows.h>
|
||||||
|
|
||||||
void RegisterPlugins(flutter::PluginRegistry* registry) {
|
void RegisterPlugins(flutter::PluginRegistry* registry) {
|
||||||
|
ConnectivityPlusWindowsPluginRegisterWithRegistrar(
|
||||||
|
registry->GetRegistrarForPlugin("ConnectivityPlusWindowsPlugin"));
|
||||||
FirebaseCorePluginCApiRegisterWithRegistrar(
|
FirebaseCorePluginCApiRegisterWithRegistrar(
|
||||||
registry->GetRegistrarForPlugin("FirebaseCorePluginCApi"));
|
registry->GetRegistrarForPlugin("FirebaseCorePluginCApi"));
|
||||||
GeolocatorWindowsRegisterWithRegistrar(
|
GeolocatorWindowsRegisterWithRegistrar(
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
#
|
#
|
||||||
|
|
||||||
list(APPEND FLUTTER_PLUGIN_LIST
|
list(APPEND FLUTTER_PLUGIN_LIST
|
||||||
|
connectivity_plus
|
||||||
firebase_core
|
firebase_core
|
||||||
geolocator_windows
|
geolocator_windows
|
||||||
maps_launcher
|
maps_launcher
|
||||||
|
|||||||
Reference in New Issue
Block a user