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
e41fc4fe
Commit
e41fc4fe
authored
Feb 27, 2025
by
DatHV
Browse files
init
parent
d87cb75e
Changes
104
Hide whitespace changes
Inline
Side-by-side
lib/dio_http_service/modify_request_interceptor.dart
0 → 100644
View file @
e41fc4fe
import
'package:dio/dio.dart'
;
import
'package:mypoint_flutter_app/preference/data_preference.dart'
;
class
ModifyRequestInterceptor
extends
Interceptor
{
@override
void
onRequest
(
RequestOptions
options
,
RequestInterceptorHandler
handler
)
{
String
authKey
=
'Authorization'
;
String
?
token
=
DataPreference
.
instance
.
token
;
if
(
token
!=
null
)
{
options
.
headers
[
authKey
]
=
"Bearer
$token
"
;
}
super
.
onRequest
(
options
,
handler
);
}
}
\ No newline at end of file
lib/extensions/context_extensions.dart
0 → 100644
View file @
e41fc4fe
import
'package:flutter/cupertino.dart'
;
extension
BuildContextAndAlert
on
BuildContext
{
showAlertDialog
(
String
message
)
{
showCupertinoDialog
(
context:
this
,
builder:
(
context
)
{
return
CupertinoAlertDialog
(
title:
Text
(
'Error'
),
content:
Text
(
message
),
actions:
<
Widget
>[
CupertinoDialogAction
(
isDefaultAction:
true
,
child:
Text
(
'OK'
),
onPressed:
()
{
Navigator
.
of
(
context
).
pop
();
},
)
],
);
},
);
}
}
\ No newline at end of file
lib/extensions/string_extension.dart
0 → 100644
View file @
e41fc4fe
extension
PhoneValidator
on
String
{
bool
isPhoneValid
()
{
return
RegExp
(
r'^0\d{9}$'
).
hasMatch
(
this
);
}
}
\ No newline at end of file
lib/main.dart
0 → 100644
View file @
e41fc4fe
import
'package:flutter/material.dart'
;
import
'package:get/get.dart'
;
import
'package:mypoint_flutter_app/splash_screen/splash_screen.dart'
;
import
'onboading/onboarding_screen.dart'
;
import
'onboading/onboarding_viewmodel.dart'
;
void
main
(
)
{
Get
.
put
(
OnboardingViewModel
());
runApp
(
const
MyApp
());
}
class
MyApp
extends
StatelessWidget
{
const
MyApp
({
super
.
key
});
@override
Widget
build
(
BuildContext
context
)
{
return
GetMaterialApp
(
debugShowCheckedModeBanner:
false
,
theme:
ThemeData
(
colorScheme:
ColorScheme
.
fromSwatch
(
primarySwatch:
Colors
.
deepPurple
),
primaryColor:
Colors
.
deepPurple
,
),
home:
OnboardingScreen
(),
//SplashScreen(),
);
}
}
lib/model/check_update_response_model.dart
0 → 100644
View file @
e41fc4fe
import
'package:json_annotation/json_annotation.dart'
;
part
'check_update_response_model.g.dart'
;
enum
UpdateStatus
{
force
,
suggest
,
none
}
@JsonSerializable
()
class
CheckUpdateResponseModel
{
@JsonKey
(
name:
'update_mode'
)
String
?
updateMode
;
@JsonKey
(
name:
'update_title'
)
String
?
updateTitle
;
@JsonKey
(
name:
'update_message'
)
String
?
updateMessage
;
@JsonKey
(
name:
'update_link'
)
String
?
updateLink
;
UpdateStatus
get
status
{
switch
(
updateMode
?.
toUpperCase
()
??
""
)
{
case
'NOW'
:
return
UpdateStatus
.
force
;
default
:
return
UpdateStatus
.
suggest
;
}
}
CheckUpdateResponseModel
({
this
.
updateMode
,
this
.
updateTitle
,
this
.
updateMessage
,
this
.
updateLink
,
});
factory
CheckUpdateResponseModel
.
fromJson
(
Map
<
String
,
dynamic
>
json
)
=>
_$CheckUpdateResponseModelFromJson
(
json
);
Map
<
String
,
dynamic
>
toJson
()
=>
_$CheckUpdateResponseModelToJson
(
this
);
}
lib/model/check_update_response_model.g.dart
0 → 100644
View file @
e41fc4fe
// GENERATED CODE - DO NOT MODIFY BY HAND
part of
'check_update_response_model.dart'
;
// **************************************************************************
// JsonSerializableGenerator
// **************************************************************************
CheckUpdateResponseModel
_$CheckUpdateResponseModelFromJson
(
Map
<
String
,
dynamic
>
json
,
)
=>
CheckUpdateResponseModel
(
updateMode:
json
[
'update_mode'
]
as
String
?,
updateTitle:
json
[
'update_title'
]
as
String
?,
updateMessage:
json
[
'update_message'
]
as
String
?,
updateLink:
json
[
'update_link'
]
as
String
?,
);
Map
<
String
,
dynamic
>
_$CheckUpdateResponseModelToJson
(
CheckUpdateResponseModel
instance
,
)
=>
<
String
,
dynamic
>{
'update_mode'
:
instance
.
updateMode
,
'update_title'
:
instance
.
updateTitle
,
'update_message'
:
instance
.
updateMessage
,
'update_link'
:
instance
.
updateLink
,
};
lib/model/update_response_model.dart
0 → 100644
View file @
e41fc4fe
import
'package:json_annotation/json_annotation.dart'
;
import
'package:mypoint_flutter_app/model/check_update_response_model.dart'
;
part
'update_response_model.g.dart'
;
@JsonSerializable
()
class
UpdateResponseModel
{
@JsonKey
(
name:
'update_request'
)
List
<
CheckUpdateResponseModel
?>?
updateRequest
;
UpdateResponseModel
({
this
.
updateRequest
});
factory
UpdateResponseModel
.
fromJson
(
Map
<
String
,
dynamic
>
json
)
=>
_$UpdateResponseModelFromJson
(
json
);
Map
<
String
,
dynamic
>
toJson
()
=>
_$UpdateResponseModelToJson
(
this
);
}
\ No newline at end of file
lib/model/update_response_model.g.dart
0 → 100644
View file @
e41fc4fe
// GENERATED CODE - DO NOT MODIFY BY HAND
part of
'update_response_model.dart'
;
// **************************************************************************
// JsonSerializableGenerator
// **************************************************************************
UpdateResponseModel
_$UpdateResponseModelFromJson
(
Map
<
String
,
dynamic
>
json
)
=>
UpdateResponseModel
(
updateRequest:
(
json
[
'update_request'
]
as
List
<
dynamic
>?)
?.
map
(
(
e
)
=>
e
==
null
?
null
:
CheckUpdateResponseModel
.
fromJson
(
e
as
Map
<
String
,
dynamic
>,
),
)
.
toList
(),
);
Map
<
String
,
dynamic
>
_$UpdateResponseModelToJson
(
UpdateResponseModel
instance
,
)
=>
<
String
,
dynamic
>{
'update_request'
:
instance
.
updateRequest
};
lib/model/update_response_object.dart
0 → 100644
View file @
e41fc4fe
import
'package:json_annotation/json_annotation.dart'
;
import
'package:mypoint_flutter_app/model/update_response_model.dart'
;
part
'update_response_object.g.dart'
;
@JsonSerializable
()
class
UpdateResponseObject
{
UpdateResponseModel
?
data
;
UpdateResponseObject
({
this
.
data
});
factory
UpdateResponseObject
.
fromJson
(
Map
<
String
,
dynamic
>
json
)
=>
_$UpdateResponseObjectFromJson
(
json
);
Map
<
String
,
dynamic
>
toJson
()
=>
_$UpdateResponseObjectToJson
(
this
);
}
\ No newline at end of file
lib/model/update_response_object.g.dart
0 → 100644
View file @
e41fc4fe
// GENERATED CODE - DO NOT MODIFY BY HAND
part of
'update_response_object.dart'
;
// **************************************************************************
// JsonSerializableGenerator
// **************************************************************************
UpdateResponseObject
_$UpdateResponseObjectFromJson
(
Map
<
String
,
dynamic
>
json
,
)
=>
UpdateResponseObject
(
data:
json
[
'data'
]
==
null
?
null
:
UpdateResponseModel
.
fromJson
(
json
[
'data'
]
as
Map
<
String
,
dynamic
>),
);
Map
<
String
,
dynamic
>
_$UpdateResponseObjectToJson
(
UpdateResponseObject
instance
,
)
=>
<
String
,
dynamic
>{
'data'
:
instance
.
data
};
lib/model/user_model.dart
0 → 100644
View file @
e41fc4fe
import
'../base/base_model.dart'
;
class
User
extends
BaseModel
{
final
int
id
;
final
String
username
;
final
String
?
name
;
final
String
?
email
;
final
String
?
phone
;
User
({
required
this
.
id
,
required
this
.
username
,
this
.
name
,
this
.
email
,
this
.
phone
,});
factory
User
.
fromJson
(
Map
<
String
,
dynamic
>
json
)
{
return
User
(
id:
json
[
'id'
],
username:
json
[
'username'
],
name:
json
[
'name'
],
email:
json
[
'email'
],
phone:
json
[
'phone'
],
);
}
}
\ No newline at end of file
lib/networking/api_service.dart
0 → 100644
View file @
e41fc4fe
import
'dart:io'
;
import
'package:dio/dio.dart'
;
import
'package:mypoint_flutter_app/configs/api_paths.dart'
;
import
'package:mypoint_flutter_app/model/check_update_response_model.dart'
;
import
'package:mypoint_flutter_app/networking/request_manager.dart'
;
class
ApiService
{
final
RequestManager
_requestManager
=
RequestManager
();
Future
<
CheckUpdateResponseModel
?>
checkUpdateWithRequestManager
()
async
{
String
version
=
Platform
.
version
;
try
{
Map
<
String
,
String
>
params
=
{
"operating_system"
:
"iOS"
,
"software_model"
:
"MyPoint"
,
"version"
:
version
,
"build_number"
:
"1"
,
};
final
response
=
await
_requestManager
.
request
(
method:
'POST'
,
path:
APIPaths
.
checkUpdate
);
return
CheckUpdateResponseModel
.
fromJson
(
response
.
data
);
}
catch
(
e
)
{
throw
Exception
(
'Failed to check update'
);
}
}
}
lib/networking/model_maker.dart
0 → 100644
View file @
e41fc4fe
typedef
Json
=
Map
<
String
,
dynamic
>;
abstract
class
Encodable
{
Json
toJson
();
}
abstract
class
Fillable
extends
Encodable
{
void
fill
(
Json
data
);
}
extension
JsonEncode
on
Json
{
Json
toJson
()
{
return
this
;
}
}
class
ModelMaker
{
static
T
?
makeFrom
<
T
extends
Encodable
>(
dynamic
json
)
{
try
{
switch
(
T
)
{
// case WorkerSiteResponse: return WorkerSiteResponse.fromJson(json as Json) as T;
// case LoginResponse: return LoginResponse.fromJson(json as Json) as T;
default
:
return
null
;
}
}
catch
(
e
)
{
print
(
"API ERROR parser:
${e.toString()}
"
);
return
null
;
}
}
}
extension
L
on
List
{
}
\ No newline at end of file
lib/networking/request_manager.dart
0 → 100644
View file @
e41fc4fe
import
'dart:io'
;
import
'package:dio/dio.dart'
;
import
'package:flutter/material.dart'
;
import
'../configs/api_paths.dart'
;
final
GlobalKey
<
NavigatorState
>
navigatorKey
=
GlobalKey
<
NavigatorState
>();
class
RequestManager
{
static
final
RequestManager
_instance
=
RequestManager
.
_internal
();
factory
RequestManager
()
=>
_instance
;
late
Dio
_dio
;
bool
_isErrorDialogShown
=
false
;
final
List
<
Future
Function
()>
_pendingRetries
=
[];
RequestManager
.
_internal
()
{
BaseOptions
options
=
BaseOptions
(
connectTimeout:
const
Duration
(
seconds:
10
),
receiveTimeout:
const
Duration
(
seconds:
10
),
responseType:
ResponseType
.
json
,
headers:
{
'Content-Type'
:
'application/json'
,
},
);
_dio
=
Dio
(
options
);
// interceptor handle errors
_dio
.
interceptors
.
add
(
InterceptorsWrapper
(
onRequest:
(
options
,
handler
)
{
// Log request
print
(
"REQUEST[
${options.method}
] =>
${options.baseUrl}${options.path}
"
);
return
handler
.
next
(
options
);
},
onResponse:
(
response
,
handler
)
{
// Log response
print
(
"RESPONSE[
${response.statusCode}
] =>
${response.data}
"
);
return
handler
.
next
(
response
);
},
onError:
(
DioException
error
,
handler
)
async
{
if
(
error
.
response
?.
statusCode
==
401
||
error
.
type
==
DioExceptionType
.
connectionTimeout
||
error
.
type
==
DioExceptionType
.
receiveTimeout
||
error
.
error
is
SocketException
)
{
_pendingRetries
.
add
(()
async
{
return
await
_retryRequest
(
error
.
requestOptions
);
});
if
(!
_isErrorDialogShown
)
{
_isErrorDialogShown
=
true
;
bool
retry
=
await
_showNetworkErrorAlert
();
if
(
retry
)
{
await
_retryPendingRequests
();
}
else
{
_pendingRetries
.
clear
();
}
_isErrorDialogShown
=
false
;
}
}
return
handler
.
next
(
error
);
},
));
}
// retry request từ RequestOptions
Future
<
Response
<
T
>>
_retryRequest
<
T
>(
RequestOptions
options
)
async
{
try
{
Response
<
T
>
response
=
await
_dio
.
request
<
T
>(
options
.
path
,
data:
options
.
data
,
queryParameters:
options
.
queryParameters
,
options:
Options
(
method:
options
.
method
,
headers:
options
.
headers
,
extra:
options
.
extra
,
),
);
return
response
;
}
catch
(
e
)
{
rethrow
;
}
}
// retry all request errors
Future
<
void
>
_retryPendingRequests
()
async
{
List
<
Future
Function
()>
retries
=
List
.
from
(
_pendingRetries
);
_pendingRetries
.
clear
();
await
Future
.
wait
(
retries
.
map
((
f
)
=>
f
()));
}
Future
<
bool
>
_showNetworkErrorAlert
()
async
{
BuildContext
?
context
=
navigatorKey
.
currentState
?.
overlay
?.
context
;
if
(
context
==
null
)
return
false
;
return
showDialog
<
bool
>(
context:
context
,
barrierDismissible:
false
,
builder:
(
context
)
=>
AlertDialog
(
title:
Text
(
"Lỗi mạng"
),
content:
Text
(
"Đã xảy ra lỗi mạng hoặc xác thực. Bạn có muốn thử lại các request lỗi không?"
),
actions:
[
TextButton
(
onPressed:
()
=>
Navigator
.
of
(
context
).
pop
(
false
),
child:
Text
(
"Hủy"
),
),
TextButton
(
onPressed:
()
=>
Navigator
.
of
(
context
).
pop
(
true
),
child:
Text
(
"Thử lại"
),
),
],
),
).
then
((
value
)
=>
value
??
false
);
}
/// [cancelToken]: Token hủy request.
/// [converter]: Hàm chuyển đổi từ Map<String, dynamic> sang model T (nếu T kế thừa BaseModel).
Future
<
T
>
request
<
T
>({
String
?
host
,
required
String
path
,
required
String
method
,
Map
<
String
,
dynamic
>?
queryParameters
,
dynamic
data
,
Map
<
String
,
dynamic
>?
headers
,
CancelToken
?
cancelToken
,
T
Function
(
Map
<
String
,
dynamic
>
json
)?
converter
,
})
async
{
_dio
.
options
.
baseUrl
=
host
??
APIPaths
.
baseUrl
;
if
(
headers
!=
null
)
{
_dio
.
options
.
headers
.
addAll
(
headers
);
}
try
{
Response
response
=
await
_dio
.
request
(
path
,
data:
data
,
queryParameters:
queryParameters
,
options:
Options
(
method:
method
),
cancelToken:
cancelToken
,
);
if
(
converter
!=
null
&&
response
.
data
is
Map
<
String
,
dynamic
>)
{
return
converter
(
response
.
data
as
Map
<
String
,
dynamic
>);
}
return
response
.
data
as
T
;
}
on
DioException
catch
(
e
)
{
throw
Exception
(
"Request error:
${e.message}
"
);
}
}
}
lib/networking/restful_api.dart
0 → 100644
View file @
e41fc4fe
import
'package:dio/dio.dart'
;
import
'package:flutter/foundation.dart'
;
import
'package:mypoint_flutter_app/base/base_response_model.dart'
;
import
'../configs/callbacks.dart'
;
import
'../configs/constants.dart'
;
import
'model_maker.dart'
;
enum
Method
{
GET
,
POST
,
PUT
}
class
RestfulAPIClient
{
final
Dio
_dio
;
RestfulAPIClient
(
this
.
_dio
);
Json
header
=
{};
Future
<
BaseResponseModel
<
T
>>
requestNormal
<
T
>(
String
path
,
Method
method
,
Json
params
,
CallbackReturn
<
T
,
dynamic
>
parser
)
async
{
final
result
=
await
request
<
BaseResponseModel
<
T
>>(
path
,
method
,
params
,
(
data
)
{
return
BaseResponseModel
<
T
>.
fromJson
(
data
,
(
json
)
=>
parser
(
json
));
});
return
result
??
BaseResponseModel
<
T
>(
errorMessage:
Constants
.
commonError
);
}
Future
<
T
?>
request
<
T
>(
String
path
,
Method
method
,
Json
params
,
CallbackReturn
<
T
,
Json
>
parser
)
async
{
final
isGet
=
method
==
Method
.
GET
;
Json
query
=
isGet
?
params
:
{};
Json
body
=
!
isGet
?
params
:
{};
final
option
=
Options
(
method:
method
.
name
)
.
compose
(
_dio
.
options
,
path
,
queryParameters:
query
,
data:
body
,
);
try
{
final
result
=
await
_dio
.
fetch
<
Map
<
String
,
dynamic
>>(
option
);
final
json
=
result
.
data
;
if
(
json
==
null
)
return
null
;
return
parser
(
json
);
}
on
DioException
catch
(
e
)
{
_print
(
e
.
toString
());
final
data
=
e
.
response
?.
data
;
if
(
data
is
Json
)
{
try
{
return
parser
(
data
);
}
catch
(
e
)
{
_print
(
e
.
toString
());
return
null
;
}
}
else
{
return
null
;
}
}
catch
(
e
)
{
_print
(
e
.
toString
());
return
null
;
}
}
void
_print
(
String
error
)
{
if
(
kDebugMode
)
{
print
(
"===================
\n
API Error Parse false
${error}
\n
================"
);
}
}
}
\ No newline at end of file
lib/networking/restful_api_request.dart
0 → 100644
View file @
e41fc4fe
import
'dart:io'
;
import
'package:mypoint_flutter_app/configs/api_paths.dart'
;
import
'package:mypoint_flutter_app/base/base_response_model.dart'
;
import
'package:mypoint_flutter_app/networking/restful_api.dart'
;
import
'../model/update_response_object.dart'
;
import
'model_maker.dart'
;
extension
RestfullAPIClientAllApi
on
RestfulAPIClient
{
Future
<
BaseResponseModel
<
UpdateResponseObject
>>
checkUpdateApp
()
async
{
String
version
=
Platform
.
version
;
final
body
=
{
"operating_system"
:
"iOS"
,
"software_model"
:
"MyPoint"
,
"version"
:
version
,
"build_number"
:
"1"
,};
return
requestNormal
(
APIPaths
.
checkUpdate
,
Method
.
POST
,
body
,
(
data
)
=>
UpdateResponseObject
.
fromJson
(
data
as
Json
));
}
}
lib/onboading/onboarding_screen.dart
0 → 100644
View file @
e41fc4fe
import
'package:flutter/material.dart'
;
import
'package:flutter_widget_from_html/flutter_widget_from_html.dart'
;
// Hiển thị HTML
import
'package:get/get.dart'
;
import
'onboarding_viewmodel.dart'
;
class
OnboardingScreen
extends
StatelessWidget
{
final
OnboardingViewModel
controller
=
Get
.
find
<
OnboardingViewModel
>();
final
FocusNode
_focusNode
=
FocusNode
();
void
hideKeyboard
()
{
FocusScope
.
of
(
Get
.
context
!).
unfocus
();
}
@override
Widget
build
(
BuildContext
context
)
{
return
GestureDetector
(
onTap:
hideKeyboard
,
child:
Scaffold
(
resizeToAvoidBottomInset:
false
,
body:
Stack
(
children:
[
/// 📌 Hiển thị background từ API (hoặc ảnh mặc định)
Obx
(()
=>
Positioned
.
fill
(
child:
controller
.
backgroundUrl
.
value
.
isNotEmpty
?
Image
.
network
(
controller
.
backgroundUrl
.
value
,
fit:
BoxFit
.
cover
)
:
Image
.
asset
(
"assets/images/bg_onboarding.png"
,
fit:
BoxFit
.
cover
),
)),
/// 📌 Nội dung chính
AnimatedPadding
(
duration:
const
Duration
(
milliseconds:
300
),
padding:
EdgeInsets
.
only
(
bottom:
MediaQuery
.
of
(
context
).
viewInsets
.
bottom
),
child:
Column
(
mainAxisAlignment:
MainAxisAlignment
.
end
,
children:
[
Container
(
padding:
const
EdgeInsets
.
symmetric
(
horizontal:
20
,
vertical:
30
),
decoration:
const
BoxDecoration
(
color:
Colors
.
redAccent
,
borderRadius:
BorderRadius
.
only
(
topLeft:
Radius
.
circular
(
30
),
topRight:
Radius
.
circular
(
30
),
),
),
child:
Column
(
crossAxisAlignment:
CrossAxisAlignment
.
start
,
mainAxisSize:
MainAxisSize
.
min
,
children:
[
/// 📌 Tiêu đề (Hiển thị nội dung HTML từ API hoặc mặc định)
Obx
(()
=>
Visibility
(
visible:
!
_focusNode
.
hasFocus
,
child:
HtmlWidget
(
controller
.
contentHtml
.
value
.
isNotEmpty
?
controller
.
contentHtml
.
value
:
"""<h2 style="
color:
white
;
">Tiêu điểm dễ - Trừ tiền mê</h2>
<p style="
color:
white
;
">Đừng bỏ lỡ cơ hội tích tới 30% tất cả giao dịch viễn thông
của các nhà mạng và đổi phiếu giảm giá tại hơn 200 thương hiệu được yêu thích nhất.</p>"""
,
),
)),
/// 📌 Ô nhập số điện thoại
TextField
(
focusNode:
_focusNode
,
keyboardType:
TextInputType
.
phone
,
style:
const
TextStyle
(
color:
Colors
.
black
),
decoration:
InputDecoration
(
filled:
true
,
fillColor:
Colors
.
white
,
hintText:
"Nhập số điện thoại"
,
hintStyle:
const
TextStyle
(
color:
Colors
.
grey
),
border:
OutlineInputBorder
(
borderRadius:
BorderRadius
.
circular
(
10
),
borderSide:
BorderSide
.
none
,
),
prefixIcon:
const
Icon
(
Icons
.
phone
,
color:
Colors
.
black
),
),
onChanged:
controller
.
updatePhoneNumber
,
),
const
SizedBox
(
height:
20
),
/// 📌 Nút Tiếp Tục
Obx
(()
=>
SizedBox
(
width:
double
.
infinity
,
child:
ElevatedButton
(
onPressed:
controller
.
isButtonEnabled
?
()
{
debugPrint
(
"Số điện thoại:
${controller.phoneNumber.value}
"
);
}
:
null
,
style:
ElevatedButton
.
styleFrom
(
padding:
const
EdgeInsets
.
symmetric
(
vertical:
15
),
backgroundColor:
controller
.
isButtonEnabled
?
Colors
.
white
:
Colors
.
white54
,
shape:
RoundedRectangleBorder
(
borderRadius:
BorderRadius
.
circular
(
10
),
),
),
child:
Text
(
"Tiếp tục"
,
style:
TextStyle
(
color:
controller
.
isButtonEnabled
?
Colors
.
redAccent
:
Colors
.
grey
,
fontSize:
18
,
fontWeight:
FontWeight
.
bold
,
),
),
),
)),
const
SizedBox
(
height:
10
),
/// 📌 Checkbox + Điều khoản sử dụng + Chính sách bảo mật
Row
(
crossAxisAlignment:
CrossAxisAlignment
.
center
,
children:
[
Obx
(()
=>
Checkbox
(
value:
controller
.
isChecked
.
value
,
onChanged:
controller
.
toggleCheckbox
,
activeColor:
Colors
.
white
,
checkColor:
Colors
.
red
,
side:
const
BorderSide
(
color:
Colors
.
white
,
width:
2
),
)),
Expanded
(
child:
RichText
(
text:
TextSpan
(
style:
const
TextStyle
(
color:
Colors
.
white70
,
fontSize:
12
),
children:
[
const
TextSpan
(
text:
"Bằng việc tiếp tục, bạn đã đọc và đồng ý với "
),
WidgetSpan
(
child:
GestureDetector
(
onTap:
()
=>
debugPrint
(
"Điều khoản sử dụng"
),
child:
const
Text
(
"Điều khoản sử dụng"
,
style:
TextStyle
(
decoration:
TextDecoration
.
underline
,
color:
Colors
.
white
,
),
),
),
),
const
TextSpan
(
text:
" và "
),
WidgetSpan
(
child:
GestureDetector
(
onTap:
()
=>
debugPrint
(
"Chính sách bảo mật"
),
child:
const
Text
(
"Chính sách bảo mật"
,
style:
TextStyle
(
decoration:
TextDecoration
.
underline
,
color:
Colors
.
white
,
),
),
),
),
const
TextSpan
(
text:
" của MyPoint"
),
],
),
),
),
],
),
],
),
),
],
),
),
],
),
),
);
}
}
lib/onboading/onboarding_viewmodel.dart
0 → 100644
View file @
e41fc4fe
import
'package:get/get.dart'
;
import
'package:mypoint_flutter_app/extensions/string_extension.dart'
;
class
OnboardingViewModel
extends
GetxController
{
var
phoneNumber
=
""
.
obs
;
var
isChecked
=
false
.
obs
;
var
backgroundUrl
=
""
.
obs
;
// URL ảnh nền
var
contentHtml
=
""
.
obs
;
// Nội dung HTML
bool
get
isButtonEnabled
=>
isChecked
.
value
&&
phoneNumber
.
value
.
isPhoneValid
();
/// Cập nhật số điện thoại
void
updatePhoneNumber
(
String
value
)
{
phoneNumber
.
value
=
value
;
}
/// Cập nhật trạng thái checkbox
void
toggleCheckbox
(
bool
?
value
)
{
isChecked
.
value
=
value
!;
}
/// Gọi API để lấy dữ liệu
Future
<
void
>
fetchOnboardingContent
()
async
{
var
data
=
{
""
:
""
};
backgroundUrl
.
value
=
data
[
"url_background"
]
??
backgroundUrl
.
value
;
// Nếu API có ảnh nền, thay thế
contentHtml
.
value
=
data
[
"content"
]
??
contentHtml
.
value
;
// Nếu API có nội dung, thay thế
}
@override
void
onInit
()
{
super
.
onInit
();
fetchOnboardingContent
();
// Gọi API khi khởi tạo ViewModel
}
}
lib/preference/data_preference.dart
0 → 100644
View file @
e41fc4fe
class
DataPreference
{
static
final
DataPreference
_instance
=
DataPreference
.
_internal
();
static
DataPreference
get
instance
=>
_instance
;
DataPreference
.
_internal
();
String
?
get
token
=>
""
;
}
\ No newline at end of file
lib/shared/e_grab_navigate.dart
0 → 100644
View file @
e41fc4fe
import
'package:get/get.dart'
;
import
'package:mypoint_flutter_app/extensions/context_extensions.dart'
;
mixin
navigateHelper
{
void
showPopupErrorMessage
(
String
message
){
Get
.
context
?.
showAlertDialog
(
message
);
}
}
\ No newline at end of file
Prev
1
2
3
4
5
6
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