Skip to content
GitLab
Menu
Projects
Groups
Snippets
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
Menu
Open sidebar
Hoàng Văn Đạt
mypoint_flutter_app
Commits
4bd580fa
Commit
4bd580fa
authored
Sep 26, 2025
by
DatHV
Browse files
update game card detail, mobile card
parent
b75a9279
Changes
43
Hide whitespace changes
Inline
Side-by-side
android/app/src/main/AndroidManifest.xml
View file @
4bd580fa
...
...
@@ -7,7 +7,8 @@
<application
android:label=
"@string/app_name"
android:name=
"${applicationName}"
android:icon=
"@mipmap/ic_launcher"
>
android:icon=
"@mipmap/ic_launcher"
android:enableOnBackInvokedCallback=
"true"
>
<activity
android:name=
".MainActivity"
android:exported=
"true"
...
...
assets/images/2.0x/bg_alert_header.png
deleted
100644 → 0
View file @
b75a9279
123 KB
assets/images/2.0x/bg_onboarding.png
deleted
100644 → 0
View file @
b75a9279
169 KB
assets/images/2.0x/splash_screen.png
deleted
100644 → 0
View file @
b75a9279
162 KB
assets/images/3.0x/bg_alert_header.png
deleted
100644 → 0
View file @
b75a9279
231 KB
assets/images/3.0x/bg_onboarding.png
deleted
100644 → 0
View file @
b75a9279
347 KB
assets/images/3.0x/splash_screen.png
deleted
100644 → 0
View file @
b75a9279
ios/Flutter/Dev.xcconfig
View file @
4bd580fa
...
...
@@ -2,6 +2,7 @@
FLAVOR=dev
BASE_URL=https://api.sandbox.mypoint.com.vn/8854/gup2start/rest
LIB_TOKEN=dev_token_here
APP_DISPLAY_NAME = MyPoint DEV
// Inherit base Flutter settings
#include "Debug.xcconfig"
...
...
ios/Flutter/Pro.xcconfig
View file @
4bd580fa
...
...
@@ -2,6 +2,7 @@
FLAVOR=pro
BASE_URL=https://api.mypoint.com.vn/8854/gup2start/rest
LIB_TOKEN=prod_token_here
APP_DISPLAY_NAME = MyPoint
// Inherit base Flutter settings
#include "Release.xcconfig"
...
...
ios/Flutter/Stg.xcconfig
View file @
4bd580fa
...
...
@@ -2,7 +2,7 @@
FLAVOR=stg
BASE_URL=https://api.sandbox.mypoint.com.vn/8854/gup2start/rest
LIB_TOKEN=stg_token_here
APP_DISPLAY_NAME = MyPoint STG
// Inherit base Flutter settings
#include "Debug.xcconfig"
...
...
ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme
View file @
4bd580fa
...
...
@@ -26,6 +26,7 @@
buildConfiguration =
"Debug"
selectedDebuggerIdentifier =
"Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier =
"Xcode.DebuggerFoundation.Launcher.LLDB"
customLLDBInitFile =
"$(SRCROOT)/Flutter/ephemeral/flutter_lldbinit"
shouldUseLaunchSchemeArgsEnv =
"YES"
>
<MacroExpansion>
<BuildableReference
...
...
@@ -54,6 +55,7 @@
buildConfiguration =
"Debug"
selectedDebuggerIdentifier =
"Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier =
"Xcode.DebuggerFoundation.Launcher.LLDB"
customLLDBInitFile =
"$(SRCROOT)/Flutter/ephemeral/flutter_lldbinit"
launchStyle =
"0"
useCustomWorkingDirectory =
"NO"
ignoresPersistentStateOnLaunch =
"NO"
...
...
ios/Runner/Info.plist
View file @
4bd580fa
...
...
@@ -2,6 +2,8 @@
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist
version=
"1.0"
>
<dict>
<key>
CFBundleDisplayName
</key>
<string>
$(APP_DISPLAY_NAME)
</string>
<key>
CADisableMinimumFrameDurationOnPhone
</key>
<true/>
<key>
CFBundleDevelopmentRegion
</key>
...
...
lib/base/app_loading.dart
View file @
4bd580fa
...
...
@@ -3,6 +3,7 @@ import 'dart:collection';
import
'package:flutter/material.dart'
;
import
'package:get/get.dart'
;
import
'../configs/constants.dart'
;
import
'../configs/constants.dart'
;
class
AppLoading
{
// Singleton ẩn
...
...
@@ -77,7 +78,7 @@ class AppLoading {
fit:
StackFit
.
expand
,
children:
[
if
(
barrierColor
!=
null
)
const
SizedBox
.
expand
(
// không dùng Positioned
const
SizedBox
.
expand
(
child:
IgnorePointer
(
ignoring:
true
,
child:
SizedBox
()),
),
if
(
barrierColor
!=
null
)
...
...
lib/base/base_screen.dart
View file @
4bd580fa
...
...
@@ -13,16 +13,148 @@ abstract class BaseScreen extends StatefulWidget {
const
BaseScreen
({
super
.
key
});
}
abstract
class
BaseState
<
Screen
extends
BaseScreen
>
extends
State
<
Screen
>
{
abstract
class
BaseState
<
Screen
extends
BaseScreen
>
extends
State
<
Screen
>
with
WidgetsBindingObserver
{
bool
_isVisible
=
false
;
bool
_isPaused
=
false
;
@override
void
initState
()
{
super
.
initState
();
WidgetsBinding
.
instance
.
addObserver
(
this
);
if
(
kDebugMode
)
{
print
(
"_show:
$runtimeType
"
);
}
// Gọi onInit sau khi initState
WidgetsBinding
.
instance
.
addPostFrameCallback
((
_
)
{
onInit
();
});
}
@override
void
dispose
()
{
WidgetsBinding
.
instance
.
removeObserver
(
this
);
onDestroy
();
super
.
dispose
();
}
@override
void
didChangeAppLifecycleState
(
AppLifecycleState
state
)
{
super
.
didChangeAppLifecycleState
(
state
);
switch
(
state
)
{
case
AppLifecycleState
.
resumed
:
if
(
_isPaused
)
{
_isPaused
=
false
;
onAppResumed
();
if
(
_isVisible
)
{
onStart
();
}
}
break
;
case
AppLifecycleState
.
paused
:
if
(!
_isPaused
)
{
_isPaused
=
true
;
onAppPaused
();
if
(
_isVisible
)
{
onStop
();
}
}
break
;
case
AppLifecycleState
.
inactive
:
onAppInactive
();
break
;
case
AppLifecycleState
.
detached
:
onAppDetached
();
break
;
case
AppLifecycleState
.
hidden
:
onAppHidden
();
break
;
}
}
@override
void
didChangeDependencies
()
{
super
.
didChangeDependencies
();
if
(!
_isVisible
)
{
_isVisible
=
true
;
onResume
();
// Gọi onStart sau frame tiếp theo
WidgetsBinding
.
instance
.
addPostFrameCallback
((
_
)
{
if
(
_isVisible
&&
!
_isPaused
)
{
onStart
();
}
});
}
}
@override
void
didUpdateWidget
(
Screen
oldWidget
)
{
super
.
didUpdateWidget
(
oldWidget
);
// Gọi khi widget được update (có thể do navigation, state changes)
WidgetsBinding
.
instance
.
addPostFrameCallback
((
_
)
{
if
(
_isVisible
&&
!
_isPaused
)
{
onStart
();
}
});
}
// MARK: - Flutter Lifecycle Hooks (Override these in your screens)
/// Called when the widget is first inserted into the tree (similar to viewDidLoad in iOS)
void
onInit
()
{
// Override in subclasses
}
/// Called when the widget is about to become visible (similar to viewWillAppear in iOS)
void
onResume
()
{
// Override in subclasses
}
/// Called when the widget has become visible (similar to viewDidAppear in iOS)
void
onStart
()
{
// Override in subclasses
}
/// Called when the widget is about to become invisible (similar to viewWillDisappear in iOS)
void
onPause
()
{
// Override in subclasses
}
/// Called when the widget has become invisible (similar to viewDidDisappear in iOS)
void
onStop
()
{
// Override in subclasses
}
/// Called when the widget is removed from the tree (similar to viewDidUnload in iOS)
void
onDestroy
()
{
// Override in subclasses
}
/// Called when app becomes active (similar to applicationDidBecomeActive in iOS)
void
onAppResumed
()
{
// Override in subclasses
}
/// Called when app becomes inactive (similar to applicationWillResignActive in iOS)
void
onAppPaused
()
{
// Override in subclasses
}
/// Called when app becomes inactive (similar to applicationWillResignActive in iOS)
void
onAppInactive
()
{
// Override in subclasses
}
/// Called when app is detached (similar to applicationWillTerminate in iOS)
void
onAppDetached
()
{
// Override in subclasses
}
/// Called when app is hidden (similar to applicationDidEnterBackground in iOS)
void
onAppHidden
()
{
// Override in subclasses
}
showPopup
({
void
showPopup
({
required
PopupDataModel
data
,
bool
?
barrierDismissibl
,
bool
showCloseButton
=
false
,
...
...
@@ -34,7 +166,7 @@ abstract class BaseState<Screen extends BaseScreen> extends State<Screen> {
);
}
showAlert
({
void
showAlert
({
required
DataAlertModel
data
,
bool
?
barrierDismissibl
,
bool
showCloseButton
=
true
,
...
...
@@ -46,7 +178,7 @@ abstract class BaseState<Screen extends BaseScreen> extends State<Screen> {
);
}
showAlertError
({
void
showAlertError
({
required
String
content
,
bool
?
barrierDismissible
,
String
headerImage
=
"assets/images/ic_pipi_03.png"
,
...
...
@@ -80,7 +212,7 @@ abstract class BaseState<Screen extends BaseScreen> extends State<Screen> {
);
}
hideKeyboard
()
{
void
hideKeyboard
()
{
FocusScope
.
of
(
context
).
unfocus
();
}
...
...
lib/base/base_view_model.dart
View file @
4bd580fa
...
...
@@ -37,11 +37,24 @@ class BaseViewModel extends GetxController with WidgetsBindingObserver {
}
}
showLoading
({
int
timeout
=
Constants
.
loadingTimeoutSeconds
})
{
void
showProgressIndicator
()
{
Get
.
dialog
(
const
Center
(
child:
CircularProgressIndicator
()),
barrierDismissible:
false
,
);
}
void
hideProgressIndicator
()
{
if
(
Get
.
isDialogOpen
??
false
)
{
Get
.
back
();
}
}
void
showLoading
({
int
timeout
=
Constants
.
loadingTimeoutSeconds
})
{
AppLoading
().
show
(
timeout:
Duration
(
seconds:
timeout
));
}
hideLoading
()
{
void
hideLoading
()
{
AppLoading
().
hide
();
}
}
lib/configs/api_paths.dart
View file @
4bd580fa
...
...
@@ -34,6 +34,7 @@ class APIPaths {//sandbox
static
const
String
getGames
=
"/campaign/api/v3.0/games"
;
static
const
String
verifyOrderProduct
=
"/order/api/v1.0/verify-product"
;
static
const
String
getGameDetail
=
"/campaign/api/v3.0/games/%@/play"
;
static
const
String
submitGameCard
=
"/campaign/api/v3.0/games/%@/%@/submit"
;
static
const
String
affiliateCategoryGetList
=
"/affiliateCategoryGetList/1.0.0"
;
static
const
String
affiliateBrandGetList
=
"/affiliateBrandGetList/1.0.0"
;
static
const
String
affiliateProductTopSale
=
"/affiliateProductTopSale/1.0.0"
;
...
...
@@ -110,4 +111,7 @@ class APIPaths {//sandbox
static
const
String
transactionHistoryGetList
=
"/transactionHistoryGetList/1.0.0"
;
static
const
String
transactionGetSummaryByDate
=
"/transactionGetSummaryByDate/1.0.0"
;
static
const
String
getOfflineBrand
=
"/user/api/v2.0/offline/brand"
;
static
const
String
pushNotificationDeviceUpdateToken
=
"/pushNotificationDeviceUpdateToken/1.0.0"
;
static
const
String
myProductMarkAsUsed
=
"/myProductMarkAsUsed/1.0.0"
;
static
const
String
myProductMarkAsNotUsedYet
=
"/myProductMarkAsNotUsedYet/1.0.0"
;
}
\ No newline at end of file
lib/configs/device_info.dart
View file @
4bd580fa
import
'dart:ffi'
as
MediaQuery
;
import
'dart:ui'
as
ui
;
import
'package:device_info_plus/device_info_plus.dart'
;
import
'package:flutter/foundation.dart'
;
import
'package:package_info_plus/package_info_plus.dart'
;
import
'package:shared_preferences/shared_preferences.dart'
;
import
'package:uuid/uuid.dart'
;
import
'dart:io'
show
Platform
;
class
DeviceDetails
{
final
String
?
hardwareType
;
// "Mobile" | "Tablet" | "Desktop" | "Web"
final
String
?
hardwareModel
;
// iPhone15,3 | iPad... | Samsung SM-... | ...
final
String
?
operatingSystem
;
// "iOS" | "Android" | "macOS" | ...
final
String
?
osVersion
;
// "17.6" | "14 (SDK 34)" | ...
final
String
?
userDeviceName
;
// iOS: tên máy; Android: fallback
final
String
?
appVersion
;
// build-name, ví dụ "3.2.6"
final
String
?
appBuildNumber
;
// build-number, ví dụ "326"
const
DeviceDetails
({
this
.
hardwareType
,
this
.
hardwareModel
,
this
.
operatingSystem
,
this
.
osVersion
,
this
.
userDeviceName
,
this
.
appVersion
,
this
.
appBuildNumber
,
});
Map
<
String
,
String
>
toMap
()
=>
{
'hardware_type'
:
hardwareType
??
'Unknown'
,
'hardware_model'
:
hardwareModel
??
'Unknown'
,
'operating_system'
:
operatingSystem
??
'Unknown'
,
'os_version'
:
osVersion
??
'Unknown'
,
'user_device_name'
:
userDeviceName
??
'Unknown'
,
'app_version'
:
appVersion
??
'Unknown'
,
'app_build_number'
:
appBuildNumber
??
'Unknown'
,
};
@override
String
toString
()
=>
toMap
().
toString
();
}
class
DeviceInfo
{
static
Future
<
String
>
getDeviceId
()
async
{
...
...
@@ -12,4 +51,84 @@ class DeviceInfo {
return
deviceId
;
}
static
Future
<
DeviceDetails
>
getDetails
()
async
{
final
deviceInfo
=
DeviceInfoPlugin
();
final
pkg
=
await
PackageInfo
.
fromPlatform
();
// OS + version
String
os
,
osVersion
=
''
;
if
(
kIsWeb
)
{
os
=
'Web'
;
final
web
=
await
deviceInfo
.
webBrowserInfo
;
osVersion
=
[
web
.
browserName
.
name
,
if
((
web
.
appVersion
??
''
).
isNotEmpty
)
'(
${web.appVersion}
)'
,
].
where
((
e
)
=>
e
.
toString
().
isNotEmpty
).
join
(
' '
);
}
else
if
(
Platform
.
isIOS
)
{
os
=
'iOS'
;
final
ios
=
await
deviceInfo
.
iosInfo
;
osVersion
=
ios
.
systemVersion
;
}
else
if
(
Platform
.
isAndroid
)
{
os
=
'Android'
;
final
and
=
await
deviceInfo
.
androidInfo
;
final
rel
=
and
.
version
.
release
;
final
sdk
=
and
.
version
.
sdkInt
.
toString
();
osVersion
=
sdk
.
isEmpty
?
rel
:
'
$rel
(SDK
$sdk
)'
;
}
else
{
os
=
Platform
.
operatingSystem
;
// macOS/linux/windows
osVersion
=
Platform
.
operatingSystemVersion
;
}
// Model + userDeviceName + iPad fallback
String
hardwareModel
=
'Unknown'
;
String
userDeviceName
=
''
;
bool
fallbackIsTablet
=
false
;
if
(
kIsWeb
)
{
hardwareModel
=
'Browser'
;
}
else
if
(
Platform
.
isIOS
)
{
final
ios
=
await
deviceInfo
.
iosInfo
;
hardwareModel
=
ios
.
utsname
.
machine
;
userDeviceName
=
ios
.
name
;
fallbackIsTablet
=
(
ios
.
model
.
toLowerCase
().
contains
(
'ipad'
))
||
(
ios
.
utsname
.
machine
.
toLowerCase
().
startsWith
(
'ipad'
));
}
else
if
(
Platform
.
isAndroid
)
{
final
and
=
await
deviceInfo
.
androidInfo
;
final
brand
=
and
.
brand
.
trim
();
final
model
=
and
.
model
.
trim
();
hardwareModel
=
[
brand
,
model
].
where
((
e
)
=>
e
.
isNotEmpty
).
join
(
' '
);
userDeviceName
=
and
.
device
;
fallbackIsTablet
=
false
;
}
final
hardwareType
=
_detectHardwareTypeWithoutContext
(
os:
os
,
fallbackIsTablet:
fallbackIsTablet
);
return
DeviceDetails
(
hardwareType:
hardwareType
,
hardwareModel:
hardwareModel
,
operatingSystem:
os
,
osVersion:
osVersion
,
userDeviceName:
userDeviceName
,
appVersion:
pkg
.
version
,
appBuildNumber:
pkg
.
buildNumber
,
);
}
/// Không cần BuildContext: dùng kích thước của view đầu tiên từ PlatformDispatcher.
/// Quy ước: shortestSide >= 600 logical pixels => Tablet.
static
String
_detectHardwareTypeWithoutContext
({
required
String
os
,
required
bool
fallbackIsTablet
})
{
if
(
os
==
'Web'
)
return
'Web'
;
if
(
os
!=
'iOS'
&&
os
!=
'Android'
)
return
'Desktop'
;
try
{
final
views
=
ui
.
PlatformDispatcher
.
instance
.
views
;
final
view
=
views
.
isNotEmpty
?
views
.
first
:
ui
.
PlatformDispatcher
.
instance
.
implicitView
;
if
(
view
!=
null
)
{
final
logicalSize
=
view
.
physicalSize
/
view
.
devicePixelRatio
;
final
shortest
=
logicalSize
.
shortestSide
;
return
shortest
>=
600
?
'Tablet'
:
'Mobile'
;
}
}
catch
(
_
)
{
// ignore and use fallback
}
return
fallbackIsTablet
?
'Tablet'
:
'Mobile'
;
}
}
lib/directional/directional_screen.dart
View file @
4bd580fa
...
...
@@ -8,6 +8,11 @@ import '../screen/webview/web_view_screen.dart';
import
'../shared/router_gage.dart'
;
import
'directional_action_type.dart'
;
class
Defines
{
static
const
String
actionType
=
'click_action_type'
;
static
const
String
actionParams
=
'click_action_param'
;
}
class
DirectionalScreen
{
final
String
?
clickActionType
;
final
String
?
clickActionParam
;
...
...
lib/firebase/notification_parse_payload.dart
0 → 100644
View file @
4bd580fa
import
'dart:convert'
;
/// Trả về Map<String, dynamic> từ payload của notification response.
/// - Hỗ trợ JSON thuần
/// - Hỗ trợ JSON bị URL-encode
/// - Fallback: parse kiểu querystring: a=1&b=2
Map
<
String
,
dynamic
>
parseNotificationPayload
(
String
?
payload
)
{
if
(
payload
==
null
)
return
{};
String
s
=
payload
.
trim
();
if
(
s
.
isEmpty
)
return
{};
// 1) Nếu là JSON thuần
Map
<
String
,
dynamic
>?
tryJson
(
String
text
)
{
try
{
final
v
=
jsonDecode
(
text
);
if
(
v
is
Map
)
{
// ép key về String
return
v
.
map
((
k
,
val
)
=>
MapEntry
(
k
.
toString
(),
val
));
}
}
catch
(
_
)
{}
return
null
;
}
// a) thử trực tiếp
var
m
=
tryJson
(
s
);
if
(
m
!=
null
)
return
m
;
// b) thử URL-decode rồi parse JSON lại
final
decoded
=
Uri
.
decodeFull
(
s
);
m
=
tryJson
(
decoded
);
if
(
m
!=
null
)
return
m
;
// 2) Fallback querystring: key1=val1&key2=val2
try
{
// Bỏ ngoặc {..} nếu server lỡ bọc
final
q
=
s
.
startsWith
(
'{'
)
&&
s
.
endsWith
(
'}'
)
?
s
.
substring
(
1
,
s
.
length
-
1
)
:
s
;
final
map
=
Uri
.
splitQueryString
(
q
,
encoding:
utf8
);
return
map
.
map
((
k
,
v
)
=>
MapEntry
(
k
,
v
));
}
catch
(
_
)
{
// 3) Fallback kiểu Dart Map.toString(): {a: 1, b: 2}
try
{
final
trimmed
=
s
.
trim
();
if
(
trimmed
.
startsWith
(
'{'
)
&&
trimmed
.
endsWith
(
'}'
))
{
final
inner
=
trimmed
.
substring
(
1
,
trimmed
.
length
-
1
).
trim
();
if
(
inner
.
isEmpty
)
return
<
String
,
dynamic
>{};
final
Map
<
String
,
dynamic
>
out
=
{};
// Tách theo dấu phẩy cấp 1 (không xử lý nested phức tạp)
for
(
final
pair
in
inner
.
split
(
','
))
{
final
idx
=
pair
.
indexOf
(
':'
);
if
(
idx
<=
0
)
continue
;
final
key
=
pair
.
substring
(
0
,
idx
).
trim
();
final
val
=
pair
.
substring
(
idx
+
1
).
trim
();
// Bỏ quote nếu có
String
normalize
(
String
v
)
{
if
((
v
.
startsWith
(
'"'
)
&&
v
.
endsWith
(
'"'
))
||
(
v
.
startsWith
(
'
\'
'
)
&&
v
.
endsWith
(
'
\'
'
)))
{
return
v
.
substring
(
1
,
v
.
length
-
1
);
}
return
v
;
}
final
nk
=
normalize
(
key
);
final
nv
=
normalize
(
val
);
out
[
nk
]
=
nv
;
}
if
(
out
.
isNotEmpty
)
return
out
;
}
}
catch
(
_
)
{}
// Cùng lắm trả string gốc
return
{
'payload'
:
s
};
}
}
lib/firebase/push_notification.dart
0 → 100644
View file @
4bd580fa
import
'package:flutter/foundation.dart'
;
import
'../directional/directional_action_type.dart'
;
import
'../directional/directional_screen.dart'
;
import
'package:firebase_messaging/firebase_messaging.dart'
;
enum
NotificationType
{
fcm
,
moengage
}
class
NotificationRouter
{
static
Future
<
void
>
handleRemoteMessage
(
RemoteMessage
m
)
async
{
final
data
=
PushNotification
(
info:
m
.
data
);
handleDirectionNotification
(
data
);
}
static
Future
<
void
>
handleDirectionNotification
(
PushNotification
notification
)
async
{
if
(
kDebugMode
)
{
print
(
'Parsed action type:
${notification.screenDirectional?.clickActionType}
'
);
print
(
'Parsed action param:
${notification.screenDirectional?.clickActionParam}
'
);
}
notification
.
screenDirectional
?.
begin
();
}
}
class
PushNotification
{
final
Map
<
String
,
dynamic
>
info
;
PushNotification
({
required
Map
info
})
:
info
=
info
.
map
((
k
,
v
)
=>
MapEntry
(
k
.
toString
(),
v
));
NotificationType
get
type
=>
info
.
containsKey
(
'moengage'
)
?
NotificationType
.
moengage
:
NotificationType
.
fcm
;
String
get
title
{
final
rootTitle
=
_asString
(
info
[
'title'
])
??
_asString
(
info
[
'gcm.notification.title'
]);
if
(
rootTitle
!=
null
)
return
rootTitle
;
final
aps
=
_asMap
(
info
[
'aps'
]);
final
alert
=
_asMap
(
aps
?[
'alert'
]);
return
_asString
(
alert
?[
'title'
])
??
''
;
}
String
get
body
{
final
rootBody
=
_asString
(
info
[
'body'
])
??
_asString
(
info
[
'gcm.notification.body'
]);
if
(
rootBody
!=
null
)
return
rootBody
;
final
aps
=
_asMap
(
info
[
'aps'
]);
final
alert
=
_asMap
(
aps
?[
'alert'
]);
return
_asString
(
alert
?[
'body'
])
??
''
;
}
String
?
get
timeString
=>
_asString
(
info
[
'time'
]);
String
?
get
id
=>
_asString
(
info
[
'notification_id'
]);
DirectionalScreen
?
get
screenDirectional
{
if
(
kDebugMode
)
{
print
(
'=== PARSING NOTIFICATION DATA ==='
);
print
(
'Raw info:
$info
'
);
}
String
?
name
;
String
?
param
;
final
extra
=
_asMap
(
info
[
'app_extra'
]);
if
(
kDebugMode
)
print
(
'App extra:
$extra
'
);
final
screenData
=
_asMap
(
extra
?[
'screenData'
]);
if
(
kDebugMode
)
print
(
'Screen data:
$screenData
'
);
if
(
screenData
!=
null
)
{
name
=
_asString
(
screenData
[
Defines
.
actionType
]);
param
=
_asString
(
screenData
[
Defines
.
actionParams
]);
if
(
kDebugMode
)
print
(
'From screenData - name:
$name
, param:
$param
'
);
}
else
{
name
=
_asString
(
info
[
Defines
.
actionType
]);
param
=
_asString
(
info
[
Defines
.
actionParams
]);
if
(
kDebugMode
)
print
(
'From info - name:
$name
, param:
$param
'
);
}
if
(
name
!=
null
||
param
!=
null
)
{
if
(
kDebugMode
)
print
(
'Building DirectionalScreen with name:
$name
, param:
$param
'
);
return
DirectionalScreen
.
build
(
clickActionType:
name
,
clickActionParam:
param
);
}
if
(
kDebugMode
)
{
print
(
'No action data found, using default notification screen with ID:
$id
'
);
print
(
'Title:
$title
, Body:
$body
'
);
}
return
DirectionalScreen
.
buildByName
(
name:
DirectionalScreenName
.
notification
,
clickActionParam:
id
);
// TODO handel title + body
}
// ===== Helpers =====
static
String
?
_asString
(
dynamic
v
)
=>
v
is
String
?
v
:
null
;
static
Map
<
String
,
dynamic
>?
_asMap
(
dynamic
v
)
{
if
(
v
is
Map
)
{
// ép key về String để thao tác dễ hơn
return
v
.
map
((
k
,
val
)
=>
MapEntry
(
k
.
toString
(),
val
));
}
return
null
;
}
}
\ No newline at end of file
Prev
1
2
3
Next
Write
Preview
Supports
Markdown
0%
Try again
or
attach a new file
.
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment