feat:synchro stores

release/mobdr-v0.0.1
Frédérik Benoist 2023-03-11 21:09:43 +01:00
parent 49115109d5
commit 394cf68976
15 changed files with 645 additions and 395 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

38
lib/db/box_etab.dart Normal file
View File

@ -0,0 +1,38 @@
import 'package:objectbox/objectbox.dart';
import 'package:mobdr/objectbox.g.dart';
// ignore_for_file: public_member_api_docs
@Entity()
class Etab {
// specify the id
@Id()
int id = 0;
int id_etab;
String nom;
String mail;
String tel;
String url_photo_principale;
String longitude;
String latitude;
Etab(
{this.id = 0,
required this.id_etab,
required this.nom,
required this.mail,
required this.tel,
required this.url_photo_principale,
required this.longitude,
required this.latitude});
Etab.fromJson(Map<String, dynamic> json)
: id_etab = json['id_etab'],
nom = json['nom'],
mail = json['mail'],
tel = json['tel'],
url_photo_principale = json['url_photo_principale'],
longitude = json['longitude'],
latitude = json['latitude'];
}

View File

@ -20,9 +20,14 @@ class ApiProvider {
try { try {
dio.options.headers['content-Type'] = 'application/json'; dio.options.headers['content-Type'] = 'application/json';
dio.options.headers['Accept'] = 'application/json'; dio.options.headers['Accept'] = 'application/json';
dio.options.headers['Cookie'] = "pguid=${SharedPrefs().guid}";
dio.options.connectTimeout = Duration(seconds: 5); dio.options.connectTimeout = Duration(seconds: 5);
dio.options.receiveTimeout = Duration(seconds: 4); dio.options.receiveTimeout = Duration(seconds: 4);
if (body != null) {
dio.options.queryParameters = body; dio.options.queryParameters = body;
}
return await dio.get(url); return await dio.get(url);
} on DioError catch (e) { } on DioError catch (e) {
@ -196,6 +201,29 @@ class ApiProvider {
} }
} }
/// Synchronize stores
Future<String> SyncEtablissements() async {
try {
var body = null;
response = await getCrud(
ApiConstants.baseUrl +
ApiConstants.externalEndpoint +
ApiConstants.restEndpoint +
'/mobDR/etablissement',
body);
if (response.statusCode == STATUS_OK) {
/// create box etab
objectbox.addEtabs(response.data['boutiques']);
return 'OK';
} else {
return response.statusMessage ?? 'Unknow error ...';
}
} catch (ex) {
return ex.toString(); // return ex.response!.data;
}
}
Future<String> getExample(apiToken) async { Future<String> getExample(apiToken) async {
response = response =
await getConnect(ApiConstants.baseUrl + '/example/getData', apiToken); await getConnect(ApiConstants.baseUrl + '/example/getData', apiToken);

View File

@ -114,9 +114,58 @@
} }
], ],
"relations": [] "relations": []
},
{
"id": "5:6220645616537106928",
"lastPropertyId": "9:4862523104151141465",
"name": "Etab",
"properties": [
{
"id": "1:2951125380051176697",
"name": "id",
"type": 6,
"flags": 1
},
{
"id": "2:986806885009634082",
"name": "id_etab",
"type": 6
},
{
"id": "3:2138930560668315243",
"name": "nom",
"type": 9
},
{
"id": "4:6458474884283325627",
"name": "mail",
"type": 9
},
{
"id": "5:8380062137599693463",
"name": "tel",
"type": 9
},
{
"id": "7:5547045221255607235",
"name": "longitude",
"type": 9
},
{
"id": "8:5768827910047585940",
"name": "latitude",
"type": 9
},
{
"id": "9:4862523104151141465",
"name": "url_photo_principale",
"type": 9
} }
], ],
"lastEntityId": "4:4389962693874538546", "relations": []
}
],
"lastEntityId": "5:6220645616537106928",
"lastIndexId": "0:0", "lastIndexId": "0:0",
"lastRelationId": "0:0", "lastRelationId": "0:0",
"lastSequenceId": "0:0", "lastSequenceId": "0:0",
@ -128,7 +177,8 @@
"retiredIndexUids": [], "retiredIndexUids": [],
"retiredPropertyUids": [ "retiredPropertyUids": [
402019719780433349, 402019719780433349,
2876428622751679696 2876428622751679696,
6435857490868115471
], ],
"retiredRelationUids": [], "retiredRelationUids": [],
"version": 1 "version": 1

View File

