Toggle navigation
Toggle navigation
This project
Loading...
Sign in
flutter_package
/
fluttertpc_get
Go to a project
Toggle navigation
Projects
Groups
Snippets
Help
Toggle navigation pinning
Project
Activity
Repository
Pipelines
Graphs
Issues
0
Merge Requests
0
Wiki
Network
Create a new issue
Builds
Commits
Authored by
Jonatas
2020-08-24 22:48:21 -0300
Browse Files
Options
Browse Files
Download
Email Patches
Plain Diff
Commit
b2ed3c7fdc4f43c5778010791ac15d72bbb28690
b2ed3c7f
1 parent
4e894f14
update to 3.5.0
Show whitespace changes
Inline
Side-by-side
Showing
10 changed files
with
227 additions
and
62 deletions
CHANGELOG.md
lib/src/core/get_interface.dart
lib/src/instance/get_instance.dart
lib/src/navigation/bottomsheet/bottomsheet.dart
lib/src/navigation/dialog/dialog_route.dart
lib/src/navigation/extension_navigation.dart
lib/src/navigation/root/root_widget.dart
lib/src/navigation/routes/default_route.dart
lib/src/navigation/routes/observers/route_observer.dart
pubspec.yaml
CHANGELOG.md
View file @
b2ed3c7
## [3.5.0]
-
Added logwritter (@stefandevo)
-
Added responsiveValue (@juanjoseleca)
-
Fixed ghost url for snackbar, bottomsheets, and dialogs and unnamed navigation.
## [3.4.6]
-
Fix TextField dispose throw on last Flutter hotfix
...
...
@@ -7,6 +12,7 @@
## [3.4.4]
-
Fix exception 'isInit called null' when tags are used in conjunction with dependencies. (@djade007)
-
Fix typos (@tiagocpeixoto)
## [3.4.3]
-
Fix onInit fired only first time
...
...
lib/src/core/get_interface.dart
View file @
b2ed3c7
...
...
@@ -16,7 +16,6 @@ import '../../utils.dart';
abstract
class
GetInterface
{
bool
defaultPopGesture
=
GetPlatform
.
isIOS
;
bool
defaultOpaqueRoute
=
true
;
bool
forceRouteName
=
true
;
Transition
defaultTransition
;
Duration
defaultDurationTransition
=
Duration
(
milliseconds:
400
);
bool
defaultGlobalState
=
true
;
...
...
lib/src/instance/get_instance.dart
View file @
b2ed3c7
import
'package:get/src/core/log.dart'
;
import
'package:get/src/navigation/root/smart_management.dart'
;
import
'package:get/src/state_manager/rx/rx_interface.dart'
;
import
'package:get/src/utils/queue/get_queue.dart'
;
class
GetConfig
{
static
SmartManagement
smartManagement
=
SmartManagement
.
full
;
...
...
@@ -196,6 +197,14 @@ class GetInstance {
return
true
;
}
static
GetQueue
queue
=
GetQueue
();
// Future<bool> delete<S>({String tag, String key, bool force = false}) async {
// final s = await queue
// .add<bool>(() async => dele<S>(tag: tag, key: key, force: force));
// return s;
// }
/// Delete class instance on [S] and clean memory
Future
<
bool
>
delete
<
S
>({
String
tag
,
String
key
,
bool
force
=
false
})
async
{
String
newKey
;
...
...
@@ -205,8 +214,10 @@ class GetInstance {
newKey
=
key
;
}
return
queue
.
add
<
bool
>(()
async
{
if
(!
_singl
.
containsKey
(
newKey
))
{
GetConfig
.
log
(
'Instance
$newKey
not found'
,
isError:
true
);
GetConfig
.
log
(
'[GETX] Instance
$newKey
already been removed.'
,
isError:
true
);
return
false
;
}
...
...
@@ -235,6 +246,7 @@ class GetInstance {
}
// _routesKey?.remove(key);
return
true
;
});
}
/// check if instance is registered
...
...
lib/src/navigation/bottomsheet/bottomsheet.dart
View file @
b2ed3c7
...
...
@@ -16,6 +16,7 @@ class GetModalBottomSheetRoute<T> extends PopupRoute<T> {
@required
this
.
isScrollControlled
,
RouteSettings
settings
,
})
:
assert
(
isScrollControlled
!=
null
),
name
=
"BOTTOMSHEET:
${builder.hashCode}
"
,
assert
(
isDismissible
!=
null
),
assert
(
enableDrag
!=
null
),
super
(
settings:
settings
);
...
...
@@ -30,6 +31,7 @@ class GetModalBottomSheetRoute<T> extends PopupRoute<T> {
final
Color
modalBarrierColor
;
final
bool
isDismissible
;
final
bool
enableDrag
;
final
String
name
;
// remove safearea from top
final
bool
removeTop
;
...
...
lib/src/navigation/dialog/dialog_route.dart
0 → 100644
View file @
b2ed3c7
import
'package:flutter/widgets.dart'
;
import
'../../../instance_manager.dart'
;
import
'../../../route_manager.dart'
;
class
GetDialogRoute
<
T
>
extends
PopupRoute
<
T
>
{
GetDialogRoute
({
@required
RoutePageBuilder
pageBuilder
,
bool
barrierDismissible
=
true
,
String
barrierLabel
,
Color
barrierColor
=
const
Color
(
0x80000000
),
Duration
transitionDuration
=
const
Duration
(
milliseconds:
200
),
RouteTransitionsBuilder
transitionBuilder
,
RouteSettings
settings
,
})
:
assert
(
barrierDismissible
!=
null
),
widget
=
pageBuilder
,
name
=
"DIALOG:
${pageBuilder.hashCode}
"
,
_barrierDismissible
=
barrierDismissible
,
_barrierLabel
=
barrierLabel
,
_barrierColor
=
barrierColor
,
_transitionDuration
=
transitionDuration
,
_transitionBuilder
=
transitionBuilder
,
super
(
settings:
settings
);
final
RoutePageBuilder
widget
;
@override
bool
get
barrierDismissible
=>
_barrierDismissible
;
final
bool
_barrierDismissible
;
final
String
name
;
@override
void
dispose
()
{
if
(
GetConfig
.
smartManagement
!=
SmartManagement
.
onlyBuilder
)
{
WidgetsBinding
.
instance
.
addPostFrameCallback
(
(
_
)
=>
GetInstance
().
removeDependencyByRoute
(
name
));
}
super
.
dispose
();
}
@override
String
get
barrierLabel
=>
_barrierLabel
;
final
String
_barrierLabel
;
@override
Color
get
barrierColor
=>
_barrierColor
;
final
Color
_barrierColor
;
@override
Duration
get
transitionDuration
=>
_transitionDuration
;
final
Duration
_transitionDuration
;
final
RouteTransitionsBuilder
_transitionBuilder
;
@override
Widget
buildPage
(
BuildContext
context
,
Animation
<
double
>
animation
,
Animation
<
double
>
secondaryAnimation
)
{
return
Semantics
(
child:
widget
(
context
,
animation
,
secondaryAnimation
),
scopesRoute:
true
,
explicitChildNodes:
true
,
);
}
@override
Widget
buildTransitions
(
BuildContext
context
,
Animation
<
double
>
animation
,
Animation
<
double
>
secondaryAnimation
,
Widget
child
)
{
if
(
_transitionBuilder
==
null
)
{
return
FadeTransition
(
opacity:
CurvedAnimation
(
parent:
animation
,
curve:
Curves
.
linear
,
),
child:
child
);
}
// Some default transition
return
_transitionBuilder
(
context
,
animation
,
secondaryAnimation
,
child
);
}
}
...
...
lib/src/navigation/extension_navigation.dart
View file @
b2ed3c7
...
...
@@ -4,6 +4,7 @@ import 'package:get/src/core/get_interface.dart';
import
'package:get/instance_manager.dart'
;
import
'package:get/route_manager.dart'
;
import
'package:get/src/core/log.dart'
;
import
'dialog/dialog_route.dart'
;
import
'root/parse_route.dart'
;
import
'routes/bindings_interface.dart'
;
...
...
@@ -46,17 +47,17 @@ extension GetNavigation on GetInterface {
bool
preventDuplicates
=
true
,
bool
popGesture
,
})
{
if
(
preventDuplicates
&&
'/
${page.runtimeType}
'
==
currentRoute
&&
forceRouteName
)
{
String
routename
=
"/
${page.runtimeType.toString()}
"
;
if
(
preventDuplicates
&&
routename
==
currentRoute
)
{
return
null
;
}
return
global
(
id
).
currentState
.
push
(
GetPageRoute
(
opaque:
opaque
??
true
,
page:
()
=>
page
,
routeName:
routename
,
settings:
RouteSettings
(
name:
forceRouteName
?
'/
${page
.runtimeType}
'
:
''
,
// name: forceRouteName ? '${a
.runtimeType}' : '',
arguments:
arguments
,
),
popGesture:
popGesture
??
defaultPopGesture
,
...
...
@@ -320,18 +321,16 @@ extension GetNavigation on GetInterface {
preventDuplicates
=
true
,
Duration
duration
,
})
{
if
(
preventDuplicates
&&
'/
${page.runtimeType}
'
==
currentRoute
&&
forceRouteName
)
{
String
routename
=
"/
${page.runtimeType.toString()}
"
;
if
(
preventDuplicates
&&
routename
==
currentRoute
)
{
return
null
;
}
return
global
(
id
).
currentState
.
pushReplacement
(
GetPageRoute
(
opaque:
opaque
??
true
,
page:
()
=>
page
,
binding:
binding
,
settings:
RouteSettings
(
name:
forceRouteName
?
'/
${page.runtimeType}
'
:
''
,
arguments:
arguments
),
settings:
RouteSettings
(
arguments:
arguments
),
routeName:
routename
,
fullscreenDialog:
fullscreenDialog
,
popGesture:
popGesture
??
defaultPopGesture
,
transition:
transition
??
defaultTransition
,
...
...
@@ -380,16 +379,17 @@ extension GetNavigation on GetInterface {
})
{
var
route
=
(
Route
<
dynamic
>
rota
)
=>
false
;
String
routename
=
"/
${page.runtimeType.toString()}
"
;
return
global
(
id
).
currentState
.
pushAndRemoveUntil
(
GetPageRoute
(
opaque:
opaque
??
true
,
popGesture:
popGesture
??
defaultPopGesture
,
page:
()
=>
page
,
binding:
binding
,
settings:
RouteSettings
(
name:
forceRouteName
?
'/
${page.runtimeType}
'
:
''
,
arguments:
arguments
),
settings:
RouteSettings
(
arguments:
arguments
),
fullscreenDialog:
fullscreenDialog
,
routeName:
routename
,
transition:
transition
??
defaultTransition
,
transitionDuration:
duration
??
defaultDurationTransition
,
),
...
...
@@ -398,44 +398,76 @@ extension GetNavigation on GetInterface {
/// Show a dialog
Future
<
T
>
dialog
<
T
>(
Widget
child
,
{
Widget
widget
,
{
bool
barrierDismissible
=
true
,
Color
barrierColor
,
bool
useSafeArea
=
true
,
bool
useRootNavigator
=
true
,
// RouteSettings routeSettings
RouteSettings
routeSettings
,
})
{
return
showDialog
(
assert
(
widget
!=
null
);
assert
(
barrierDismissible
!=
null
);
assert
(
useSafeArea
!=
null
);
assert
(
useRootNavigator
!=
null
);
assert
(
debugCheckHasMaterialLocalizations
(
context
));
final
ThemeData
theme
=
Theme
.
of
(
context
,
shadowThemeOnly:
true
);
return
generalDialog
(
pageBuilder:
(
BuildContext
buildContext
,
Animation
<
double
>
animation
,
Animation
<
double
>
secondaryAnimation
)
{
final
Widget
pageChild
=
widget
;
Widget
dialog
=
Builder
(
builder:
(
BuildContext
context
)
{
return
theme
!=
null
?
Theme
(
data:
theme
,
child:
pageChild
)
:
pageChild
;
});
if
(
useSafeArea
)
{
dialog
=
SafeArea
(
child:
dialog
);
}
return
dialog
;
},
barrierDismissible:
barrierDismissible
,
useRootNavigator:
useRootNavigator
,
routeSettings:
RouteSettings
(
name:
'dialog'
),
context:
overlayContext
,
builder:
(
_
)
{
return
child
;
barrierLabel:
MaterialLocalizations
.
of
(
context
).
modalBarrierDismissLabel
,
barrierColor:
barrierColor
??
Colors
.
black54
,
transitionDuration:
const
Duration
(
milliseconds:
150
),
transitionBuilder:
(
context
,
animation
,
secondaryAnimation
,
child
)
{
return
FadeTransition
(
opacity:
CurvedAnimation
(
parent:
animation
,
curve:
Curves
.
easeOut
,
),
child:
child
,
);
},
useRootNavigator:
useRootNavigator
,
routeSettings:
routeSettings
,
);
}
/// Api from showGeneralDialog with no context
Future
<
T
>
generalDialog
<
T
>({
@required
RoutePageBuilder
pageBuilder
,
String
barrierLabel
=
"Dismiss"
,
bool
barrierDismissible
=
true
,
bool
barrierDismissible
=
false
,
String
barrierLabel
,
Color
barrierColor
=
const
Color
(
0x80000000
),
Duration
transitionDuration
=
const
Duration
(
milliseconds:
200
),
RouteTransitionsBuilder
transitionBuilder
,
bool
useRootNavigator
=
true
,
RouteSettings
routeSettings
,
})
{
return
showGeneralDialog
(
assert
(
pageBuilder
!=
null
);
assert
(
useRootNavigator
!=
null
);
assert
(!
barrierDismissible
||
barrierLabel
!=
null
);
return
Navigator
.
of
(
overlayContext
,
rootNavigator:
useRootNavigator
)
.
push
<
T
>(
GetDialogRoute
<
T
>(
pageBuilder:
pageBuilder
,
barrierDismissible:
barrierDismissible
,
barrierLabel:
barrierLabel
,
barrierColor:
barrierColor
,
transitionDuration:
transitionDuration
,
transitionBuilder:
transitionBuilder
,
useRootNavigator:
useRootNavigator
,
routeSettings:
RouteSettings
(
name:
'dialog'
),
context:
overlayContext
,
);
settings:
routeSettings
,
));
}
Future
<
T
>
defaultDialog
<
T
>({
...
...
@@ -567,7 +599,7 @@ extension GetNavigation on GetInterface {
clipBehavior:
clipBehavior
,
isDismissible:
isDismissible
,
modalBarrierColor:
barrierColor
,
settings:
RouteSettings
(
name:
"bottomsheet"
)
,
settings:
settings
,
enableDrag:
enableDrag
,
));
}
...
...
lib/src/navigation/root/root_widget.dart
View file @
b2ed3c7
...
...
@@ -50,7 +50,6 @@ class GetMaterialApp extends StatelessWidget {
this
.
defaultTransition
,
// this.actions,
this
.
getPages
,
this
.
forceRouteName
=
true
,
this
.
opaqueRoute
,
this
.
enableLog
,
this
.
logWriterCallback
,
...
...
@@ -103,7 +102,6 @@ class GetMaterialApp extends StatelessWidget {
final
Function
(
Routing
)
routingCallback
;
final
Transition
defaultTransition
;
final
bool
opaqueRoute
;
final
bool
forceRouteName
;
final
VoidCallback
onInit
;
final
VoidCallback
onDispose
;
final
bool
enableLog
;
...
...
@@ -191,8 +189,6 @@ class GetMaterialApp extends StatelessWidget {
if
(
fallbackLocale
!=
null
)
Get
.
fallbackLocale
=
fallbackLocale
;
Get
.
forceRouteName
=
forceRouteName
;
if
(
translations
!=
null
)
{
Get
.
translations
=
translations
.
keys
;
}
else
if
(
translationsKeys
!=
null
)
{
...
...
@@ -297,6 +293,7 @@ extension Trans on String {
Get
.
translations
[
Get
.
fallbackLocale
.
languageCode
].
containsKey
(
this
))
{
return
Get
.
translations
[
Get
.
fallbackLocale
.
languageCode
][
this
];
}
return
this
;
}
else
{
return
this
;
}
...
...
lib/src/navigation/routes/default_route.dart
View file @
b2ed3c7
...
...
@@ -27,6 +27,7 @@ class GetPageRoute<T> extends PageRoute<T> {
this
.
barrierColor
,
this
.
binding
,
this
.
bindings
,
this
.
routeName
,
this
.
page
,
this
.
barrierLabel
,
this
.
maintainState
=
true
,
...
...
@@ -42,6 +43,8 @@ class GetPageRoute<T> extends PageRoute<T> {
final
GetPageBuilder
page
;
final
String
routeName
;
final
CustomTransition
customTransition
;
final
Bindings
binding
;
...
...
@@ -119,8 +122,9 @@ class GetPageRoute<T> extends PageRoute<T> {
element
.
dependencies
();
}
}
GetConfig
.
currentRoute
=
settings
.
name
;
return
page
();
final
pageWidget
=
page
();
GetConfig
.
currentRoute
=
settings
.
name
??
routeName
;
return
pageWidget
;
}
static
bool
isPopGestureInProgress
(
PageRoute
<
dynamic
>
route
)
{
...
...
@@ -389,8 +393,8 @@ class GetPageRoute<T> extends PageRoute<T> {
@override
void
dispose
()
{
if
(
GetConfig
.
smartManagement
!=
SmartManagement
.
onlyBuilder
)
{
WidgetsBinding
.
instance
.
addPostFrameCallback
(
(
_
)
=>
GetInstance
().
removeDependencyByRoute
(
"
${settings.name}
"
));
WidgetsBinding
.
instance
.
addPostFrameCallback
((
_
)
=>
GetInstance
()
.
removeDependencyByRoute
(
"
${settings?.name ?? routeName}
"
));
}
super
.
dispose
();
}
...
...
lib/src/navigation/routes/observers/route_observer.dart
View file @
b2ed3c7
import
'package:flutter/widgets.dart'
;
import
'package:get/route_manager.dart'
;
import
'package:get/src/instance/get_instance.dart'
;
import
'package:get/src/navigation/dialog/dialog_route.dart'
;
import
'package:get/src/navigation/routes/default_route.dart'
;
import
'package:get/src/navigation/snackbar/snack_route.dart'
;
class
Routing
{
String
current
;
...
...
@@ -25,7 +29,6 @@ class Routing {
void
update
(
void
fn
(
Routing
value
))
{
fn
(
this
);
GetConfig
.
currentRoute
=
this
.
current
;
}
}
...
...
@@ -46,28 +49,49 @@ class GetObserver extends NavigatorObserver {
// String previousArgs;
String
removed
;
String
name
(
Route
<
dynamic
>
route
)
{
if
(
route
?.
settings
?.
name
!=
null
)
{
return
route
?.
settings
?.
name
;
}
else
if
(
route
is
GetPageRoute
)
{
return
route
.
routeName
;
}
else
if
(
route
is
GetDialogRoute
)
{
return
route
.
name
;
}
else
if
(
route
is
GetModalBottomSheetRoute
)
{
return
route
.
name
;
}
else
{
return
route
?.
settings
?.
name
;
}
}
@override
void
didPush
(
Route
<
dynamic
>
route
,
Route
<
dynamic
>
previousRoute
)
{
if
(
'
${route?.settings?.name}
'
==
'snackbar'
)
{
GetConfig
.
log
(
"[GETX] OPEN SNACKBAR
${route?.settings?.name}
"
);
}
else
if
(
'
${route?.settings?.name}
'
==
'bottomsheet'
)
{
GetConfig
.
log
(
"[GETX] OPEN BOTTOMSHEET
${route?.settings?.name}
"
);
}
else
if
(
'
${route?.settings?.name}
'
==
'dialog'
)
{
GetConfig
.
log
(
"[GETX] OPEN DIALOG
${route?.settings?.name}
"
);
}
else
{
GetConfig
.
log
(
"[GETX] GOING TO ROUTE
${route?.settings?.name}
"
);
bool
isGetPageRoute
=
route
is
GetPageRoute
;
bool
isSnackbar
=
route
is
SnackRoute
;
bool
isDialog
=
route
is
GetDialogRoute
;
bool
isBottomSheet
=
route
is
GetModalBottomSheetRoute
;
String
routeName
=
name
(
route
);
if
(
isSnackbar
)
{
GetConfig
.
log
(
"[GETX] OPEN SNACKBAR
$routeName
"
);
}
else
if
(
isBottomSheet
)
{
GetConfig
.
log
(
"[GETX] OPEN
$routeName
"
);
}
else
if
(
isDialog
)
{
GetConfig
.
log
(
"[GETX] OPEN
$routeName
"
);
}
else
if
(
isGetPageRoute
)
{
GetConfig
.
log
(
"[GETX] GOING TO ROUTE
$routeName
"
);
}
GetConfig
.
currentRoute
=
routeName
;
_routeSend
.
update
((
value
)
{
if
(
route
is
PageRoute
)
value
.
current
=
'
${route?.settings?.name}
'
;
if
(
route
is
PageRoute
)
value
.
current
=
routeName
;
value
.
args
=
route
?.
settings
?.
arguments
;
value
.
route
=
route
;
value
.
isBack
=
false
;
value
.
removed
=
''
;
value
.
previous
=
'
${previousRoute?.settings?.name}
'
;
value
.
isSnackbar
=
'
${route?.settings?.name}
'
==
'snackbar'
;
value
.
isBottomSheet
=
'
${route?.settings?.name}
'
==
'bottomsheet'
;
value
.
isDialog
=
'
${route?.settings?.name}
'
==
'dialog'
;
value
.
isSnackbar
=
isSnackbar
;
value
.
isBottomSheet
=
isBottomSheet
;
value
.
isDialog
=
isDialog
;
});
if
(
routing
!=
null
)
routing
(
_routeSend
);
}
...
...
@@ -76,15 +100,22 @@ class GetObserver extends NavigatorObserver {
void
didPop
(
Route
route
,
Route
previousRoute
)
{
super
.
didPop
(
route
,
previousRoute
);
if
(
'
${route?.settings?.name}
'
==
'snackbar'
)
{
GetConfig
.
log
(
"[GETX] CLOSE SNACKBAR
${route?.settings?.name}
"
);
}
else
if
(
'
${route?.settings?.name}
'
==
'bottomsheet'
)
{
GetConfig
.
log
(
"[GETX] CLOSE BOTTOMSHEET
${route?.settings?.name}
"
);
}
else
if
(
'
${route?.settings?.name}
'
==
'dialog'
)
{
GetConfig
.
log
(
"[GETX] CLOSE DIALOG
${route?.settings?.name}
"
);
}
else
{
GetConfig
.
log
(
"[GETX] BACK ROUTE
${route?.settings?.name}
"
);
bool
isGetPageRoute
=
route
is
GetPageRoute
;
bool
isSnackbar
=
route
is
SnackRoute
;
bool
isDialog
=
route
is
GetDialogRoute
;
bool
isBottomSheet
=
route
is
GetModalBottomSheetRoute
;
String
routeName
=
name
(
route
);
if
(
isSnackbar
)
{
GetConfig
.
log
(
"[GETX] CLOSE SNACKBAR
$routeName
"
);
}
else
if
(
isBottomSheet
)
{
GetConfig
.
log
(
"[GETX] CLOSE
$routeName
"
);
}
else
if
(
isDialog
)
{
GetConfig
.
log
(
"[GETX] CLOSE
$routeName
"
);
}
else
if
(
isGetPageRoute
)
{
GetConfig
.
log
(
"[GETX] CLOSE TO ROUTE
$routeName
"
);
}
GetConfig
.
currentRoute
=
routeName
;
_routeSend
.
update
((
value
)
{
if
(
previousRoute
is
PageRoute
)
...
...
@@ -104,9 +135,12 @@ class GetObserver extends NavigatorObserver {
@override
void
didReplace
({
Route
newRoute
,
Route
oldRoute
})
{
super
.
didReplace
(
newRoute:
newRoute
,
oldRoute:
oldRoute
);
GetConfig
.
log
(
"[GETX] REPLACE ROUTE
${oldRoute?.settings?.name}
"
);
GetConfig
.
log
(
"[GETX] NEW ROUTE
${newRoute?.settings?.name}
"
);
GetConfig
.
currentRoute
=
name
(
newRoute
);
_routeSend
.
update
((
value
)
{
if
(
newRoute
is
PageRoute
)
value
.
current
=
'
${newRoute?.settings?.name}
'
;
value
.
args
=
newRoute
?.
settings
?.
arguments
;
...
...
pubspec.yaml
View file @
b2ed3c7
name
:
get
description
:
Open screens/snackbars/dialogs/bottomSheets without context, manage states and inject dependencies easily with GetX.
version
:
3.
4.6
version
:
3.
5.0
homepage
:
https://github.com/jonataslaw/get
environment
:
sdk
:
"
>=2.
6
.0
<3.0.0"
sdk
:
"
>=2.
8
.0
<3.0.0"
dependencies
:
flutter
:
...
...
Please
register
or
login
to post a comment