@ -1,6 +1,8 @@
import 'package:mobdr/db/box_user.dart'; import 'package:mobdr/db/box_user.dart';
import 'package:mobdr/db/box_log.dart'; import 'package:mobdr/db/box_log.dart';
import 'package:mobdr/db/box_etab.dart';
import 'model.dart'; import 'model.dart';
import 'dart:convert';
import 'objectbox.g.dart'; // created by `flutter pub run build_runner build` import 'objectbox.g.dart'; // created by `flutter pub run build_runner build`
/// Provides access to the ObjectBox Store throughout the app. /// Provides access to the ObjectBox Store throughout the app.
@ -16,12 +18,16 @@ class ObjectBox {
/// A Box of user. /// A Box of user.
late final Box<User> userBox; late final Box<User> userBox;
/// A Box of Etab.
late final Box<Etab> etabBox;
/// A Box of log. /// A Box of log.
late final Box<Log> logBox; late final Box<Log> logBox;
ObjectBox._create(this.store) { ObjectBox._create(this.store) {
noteBox = Box<Note>(store); noteBox = Box<Note>(store);
userBox = Box<User>(store); userBox = Box<User>(store);
etabBox = Box<Etab>(store);
logBox = Box<Log>(store); logBox = Box<Log>(store);
// Add some demo data if the box is empty. // Add some demo data if the box is empty.
@ -53,13 +59,6 @@ class ObjectBox {
store.runInTransactionAsync(TxMode.write, _putNotesInTx, demoNotes); store.runInTransactionAsync(TxMode.write, _putNotesInTx, demoNotes);
} }
String getUserAvatar(int id_utilisateur) {
final query =
userBox.query(User_.id_utilisateur.equals(id_utilisateur)).build();
var p = query.findFirst();
return p!.photo;
}
void _putUserAdminData() { void _putUserAdminData() {
//addUSer(0, 'root', 'admim', 'admin', ''); //addUSer(0, 'root', 'admim', 'admin', '');
} }
@ -98,6 +97,9 @@ class ObjectBox {
store.box<Note>().put(Note(text)); store.box<Note>().put(Note(text));
} }
/// USER ---------------------------------------------------------------------
///
Future<void> addUSer(int _id_utilisateur, String _login, String _nom, Future<void> addUSer(int _id_utilisateur, String _login, String _nom,
String _prenom, String _photo) => String _prenom, String _photo) =>
store.runInTransactionAsync( store.runInTransactionAsync(
@ -116,6 +118,68 @@ class ObjectBox {
store.box<User>().put(_User); store.box<User>().put(_User);
} }
String getUserAvatar(int id_utilisateur) {
final query =
userBox.query(User_.id_utilisateur.equals(id_utilisateur)).build();
var p = query.findFirst();
return p!.photo;
}
///
/// /USER --------------------------------------------------------------------
/// ETAB ---------------------------------------------------------------------
///
// A function that converts a response body into a List<Etab>.
List<Etab> parseEtabs(List responseData) {
final parsed = responseData.cast<Map<String, dynamic>>();
return parsed.map<Etab>((json) => Etab.fromJson(json)).toList();
}
Future<void> addEtabs(List<dynamic> _etabs) => store.runInTransactionAsync(
TxMode.write, _addEtabsInTx, parseEtabs(_etabs));
static void _addEtabsInTx(Store store, _Etabs) {
// Perform ObjectBox operations that take longer than a few milliseconds
// here. To keep it simple, this example just puts multiple object.
store.box<Etab>().putMany(_Etabs);
}
Future<void> addEtab(int _id_etab, String _nom, String _mail, String _tel,
String _photo_principale, String _longitude, String _latitude) =>
store.runInTransactionAsync(
TxMode.write,
_addEtabInTx,
Etab(
id_etab: _id_etab,
nom: _nom,
mail: _mail,
tel: _tel,
url_photo_principale: _photo_principale,
longitude: _longitude,
latitude: _latitude));
static void _addEtabInTx(Store store, _Etab) {
// Perform ObjectBox operations that take longer than a few milliseconds
// here. To keep it simple, this example just puts a single object.
store.box<Etab>().put(_Etab);
}
String getEtabPhotoPrincipale(int _id_etab) {
final query = etabBox.query(Etab_.id_etab.equals(_id_etab)).build();
var p = query.findFirst();
return p!.url_photo_principale;
}
int getEtabCount() {
return etabBox.count();
}
///
/// /ETAB --------------------------------------------------------------------
/// LOG ---------------------------------------------------------------------- /// LOG ----------------------------------------------------------------------
/// ///
Future<void> addLog(String type, String module, String libelle, int duree) => Future<void> addLog(String type, String module, String libelle, int duree) =>

View File

@ -14,6 +14,7 @@ import 'package:objectbox/internal.dart'; // generated code can access "internal
import 'package:objectbox/objectbox.dart'; import 'package:objectbox/objectbox.dart';
import 'package:objectbox_flutter_libs/objectbox_flutter_libs.dart'; import 'package:objectbox_flutter_libs/objectbox_flutter_libs.dart';
import 'db/box_etab.dart';
import 'db/box_log.dart'; import 'db/box_log.dart';
import 'db/box_user.dart'; import 'db/box_user.dart';
import 'model.dart'; import 'model.dart';
@ -132,6 +133,55 @@ final _entities = <ModelEntity>[
flags: 0) flags: 0)
], ],
relations: <ModelRelation>[], relations: <ModelRelation>[],
backlinks: <ModelBacklink>[]),
ModelEntity(
id: const IdUid(5, 6220645616537106928),
name: 'Etab',
lastPropertyId: const IdUid(9, 4862523104151141465),
flags: 0,
properties: <ModelProperty>[
ModelProperty(
id: const IdUid(1, 2951125380051176697),
name: 'id',
type: 6,
flags: 1),
ModelProperty(
id: const IdUid(2, 986806885009634082),
name: 'id_etab',
type: 6,
flags: 0),
ModelProperty(
id: const IdUid(3, 2138930560668315243),
name: 'nom',
type: 9,
flags: 0),
ModelProperty(
id: const IdUid(4, 6458474884283325627),
name: 'mail',
type: 9,
flags: 0),
ModelProperty(
id: const IdUid(5, 8380062137599693463),
name: 'tel',
type: 9,
flags: 0),
ModelProperty(
id: const IdUid(7, 5547045221255607235),
name: 'longitude',
type: 9,
flags: 0),
ModelProperty(
id: const IdUid(8, 5768827910047585940),
name: 'latitude',
type: 9,
flags: 0),
ModelProperty(
id: const IdUid(9, 4862523104151141465),
name: 'url_photo_principale',
type: 9,
flags: 0)
],
relations: <ModelRelation>[],
backlinks: <ModelBacklink>[]) backlinks: <ModelBacklink>[])
]; ];
@ -155,13 +205,17 @@ Future<Store> openStore(
ModelDefinition getObjectBoxModel() { ModelDefinition getObjectBoxModel() {
final model = ModelInfo( final model = ModelInfo(
entities: _entities, entities: _entities,
lastEntityId: const IdUid(4, 4389962693874538546), lastEntityId: const IdUid(5, 6220645616537106928),
lastIndexId: const IdUid(0, 0), lastIndexId: const IdUid(0, 0),
lastRelationId: const IdUid(0, 0), lastRelationId: const IdUid(0, 0),
lastSequenceId: const IdUid(0, 0), lastSequenceId: const IdUid(0, 0),
retiredEntityUids: const [7401686910042688313], retiredEntityUids: const [7401686910042688313],
retiredIndexUids: const [], retiredIndexUids: const [],
retiredPropertyUids: const [402019719780433349, 2876428622751679696], retiredPropertyUids: const [
402019719780433349,
2876428622751679696,
6435857490868115471
],
retiredRelationUids: const [], retiredRelationUids: const [],
modelVersion: 5, modelVersion: 5,
modelVersionParserMinimum: 5, modelVersionParserMinimum: 5,
@ -286,6 +340,58 @@ ModelDefinition getObjectBoxModel() {
uploaded: uploaded:
const fb.Int64Reader().vTableGet(buffer, rootOffset, 16, 0)); const fb.Int64Reader().vTableGet(buffer, rootOffset, 16, 0));
return object;
}),
Etab: EntityDefinition<Etab>(
model: _entities[3],
toOneRelations: (Etab object) => [],
toManyRelations: (Etab object) => {},
getId: (Etab object) => object.id,
setId: (Etab object, int id) {
object.id = id;
},
objectToFB: (Etab object, fb.Builder fbb) {
final nomOffset = fbb.writeString(object.nom);
final mailOffset = fbb.writeString(object.mail);
final telOffset = fbb.writeString(object.tel);
final longitudeOffset = fbb.writeString(object.longitude);
final latitudeOffset = fbb.writeString(object.latitude);
final url_photo_principaleOffset =
fbb.writeString(object.url_photo_principale);
fbb.startTable(10);
fbb.addInt64(0, object.id);
fbb.addInt64(1, object.id_etab);
fbb.addOffset(2, nomOffset);
fbb.addOffset(3, mailOffset);
fbb.addOffset(4, telOffset);
fbb.addOffset(6, longitudeOffset);
fbb.addOffset(7, latitudeOffset);
fbb.addOffset(8, url_photo_principaleOffset);
fbb.finish(fbb.endTable());
return object.id;
},
objectFromFB: (Store store, ByteData fbData) {
final buffer = fb.BufferContext(fbData);
final rootOffset = buffer.derefObject(0);
final object = Etab(
id: const fb.Int64Reader().vTableGet(buffer, rootOffset, 4, 0),
id_etab:
const fb.Int64Reader().vTableGet(buffer, rootOffset, 6, 0),
nom: const fb.StringReader(asciiOptimization: true)
.vTableGet(buffer, rootOffset, 8, ''),
mail: const fb.StringReader(asciiOptimization: true)
.vTableGet(buffer, rootOffset, 10, ''),
tel: const fb.StringReader(asciiOptimization: true)
.vTableGet(buffer, rootOffset, 12, ''),
url_photo_principale:
const fb.StringReader(asciiOptimization: true)
.vTableGet(buffer, rootOffset, 20, ''),
longitude: const fb.StringReader(asciiOptimization: true)
.vTableGet(buffer, rootOffset, 16, ''),
latitude: const fb.StringReader(asciiOptimization: true)
.vTableGet(buffer, rootOffset, 18, ''));
return object; return object;
}) })
}; };
@ -353,3 +459,32 @@ class Log_ {
/// see [Log.uploaded] /// see [Log.uploaded]
static final uploaded = QueryIntegerProperty<Log>(_entities[2].properties[6]); static final uploaded = QueryIntegerProperty<Log>(_entities[2].properties[6]);
} }
/// [Etab] entity fields to define ObjectBox queries.
class Etab_ {
/// see [Etab.id]
static final id = QueryIntegerProperty<Etab>(_entities[3].properties[0]);
/// see [Etab.id_etab]
static final id_etab = QueryIntegerProperty<Etab>(_entities[3].properties[1]);
/// see [Etab.nom]
static final nom = QueryStringProperty<Etab>(_entities[3].properties[2]);
/// see [Etab.mail]
static final mail = QueryStringProperty<Etab>(_entities[3].properties[3]);
/// see [Etab.tel]
static final tel = QueryStringProperty<Etab>(_entities[3].properties[4]);
/// see [Etab.longitude]
static final longitude =
QueryStringProperty<Etab>(_entities[3].properties[5]);
/// see [Etab.latitude]
static final latitude = QueryStringProperty<Etab>(_entities[3].properties[6]);
/// see [Etab.url_photo_principale]
static final url_photo_principale =
QueryStringProperty<Etab>(_entities[3].properties[7]);
}

View File

@ -1,7 +1,7 @@
import 'package:mobdr/ui/account/tab_account.dart'; import 'package:mobdr/ui/account/tab_account.dart';
import 'package:mobdr/ui/home/tab_home.dart'; import 'package:mobdr/ui/home/tab_home.dart';
import 'package:mobdr/ui/shopping_cart/tab_shopping_cart.dart'; import 'package:mobdr/ui/shopping_cart/tab_shopping_cart.dart';
import 'package:mobdr/ui/wishlist/tab_wishlist.dart'; import 'package:mobdr/ui/sync/tab_sync.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:mobdr/config/constant.dart'; import 'package:mobdr/config/constant.dart';
@ -18,7 +18,7 @@ class _HomePageState extends State<HomePage>
// Pages if you click bottom navigation // Pages if you click bottom navigation
final List<Widget> _contentPages = <Widget>[ final List<Widget> _contentPages = <Widget>[
TabHomePage(), TabHomePage(),
TabWishlistPage(), TabSyncPage(),
TabShoppingCartPage(), TabShoppingCartPage(),
TabAccountPage(), TabAccountPage(),
]; ];
@ -76,8 +76,8 @@ class _HomePageState extends State<HomePage>
icon: Icon(Icons.home, icon: Icon(Icons.home,
color: _currentIndex == 0 ? PRIMARY_COLOR : CHARCOAL)), color: _currentIndex == 0 ? PRIMARY_COLOR : CHARCOAL)),
BottomNavigationBarItem( BottomNavigationBarItem(
label: 'Wishlist', label: 'Sync',
icon: Icon(Icons.favorite, icon: Icon(Icons.sync,
color: _currentIndex == 1 ? ASSENT_COLOR : CHARCOAL)), color: _currentIndex == 1 ? ASSENT_COLOR : CHARCOAL)),
BottomNavigationBarItem( BottomNavigationBarItem(
label: 'Cart', label: 'Cart',

300
lib/ui/sync/tab_sync.dart Normal file
View File

@ -0,0 +1,300 @@
/*
This is wishlist page
we used AutomaticKeepAliveClientMixin to keep the state when moving from 1 navbar to another navbar, so the page is not refresh overtime
*/
import 'package:mobdr/config/global_style.dart';
import 'package:mobdr/main.dart';
import 'package:mobdr/ui/reusable/reusable_widget.dart';
import 'package:mobdr/network/api_provider.dart';
import 'package:flutter/material.dart';
import 'package:timelines/timelines.dart';
import 'dart:math';
const completeColor = Color(0xff5e6172);
const inProgressColor = Color(0xff5ec792);
const todoColor = Color(0xffd1d2d7);
const failedColor = Colors.red;
class TabSyncPage extends StatefulWidget {
@override
_TabSyncPageState createState() => _TabSyncPageState();
}
class _TabSyncPageState extends State<TabSyncPage>
with AutomaticKeepAliveClientMixin {
// initialize global function and reusable widget
//final _globalFunction = GlobalFunction();
final _reusableWidget = ReusableWidget();
final _processes = ['Btqs', 'Params', 'Visites', 'Photos', 'Logs'];
final ApiProvider _apiProvider =
ApiProvider(); // TODO: A voir si bien positionné
// _listKey is used for AnimatedList
//final GlobalKey<AnimatedListState> _listKey = GlobalKey();
int _processIndex = 1;
Color getColor(int index) {
if (index == _processIndex) {
return inProgressColor;
} else if (index < _processIndex) {
return completeColor;
} else {
return todoColor;
}
}
// keep the state to do not refresh when switch navbar
@override
bool get wantKeepAlive => true;
@override
void initState() {
super.initState();
}
@override
void dispose() {
super.dispose();
}
@override
Widget build(BuildContext context) {
// if we used AutomaticKeepAliveClientMixin, we must call super.build(context);
super.build(context);
return Scaffold(
backgroundColor: Colors.white,
appBar: AppBar(
iconTheme: IconThemeData(
color: GlobalStyle.appBarIconThemeColor,
),
elevation: GlobalStyle.appBarElevation,
title: Text(
'Synchronisation',
style: GlobalStyle.appBarTitle,
),
backgroundColor: GlobalStyle.appBarBackgroundColor,
systemOverlayStyle: GlobalStyle.appBarSystemOverlayStyle,
bottom: _reusableWidget.bottomAppBar(),
),
body: Timeline.tileBuilder(
theme: TimelineThemeData(
direction: Axis.horizontal,
connectorTheme: ConnectorThemeData(
space: 30.0,
thickness: 5.0,
),
),
builder: TimelineTileBuilder.connected(
connectionDirection: ConnectionDirection.before,
itemExtentBuilder: (_, __) =>
MediaQuery.of(context).size.width / _processes.length,
oppositeContentsBuilder: (context, index) {
return Padding(
padding: const EdgeInsets.only(bottom: 15.0),
child: Image.asset(
'assets/images/process_timeline/status${index + 1}.png',
width: 50.0,
color: getColor(index),
),
);
},
contentsBuilder: (context, index) {
return Padding(
padding: const EdgeInsets.only(top: 15.0),
child: Text(
_processes[index],
style: TextStyle(
fontWeight: FontWeight.bold,
color: getColor(index),
),
),
);
},
indicatorBuilder: (_, index) {
var color;
var child;
if (index == _processIndex) {
color = inProgressColor;
child = Padding(
padding: const EdgeInsets.all(8.0),
child: CircularProgressIndicator(
strokeWidth: 3.0,
valueColor: AlwaysStoppedAnimation(Colors.white),
),
);
} else if (index < _processIndex) {
color = failedColor;
child = Icon(
Icons.cancel,
color: Colors.white,
size: 20.0,
);
} else {
color = todoColor;
}
if (index <= _processIndex) {
return Stack(
children: [
CustomPaint(
size: Size(30.0, 30.0),
painter: _BezierPainter(
color: color,
drawStart: index > 0,
drawEnd: index < _processIndex,
),
),
DotIndicator(
size: 30.0,
color: color,
child: child,
),
],
);
} else {
return Stack(
children: [
CustomPaint(
size: Size(15.0, 15.0),
painter: _BezierPainter(
color: color,
drawEnd: index < _processes.length - 1,
),
),
OutlinedDotIndicator(
borderWidth: 4.0,
color: color,
),
],
);
}
},
connectorBuilder: (_, index, type) {
if (index > 0) {
if (index == _processIndex) {
final prevColor = getColor(index - 1);
final color = getColor(index);
List<Color> gradientColors;
if (type == ConnectorType.start) {
gradientColors = [Color.lerp(prevColor, color, 0.5)!, color];
} else {
gradientColors = [
prevColor,
Color.lerp(prevColor, color, 0.5)!
];
}
return DecoratedLineConnector(
decoration: BoxDecoration(
gradient: LinearGradient(
colors: gradientColors,
),
),
);
} else {
return SolidLineConnector(
color: getColor(index),
);
}
} else {
return null;
}
},
itemCount: _processes.length,
),
),
floatingActionButton: FloatingActionButton(
child: Icon(Icons.chevron_right),
onPressed: () async {
///FRED
print("Nombre étab avant:" + objectbox.getEtabCount().toString());
var apiResponse = await _apiProvider.SyncEtablissements();
if (apiResponse == 'OK') {
print("getEtablissement:" + apiResponse);
print("Nombre étab apres" + objectbox.getEtabCount().toString());
} else {
print("getEtablissement Error:" + apiResponse);
}
///
setState(() {
_processIndex = (_processIndex + 1) % _processes.length;
});
},
backgroundColor: inProgressColor,
),
);
}
}
/// hardcoded bezier painter
class _BezierPainter extends CustomPainter {
const _BezierPainter({
required this.color,
this.drawStart = true,
this.drawEnd = true,
});
final Color color;
final bool drawStart;
final bool drawEnd;
Offset _offset(double radius, double angle) {
return Offset(
radius * cos(angle) + radius,
radius * sin(angle) + radius,
);
}
@override
void paint(Canvas canvas, Size size) {
final paint = Paint()
..style = PaintingStyle.fill
..color = color;
final radius = size.width / 2;
var angle;
var offset1;
var offset2;
var path;
if (drawStart) {
angle = 3 * pi / 4;
offset1 = _offset(radius, angle);
offset2 = _offset(radius, -angle);
path = Path()
..moveTo(offset1.dx, offset1.dy)
..quadraticBezierTo(0.0, size.height / 2, -radius, radius)
..quadraticBezierTo(0.0, size.height / 2, offset2.dx, offset2.dy)
..close();
canvas.drawPath(path, paint);
}
if (drawEnd) {
angle = -pi / 4;
offset1 = _offset(radius, angle);
offset2 = _offset(radius, -angle);
path = Path()
..moveTo(offset1.dx, offset1.dy)
..quadraticBezierTo(
size.width, size.height / 2, size.width + radius, radius)
..quadraticBezierTo(size.width, size.height / 2, offset2.dx, offset2.dy)
..close();
canvas.drawPath(path, paint);
}
}
@override
bool shouldRepaint(_BezierPainter oldDelegate) {
return oldDelegate.color != color ||
oldDelegate.drawStart != drawStart ||
oldDelegate.drawEnd != drawEnd;
}
}

View File

@ -1,378 +0,0 @@
/*
This is wishlist page
we used AutomaticKeepAliveClientMixin to keep the state when moving from 1 navbar to another navbar, so the page is not refresh overtime
*/
import 'package:mobdr/config/constant.dart';
import 'package:mobdr/config/global_style.dart';
import 'package:mobdr/model/wishlist_model.dart';
import 'package:mobdr/ui/general/chat_us.dart';
import 'package:mobdr/ui/general/notification.dart';
import 'package:mobdr/ui/general/product_detail/product_detail.dart';
import 'package:mobdr/ui/reusable/reusable_widget.dart';
import 'package:mobdr/ui/reusable/cache_image_network.dart';
import 'package:mobdr/ui/reusable/global_function.dart';
import 'package:flutter/material.dart';
import 'package:fluttertoast/fluttertoast.dart';
class TabWishlistPage extends StatefulWidget {
@override
_TabWishlistPageState createState() => _TabWishlistPageState();
}
class _TabWishlistPageState extends State<TabWishlistPage>
with AutomaticKeepAliveClientMixin {
// initialize global function and reusable widget
final _globalFunction = GlobalFunction();
final _reusableWidget = ReusableWidget();
// _listKey is used for AnimatedList
final GlobalKey<AnimatedListState> _listKey = GlobalKey();
TextEditingController _etSearch = TextEditingController();
// keep the state to do not refresh when switch navbar
@override
bool get wantKeepAlive => true;
@override
void initState() {
super.initState();
}
@override
void dispose() {
_etSearch.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
// if we used AutomaticKeepAliveClientMixin, we must call super.build(context);
super.build(context);
final double boxImageSize = (MediaQuery.of(context).size.width / 4);
return Scaffold(
appBar: AppBar(
automaticallyImplyLeading: false,
elevation: GlobalStyle.appBarElevation,
title: Text(
'Wishlist',
style: GlobalStyle.appBarTitle,
),
backgroundColor: GlobalStyle.appBarBackgroundColor,
systemOverlayStyle: GlobalStyle.appBarSystemOverlayStyle,
actions: [
GestureDetector(
onTap: () {
Navigator.push(context,
MaterialPageRoute(builder: (context) => ChatUsPage()));
},
child: Icon(Icons.email, color: BLACK_GREY)),
IconButton(
icon: _reusableWidget.customNotifIcon(
count: 8, notifColor: BLACK_GREY),
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => NotificationPage()));
}),
],
// create search text field in the app bar
bottom: PreferredSize(
child: Container(
decoration: BoxDecoration(
border: Border(
bottom: BorderSide(
color: Colors.grey[100]!,
width: 1.0,
)),
),
padding: EdgeInsets.fromLTRB(16, 0, 16, 12),
height: kToolbarHeight,
child: TextFormField(
controller: _etSearch,
textAlignVertical: TextAlignVertical.bottom,
maxLines: 1,
style: TextStyle(fontSize: 16, color: Colors.grey[600]),
onChanged: (textValue) {
setState(() {});
},
decoration: InputDecoration(
fillColor: Colors.grey[100],
filled: true,
hintText: 'Search Wishlist',
prefixIcon: Icon(Icons.search, color: Colors.grey[500]),
suffixIcon: (_etSearch.text == '')
? null
: GestureDetector(
onTap: () {
setState(() {
_etSearch = TextEditingController(text: '');
});
},
child: Icon(Icons.close, color: Colors.grey[500])),
focusedBorder: UnderlineInputBorder(
borderRadius: BorderRadius.all(Radius.circular(5.0)),
borderSide: BorderSide(color: Colors.grey[200]!)),
enabledBorder: UnderlineInputBorder(
borderRadius: BorderRadius.all(Radius.circular(5.0)),
borderSide: BorderSide(color: Colors.grey[200]!),
),
),
),
),
preferredSize: Size.fromHeight(kToolbarHeight),
),
),
body: AnimatedList(
key: _listKey,
initialItemCount: wishlistData.length,
physics: AlwaysScrollableScrollPhysics(),
itemBuilder: (context, index, animation) {
return _buildWishlistCard(
wishlistData[index], boxImageSize, animation, index);
},
));
}
Widget _buildWishlistCard(
WishlistModel wishlistData, boxImageSize, animation, index) {
return SizeTransition(
sizeFactor: animation,
child: Container(
margin: EdgeInsets.fromLTRB(12, 6, 12, 0),
child: Card(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(10),
),
elevation: 2,
color: Colors.white,
child: Container(
margin: EdgeInsets.all(8),
child: Column(
children: [
GestureDetector(
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => ProductDetailPage(
name: wishlistData.name,
image: wishlistData.image,
price: wishlistData.price,
rating: wishlistData.rating,
review: wishlistData.review,
sale: wishlistData.sale)));
},
child: Row(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
ClipRRect(
borderRadius: BorderRadius.all(Radius.circular(10)),
child: buildCacheNetworkImage(
width: boxImageSize,
height: boxImageSize,
url: wishlistData.image)),
SizedBox(
width: 10,
),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
wishlistData.name,
style: GlobalStyle.productName
.copyWith(fontSize: 13),
maxLines: 3,
overflow: TextOverflow.ellipsis,
),
Container(
margin: EdgeInsets.only(top: 5),
child: Text(
'\$ ' +
_globalFunction.removeDecimalZeroFormat(
wishlistData.price),
style: GlobalStyle.productPrice),
),
Container(
margin: EdgeInsets.only(top: 5),
child: Row(
children: [
Icon(Icons.location_on,
color: SOFT_GREY, size: 12),
Text(' ' + wishlistData.location,
style: GlobalStyle.productLocation)
],
),
),
Container(
margin: EdgeInsets.only(top: 5),
child: Row(
children: [
_reusableWidget.createRatingBar(
rating: wishlistData.rating, size: 12),
Text(
'(' +
wishlistData.review.toString() +
')',
style: GlobalStyle.productTotalReview)
],
),
),
Container(
margin: EdgeInsets.only(top: 5),
child: Text(
wishlistData.sale.toString() + ' Sale',
style: GlobalStyle.productSale),
),
],
),
)
],
),
),
Container(
margin: EdgeInsets.only(top: 12),
child: Row(
children: [
GestureDetector(
behavior: HitTestBehavior.translucent,
onTap: () {
showPopupDeleteTabWishlist(index, boxImageSize);
},
child: Container(
padding: EdgeInsets.fromLTRB(5, 0, 5, 0),
height: 30,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(8),
border: Border.all(
width: 1, color: Colors.grey[300]!)),
child:
Icon(Icons.delete, color: BLACK_GREY, size: 20),
),
),
SizedBox(
width: 8,
),
Expanded(
child: (wishlistData.stock == 0)
? TextButton(
style: ButtonStyle(
minimumSize:
MaterialStateProperty.all(Size(0, 30)),
backgroundColor:
MaterialStateProperty.resolveWith<Color>(
(Set<MaterialState> states) =>
Colors.grey[300]!,
),
overlayColor: MaterialStateProperty.all(
Colors.transparent),
shape: MaterialStateProperty.all(
RoundedRectangleBorder(
borderRadius: BorderRadius.circular(5.0),
)),
),
onPressed: null,
child: Text(
'Out of Stock',
style: TextStyle(
color: Colors.grey[600],
fontWeight: FontWeight.bold,
fontSize: 13),
textAlign: TextAlign.center,
))
: OutlinedButton(
onPressed: () {
Fluttertoast.showToast(
msg:
'Item has been added to Shopping Cart');
},
style: ButtonStyle(
minimumSize:
MaterialStateProperty.all(Size(0, 30)),
overlayColor: MaterialStateProperty.all(
Colors.transparent),
shape: MaterialStateProperty.all(
RoundedRectangleBorder(
borderRadius: BorderRadius.circular(5.0),
)),
side: MaterialStateProperty.all(
BorderSide(color: SOFT_BLUE, width: 1.0),
)),
child: Text(
'Add to Shopping Cart',
style: TextStyle(
color: SOFT_BLUE,
fontWeight: FontWeight.bold,
fontSize: 13),
textAlign: TextAlign.center,
)),
),
],
),
)
],
),
),
),
),
);
}
void showPopupDeleteTabWishlist(index, boxImageSize) {
// set up the buttons
Widget cancelButton = TextButton(
onPressed: () {
Navigator.pop(context);
},
child: Text('No', style: TextStyle(color: SOFT_BLUE)));
Widget continueButton = TextButton(
onPressed: () {
int removeIndex = index;
var removedItem = wishlistData.removeAt(removeIndex);
// This builder is just so that the animation has something
// to work with before it disappears from view since the original
// has already been deleted.
AnimatedRemovedItemBuilder builder = (context, animation) {
// A method to build the Card widget.
return _buildWishlistCard(
removedItem, boxImageSize, animation, removeIndex);
};
_listKey.currentState!.removeItem(removeIndex, builder);
Navigator.pop(context);
Fluttertoast.showToast(
msg: 'Item has been deleted from your wishlist',
toastLength: Toast.LENGTH_LONG);
},
child: Text('Yes', style: TextStyle(color: SOFT_BLUE)));
// set up the AlertDialog
AlertDialog alert = AlertDialog(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(10),
),
title: Text(
'Delete Wishlist',
style: TextStyle(fontSize: 18),
),
content: Text('Are you sure to delete this item from your Wishlist ?',
style: TextStyle(fontSize: 13, color: BLACK_GREY)),
actions: [
cancelButton,
continueButton,
],
);
// show the dialog
showDialog(
context: context,
builder: (BuildContext context) {
return alert;
},
);
}
}

View File

@ -850,6 +850,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.4.16" version: "0.4.16"
timelines:
dependency: "direct main"
description:
name: timelines
sha256: "40214f5ab772ff45459cb8c15e5f60505a6828af0c0eb1eec6f29ed911a4c1c5"
url: "https://pub.dev"
source: hosted
version: "0.1.0"
timing: timing:
dependency: transitive dependency: transitive
description: description:

View File

@ -54,7 +54,7 @@ dependencies:
cached_network_image: 3.2.3 cached_network_image: 3.2.3
get_it: ^7.2.0 get_it: ^7.2.0
shared_preferences: 2.0.18 shared_preferences: 2.0.18
timelines: ^0.1.0
universal_io: 2.2.0 universal_io: 2.2.0
dev_dependencies: dev_dependencies:
@ -125,6 +125,11 @@ flutter:
- assets/images/logo_dark.png - assets/images/logo_dark.png
- assets/images/logo_horizontal.png - assets/images/logo_horizontal.png
- assets/images/onboarding/search_product.gif - assets/images/onboarding/search_product.gif
- assets/images/process_timeline/status1.png
- assets/images/process_timeline/status2.png
- assets/images/process_timeline/status3.png
- assets/images/process_timeline/status4.png
- assets/images/process_timeline/status5.png
- assets/lang/fr.json - assets/lang/fr.json
- assets/lang/en.json - assets/lang/en.json