backup. before shop update
This commit is contained in:
4
lib/utils/configure_nonweb.dart
Normal file
4
lib/utils/configure_nonweb.dart
Normal file
@@ -0,0 +1,4 @@
|
||||
|
||||
void configureApp() {
|
||||
|
||||
}
|
||||
6
lib/utils/configure_web.dart
Normal file
6
lib/utils/configure_web.dart
Normal file
@@ -0,0 +1,6 @@
|
||||
|
||||
import 'package:flutter_web_plugins/flutter_web_plugins.dart';
|
||||
|
||||
void configureApp() {
|
||||
setUrlStrategy(PathUrlStrategy());
|
||||
}
|
||||
24
lib/utils/fake_iframe_web.dart
Normal file
24
lib/utils/fake_iframe_web.dart
Normal file
@@ -0,0 +1,24 @@
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class IFrameWeb extends StatefulWidget {
|
||||
final String width;
|
||||
final String height;
|
||||
final String src;
|
||||
|
||||
const IFrameWeb({this.width, this.height, this.src});
|
||||
|
||||
@override
|
||||
State<StatefulWidget> createState() {
|
||||
return IFrameWebState();
|
||||
}
|
||||
}
|
||||
|
||||
class IFrameWebState extends State<IFrameWeb> {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -81,14 +81,13 @@ class HttpUtil {
|
||||
onReceiveProgress: receiveProgress,
|
||||
).timeout(const Duration(seconds: 30));
|
||||
|
||||
print('HttpGet success. Request: $requestUrl, Response: ${response.statusCode}, Headers: ${response.request.headers}');
|
||||
// print(response.data);
|
||||
// Utils.jsonPrettyPrint(response.data);
|
||||
|
||||
int statusCode = response.statusCode;
|
||||
return response.data;
|
||||
} on DioError catch(e) {
|
||||
print('error $e');
|
||||
print('HttpGet failed, request: $requestUrl, headers: $localHeaders, error ${e.response}');
|
||||
if (returnError) {
|
||||
return e;
|
||||
}
|
||||
@@ -103,6 +102,7 @@ class HttpUtil {
|
||||
bool isFormData = false,
|
||||
Map<String, String> additionalHeaders,
|
||||
Map<String, dynamic> body,
|
||||
FormData formData,
|
||||
Function(int, int) sendProgress,
|
||||
Function(int, int) receiveProgress,
|
||||
bool returnError = false,
|
||||
@@ -121,6 +121,9 @@ class HttpUtil {
|
||||
localHeaders['Http-App-Key'] = platformName;
|
||||
localHeaders['Http-Device-Type'] = platformName;
|
||||
|
||||
// Add the line below will not work.
|
||||
//localHeaders['Content-Type'] = isFormData ? Headers.formUrlEncodedContentType : Headers.jsonContentType;
|
||||
|
||||
String requestUrl = Constants.BASE_API_URL + url;
|
||||
if (url.startsWith('http')) {
|
||||
requestUrl = url;
|
||||
@@ -135,16 +138,15 @@ class HttpUtil {
|
||||
Response response = await dio.post(
|
||||
requestUrl,
|
||||
queryParameters: queryParameters == null ? {} : queryParameters,
|
||||
data: isFormData ? FormData.fromMap(body) : json.encode(body),
|
||||
data: isFormData ? ((formData != null)?formData:FormData.fromMap(body)) : json.encode(body),
|
||||
options: Options(
|
||||
headers: localHeaders,
|
||||
// contentType: isFormData ? Headers.formUrlEncodedContentType : Headers.jsonContentType,
|
||||
contentType: isFormData ? Headers.formUrlEncodedContentType : Headers.jsonContentType,
|
||||
),
|
||||
onSendProgress: sendProgress,
|
||||
onReceiveProgress: receiveProgress,
|
||||
).timeout(Duration(seconds: 30));
|
||||
|
||||
print('HttpPost Success. Request: ${response.request.uri}, Response: ${response.statusCode}, Headers: ${response.request.headers}');
|
||||
// Utils.jsonPrettyPrint(response.data);
|
||||
|
||||
int statusCode = response.statusCode;
|
||||
@@ -152,9 +154,8 @@ class HttpUtil {
|
||||
callback(response);
|
||||
}
|
||||
return response.data;
|
||||
} on DioError catch(e) {
|
||||
} on DioError catch(e, stacktrace) {
|
||||
if (e.response != null) {
|
||||
print('HttpPost failed. Request: ${e.request.uri}, Headers: ${e.response.request.headers}');
|
||||
try {
|
||||
Utils.jsonPrettyPrint(e.response.data);
|
||||
} catch (err) {
|
||||
@@ -215,7 +216,6 @@ class HttpUtil {
|
||||
onReceiveProgress: receiveProgress,
|
||||
).timeout(Duration(seconds: 30));
|
||||
|
||||
print('HttpPost Success. Request: ${response.request.uri}, Response: ${response.statusCode}, Headers: ${response.request.headers}');
|
||||
// Utils.jsonPrettyPrint(response.data);
|
||||
|
||||
int statusCode = response.statusCode;
|
||||
@@ -224,7 +224,6 @@ class HttpUtil {
|
||||
}
|
||||
return response.data;
|
||||
} on DioError catch(e) {
|
||||
print('HttpPost failed. Request: ${e.request.uri}');
|
||||
if (e.response != null) {
|
||||
Utils.jsonPrettyPrint(e.response.data);
|
||||
}
|
||||
@@ -284,7 +283,6 @@ class HttpUtil {
|
||||
onReceiveProgress: receiveProgress,
|
||||
).timeout(Duration(seconds: 30));
|
||||
|
||||
print('HttpPost Success. Request: ${response.request.uri}, Response: ${response.statusCode}, Headers: ${response.request.headers}');
|
||||
// Utils.jsonPrettyPrint(response.data);
|
||||
|
||||
int statusCode = response.statusCode;
|
||||
@@ -293,7 +291,6 @@ class HttpUtil {
|
||||
}
|
||||
return response.data;
|
||||
} on DioError catch(e) {
|
||||
print('HttpPost failed. Request: ${e.request.uri}');
|
||||
if (e.response != null) {
|
||||
Utils.jsonPrettyPrint(e.response.data);
|
||||
}
|
||||
@@ -349,7 +346,6 @@ class HttpUtil {
|
||||
),
|
||||
).timeout(Duration(seconds: 30));
|
||||
|
||||
print('HttpPost Success. Request: ${response.request.uri}, Response: ${response.statusCode}, Headers: ${response.request.headers}');
|
||||
// Utils.jsonPrettyPrint(response.data);
|
||||
|
||||
int statusCode = response.statusCode;
|
||||
@@ -358,7 +354,6 @@ class HttpUtil {
|
||||
}
|
||||
return response.data;
|
||||
} on DioError catch(e) {
|
||||
print('HttpPost failed. Request: ${e.request.uri}');
|
||||
if (e.response != null) {
|
||||
Utils.jsonPrettyPrint(e.response.data);
|
||||
}
|
||||
|
||||
39
lib/utils/iframe_web.dart
Normal file
39
lib/utils/iframe_web.dart
Normal file
@@ -0,0 +1,39 @@
|
||||
|
||||
import 'dart:html';
|
||||
|
||||
import 'dart:ui' as ui;
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class IFrameWeb extends StatefulWidget {
|
||||
final String width;
|
||||
final String height;
|
||||
final String src;
|
||||
|
||||
const IFrameWeb({this.width, this.height, this.src});
|
||||
|
||||
@override
|
||||
State<StatefulWidget> createState() {
|
||||
return IFrameWebState();
|
||||
}
|
||||
}
|
||||
|
||||
class IFrameWebState extends State<IFrameWeb> {
|
||||
final IFrameElement _iframeElement = IFrameElement();
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
_iframeElement.height = widget.height;
|
||||
_iframeElement.width = widget.width;
|
||||
_iframeElement.src = widget.src;
|
||||
_iframeElement.style.border = 'none';
|
||||
ui.platformViewRegistry.registerViewFactory(
|
||||
'iframeElement',
|
||||
(int viewId) => _iframeElement,
|
||||
);
|
||||
return HtmlElementView(
|
||||
key: UniqueKey(),
|
||||
viewType: 'iframeElement',
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
191
lib/utils/shop_scroll_controller.dart
Normal file
191
lib/utils/shop_scroll_controller.dart
Normal file
@@ -0,0 +1,191 @@
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
|
||||
import 'shop_scroll_coordinator.dart';
|
||||
import 'shop_scroll_position.dart';
|
||||
|
||||
/// 滑动控制器
|
||||
class ShopScrollController extends ScrollController {
|
||||
final ShopScrollCoordinator coordinator;
|
||||
|
||||
/// 为可滚动小部件创建一个控制器。
|
||||
/// `initialScrollOffset`和`keepScrollOffset`的值不能为null。
|
||||
ShopScrollController(
|
||||
this.coordinator, {
|
||||
double initialScrollOffset = 0.0,
|
||||
this.keepScrollOffset = true,
|
||||
this.debugLabel,
|
||||
}) : assert(initialScrollOffset != null),
|
||||
assert(keepScrollOffset != null),
|
||||
_initialScrollOffset = initialScrollOffset;
|
||||
|
||||
/// 用于[offset]的初始值。
|
||||
/// 如果[keepScrollOffset]为false或尚未保存滚动偏移量,
|
||||
/// 则创建并附加到此控制器的新[ScrollPosition]对象的偏移量将初始化为该值。
|
||||
/// 默认为0.0。
|
||||
double get initialScrollOffset => _initialScrollOffset;
|
||||
final double _initialScrollOffset;
|
||||
|
||||
/// 每次滚动完成时,请使用[PageStorage]保存当前滚动[offset],如果重新创建了此控制器的可滚动内容,则将其还原。
|
||||
///
|
||||
/// 如果将此属性设置为false,则永远不会保存滚动偏移量,并且始终使用[initialScrollOffset]
|
||||
/// 来初始化滚动偏移量。 如果为true(默认值),则第一次创建控制器的可滚动对象时将使用初始
|
||||
/// 滚动偏移量,因为尚无要还原的滚动偏移量。 随后,将恢复保存的偏移,并且忽略[initialScrollOffset]。
|
||||
///
|
||||
/// 也可以看看:
|
||||
/// * [PageStorageKey],当同一路径中出现多个滚动条时,应使用[PageStorageKey]
|
||||
/// 来区分用于保存滚动偏移量的[PageStorage]位置。
|
||||
final bool keepScrollOffset;
|
||||
|
||||
/// [toString]输出中使用的标签。 旨在帮助在调试输出中标识滚动控制器实例。
|
||||
final String debugLabel;
|
||||
|
||||
/// The currently attached positions. 当前附加的 [positions]。
|
||||
/// 这不应直接突变。 可以使用[attach]和[detach]添加和删除[ScrollPosition]对象。
|
||||
@protected
|
||||
Iterable<ScrollPosition> get positions => _positions;
|
||||
final List<ScrollPosition> _positions = <ScrollPosition>[];
|
||||
|
||||
/// 是否有任何[ScrollPosition]对象已使用[attach]方法将自身附加到[ScrollController]。
|
||||
///
|
||||
/// 如果为false,则不得调用与[ScrollPosition]交互的成员,
|
||||
/// 例如[position],[offset],[animateTo]和[jumpTo]。
|
||||
bool get hasClients => _positions.isNotEmpty;
|
||||
|
||||
/// 返回附加的[ScrollPosition],可以从中获取[ScrollView]的实际滚动偏移量。
|
||||
///
|
||||
/// Calling this is only valid when only a single position is attached.
|
||||
/// 仅在仅连接一个[position]时调用此选项才有效。
|
||||
ShopScrollPosition get position {
|
||||
assert(_positions.isNotEmpty,
|
||||
'ScrollController not attached to any scroll views.');
|
||||
assert(_positions.length == 1,
|
||||
'ScrollController attached to multiple scroll views.');
|
||||
return _positions.single;
|
||||
}
|
||||
|
||||
/// 可滚动小部件的当前滚动偏移量
|
||||
/// 可滚动小部件的当前滚动偏移量。要求控制器仅控制一个可滚动小部件。
|
||||
double get offset => position.pixels;
|
||||
|
||||
/// 从当前位置到给定值的位置动画。
|
||||
/// 任何活动的动画都将被取消。 如果用户当前正在滚动,则该操作将被取消。
|
||||
/// 返回的[Future]将在动画结束时完成,无论它是否成功完成或是否被过早中断。
|
||||
///
|
||||
/// 每当用户尝试手动滚动或启动其他活动,或者动画到达视口边缘并尝试过度滚动时,动画都会中断。
|
||||
/// (如果[ScrollPosition]不会过度滚动,而是允许滚动超出范围,那么超出范围不会中断动画。)
|
||||
///
|
||||
/// 动画对视口或内容尺寸的更改无动于衷。
|
||||
///
|
||||
/// 一旦动画完成,如果滚动位置的值不稳定,则滚动位置将尝试开始弹道活动。
|
||||
/// (例如,如果滚动超出范围,并且在这种情况下滚动位置通常会弹回)
|
||||
///
|
||||
/// 持续时间不能为零。 要在没有动画的情况下跳至特定值,请使用[jumpTo]。
|
||||
Future<void> animateTo(
|
||||
double offset, {
|
||||
@required Duration duration,
|
||||
@required Curve curve,
|
||||
}) {
|
||||
assert(_positions.isNotEmpty,
|
||||
'ScrollController not attached to any scroll views.');
|
||||
final List<Future<void>> animations = List<Future<void>>(_positions.length);
|
||||
for (int i = 0; i < _positions.length; i += 1)
|
||||
animations[i] =
|
||||
_positions[i].animateTo(offset, duration: duration, curve: curve);
|
||||
return Future.wait<void>(animations).then<void>((List<void> _) => null);
|
||||
}
|
||||
|
||||
/// 将滚动位置从其当前值跳转到给定值,而不进行动画处理,也无需检查新值是否在范围内。
|
||||
/// 任何活动的动画都将被取消。 如果用户当前正在滚动,则该操作将被取消。
|
||||
///
|
||||
/// 如果此方法更改了滚动位置,则将分派开始/更新/结束滚动通知的序列。 此方法不能生成过滚动通知。
|
||||
///
|
||||
/// 跳跃之后,如果数值超出范围,则立即开始弹道活动。
|
||||
void jumpTo(double value) {
|
||||
assert(_positions.isNotEmpty,
|
||||
'ScrollController not attached to any scroll views.');
|
||||
for (ScrollPosition position in List<ScrollPosition>.from(_positions))
|
||||
position.jumpTo(value);
|
||||
}
|
||||
|
||||
/// 在此控制器上注册给定位置。
|
||||
/// 此函数返回后,此控制器上的[animateTo]和[jumpTo]方法将操纵给定位置。
|
||||
void attach(ScrollPosition position) {
|
||||
assert(!_positions.contains(position));
|
||||
_positions.add(position);
|
||||
position.addListener(notifyListeners);
|
||||
}
|
||||
|
||||
/// 用此控制器注销给定位置。
|
||||
/// 此函数返回后,此控制器上的[animateTo]和[jumpTo]方法将不会操纵给定位置。
|
||||
void detach(ScrollPosition position) {
|
||||
assert(_positions.contains(position));
|
||||
position.removeListener(notifyListeners);
|
||||
_positions.remove(position);
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
for (ScrollPosition position in _positions)
|
||||
position.removeListener(notifyListeners);
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
/// 创建一个[ScrollPosition]供[Scrollable]小部件使用。
|
||||
///
|
||||
/// 子类可以重写此功能,以自定义其控制的可滚动小部件使用的[ScrollPosition]。
|
||||
/// 例如,[PageController]重写此函数以返回面向页面的滚动位置子类,
|
||||
/// 该子类在可滚动窗口小部件调整大小时保持同一页面可见。
|
||||
///
|
||||
/// 默认情况下,返回[ScrollPositionWithSingleContext]。
|
||||
/// 参数通常传递给正在创建的[ScrollPosition]:
|
||||
///
|
||||
/// * `physics`:[ScrollPhysics]的一个实例,它确定[ScrollPosition]对用户交互的反应方式,
|
||||
/// 释放或甩动时如何模拟滚动等。该值不会为null。 它通常来自[ScrollView]或其他创建
|
||||
/// [Scrollable]的小部件,或者(如果未提供)来自环境[ScrollConfiguration]。
|
||||
/// * `context`:一个[ScrollContext],用于与拥有[ScrollPosition]的对象进行通信
|
||||
/// (通常是[Scrollable]本身)。
|
||||
/// * `oldPosition`: 如果这不是第一次为此[Scrollable]创建[ScrollPosition],
|
||||
/// 则它将是前一个实例。 当环境已更改并且[Scrollable]需要重新创建[ScrollPosition]
|
||||
/// 对象时,将使用此方法。 第一次创建[ScrollPosition]时为null。
|
||||
ScrollPosition createScrollPosition(ScrollPhysics physics,
|
||||
ScrollContext context, ScrollPosition oldPosition) {
|
||||
return ShopScrollPosition(
|
||||
coordinator: coordinator,
|
||||
physics: physics,
|
||||
context: context,
|
||||
initialPixels: initialScrollOffset,
|
||||
keepScrollOffset: keepScrollOffset,
|
||||
oldPosition: oldPosition,
|
||||
debugLabel: debugLabel,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
final List<String> description = <String>[];
|
||||
debugFillDescription(description);
|
||||
return '${describeIdentity(this)}(${description.join(", ")})';
|
||||
}
|
||||
|
||||
/// 在给定的描述中添加其他信息,以供[toString]使用。
|
||||
/// 此方法使子类更易于协调以提供高质量的[toString]实现。 [ScrollController]
|
||||
/// 基类上的[toString]实现调用[debugFillDescription]来从子类中收集有用的信息,以合并到其返回值中。
|
||||
/// 如果您重写了此方法,请确保通过调用`super.debugFillDescription(description)`来启动方法。
|
||||
@mustCallSuper
|
||||
void debugFillDescription(List<String> description) {
|
||||
super.debugFillDescription(description);
|
||||
if (debugLabel != null) description.add(debugLabel);
|
||||
if (initialScrollOffset != 0.0)
|
||||
description.add(
|
||||
'initialScrollOffset: ${initialScrollOffset.toStringAsFixed(1)}, ');
|
||||
if (_positions.isEmpty) {
|
||||
description.add('no clients');
|
||||
} else if (_positions.length == 1) {
|
||||
// 实际上不列出客户端本身,因为它的toString可能引用了我们。
|
||||
description.add('one client, offset ${offset?.toStringAsFixed(1)}');
|
||||
} else {
|
||||
description.add('${_positions.length} clients');
|
||||
}
|
||||
}
|
||||
}
|
||||
110
lib/utils/shop_scroll_coordinator.dart
Normal file
110
lib/utils/shop_scroll_coordinator.dart
Normal file
@@ -0,0 +1,110 @@
|
||||
import 'dart:math' as math;
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/rendering.dart';
|
||||
|
||||
import 'shop_scroll_controller.dart';
|
||||
import 'shop_scroll_position.dart';
|
||||
|
||||
enum PageExpandState { NotExpand, Expanding, Expanded }
|
||||
|
||||
/// 协调器
|
||||
class ShopScrollCoordinator {
|
||||
/// 页面主 CustomScrollView 控制
|
||||
final String pageLabel = "page";
|
||||
|
||||
ShopScrollController _pageScrollController;
|
||||
double Function() pinnedHeaderSliverHeightBuilder;
|
||||
|
||||
ShopScrollPosition get _pageScrollPosition => _pageScrollController.position;
|
||||
|
||||
ScrollDragController scrollDragController;
|
||||
|
||||
/// 主页面滑动部件默认位置
|
||||
double _pageInitialOffset;
|
||||
|
||||
/// 获取主页面滑动控制器
|
||||
ShopScrollController pageScrollController([double initialOffset = 0.0]) {
|
||||
assert(initialOffset != null, initialOffset >= 0.0);
|
||||
_pageInitialOffset = initialOffset;
|
||||
_pageScrollController = ShopScrollController(this,
|
||||
debugLabel: pageLabel, initialScrollOffset: initialOffset);
|
||||
return _pageScrollController;
|
||||
}
|
||||
|
||||
/// 创建并获取一个子滑动控制器
|
||||
ShopScrollController newChildScrollController([String debugLabel]) =>
|
||||
ShopScrollController(this, debugLabel: debugLabel);
|
||||
|
||||
/// 子部件滑动数据协调
|
||||
/// [userScrollDirection]用户滑动方向
|
||||
/// [position]被滑动的子部件的位置信息
|
||||
void applyUserOffset(double delta,
|
||||
[ScrollDirection userScrollDirection, ShopScrollPosition position]) {
|
||||
if (userScrollDirection == ScrollDirection.reverse) {
|
||||
updateUserScrollDirection(_pageScrollPosition, userScrollDirection);
|
||||
final innerDelta = _pageScrollPosition.applyClampedDragUpdate(delta);
|
||||
if (innerDelta != 0.0) {
|
||||
updateUserScrollDirection(position, userScrollDirection);
|
||||
position.applyFullDragUpdate(innerDelta);
|
||||
}
|
||||
} else {
|
||||
updateUserScrollDirection(position, userScrollDirection);
|
||||
final outerDelta = position.applyClampedDragUpdate(delta);
|
||||
if (outerDelta != 0.0) {
|
||||
updateUserScrollDirection(_pageScrollPosition, userScrollDirection);
|
||||
_pageScrollPosition.applyFullDragUpdate(outerDelta);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool applyContentDimensions(double minScrollExtent, double maxScrollExtent,
|
||||
ShopScrollPosition position) {
|
||||
if (pinnedHeaderSliverHeightBuilder != null) {
|
||||
maxScrollExtent = maxScrollExtent - pinnedHeaderSliverHeightBuilder();
|
||||
maxScrollExtent = math.max(0.0, maxScrollExtent);
|
||||
}
|
||||
return position.applyContentDimensions(
|
||||
minScrollExtent, maxScrollExtent, true);
|
||||
}
|
||||
|
||||
/// 当默认位置不为0时,主部件已下拉距离超过默认位置,但超过的距离不大于该值时,
|
||||
/// 若手指离开屏幕,主部件头部会回弹至默认位置
|
||||
double _scrollRedundancy = 80;
|
||||
|
||||
/// 当前页面Header最大程度展开状态
|
||||
PageExpandState pageExpand = PageExpandState.NotExpand;
|
||||
|
||||
/// 当手指离开屏幕
|
||||
void onPointerUp(PointerUpEvent event) {
|
||||
final double _pagePixels = _pageScrollPosition.pixels;
|
||||
if (0.0 < _pagePixels && _pagePixels < _pageInitialOffset) {
|
||||
if (pageExpand == PageExpandState.NotExpand &&
|
||||
_pageInitialOffset - _pagePixels > _scrollRedundancy) {
|
||||
_pageScrollPosition
|
||||
.animateTo(0.0,
|
||||
duration: const Duration(milliseconds: 400), curve: Curves.ease)
|
||||
.then((value) => pageExpand = PageExpandState.Expanded);
|
||||
} else {
|
||||
pageExpand = PageExpandState.Expanding;
|
||||
_pageScrollPosition
|
||||
.animateTo(_pageInitialOffset,
|
||||
duration: const Duration(milliseconds: 400), curve: Curves.ease)
|
||||
.then((value) => pageExpand = PageExpandState.NotExpand);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// 更新用户滑动方向
|
||||
void updateUserScrollDirection(
|
||||
ShopScrollPosition position, ScrollDirection value) {
|
||||
assert(position != null && value != null);
|
||||
position.didUpdateScrollDirection(value);
|
||||
}
|
||||
|
||||
/// 以特定的速度开始一个物理驱动的模拟,该模拟确定[pixels]位置。
|
||||
/// 此方法遵从[ScrollPhysics.createBallisticSimulation],该方法通常在当前位置超出
|
||||
/// 范围时提供滑动模拟,而在当前位置超出范围但具有非零速度时提供摩擦模拟。
|
||||
/// 速度应以每秒逻辑像素为单位。
|
||||
void goBallistic(double velocity) =>
|
||||
_pageScrollPosition.goBallistic(velocity);
|
||||
}
|
||||
292
lib/utils/shop_scroll_position.dart
Normal file
292
lib/utils/shop_scroll_position.dart
Normal file
@@ -0,0 +1,292 @@
|
||||
import 'dart:math' as math;
|
||||
|
||||
import 'package:flutter/gestures.dart';
|
||||
import 'package:flutter/physics.dart';
|
||||
import 'package:flutter/rendering.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
|
||||
import 'shop_scroll_coordinator.dart';
|
||||
|
||||
/// 滑动位置信息
|
||||
class ShopScrollPosition extends ScrollPosition
|
||||
implements ScrollActivityDelegate {
|
||||
final ShopScrollCoordinator coordinator; // 协调器
|
||||
ScrollDragController _currentDrag;
|
||||
double _heldPreviousVelocity = 0.0;
|
||||
|
||||
ShopScrollPosition(
|
||||
{@required ScrollPhysics physics,
|
||||
@required ScrollContext context,
|
||||
double initialPixels = 0.0,
|
||||
bool keepScrollOffset = true,
|
||||
ScrollPosition oldPosition,
|
||||
String debugLabel,
|
||||
@required this.coordinator})
|
||||
: super(
|
||||
physics: physics,
|
||||
context: context,
|
||||
keepScrollOffset: keepScrollOffset,
|
||||
oldPosition: oldPosition,
|
||||
debugLabel: debugLabel,
|
||||
) {
|
||||
// 如果oldPosition不为null,则父级将首先调用Absorb(),它可以设置_pixels和_activity.
|
||||
if (!hasPixels && initialPixels != null) correctPixels(initialPixels);
|
||||
if (activity == null) goIdle();
|
||||
assert(activity != null);
|
||||
}
|
||||
|
||||
@override
|
||||
AxisDirection get axisDirection => context.axisDirection;
|
||||
|
||||
@override
|
||||
double setPixels(double newPixels) {
|
||||
assert(activity.isScrolling);
|
||||
return super.setPixels(newPixels);
|
||||
}
|
||||
|
||||
@override
|
||||
void absorb(ScrollPosition other) {
|
||||
super.absorb(other);
|
||||
if (other is! ScrollPositionWithSingleContext) {
|
||||
goIdle();
|
||||
return;
|
||||
}
|
||||
activity.updateDelegate(this);
|
||||
final ShopScrollPosition typedOther = other as ShopScrollPosition;
|
||||
_userScrollDirection = typedOther._userScrollDirection;
|
||||
assert(_currentDrag == null);
|
||||
if (typedOther._currentDrag != null) {
|
||||
_currentDrag = typedOther._currentDrag;
|
||||
_currentDrag.updateDelegate(this);
|
||||
typedOther._currentDrag = null;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void applyNewDimensions() {
|
||||
super.applyNewDimensions();
|
||||
context.setCanDrag(physics.shouldAcceptUserOffset(this));
|
||||
}
|
||||
|
||||
/// 返回未使用的增量。
|
||||
/// 正增量表示下降(在上方显示内容),负增量向上(在下方显示内容)。
|
||||
double applyClampedDragUpdate(double delta) {
|
||||
assert(delta != 0.0);
|
||||
// 如果我们要朝向maxScrollExtent(负滚动偏移),那么我们在minScrollExtent方向上
|
||||
// 可以达到的最大距离是负无穷大。 例如,如果我们已经过度滚动,则滚动以减少过度滚动不应禁止过度滚动。
|
||||
// 如果我们要朝minScrollExtent(正滚动偏移量)方向移动,那么我们在minScrollExtent方向
|
||||
// 上可以达到的最大距离是我们现在所处的位置(如果我们已经过度滚动)
|
||||
// (在这种情况下,像素小于minScrollExtent),或者如果minScrollExtent可以 我们不是。
|
||||
// 换句话说,我们不能通过applyClampedDragUpdate进入过滚动状态。
|
||||
// 尽管如此,可能通过多种方式进入了过度滚动的情况。 一种是物理是否允许通过
|
||||
// applyFullDragUpdate(请参见下文)。也可能会发生过度滚动的情况,例如 如果使用滚动控制器人工设置了滚动位置。
|
||||
final double min =
|
||||
delta < 0.0 ? -double.infinity : math.min(minScrollExtent, pixels);
|
||||
// max的逻辑是等效的,但反向。
|
||||
final double max =
|
||||
delta > 0.0 ? double.infinity : math.max(maxScrollExtent, pixels);
|
||||
final double oldPixels = pixels;
|
||||
final double newPixels = (pixels - delta).clamp(min, max) as double;
|
||||
final double clampedDelta = newPixels - pixels;
|
||||
if (clampedDelta == 0.0) return delta;
|
||||
final double overScroll = physics.applyBoundaryConditions(this, newPixels);
|
||||
final double actualNewPixels = newPixels - overScroll;
|
||||
final double offset = actualNewPixels - oldPixels;
|
||||
if (offset != 0.0) {
|
||||
forcePixels(actualNewPixels);
|
||||
didUpdateScrollPositionBy(offset);
|
||||
}
|
||||
return delta + offset;
|
||||
}
|
||||
|
||||
// Returns the overScroll. 返回过度滚动。
|
||||
double applyFullDragUpdate(double delta) {
|
||||
assert(delta != 0.0);
|
||||
final double oldPixels = pixels;
|
||||
// Apply friction: 施加摩擦:
|
||||
final double newPixels =
|
||||
pixels - physics.applyPhysicsToUserOffset(this, delta);
|
||||
if (oldPixels == newPixels) return 0.0; // 增量一定很小,我们在添加浮点数时将其删除了
|
||||
// Check for overScroll: 检查过度滚动:
|
||||
final double overScroll = physics.applyBoundaryConditions(this, newPixels);
|
||||
final double actualNewPixels = newPixels - overScroll;
|
||||
if (actualNewPixels != oldPixels) {
|
||||
forcePixels(actualNewPixels);
|
||||
didUpdateScrollPositionBy(actualNewPixels - oldPixels);
|
||||
}
|
||||
return overScroll;
|
||||
}
|
||||
|
||||
/// 当手指滑动时,该方法会获取到滑动距离
|
||||
/// [delta]滑动距离,正增量表示下滑,负增量向上滑
|
||||
/// 我们需要把子部件的 滑动数据 交给协调器处理,主部件无干扰
|
||||
@override
|
||||
void applyUserOffset(double delta) {
|
||||
ScrollDirection userScrollDirection =
|
||||
delta > 0.0 ? ScrollDirection.forward : ScrollDirection.reverse;
|
||||
if (debugLabel != coordinator.pageLabel)
|
||||
return coordinator.applyUserOffset(delta, userScrollDirection, this);
|
||||
updateUserScrollDirection(userScrollDirection);
|
||||
setPixels(pixels - physics.applyPhysicsToUserOffset(this, delta));
|
||||
}
|
||||
|
||||
@override
|
||||
void beginActivity(ScrollActivity newActivity) {
|
||||
_heldPreviousVelocity = 0.0;
|
||||
if (newActivity == null) return;
|
||||
assert(newActivity.delegate == this);
|
||||
super.beginActivity(newActivity);
|
||||
_currentDrag?.dispose();
|
||||
_currentDrag = null;
|
||||
if (!activity.isScrolling) updateUserScrollDirection(ScrollDirection.idle);
|
||||
}
|
||||
|
||||
/// 将[用户滚动方向]设置为给定值。
|
||||
/// 如果更改了该值,则将分派[User ScrollNotification]。
|
||||
@protected
|
||||
@visibleForTesting
|
||||
void updateUserScrollDirection(ScrollDirection value) {
|
||||
assert(value != null);
|
||||
if (userScrollDirection == value) return;
|
||||
_userScrollDirection = value;
|
||||
didUpdateScrollDirection(value);
|
||||
}
|
||||
|
||||
@override
|
||||
ScrollHoldController hold(VoidCallback holdCancelCallback) {
|
||||
final double previousVelocity = activity.velocity;
|
||||
final HoldScrollActivity holdActivity =
|
||||
HoldScrollActivity(delegate: this, onHoldCanceled: holdCancelCallback);
|
||||
beginActivity(holdActivity);
|
||||
_heldPreviousVelocity = previousVelocity;
|
||||
return holdActivity;
|
||||
}
|
||||
|
||||
@override
|
||||
Drag drag(DragStartDetails details, VoidCallback dragCancelCallback) {
|
||||
final ScrollDragController drag = ScrollDragController(
|
||||
delegate: this,
|
||||
details: details,
|
||||
onDragCanceled: dragCancelCallback,
|
||||
carriedVelocity: physics.carriedMomentum(_heldPreviousVelocity),
|
||||
motionStartDistanceThreshold: physics.dragStartDistanceMotionThreshold,
|
||||
);
|
||||
beginActivity(DragScrollActivity(this, drag));
|
||||
assert(_currentDrag == null);
|
||||
_currentDrag = drag;
|
||||
return drag;
|
||||
}
|
||||
|
||||
@override
|
||||
void goIdle() {
|
||||
beginActivity(IdleScrollActivity(this));
|
||||
}
|
||||
|
||||
/// 以特定的速度开始一个物理驱动的模拟,该模拟确定[pixels]位置。
|
||||
/// 此方法遵从[ScrollPhysics.createBallisticSimulation],该方法通常在当前位置超出
|
||||
/// 范围时提供滑动模拟,而在当前位置超出范围但具有非零速度时提供摩擦模拟。
|
||||
/// 速度应以每秒逻辑像素为单位。
|
||||
@override
|
||||
void goBallistic(double velocity, [bool fromCoordinator = false]) {
|
||||
if (debugLabel != coordinator.pageLabel) {
|
||||
if (velocity > 0.0) coordinator.goBallistic(velocity);
|
||||
} else {
|
||||
if (fromCoordinator && velocity <= 0.0) return;
|
||||
if (coordinator.pageExpand == PageExpandState.Expanding) return;
|
||||
}
|
||||
assert(pixels != null);
|
||||
final Simulation simulation =
|
||||
physics.createBallisticSimulation(this, velocity);
|
||||
if (simulation != null) {
|
||||
beginActivity(BallisticScrollActivity(this, simulation, context.vsync));
|
||||
} else {
|
||||
goIdle();
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
bool applyContentDimensions(double minScrollExtent, double maxScrollExtent,
|
||||
[bool fromCoordinator = false]) {
|
||||
if (debugLabel == coordinator.pageLabel && !fromCoordinator)
|
||||
return coordinator.applyContentDimensions(
|
||||
minScrollExtent, maxScrollExtent, this);
|
||||
return super.applyContentDimensions(minScrollExtent, maxScrollExtent);
|
||||
}
|
||||
|
||||
@override
|
||||
ScrollDirection get userScrollDirection => _userScrollDirection;
|
||||
ScrollDirection _userScrollDirection = ScrollDirection.idle;
|
||||
|
||||
@override
|
||||
Future<void> animateTo(
|
||||
double to, {
|
||||
@required Duration duration,
|
||||
@required Curve curve,
|
||||
}) {
|
||||
if (nearEqual(to, pixels, physics.tolerance.distance)) {
|
||||
// 跳过动画,直接移到我们已经靠近的位置。
|
||||
jumpTo(to);
|
||||
return Future<void>.value();
|
||||
}
|
||||
|
||||
final DrivenScrollActivity activity = DrivenScrollActivity(
|
||||
this,
|
||||
from: pixels,
|
||||
to: to,
|
||||
duration: duration,
|
||||
curve: curve,
|
||||
vsync: context.vsync,
|
||||
);
|
||||
beginActivity(activity);
|
||||
return activity.done;
|
||||
}
|
||||
|
||||
@override
|
||||
void jumpTo(double value) {
|
||||
goIdle();
|
||||
if (pixels != value) {
|
||||
final double oldPixels = pixels;
|
||||
forcePixels(value);
|
||||
notifyListeners();
|
||||
didStartScroll();
|
||||
didUpdateScrollPositionBy(pixels - oldPixels);
|
||||
didEndScroll();
|
||||
}
|
||||
goBallistic(0.0);
|
||||
}
|
||||
|
||||
@Deprecated('This will lead to bugs.')
|
||||
@override
|
||||
void jumpToWithoutSettling(double value) {
|
||||
goIdle();
|
||||
if (pixels != value) {
|
||||
final double oldPixels = pixels;
|
||||
forcePixels(value);
|
||||
notifyListeners();
|
||||
didStartScroll();
|
||||
didUpdateScrollPositionBy(pixels - oldPixels);
|
||||
didEndScroll();
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_currentDrag?.dispose();
|
||||
_currentDrag = null;
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
void debugFillDescription(List<String> description) {
|
||||
super.debugFillDescription(description);
|
||||
description.add('${context.runtimeType}');
|
||||
description.add('$physics');
|
||||
description.add('$activity');
|
||||
description.add('$userScrollDirection');
|
||||
}
|
||||
|
||||
@override
|
||||
void pointerScroll(double delta) {
|
||||
// TODO: implement pointerScroll
|
||||
}
|
||||
}
|
||||
@@ -1,12 +1,199 @@
|
||||
|
||||
import 'dart:convert';
|
||||
import 'dart:io';
|
||||
import 'dart:typed_data';
|
||||
import 'dart:ui' as ui;
|
||||
|
||||
import 'package:cached_network_image/cached_network_image.dart';
|
||||
import 'package:dio/dio.dart';
|
||||
// import 'package:firebase_messaging/firebase_messaging.dart';
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import '../routes.dart';
|
||||
import 'utils.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_local_notifications/flutter_local_notifications.dart';
|
||||
import 'package:hive/hive.dart';
|
||||
import 'package:image_picker/image_picker.dart';
|
||||
import 'package:path/path.dart';
|
||||
import 'package:path_provider/path_provider.dart';
|
||||
import 'package:stripe_payment/stripe_payment.dart';
|
||||
|
||||
import '../constants.dart';
|
||||
import '../events/eventbus.dart';
|
||||
import '../events/events.dart';
|
||||
import '../generated/l10n.dart';
|
||||
import '../models/comment.dart';
|
||||
import '../models/order.dart';
|
||||
import '../models/payment_platform.dart';
|
||||
import '../models/stripe_payment_method.dart';
|
||||
import '../models/user.dart';
|
||||
import '../routes.dart';
|
||||
import '../store/actions.dart';
|
||||
import '../store/store.dart';
|
||||
import '../widgets/general/stripe_pay.dart';
|
||||
import 'http_util.dart';
|
||||
import 'utils.dart';
|
||||
|
||||
typedef void OnGotFile(int imageId, String filePath);
|
||||
|
||||
class Util {
|
||||
static final Util _util = Util._internal();
|
||||
factory Util() {
|
||||
return _util;
|
||||
}
|
||||
Util._internal();
|
||||
|
||||
// final FirebaseMessaging firebaseMessaging = FirebaseMessaging();
|
||||
final FlutterLocalNotificationsPlugin flutterLocalNotificationsPlugin = new FlutterLocalNotificationsPlugin();
|
||||
static bool notificationTapped = false;
|
||||
|
||||
init() {
|
||||
// initLocalNotification();
|
||||
// initFirebaseMessaging();
|
||||
|
||||
// ePmqRwRUTmKuFNfbHA-6zq:APA91bEFEQx1YgJqfx0V0VYo86uRDGjG6SP1SPCAZW4OvQ7gMOeKWDQ47bfuEM00YavtKq7ZGpsWfFL7B3ypm00ZA6crjJJLK_-j_H8bS_dnQbLFwyRcW67IXCs5sRkro71bRZv7L1WM
|
||||
|
||||
// eventBus.on<SubscribeToTopic>().listen((event) {
|
||||
// if (firebaseMessaging != null) {
|
||||
// firebaseMessaging.subscribeToTopic(event.topic);
|
||||
// }
|
||||
// });
|
||||
// eventBus.on<UnSubscribeToTopic>().listen((event) {
|
||||
// if (firebaseMessaging != null) {
|
||||
// firebaseMessaging.unsubscribeFromTopic(event.topic);
|
||||
// }
|
||||
// });
|
||||
}
|
||||
|
||||
initLocalNotification() async {
|
||||
var initializationSettingsAndroid = new AndroidInitializationSettings('ic_launcher');
|
||||
var initializationSettingsIOS = new IOSInitializationSettings(
|
||||
onDidReceiveLocalNotification: onDidReceiveLocalNotification);
|
||||
var initializationSettings = new InitializationSettings(
|
||||
android: initializationSettingsAndroid,
|
||||
iOS: initializationSettingsIOS,
|
||||
);
|
||||
await flutterLocalNotificationsPlugin.initialize(initializationSettings,
|
||||
onSelectNotification: onSelectNotification);
|
||||
}
|
||||
|
||||
Future onSelectNotification(String payload, {bool replace=false}) async {
|
||||
print('payload: $payload');
|
||||
if (payload != null && payload.isNotEmpty) {
|
||||
Routes.router.navigateTo(store.state.context, payload, replace: replace);
|
||||
}
|
||||
}
|
||||
|
||||
// initFirebaseMessaging() {
|
||||
// if (Platform.isIOS) {
|
||||
// iosPermission();
|
||||
// }
|
||||
// firebaseMessaging.getToken().then((token) {
|
||||
// print('FCM token: $token');
|
||||
// store.dispatch(UpdateFcmToken(token));
|
||||
// });
|
||||
// firebaseMessaging.configure(
|
||||
// onMessage: (Map<String, dynamic> message) {
|
||||
// print('FCM onMessage: $message');
|
||||
// // eventBus.fire(OnFcmReceived(message));
|
||||
// showNotificationWithDefaultSound(message);
|
||||
// return;
|
||||
// },
|
||||
// onBackgroundMessage: Platform.isIOS ? null : backgroundMessageHandler,
|
||||
// onResume: (Map<String, dynamic> message) {
|
||||
// print('FCM onResume: $message');
|
||||
// if (Platform.isAndroid) {
|
||||
// if (message != null && message['data'] != null && message['data']['route'] != null) {
|
||||
// notificationTapped = true;
|
||||
// onSelectNotification(message['data']['route']);
|
||||
// }
|
||||
// } else {
|
||||
// if (message != null && message['route'] != null) {
|
||||
// notificationTapped = true;
|
||||
// onSelectNotification(message['route']);
|
||||
// }
|
||||
// }
|
||||
// return;
|
||||
// },
|
||||
// onLaunch: (Map<String, dynamic> message) {
|
||||
// print('FCM onLaunch: $message');
|
||||
// if (Platform.isAndroid) {
|
||||
// if (message != null && message['data'] != null && message['data']['route'] != null) {
|
||||
// notificationTapped = true;
|
||||
// onSelectNotification(message['data']['route'], replace: true);
|
||||
// }
|
||||
// } else {
|
||||
// if (message != null && message['route'] != null) {
|
||||
// notificationTapped = true;
|
||||
// onSelectNotification(message['route'], replace: true);
|
||||
// }
|
||||
// }
|
||||
// return;
|
||||
// },
|
||||
// );
|
||||
// }
|
||||
|
||||
Future onDidReceiveLocalNotification(int id, String title, String body, String payload) async {
|
||||
showDialog(
|
||||
context: store.state.context,
|
||||
builder: (BuildContext context) => CupertinoAlertDialog(
|
||||
title: Text(title),
|
||||
content: Text(body),
|
||||
actions: [
|
||||
CupertinoDialogAction(
|
||||
isDefaultAction: true,
|
||||
child: Text(S.of(context).ok),
|
||||
onPressed: () async {
|
||||
Navigator.of(context, rootNavigator: true).pop();
|
||||
if (payload != null && payload.isNotEmpty) {
|
||||
Routes.router.navigateTo(context, payload);
|
||||
}
|
||||
},
|
||||
)
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Future showNotificationWithDefaultSound(Map<String, dynamic> message) async {
|
||||
var androidPlatformChannelSpecifics = new AndroidNotificationDetails(
|
||||
'channelId',
|
||||
'channelName',
|
||||
'channelDescription',
|
||||
importance: Importance.max,
|
||||
priority: Priority.high,
|
||||
);
|
||||
var iOSPlatformChannelSpecifics = new IOSNotificationDetails(presentSound: true, badgeNumber: 1);
|
||||
var platformChannelSpecifics = new NotificationDetails(
|
||||
android: androidPlatformChannelSpecifics,
|
||||
iOS: iOSPlatformChannelSpecifics,
|
||||
);
|
||||
await flutterLocalNotificationsPlugin.show(
|
||||
0,
|
||||
Platform.isAndroid ? message['notification']['title'] : message['title'],
|
||||
Platform.isAndroid ? message['notification']['body'] : message['body'],
|
||||
platformChannelSpecifics,
|
||||
payload: Platform.isAndroid ? message['data']['route'] : message['route'],
|
||||
);
|
||||
}
|
||||
|
||||
// iosPermission() {
|
||||
// firebaseMessaging.requestNotificationPermissions(
|
||||
// const IosNotificationSettings(
|
||||
// sound: true,
|
||||
// badge: true,
|
||||
// alert: true,
|
||||
// )
|
||||
// );
|
||||
// firebaseMessaging.onIosSettingsRegistered.listen((IosNotificationSettings settings) {
|
||||
// print('IOS settings registered: $settings');
|
||||
// });
|
||||
// }
|
||||
|
||||
static Future<dynamic> backgroundMessageHandler(Map<String, dynamic> message) {
|
||||
print('FCM background message handler: $message');
|
||||
return Future<void>.value();
|
||||
}
|
||||
|
||||
static Future<Box> getBox() async {
|
||||
final dir = await getApplicationDocumentsDirectory();
|
||||
Hive.init(dir.path);
|
||||
@@ -16,31 +203,487 @@ class Util {
|
||||
|
||||
static Widget showImage(String imageUrl, {double width, double height,
|
||||
BoxFit fit, Widget Function(BuildContext, String, dynamic) errorWidget}) {
|
||||
if (imageUrl == null || imageUrl.isEmpty) {
|
||||
return Container(
|
||||
if (imageUrl != null && imageUrl.isNotEmpty && imageUrl.startsWith('https:')) {
|
||||
return CachedNetworkImage(
|
||||
imageUrl: imageUrl,
|
||||
width: width,
|
||||
height: width,
|
||||
fit: fit,
|
||||
placeholder: (context, url) => Utils.imageLoadingIndicator(width: width, height: height),
|
||||
errorWidget: errorWidget != null ? errorWidget : (context, url, error) {
|
||||
return Image.asset(
|
||||
'assets/images/not_found.png',
|
||||
width: width,
|
||||
height: height,
|
||||
fit: fit,
|
||||
);
|
||||
},
|
||||
);
|
||||
} else if (imageUrl != null && imageUrl.isNotEmpty) {
|
||||
return Image.file(
|
||||
File(imageUrl),
|
||||
width: width,
|
||||
height: height,
|
||||
child: Text(''),
|
||||
fit: fit,
|
||||
errorBuilder: (BuildContext context, Object object, StackTrace stackTrace) {
|
||||
return Image.asset(
|
||||
'assets/images/not_found.png',
|
||||
width: width,
|
||||
height: height,
|
||||
fit: fit,
|
||||
);
|
||||
},
|
||||
);
|
||||
} else {
|
||||
return Image.asset(
|
||||
'assets/images/not_found.png',
|
||||
width: width,
|
||||
height: height,
|
||||
fit: fit,
|
||||
);
|
||||
}
|
||||
return CachedNetworkImage(
|
||||
imageUrl: imageUrl,
|
||||
width: width,
|
||||
height: width,
|
||||
fit: fit,
|
||||
placeholder: (context, url) => Utils.imageLoadingIndicator(),
|
||||
errorWidget: errorWidget != null ? errorWidget : (context, url, error) {
|
||||
return Image.asset(
|
||||
'assets/images/not_found.png',
|
||||
width: width,
|
||||
height: height,
|
||||
fit: fit,
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
static void openWebUrl(BuildContext context, String link) {
|
||||
Routes.router.navigateTo(context, '/webview/$link');
|
||||
}
|
||||
|
||||
AlertDialog getPicture(BuildContext context, User user, {int commentId = -1, int orderId = 0}) {
|
||||
return AlertDialog(
|
||||
title: Text(
|
||||
S.of(context).get_picture,
|
||||
),
|
||||
content: Container(
|
||||
height: 130.0,
|
||||
child: Column(
|
||||
children: <Widget>[
|
||||
Container(
|
||||
margin: EdgeInsets.only(bottom: 12.0),
|
||||
child: Text(
|
||||
S.of(context).get_picture_from,
|
||||
style: TextStyle(
|
||||
fontSize: 15.0,
|
||||
color: Colors.black26,
|
||||
),
|
||||
),
|
||||
),
|
||||
Container(
|
||||
child: TextButton(
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
children: <Widget>[
|
||||
Icon(
|
||||
Icons.photo_library,
|
||||
),
|
||||
Container(
|
||||
margin: EdgeInsets.only(left: 5.0),
|
||||
child: Text(
|
||||
S.of(context).gallery,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
onPressed: () {
|
||||
getPictureFromGallery(context, user, commentId: commentId, orderId: orderId);
|
||||
},
|
||||
),
|
||||
),
|
||||
Container(
|
||||
child: TextButton(
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
children: <Widget>[
|
||||
Icon(
|
||||
Icons.photo_camera,
|
||||
),
|
||||
Container(
|
||||
margin: EdgeInsets.only(left: 5.0),
|
||||
child: Text(
|
||||
S.of(context).camera,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
onPressed: () {
|
||||
getPictureFromCamera(context, user, commentId: commentId, orderId: orderId);
|
||||
},
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void getPictureFromGallery(BuildContext context, User user, {int commentId = -1, int orderId = 0}) async {
|
||||
final picker = ImagePicker();
|
||||
var image = await picker.pickImage(source: ImageSource.gallery);
|
||||
Navigator.of(context).pop();
|
||||
uploadPicture(context, File(image.path), user, commentId: commentId, orderId: orderId);
|
||||
}
|
||||
|
||||
void getPictureFromCamera(BuildContext context, User user, {int commentId = -1, int orderId = 0}) async {
|
||||
final picker = ImagePicker();
|
||||
var image = await picker.pickImage(source: ImageSource.camera);
|
||||
Navigator.of(context).pop();
|
||||
uploadPicture(context, File(image.path), user, commentId: commentId, orderId: orderId);
|
||||
}
|
||||
|
||||
void uploadPicture(BuildContext context, File image, User user, {int commentId = -1, int orderId = 0}) async {
|
||||
if (await image.exists()) {
|
||||
print('image: ${image.path}');
|
||||
String imageName = basename(image.path);
|
||||
MultipartFile imageFile = await MultipartFile.fromFile(image.path, filename: imageName);
|
||||
Map<String, dynamic> formMap = {
|
||||
'id': user.id,
|
||||
'file': imageFile,
|
||||
};
|
||||
if (commentId >= 0) {
|
||||
formMap = {
|
||||
'contact_id': user.id,
|
||||
'comment_id': commentId,
|
||||
'order_id': orderId,
|
||||
'file': imageFile,
|
||||
};
|
||||
HttpUtil.httpPost(
|
||||
'v1/upload-comment-image',
|
||||
(response) {
|
||||
eventBus.fire(new OnCommentUpdatedEvent(Comment.fromJson(response.data)));
|
||||
},
|
||||
isFormData: true,
|
||||
body: formMap,
|
||||
sendProgress: (int sent, int total) {
|
||||
if (sent == total) {
|
||||
eventBus.fire(OnUploadCommentImageProgressEvent(false, 0.0));
|
||||
} else {
|
||||
eventBus.fire(OnUploadCommentImageProgressEvent(true, sent / total));
|
||||
}
|
||||
},
|
||||
).catchError((error) {
|
||||
Utils.showMessageDialog(context, error);
|
||||
});
|
||||
} else {
|
||||
HttpUtil.httpPost(
|
||||
'v1/upload-avatar',
|
||||
(response) {
|
||||
store.dispatch(new UpdateCurrentUser(User.fromJson(response.data)));
|
||||
eventBus.fire(new OnCurrentUserUpdated());
|
||||
},
|
||||
isFormData: true,
|
||||
body: formMap,
|
||||
sendProgress: (int sent, int total) {
|
||||
if (sent == total) {
|
||||
eventBus.fire(OnProgressEvent(false, 0.0));
|
||||
} else {
|
||||
eventBus.fire(OnProgressEvent(true, sent / total));
|
||||
}
|
||||
},
|
||||
).catchError((error) {
|
||||
Utils.showMessageDialog(context, error);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
AlertDialog getPicture2(BuildContext context, int imageId, OnGotFile onGotFile) {
|
||||
return AlertDialog(
|
||||
title: Text(
|
||||
S.of(context).get_picture,
|
||||
),
|
||||
content: Container(
|
||||
height: 130.0,
|
||||
child: Column(
|
||||
children: <Widget>[
|
||||
Container(
|
||||
margin: EdgeInsets.only(bottom: 12.0),
|
||||
child: Text(
|
||||
S.of(context).get_picture_from,
|
||||
style: TextStyle(
|
||||
fontSize: 15.0,
|
||||
color: Colors.black26,
|
||||
),
|
||||
),
|
||||
),
|
||||
Container(
|
||||
child: TextButton(
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
children: <Widget>[
|
||||
Icon(
|
||||
Icons.photo_library,
|
||||
),
|
||||
Container(
|
||||
margin: EdgeInsets.only(left: 5.0),
|
||||
child: Text(
|
||||
S.of(context).gallery,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
onPressed: () {
|
||||
getPictureFromGallery2(context, imageId, onGotFile);
|
||||
},
|
||||
),
|
||||
),
|
||||
Container(
|
||||
child: TextButton(
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
children: <Widget>[
|
||||
Icon(
|
||||
Icons.photo_camera,
|
||||
),
|
||||
Container(
|
||||
margin: EdgeInsets.only(left: 5.0),
|
||||
child: Text(
|
||||
S.of(context).camera,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
onPressed: () {
|
||||
getPictureFromCamera2(context, imageId, onGotFile);
|
||||
},
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void getPictureFromGallery2(BuildContext context, int imageId, OnGotFile onGotFile) async {
|
||||
ImagePicker picker = ImagePicker();
|
||||
var image = await picker.pickImage(source: ImageSource.gallery);
|
||||
Navigator.of(context).pop();
|
||||
onGotFile(imageId, image.path);
|
||||
}
|
||||
|
||||
void getPictureFromCamera2(BuildContext context, int imageId, OnGotFile onGotFile) async {
|
||||
ImagePicker picker = ImagePicker();
|
||||
var image = await picker.pickImage(source: ImageSource.camera);
|
||||
Navigator.of(context).pop();
|
||||
onGotFile(imageId, image.path);
|
||||
}
|
||||
|
||||
Future<void> createTicket(BuildContext context, String msg, List<Map<String, dynamic>> images,
|
||||
OnSuccess onSuccess, OnError onError, {int id}) {
|
||||
var formData = FormData();
|
||||
formData.fields.add(MapEntry("msg", msg));
|
||||
formData.fields.add(MapEntry('id', id == null ? '0' : id.toString()));
|
||||
for (int i = 0; i < images.length; i++) {
|
||||
String imagePath = images[i]['path'];
|
||||
if (imagePath.isNotEmpty) {
|
||||
String imageName = basename(imagePath);
|
||||
formData.files.add(
|
||||
MapEntry(
|
||||
"files",
|
||||
MultipartFile.fromFileSync(imagePath, filename: imageName)
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
HttpUtil.httpPost('create-new-ticket-app/', (response) {
|
||||
if (onSuccess != null) {
|
||||
onSuccess(response);
|
||||
}
|
||||
},
|
||||
isFormData: true,
|
||||
// body: null,
|
||||
formData: formData,
|
||||
sendProgress: (int sent, int total) {
|
||||
if (sent == total) {
|
||||
eventBus.fire(OnSubmitProgressEvent(false, 100.0));
|
||||
} else {
|
||||
eventBus.fire(OnSubmitProgressEvent(true, sent / total * 100.0));
|
||||
}
|
||||
},
|
||||
businessId: Constants.BUSINESS_ID,
|
||||
).catchError((error) {
|
||||
if (onError != null) {
|
||||
onError(error);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
Widget getNativePay(BuildContext context, Order order, PaymentPlatform paymentPlatform) {
|
||||
return GestureDetector(
|
||||
child: Container(
|
||||
padding: EdgeInsets.only(top: 16.0, left: 16.0, right: 16.0, bottom: 16.0),
|
||||
child: Center(
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: <Widget>[
|
||||
Container(
|
||||
margin: EdgeInsets.only(right: 6.0),
|
||||
child: Text(
|
||||
Platform.isAndroid ? S.of(context).pay_with : S.of(context).pay_with,
|
||||
style: TextStyle(
|
||||
fontSize: 18.0,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
),
|
||||
Container(
|
||||
padding: EdgeInsets.only(right: 10.0),
|
||||
child: Platform.isAndroid ? Image.asset(
|
||||
'assets/images/google_pay.png',
|
||||
height: 22.0,
|
||||
fit: BoxFit.fitHeight,
|
||||
) : Image.asset(
|
||||
'assets/images/apple_pay.png',
|
||||
height: 22.0,
|
||||
fit: BoxFit.fitHeight,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
decoration: BoxDecoration(
|
||||
border: Border(
|
||||
bottom: BorderSide(
|
||||
width: 10,
|
||||
color: Colors.black26,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
onTap: () {
|
||||
print(paymentPlatform);
|
||||
PaymentPlatform newPaymentPlatform = PaymentPlatform.fromJson(json.decode(json.encode(paymentPlatform)));
|
||||
newPaymentPlatform.code = Constants.PAYMENT_METHOD_NATIVE_PAY;
|
||||
goPayment(context, order, newPaymentPlatform);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
static void goPayment(BuildContext context, Order order,
|
||||
PaymentPlatform paymentPlatform, {
|
||||
bool googlePay=false,
|
||||
bool applePay=false,
|
||||
StripePaymentMethod stripePaymentMethod,
|
||||
}) {
|
||||
switch(paymentPlatform.code) {
|
||||
case Constants.PAYMENT_METHOD_CODE_SQUARE:
|
||||
|
||||
break;
|
||||
case Constants.PAYMENT_METHOD_CODE_CHASE:
|
||||
|
||||
break;
|
||||
case Constants.PAYMENT_METHOD_CODE_PAYPAL:
|
||||
|
||||
break;
|
||||
case Constants.PAYMENT_METHOD_CODE_OTT_ALIPAY:
|
||||
|
||||
break;
|
||||
case Constants.PAYMENT_METHOD_CODE_OTT_WECHATPAY:
|
||||
|
||||
break;
|
||||
case Constants.PAYMENT_METHOD_CODE_POD:
|
||||
|
||||
break;
|
||||
case Constants.PAYMENT_METHOD_NATIVE_PAY:
|
||||
StripePayment.setOptions(
|
||||
StripeOptions(publishableKey: paymentPlatform.publishableKey,
|
||||
merchantId: paymentPlatform.merchantId,
|
||||
androidPayMode: paymentPlatform.publishableKey.contains('_test_')
|
||||
? 'test'
|
||||
: 'production'
|
||||
)
|
||||
);
|
||||
StripePayment.paymentRequestWithNativePay(
|
||||
androidPayOptions: AndroidPayPaymentRequest(
|
||||
totalPrice: order.totalPrice.toStringAsFixed(2),
|
||||
currencyCode: order.businessInfo.currency,
|
||||
),
|
||||
applePayOptions: ApplePayPaymentOptions(
|
||||
currencyCode: order.businessInfo.currency,
|
||||
countryCode: order.businessInfo.address.country,
|
||||
items: [
|
||||
ApplePayItem(
|
||||
label: order.businessInfo.name,
|
||||
amount: order.totalPrice.toStringAsFixed(2),
|
||||
),
|
||||
],
|
||||
),
|
||||
).then((token) {
|
||||
print('return native token: ${token.tokenId}');
|
||||
processNativePay(context, token, order);
|
||||
}).catchError((error) {
|
||||
print('native pay error: $error');
|
||||
});
|
||||
break;
|
||||
case Constants.PAYMENT_METHOD_CODE_STRIPE:
|
||||
Navigator.pushReplacement(context, MaterialPageRoute(
|
||||
builder: (BuildContext context) {
|
||||
return StripePay(order, paymentPlatform, stripePaymentMethod: stripePaymentMethod,);
|
||||
}
|
||||
));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static void processNativePay(BuildContext context, Token token, Order order) async {
|
||||
PaymentMethod paymentMethod = await StripePayment.createPaymentMethod(
|
||||
PaymentMethodRequest(
|
||||
card: CreditCard(
|
||||
token: token.tokenId,
|
||||
),
|
||||
),
|
||||
);
|
||||
if (paymentMethod != null) {
|
||||
Utils.stripePaymentIntent(order, null, paymentMethod.id, paymentMethod.type, (response) {
|
||||
if (response.data['status'] == Constants.STRIPE_STATUS_REQUIRES_CONFIRMATION) {
|
||||
StripePayment.confirmPaymentIntent(
|
||||
PaymentIntent(
|
||||
clientSecret: response.data[Constants.STRIPE_CLIENT_SECRET],
|
||||
paymentMethodId: response.data['payment_method'],
|
||||
),
|
||||
).then((paymentIntentResult) {
|
||||
if (paymentIntentResult.status == Constants.STRIPE_STATUS_SUCCEDED) {
|
||||
Utils.stripeChargedSuccess(order,
|
||||
paymentMethod.id,
|
||||
paymentIntentResult.paymentIntentId,
|
||||
(response) {
|
||||
StripePayment.completeNativePayRequest().then((_) {
|
||||
eventBus.fire(OnOrderUpdated());
|
||||
Routes.router.navigateTo(context, '/orderdetail/${order.id}', replace: true);
|
||||
}).catchError((error) {
|
||||
Utils.showMessageDialog(context, error, onOk: () {
|
||||
Navigator.of(context).pop();
|
||||
});
|
||||
});
|
||||
},
|
||||
(error) {
|
||||
Utils.showMessageDialog(context, error, onOk: () {
|
||||
Navigator.of(context).pop();
|
||||
});
|
||||
}
|
||||
);
|
||||
} else {
|
||||
Utils.showMessageDialog(context, Exception('Unknown error'));
|
||||
}
|
||||
}).catchError((error) {
|
||||
Utils.showMessageDialog(context, error, onOk: () {
|
||||
Navigator.of(context).pop();
|
||||
});
|
||||
});
|
||||
}
|
||||
}, (error) {
|
||||
Utils.showMessageDialog(context, error, onOk: () {
|
||||
Navigator.of(context).pop();
|
||||
});
|
||||
});
|
||||
} else {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
static Future<Uint8List> getBytesFromAsset(String path, int width) async {
|
||||
ByteData data = await rootBundle.load(path);
|
||||
ui.Codec codec = await ui.instantiateImageCodec(data.buffer.asUint8List(), targetWidth: width);
|
||||
ui.FrameInfo fi = await codec.getNextFrame();
|
||||
return (await fi.image.toByteData(format: ui.ImageByteFormat.png)).buffer.asUint8List();
|
||||
}
|
||||
}
|
||||
@@ -1,11 +1,40 @@
|
||||
|
||||
import 'dart:html';
|
||||
import 'dart:typed_data';
|
||||
|
||||
import 'package:dio/dio.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:url_launcher/url_launcher.dart';
|
||||
import 'utils.dart';
|
||||
import 'package:fluttertoast/fluttertoast.dart';
|
||||
import 'package:hive/hive.dart';
|
||||
import 'package:hive_flutter/hive_flutter.dart';
|
||||
import 'package:url_launcher/url_launcher.dart';
|
||||
|
||||
import '../constants.dart';
|
||||
import '../events/eventbus.dart';
|
||||
import '../events/events.dart';
|
||||
import '../generated/l10n.dart';
|
||||
import '../models/comment.dart' as mComment;
|
||||
import '../models/order.dart';
|
||||
import '../models/payment_platform.dart';
|
||||
import '../models/stripe_payment_method.dart';
|
||||
import '../models/user.dart';
|
||||
import '../store/actions.dart';
|
||||
import '../store/store.dart';
|
||||
import '../pages/stripe_pay_web.dart';
|
||||
import 'http_util.dart';
|
||||
import 'utils.dart';
|
||||
|
||||
typedef void OnGotFile(int imageId, String filePath);
|
||||
|
||||
class Util {
|
||||
static final Util _instance = Util._internal();
|
||||
factory Util() {
|
||||
return _instance;
|
||||
}
|
||||
Util._internal();
|
||||
init() {
|
||||
|
||||
}
|
||||
|
||||
static Future<Box> getBox() async {
|
||||
Hive.initFlutter();
|
||||
@@ -28,6 +57,7 @@ class Util {
|
||||
);
|
||||
},
|
||||
errorBuilder: (BuildContext context, Object object, StackTrace stackTrace) {
|
||||
print(stackTrace);
|
||||
if (errorWidget != null) {
|
||||
return errorWidget(context, null, null);
|
||||
}
|
||||
@@ -49,4 +79,250 @@ class Util {
|
||||
print('Could not launch $url');
|
||||
}
|
||||
}
|
||||
|
||||
AlertDialog getPicture(BuildContext context, User user, {int commentId = -1, int orderId = 0}) {
|
||||
return AlertDialog(
|
||||
title: Text(S.of(context).please_select),
|
||||
content: Text(S.of(context).please_select_an_image),
|
||||
actions: <Widget>[
|
||||
TextButton(
|
||||
child: Text(S.of(context).cancel),
|
||||
onPressed: () {
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
),
|
||||
TextButton(
|
||||
child: Text(S.of(context).select, style: TextStyle(color: Colors.white),),
|
||||
style: TextButton.styleFrom(
|
||||
backgroundColor: Theme.of(context).primaryColor,
|
||||
),
|
||||
onPressed: () {
|
||||
Navigator.of(context).pop();
|
||||
startFilePicker(context, user, commentId: commentId, orderId: orderId);
|
||||
},
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
void startFilePicker(BuildContext context, User user, {int commentId = -1, int orderId = 0}) async {
|
||||
InputElement uploadInput = FileUploadInputElement();
|
||||
uploadInput.click();
|
||||
uploadInput.onChange.listen((e) {
|
||||
final files = uploadInput.files;
|
||||
if (files.length >= 1) {
|
||||
final File file = files[0];
|
||||
final FileReader reader = new FileReader();
|
||||
|
||||
reader.onLoadEnd.listen((e) {
|
||||
uploadPicture(context, reader.result, file.name, user, commentId: commentId, orderId: orderId);
|
||||
});
|
||||
reader.onError.listen((e) {
|
||||
Fluttertoast.showToast(
|
||||
msg: S.of(context).error_read_file,
|
||||
toastLength: Toast.LENGTH_SHORT,
|
||||
gravity: ToastGravity.CENTER,
|
||||
backgroundColor: Colors.black54,
|
||||
textColor: Colors.white
|
||||
);
|
||||
});
|
||||
reader.readAsArrayBuffer(file);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void uploadPicture(BuildContext context, Uint8List fileBytes, String fileName, User user, {int commentId = -1, int orderId = 0}) {
|
||||
MultipartFile imageFile = MultipartFile.fromBytes(fileBytes, filename: fileName);
|
||||
Map<String, dynamic> formMap = {
|
||||
'id': user.id,
|
||||
'file': imageFile,
|
||||
};
|
||||
if (commentId >= 0) {
|
||||
formMap = {
|
||||
'contact_id': user.id,
|
||||
'comment_id': commentId,
|
||||
'order_id': orderId,
|
||||
'file': imageFile,
|
||||
};
|
||||
HttpUtil.httpPost(
|
||||
'v1/upload-comment-image',
|
||||
(response) {
|
||||
eventBus.fire(new OnCommentUpdatedEvent(mComment.Comment.fromJson(response.data)));
|
||||
},
|
||||
isFormData: true,
|
||||
body: formMap,
|
||||
sendProgress: (int sent, int total) {
|
||||
if (sent == total) {
|
||||
eventBus.fire(OnUploadCommentImageProgressEvent(false, 0.0));
|
||||
} else {
|
||||
eventBus.fire(OnUploadCommentImageProgressEvent(true, sent / total));
|
||||
}
|
||||
},
|
||||
).catchError((error) {
|
||||
Utils.showMessageDialog(context, error);
|
||||
});
|
||||
} else {
|
||||
HttpUtil.httpPost(
|
||||
'v1/upload-avatar',
|
||||
(response) {
|
||||
store.dispatch(new UpdateCurrentUser(User.fromJson(response.data)));
|
||||
eventBus.fire(new OnCurrentUserUpdated());
|
||||
},
|
||||
isFormData: true,
|
||||
body: formMap,
|
||||
sendProgress: (int sent, int total) {
|
||||
if (sent == total) {
|
||||
eventBus.fire(OnProgressEvent(false, 0.0));
|
||||
} else {
|
||||
eventBus.fire(OnProgressEvent(true, sent / total));
|
||||
}
|
||||
},
|
||||
).catchError((error) {
|
||||
Utils.showMessageDialog(context, error);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
AlertDialog getPicture2(BuildContext context, int imageId, OnGotFile onGotFile) {
|
||||
return AlertDialog(
|
||||
title: Text(S.of(context).please_select),
|
||||
content: Text(S.of(context).please_select_an_image),
|
||||
actions: <Widget>[
|
||||
TextButton(
|
||||
child: Text(S.of(context).cancel),
|
||||
onPressed: () {
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
),
|
||||
TextButton(
|
||||
child: Text(S.of(context).select, style: TextStyle(color: Colors.white),),
|
||||
style: TextButton.styleFrom(
|
||||
backgroundColor: Theme.of(context).primaryColor,
|
||||
),
|
||||
onPressed: () {
|
||||
Navigator.of(context).pop();
|
||||
startFilePicker2(context, imageId, onGotFile);
|
||||
},
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
void startFilePicker2(BuildContext context, int imageId, OnGotFile onGotFile) async {
|
||||
final _image = await WebImagePicker().pickImage();
|
||||
onGotFile(imageId, _image.dataScheme);
|
||||
}
|
||||
|
||||
void createTicket(BuildContext context, String msg, List<Map<String, dynamic>> images,
|
||||
OnSuccess onSuccess, OnError onError, {int id}) {
|
||||
var formData = FormData();
|
||||
formData.fields.add(MapEntry("msg", msg));
|
||||
formData.fields.add(MapEntry('id', id == null ? '0' : id.toString()));
|
||||
for (int i = 0; i < images.length; i++) {
|
||||
String imagePath = images[i]['path'];
|
||||
if (imagePath.isNotEmpty) {
|
||||
// (formMap['images'] as List).add(imagePath);
|
||||
formData.fields.add(MapEntry('images', imagePath));
|
||||
}
|
||||
}
|
||||
HttpUtil.httpPost('create-new-ticket-web/', (response) {
|
||||
if (onSuccess != null) {
|
||||
onSuccess(response);
|
||||
}
|
||||
},
|
||||
isFormData: true,
|
||||
// body: formMap,
|
||||
formData: formData,
|
||||
sendProgress: (int sent, int total) {
|
||||
if (sent == total) {
|
||||
eventBus.fire(OnSubmitProgressEvent(false, 100.0));
|
||||
} else {
|
||||
eventBus.fire(OnSubmitProgressEvent(true, sent / total * 100.0));
|
||||
}
|
||||
},
|
||||
businessId: Constants.BUSINESS_ID,
|
||||
).catchError((error) {
|
||||
if (onError != null) {
|
||||
onError(error);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
static void goPayment(BuildContext context, Order order,
|
||||
PaymentPlatform paymentPlatform, {
|
||||
bool googlePay=false,
|
||||
bool applePay=false,
|
||||
StripePaymentMethod stripePaymentMethod,
|
||||
}) {
|
||||
switch(paymentPlatform.code) {
|
||||
case Constants.PAYMENT_METHOD_CODE_SQUARE:
|
||||
|
||||
break;
|
||||
case Constants.PAYMENT_METHOD_CODE_CHASE:
|
||||
|
||||
break;
|
||||
case Constants.PAYMENT_METHOD_CODE_PAYPAL:
|
||||
|
||||
break;
|
||||
case Constants.PAYMENT_METHOD_CODE_OTT_ALIPAY:
|
||||
|
||||
break;
|
||||
case Constants.PAYMENT_METHOD_CODE_OTT_WECHATPAY:
|
||||
|
||||
break;
|
||||
case Constants.PAYMENT_METHOD_CODE_POD:
|
||||
|
||||
break;
|
||||
case Constants.PAYMENT_METHOD_CODE_STRIPE:
|
||||
Navigator.pushReplacement(context, MaterialPageRoute(
|
||||
builder: (BuildContext context) {
|
||||
return StripePayWeb(order, paymentPlatform, stripePaymentMethod: stripePaymentMethod,);
|
||||
}
|
||||
));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
Widget getNativePay(BuildContext context, Order order, PaymentPlatform paymentPlatform) {
|
||||
return SizedBox.shrink();
|
||||
}
|
||||
|
||||
static Future<Uint8List> getBytesFromAsset(String path, int width) async {
|
||||
return Uint8List(0);
|
||||
}
|
||||
}
|
||||
|
||||
class ImageInfo {
|
||||
String name;
|
||||
String data;
|
||||
String dataScheme;
|
||||
String path;
|
||||
}
|
||||
|
||||
class WebImagePicker {
|
||||
Future<ImageInfo> pickImage() async {
|
||||
print('pickImage');
|
||||
final ImageInfo data = ImageInfo();
|
||||
final FileUploadInputElement input = FileUploadInputElement();
|
||||
document.body.children.add(input);
|
||||
input..accept = 'image/*';
|
||||
input.click();
|
||||
await input.onChange.first;
|
||||
if (input.files.isEmpty) return null;
|
||||
final reader = FileReader();
|
||||
reader.readAsDataUrl(input.files[0]);
|
||||
await reader.onLoad.first;
|
||||
final encoded = reader.result as String;
|
||||
// remove data:image/*;base64 preambule
|
||||
final stripped =
|
||||
encoded.replaceFirst(RegExp(r'data:image/[^;]+;base64,'), '');
|
||||
//final imageBase64 = base64.decode(stripped);
|
||||
final imageName = input.files?.first?.name;
|
||||
final imagePath = input.files?.first?.relativePath;
|
||||
data.name = imageName;
|
||||
data.data = stripped;
|
||||
data.dataScheme = encoded;
|
||||
data.path = imagePath;
|
||||
return data;
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,3 @@
|
||||
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:dio/dio.dart';
|
||||
@@ -6,31 +5,43 @@ import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_spinkit/flutter_spinkit.dart';
|
||||
import 'package:flutter_wisetronic/generated/l10n.dart';
|
||||
import 'package:flutter_wisetronic/models/product.dart';
|
||||
import 'package:fluttertoast/fluttertoast.dart';
|
||||
import 'package:hive/hive.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
import 'package:url_launcher/url_launcher.dart';
|
||||
import 'package:path/path.dart' as path;
|
||||
import 'package:universal_io/io.dart';
|
||||
import '../constants.dart';
|
||||
import 'util_web.dart' if (dart.library.io) 'util_io.dart';
|
||||
import 'package:url_launcher/url_launcher.dart';
|
||||
|
||||
import '../constants.dart';
|
||||
import '../events/eventbus.dart';
|
||||
import '../events/events.dart';
|
||||
import '../generated/l10n.dart';
|
||||
import '../models/business.dart';
|
||||
import '../models/cart_info.dart';
|
||||
import '../models/cart_line_item.dart';
|
||||
import '../models/order.dart';
|
||||
import '../models/user.dart';
|
||||
import '../routes.dart';
|
||||
import '../store/actions.dart';
|
||||
import '../store/store.dart';
|
||||
import 'http_util.dart';
|
||||
import 'util_web.dart' if (dart.library.io) 'util_io.dart';
|
||||
|
||||
typedef void OnSuccess(Response response);
|
||||
typedef void OnError(dynamic error);
|
||||
typedef void OnComplete(dynamic data);
|
||||
|
||||
typedef void OnOk();
|
||||
typedef void OnLoadImageError();
|
||||
|
||||
class Utils {
|
||||
|
||||
static bool equalsIgnoreCase(String a, String b) =>
|
||||
(a == null && b == null) ||
|
||||
(a != null && b != null && a.toLowerCase() == b.toLowerCase());
|
||||
(a != null && b != null && a.toLowerCase() == b.toLowerCase());
|
||||
|
||||
static int selectionsContains(Map<String, dynamic> selections, String key, String name) {
|
||||
static int selectionsContains(
|
||||
Map<String, dynamic> selections, String key, String name) {
|
||||
if (selections.containsKey(key.toUpperCase())) {
|
||||
for (var i = 0; i < (selections[key.toUpperCase()] as List).length; i++) {
|
||||
Map<String, dynamic> item = (selections[key.toUpperCase()] as List)[i];
|
||||
@@ -42,18 +53,22 @@ class Utils {
|
||||
return -1;
|
||||
}
|
||||
|
||||
static List<String> getSelectedAttributeValue(Map<String, dynamic> selections, String key) {
|
||||
static List<String> getSelectedAttributeValue(
|
||||
Map<String, dynamic> selections, String key) {
|
||||
List<String> valueArr = [];
|
||||
if (selections.containsKey(key.toUpperCase())) {
|
||||
for (var i = 0; i < (selections[key.toUpperCase()] as List).length; i++) {
|
||||
valueArr.add((selections[key.toUpperCase()][i]['name'] as String).toLowerCase());
|
||||
valueArr.add(
|
||||
(selections[key.toUpperCase()][i]['name'] as String).toLowerCase());
|
||||
}
|
||||
}
|
||||
return valueArr;
|
||||
}
|
||||
|
||||
static bool selectionsNotEmptyAt(Map<String, dynamic> selections, String key) {
|
||||
if (selections.containsKey(key.toUpperCase()) && (selections[key.toUpperCase()] as List).length > 0) {
|
||||
static bool selectionsNotEmptyAt(
|
||||
Map<String, dynamic> selections, String key) {
|
||||
if (selections.containsKey(key.toUpperCase()) &&
|
||||
(selections[key.toUpperCase()] as List).length > 0) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
@@ -96,13 +111,18 @@ class Utils {
|
||||
return Util.getBox();
|
||||
}
|
||||
|
||||
static Widget imageLoadingIndicator() {
|
||||
static Widget imageLoadingIndicator(
|
||||
{double width = 30.0, double height = 30.0}) {
|
||||
return SizedBox(
|
||||
width: 30,
|
||||
height: 30,
|
||||
child: Container(
|
||||
child: CupertinoActivityIndicator(),
|
||||
color: Colors.transparent,
|
||||
width: width,
|
||||
height: height,
|
||||
child: Center(
|
||||
child: Container(
|
||||
width: 30.0,
|
||||
height: 30.0,
|
||||
child: CupertinoActivityIndicator(),
|
||||
color: Colors.transparent,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
@@ -135,20 +155,23 @@ class Utils {
|
||||
}
|
||||
|
||||
static void createOrUpdateStripePaymentMethod(
|
||||
String paymentMethodId,
|
||||
String cardBrand,
|
||||
String paymentMethodType,
|
||||
String cardCountry,
|
||||
int cardExpMonth,
|
||||
int cardExpYear,
|
||||
String cardFunding,
|
||||
String cardLast4,
|
||||
) {
|
||||
HttpUtil.httpPost('v1/create-update-stripe-payment-method', (response) {
|
||||
if (response.statusCode == 200) {
|
||||
print('create or update customer stripe payment method success. ${response.data}');
|
||||
}
|
||||
},
|
||||
String paymentMethodId,
|
||||
String cardBrand,
|
||||
String paymentMethodType,
|
||||
String cardCountry,
|
||||
int cardExpMonth,
|
||||
int cardExpYear,
|
||||
String cardFunding,
|
||||
String cardLast4,
|
||||
) {
|
||||
HttpUtil.httpPost(
|
||||
'v1/create-update-stripe-payment-method',
|
||||
(response) {
|
||||
if (response.statusCode == 200) {
|
||||
print(
|
||||
'create or update customer stripe payment method success. ${response.data}');
|
||||
}
|
||||
},
|
||||
body: {
|
||||
'payment_method_id': paymentMethodId,
|
||||
'card_brand': cardBrand,
|
||||
@@ -197,11 +220,9 @@ class Utils {
|
||||
toastLength: Toast.LENGTH_SHORT,
|
||||
gravity: ToastGravity.CENTER,
|
||||
backgroundColor: Colors.red,
|
||||
textColor: Colors.white
|
||||
);
|
||||
textColor: Colors.white);
|
||||
return false;
|
||||
}
|
||||
),
|
||||
}),
|
||||
);
|
||||
},
|
||||
);
|
||||
@@ -239,11 +260,9 @@ class Utils {
|
||||
toastLength: Toast.LENGTH_SHORT,
|
||||
gravity: ToastGravity.CENTER,
|
||||
backgroundColor: Colors.red,
|
||||
textColor: Colors.white
|
||||
);
|
||||
textColor: Colors.white);
|
||||
return false;
|
||||
}
|
||||
),
|
||||
}),
|
||||
);
|
||||
},
|
||||
);
|
||||
@@ -256,13 +275,14 @@ class Utils {
|
||||
return body.substring(0, 29);
|
||||
}
|
||||
|
||||
static void getMiniLink(String realLink, OnSuccess onSuccess, OnError onError) {
|
||||
HttpUtil.httpPost('get-minilink/', (response) {
|
||||
onSuccess(response);
|
||||
},
|
||||
body: {
|
||||
'link': realLink
|
||||
static void getMiniLink(
|
||||
String realLink, OnSuccess onSuccess, OnError onError) {
|
||||
HttpUtil.httpPost(
|
||||
'get-minilink/',
|
||||
(response) {
|
||||
onSuccess(response);
|
||||
},
|
||||
body: {'link': realLink},
|
||||
isFormData: true,
|
||||
).catchError((error) {
|
||||
onError(error);
|
||||
@@ -309,11 +329,648 @@ class Utils {
|
||||
}
|
||||
return Constants.FONT_TOPTONS;
|
||||
}
|
||||
|
||||
static void getCurrentUser() {
|
||||
getBox().then((box) {
|
||||
int userId = box.get(Constants.KEY_USER_ID, defaultValue: 0);
|
||||
String accessToken =
|
||||
box.get(Constants.KEY_ACCESS_TOKEN, defaultValue: '');
|
||||
if (userId == 0 || accessToken.isEmpty) {
|
||||
eventBus.fire(new OnGetCurrentUserFailed(Error()));
|
||||
} else {
|
||||
HttpUtil.httpGet(
|
||||
'v1/users/$userId',
|
||||
queryParameters: {},
|
||||
businessId: Constants.BUSINESS_ID,
|
||||
).then((value) {
|
||||
User user = User.fromJson(value);
|
||||
store.dispatch(UpdateCurrentUser(user));
|
||||
eventBus.fire(OnCurrentUserUpdated());
|
||||
}).catchError((err) {
|
||||
eventBus.fire(new OnGetCurrentUserFailed(err));
|
||||
});
|
||||
}
|
||||
}).catchError((err) {
|
||||
eventBus.fire(new OnGetCurrentUserFailed(err));
|
||||
});
|
||||
}
|
||||
|
||||
static void showMessageDialog(BuildContext context, dynamic error,
|
||||
{String title, List<Widget> actions, OnOk onOk}) {
|
||||
String message = '';
|
||||
print('error $error');
|
||||
if (error is DioError && error.response != null) {
|
||||
if (error.response.data['message'] != null) {
|
||||
message = error.response.data['message'];
|
||||
} else if (error.response.data['error'] != null) {
|
||||
if (error.response.data['error'] is String) {
|
||||
message = error.response.data['error'];
|
||||
} else if (error.response.data['error']['errmsg'] != null) {
|
||||
message = error.response.data['error']['errmsg'];
|
||||
} else if (error.response.data['error']['message'] != null) {
|
||||
message = error.response.data['error']['message'];
|
||||
} else {
|
||||
message = error.response.data.toString();
|
||||
}
|
||||
}
|
||||
} else if (error.message != null) {
|
||||
message = error.message;
|
||||
} else {
|
||||
message = '$error';
|
||||
}
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (BuildContext context) {
|
||||
return AlertDialog(
|
||||
title: Text(title != null ? title : S.of(context).error),
|
||||
content: Text(message),
|
||||
actions: actions == null
|
||||
? <Widget>[
|
||||
FlatButton(
|
||||
child: Text(S.of(context).ok),
|
||||
onPressed: onOk == null
|
||||
? () {
|
||||
Navigator.of(context).pop();
|
||||
}
|
||||
: onOk,
|
||||
),
|
||||
]
|
||||
: actions,
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
static Future<List<int>> getLastVisit() async {
|
||||
Box box = await getBox();
|
||||
List<dynamic> lastVisit =
|
||||
json.decode(box.get(Constants.KEY_LAST_VISIT, defaultValue: '[]'));
|
||||
List<int> lv = lastVisit.map((e) => e as int).toList();
|
||||
store.dispatch(UpdateLastVisit(lv));
|
||||
return lv;
|
||||
}
|
||||
|
||||
static void saveLastVisit(List<int> lastVisit) {
|
||||
getBox().then((box) {
|
||||
box.put(Constants.KEY_LAST_VISIT, lastVisit.toString());
|
||||
}).catchError((error) {
|
||||
print('Error occurred: $error');
|
||||
});
|
||||
}
|
||||
|
||||
static String utcDatetimeStringToLocalDatetimeString(
|
||||
BuildContext context, String utcDatetimeString,
|
||||
{bool withTime = false}) {
|
||||
var utcDatetime;
|
||||
try {
|
||||
utcDatetime = DateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'")
|
||||
.parse(utcDatetimeString, true);
|
||||
} catch (error) {
|
||||
utcDatetime = DateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'")
|
||||
.parse(utcDatetimeString, true);
|
||||
}
|
||||
var localDatetime = utcDatetime.toLocal();
|
||||
return datetimeFormat(context, localDatetime, withTime: withTime);
|
||||
}
|
||||
|
||||
static String datetimeFormat(BuildContext context, DateTime date,
|
||||
{bool withTime = false}) {
|
||||
final theDate = DateTime(date.year, date.month, date.day);
|
||||
final now = DateTime.now();
|
||||
final today = DateTime(now.year, now.month, now.day);
|
||||
final yesterday = DateTime(now.year, now.month, now.day - 1);
|
||||
final tomorrow = DateTime(now.year, now.month, now.day + 1);
|
||||
DateFormat formatter = DateFormat('yyyy/MM/dd');
|
||||
DateFormat timeFormatter = DateFormat('HH:mm');
|
||||
if (withTime != null && withTime) {
|
||||
formatter = DateFormat('yyyy/MM/dd HH:mm:ss');
|
||||
}
|
||||
if (theDate == today) {
|
||||
return S.of(context).today_with_time(timeFormatter.format(date));
|
||||
}
|
||||
if (theDate == tomorrow) {
|
||||
return S.of(context).tomorrow_with_time(timeFormatter.format(date));
|
||||
}
|
||||
return formatter.format(date);
|
||||
}
|
||||
|
||||
void uploadPicture(
|
||||
BuildContext context,
|
||||
File image,
|
||||
Map<String, dynamic> product,
|
||||
int imageId,
|
||||
String currentImageUrl,
|
||||
OnSuccess onSuccess,
|
||||
{int originalImageId = 0}) async {
|
||||
if (image != null && await image.exists()) {
|
||||
print('image: ${image.path}');
|
||||
String imageName = path.basename(image.path);
|
||||
MultipartFile imageFile =
|
||||
await MultipartFile.fromFile(image.path, filename: imageName);
|
||||
Map<String, dynamic> formMap = {
|
||||
'id': product['id'],
|
||||
'image': imageFile,
|
||||
'original_image_id': originalImageId
|
||||
};
|
||||
if (imageId == -1) {
|
||||
HttpUtil.httpPost(
|
||||
'upload-product-image/',
|
||||
(response) {
|
||||
if (onSuccess != null) {
|
||||
onSuccess(response);
|
||||
}
|
||||
},
|
||||
isFormData: true,
|
||||
body: formMap,
|
||||
sendProgress: (int sent, int total) {
|
||||
if (sent == total) {
|
||||
eventBus.fire(OnUploadImageProgressEvent(imageId, false, 1.0));
|
||||
} else {
|
||||
eventBus.fire(
|
||||
OnUploadImageProgressEvent(imageId, true, sent / total));
|
||||
}
|
||||
},
|
||||
).catchError((error) {
|
||||
Utils.showMessageDialog(context, error);
|
||||
});
|
||||
} else {
|
||||
HttpUtil.httpPost(
|
||||
'upload-product-gallery-image/',
|
||||
(response) {
|
||||
if (onSuccess != null) {
|
||||
onSuccess(response);
|
||||
}
|
||||
},
|
||||
isFormData: true,
|
||||
body: formMap,
|
||||
sendProgress: (int sent, int total) {
|
||||
if (sent == total) {
|
||||
eventBus.fire(OnUploadImageProgressEvent(imageId, false, 0.0));
|
||||
} else {
|
||||
eventBus.fire(
|
||||
OnUploadImageProgressEvent(imageId, true, sent / total));
|
||||
}
|
||||
},
|
||||
).catchError((error) {
|
||||
Utils.showMessageDialog(context, error);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static CartInfo getCartInfoByBusiness(
|
||||
List<CartInfo> cartInfos, Business business) {
|
||||
if (cartInfos == null || cartInfos.length <= 0) {
|
||||
return null;
|
||||
}
|
||||
for (var i = 0; i < cartInfos.length; i++) {
|
||||
if (cartInfos[i].businessInfo.id == business.id) {
|
||||
return cartInfos[i];
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
static CartInfo getCartInfoByBusinessId(
|
||||
List<CartInfo> cartInfos, int businessId) {
|
||||
if (cartInfos == null || cartInfos.length <= 0) {
|
||||
return null;
|
||||
}
|
||||
for (var i = 0; i < cartInfos.length; i++) {
|
||||
if (cartInfos[i].businessInfo.id == businessId) {
|
||||
return cartInfos[i];
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
static List<CartInfo> addCartInfoToCartInfoList(
|
||||
List<CartInfo> cartInfos, CartInfo cartInfo) {
|
||||
if (cartInfos == null || cartInfos.length <= 0) {
|
||||
cartInfos = [cartInfo];
|
||||
} else {
|
||||
bool found = false;
|
||||
for (var i = 0; i < cartInfos.length; i++) {
|
||||
if (cartInfos[i].businessInfo.id == cartInfo.businessInfo.id) {
|
||||
cartInfos[i] = cartInfo;
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!found) {
|
||||
cartInfos.add(cartInfo);
|
||||
}
|
||||
}
|
||||
Utils.getBox().then((box) {
|
||||
box.put(Constants.KEY_CARTINFOS, cartInfos.toString());
|
||||
}).catchError((error) {
|
||||
print('Error occurred: $error');
|
||||
});
|
||||
return cartInfos;
|
||||
}
|
||||
|
||||
static List<CartInfo> removeCartInfoFromCartInfoList(
|
||||
List<CartInfo> cartInfos, CartInfo cartInfo) {
|
||||
if (cartInfos == null || cartInfos.length <= 0 || cartInfo == null) {
|
||||
return [];
|
||||
}
|
||||
int removeIndex = -1;
|
||||
for (var i = 0; i < cartInfos.length; i++) {
|
||||
if (cartInfo.businessInfo.id == cartInfos[i].businessInfo.id) {
|
||||
removeIndex = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (removeIndex != -1) {
|
||||
cartInfos.removeAt(removeIndex);
|
||||
}
|
||||
getBox().then((box) {
|
||||
box.put(Constants.KEY_CARTINFOS, cartInfos.toString());
|
||||
}).catchError((error) {
|
||||
print('Error occurred: $error');
|
||||
});
|
||||
return cartInfos;
|
||||
}
|
||||
|
||||
static void clearCart(BuildContext context, CartInfo cartInfo,
|
||||
{OnComplete onComplete}) {
|
||||
if (cartInfo != null && cartInfo.productList.length > 0) {
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (BuildContext context) {
|
||||
return AlertDialog(
|
||||
title: Text(S.of(context).warning),
|
||||
content: Text(S.of(context).are_you_sure_to_empty_basket),
|
||||
actions: <Widget>[
|
||||
FlatButton(
|
||||
child: Text(S.of(context).cancel),
|
||||
onPressed: () {
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
),
|
||||
FlatButton(
|
||||
child: Text(S.of(context).yes_i_am_sure),
|
||||
onPressed: () {
|
||||
store.dispatch(new UpdateCartInfo(
|
||||
Utils.removeCartInfoFromCartInfoList(
|
||||
store.state.cartInfos, cartInfo)));
|
||||
eventBus.fire(new OnCartInfoUpdated());
|
||||
Navigator.of(context).pop();
|
||||
if (onComplete != null) {
|
||||
onComplete(null);
|
||||
}
|
||||
},
|
||||
)
|
||||
],
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
static void requireLogin(BuildContext context,
|
||||
{String returnUrl, bool replace = true, int delayMilliseconds = 400}) {
|
||||
Future.delayed(Duration(milliseconds: delayMilliseconds), () {
|
||||
if (returnUrl != null) {
|
||||
store.dispatch(UpdateRedirectRoute(returnUrl));
|
||||
}
|
||||
Routes.router.navigateTo(context, '/login', replace: replace);
|
||||
});
|
||||
}
|
||||
|
||||
static String getFirstNumberFromString(String numString) {
|
||||
final intInStr = RegExp(r'\d+');
|
||||
return intInStr.firstMatch(numString).group(0);
|
||||
}
|
||||
|
||||
static void loadProducts(int businessId, int categoryId, int page, bool more,
|
||||
OnComplete onComplete, OnError onError) {
|
||||
HttpUtil.httpGet(
|
||||
more ? 'v1/service-category-products-more' : 'v1/service-category-products',
|
||||
businessId: businessId,
|
||||
queryParameters: {
|
||||
// 'lat': store.state.position.latitude.toString(),
|
||||
// 'lng': store.state.position.longitude.toString(),
|
||||
'category_id': categoryId,
|
||||
'preview': 'no',
|
||||
'page': page,
|
||||
'num_per_page': Constants.ORDERS_PER_PAGE,
|
||||
},
|
||||
).then((value) {
|
||||
if (onComplete != null) {
|
||||
onComplete(value);
|
||||
}
|
||||
}).catchError((error) {
|
||||
if (onError != null) {
|
||||
onError(error);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
static String timestampToString(BuildContext context, int timestamp, {bool withTime=false}) {
|
||||
DateTime date = DateTime.fromMillisecondsSinceEpoch(timestamp * 1000);
|
||||
return datetimeFormat(context, date, withTime: withTime);
|
||||
}
|
||||
|
||||
static String errorMsg2(BuildContext context, String code, String defaultString) {
|
||||
switch (code) {
|
||||
case 'no_delivery':
|
||||
return S.of(context).checkout_no_deliver;
|
||||
case 'no_shipping_address':
|
||||
return S.of(context).please_provide_shipping_address;
|
||||
case 'over_delivery_distance':
|
||||
return S.of(context).over_delivery_distance;
|
||||
case 'amount_not_meet':
|
||||
return S.of(context).amount_not_meet;
|
||||
case 'select_canada_post_shipping_rate':
|
||||
return S.of(context).select_canada_post_shipping_rate;
|
||||
case 'store_close':
|
||||
return S.of(context).store_closed;
|
||||
}
|
||||
return defaultString;
|
||||
}
|
||||
|
||||
static String knownName(BuildContext context, String name) {
|
||||
switch (name) {
|
||||
case 'pickup-discount':
|
||||
return S.of(context).pickup_discount;
|
||||
case 'collect-tips':
|
||||
return S.of(context).service_fee;
|
||||
default:
|
||||
return name;
|
||||
}
|
||||
}
|
||||
|
||||
static String getWeekDayName(BuildContext context, int timestamp, bool shortName) {
|
||||
DateTime date = DateTime.fromMillisecondsSinceEpoch(timestamp * 1000);
|
||||
switch(date.weekday) {
|
||||
case 1:
|
||||
if (shortName) {
|
||||
return S.of(context).mon;
|
||||
} else {
|
||||
return S.of(context).monday;
|
||||
}
|
||||
break;
|
||||
case 2:
|
||||
if (shortName) {
|
||||
return S.of(context).tue;
|
||||
} else {
|
||||
return S.of(context).tuesday;
|
||||
}
|
||||
break;
|
||||
case 3:
|
||||
if (shortName) {
|
||||
return S.of(context).wed;
|
||||
} else {
|
||||
return S.of(context).wednesday;
|
||||
}
|
||||
break;
|
||||
case 4:
|
||||
if (shortName) {
|
||||
return S.of(context).thu;
|
||||
} else {
|
||||
return S.of(context).thursday;
|
||||
}
|
||||
break;
|
||||
case 5:
|
||||
if (shortName) {
|
||||
return S.of(context).fri;
|
||||
} else {
|
||||
return S.of(context).friday;
|
||||
}
|
||||
break;
|
||||
case 6:
|
||||
if (shortName) {
|
||||
return S.of(context).sat;
|
||||
} else {
|
||||
return S.of(context).saturday;
|
||||
}
|
||||
break;
|
||||
case 7:
|
||||
if (shortName) {
|
||||
return S.of(context).sun;
|
||||
} else {
|
||||
return S.of(context).sunday;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static void stripePaymentIntent(Order order, String customerId, String paymentMethodId, String cardType,
|
||||
OnSuccess onSuccess, OnError onError,
|
||||
{
|
||||
String cardBrand,
|
||||
String cardCountry,
|
||||
int cardExpMonth,
|
||||
int cardExpYear,
|
||||
String cardFunding,
|
||||
String cardLast4,
|
||||
}) {
|
||||
|
||||
HttpUtil.httpPost('v1/stripe-payment-intent',
|
||||
onSuccess,
|
||||
body: {
|
||||
'order_id': order.id,
|
||||
'pay_amount': order.totalPrice,
|
||||
'customer_id': customerId == null ? '' : customerId,
|
||||
'payment_method_id': paymentMethodId,
|
||||
'payment_method_type': cardType,
|
||||
'card_brand': cardBrand,
|
||||
'card_country': cardCountry,
|
||||
'card_exp_month': cardExpMonth,
|
||||
'card_exp_year': cardExpYear,
|
||||
'card_funding': cardFunding,
|
||||
'card_last4': cardLast4,
|
||||
},
|
||||
isFormData: true,
|
||||
).catchError(onError);
|
||||
}
|
||||
|
||||
static void stripeChargedSuccess(Order order, String paymentMethodId,
|
||||
String paymentIntentId, OnSuccess onSuccess, OnError onError) {
|
||||
HttpUtil.httpPost('v1/stripe-charged-success',
|
||||
onSuccess,
|
||||
isFormData: true,
|
||||
body: {
|
||||
'order_id': order.id,
|
||||
'payment_method_id': paymentMethodId,
|
||||
'payment_intent_id': paymentIntentId,
|
||||
}
|
||||
).catchError(onError);
|
||||
}
|
||||
|
||||
static int getProductLineInOrder(CartInfo cartInfo) {
|
||||
int qty = 0;
|
||||
for (CartLineItem lineItem in cartInfo.productList) {
|
||||
if (lineItem.product.id != null) {
|
||||
qty += 1;
|
||||
}
|
||||
}
|
||||
return qty;
|
||||
}
|
||||
|
||||
static void orderAgain(BuildContext context, CartInfo cartInfo) {
|
||||
cartInfo.productList.removeWhere((element) => element.product == null || element.product.id == null);
|
||||
store.dispatch(UpdateCartInfo(Utils.addCartInfoToCartInfoList(store.state.cartInfos, cartInfo)));
|
||||
eventBus.fire(new OnCartInfoUpdated());
|
||||
Routes.router.navigateTo(context, '/checkout/${cartInfo.businessInfo.id}');
|
||||
}
|
||||
|
||||
static String getOrderStatus(BuildContext context, int statusCode) {
|
||||
switch(statusCode) {
|
||||
case Constants.STATUS_PENDING:
|
||||
return S.of(context).pending;
|
||||
case Constants.STATUS_ACCEPT:
|
||||
return S.of(context).order_acceipt;
|
||||
case Constants.STATUS_PROCESSING:
|
||||
return S.of(context).order_processing;
|
||||
case Constants.STATUS_COMPLETE:
|
||||
return S.of(context).order_complete;
|
||||
case Constants.STATUS_CANCELLED:
|
||||
return S.of(context).order_cancelled;
|
||||
default:
|
||||
return S.of(context).pending;
|
||||
}
|
||||
}
|
||||
|
||||
static CartLineItem newCartLineItem(
|
||||
{int id,
|
||||
double price,
|
||||
Product product,
|
||||
String name,
|
||||
String description,
|
||||
double quantity,
|
||||
String parentUuid,
|
||||
}) {
|
||||
CartLineItem lineItem = CartLineItem();
|
||||
lineItem.unitPrice = price;
|
||||
lineItem.product = product;
|
||||
lineItem.name = product.name;
|
||||
lineItem.description = description;
|
||||
lineItem.quantity = quantity;
|
||||
lineItem.parentUuid = parentUuid;
|
||||
return lineItem;
|
||||
}
|
||||
|
||||
static void addSubproductToCard(CartInfo cartInfo, CartLineItem lineItem) {
|
||||
if (lineItem.product.subproducts.length > 0) {
|
||||
for (int j = 0; j < lineItem.product.subproducts.length; j++) {
|
||||
cartInfo.productList.add(
|
||||
newCartLineItem(
|
||||
id: 0,
|
||||
price: 0.0,
|
||||
product: Product(
|
||||
lineItem.product.subproducts[j].product.id,
|
||||
lineItem.product.businessId,
|
||||
lineItem.product.subproducts[j].product.name,
|
||||
lineItem.product.subproducts[j].product.price,
|
||||
lineItem.product.subproducts[j].product.description,
|
||||
lineItem.product.subproducts[j].product.image,
|
||||
lineItem.product.subproducts[j].product.productAttributes
|
||||
),
|
||||
name: lineItem.product.subproducts[j].product.name,
|
||||
description: lineItem.product.subproducts[j].product.description,
|
||||
quantity: 1.0,
|
||||
parentUuid: lineItem.uuid
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void addSubproductQty(CartInfo cartInfo, CartLineItem lineItem,
|
||||
{bool remove = false}) {
|
||||
if (lineItem.product.subproducts.length > 0) {
|
||||
for (var i = 0; i < cartInfo.productList.length; i++) {
|
||||
CartLineItem lineItem1 = cartInfo.productList[i];
|
||||
if (lineItem1.parentUuid == lineItem.uuid) {
|
||||
for (var j = 0; j < lineItem.product.subproducts.length; j++) {
|
||||
if (lineItem.product.subproducts[j].product.id == lineItem1.product.id) {
|
||||
if (remove) {
|
||||
lineItem1.quantity -= lineItem.product.subproducts[j].quantity;
|
||||
} else {
|
||||
lineItem1.quantity += lineItem.product.subproducts[j].quantity;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void removeSubproduct(CartInfo cartInfo, String uuid) {
|
||||
cartInfo.productList.removeWhere((element) => element.parentUuid == uuid);
|
||||
}
|
||||
|
||||
static void openEmail(String email) async {
|
||||
final Uri params = Uri(
|
||||
scheme: 'mailto',
|
||||
path: '$email',
|
||||
);
|
||||
String url = params.toString();
|
||||
if (await canLaunch(url)) {
|
||||
await launchURL(url);
|
||||
} else {
|
||||
print('Could not launch $url');
|
||||
}
|
||||
}
|
||||
|
||||
static void callPhone(String phone) async {
|
||||
String callNo = 'tel://$phone';
|
||||
if (await canLaunch(callNo)) {
|
||||
await launchURL(callNo);
|
||||
} else {
|
||||
print('Could not call $phone');
|
||||
}
|
||||
}
|
||||
|
||||
static Widget buildLine(String name, dynamic value, {double nameSize, double valueSize}) {
|
||||
Row row = Row(
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [],
|
||||
);
|
||||
row.children.add(
|
||||
Container(
|
||||
width: 150,
|
||||
padding: EdgeInsets.only(),
|
||||
child: Text(
|
||||
name,
|
||||
style: TextStyle(
|
||||
color: Colors.black38,
|
||||
fontSize: nameSize,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
if (value is String) {
|
||||
row.children.add(
|
||||
Expanded(
|
||||
child: Align(
|
||||
alignment: Alignment.centerRight,
|
||||
child: Text(
|
||||
value,
|
||||
style: TextStyle(
|
||||
fontSize: valueSize,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
} else {
|
||||
row.children.add(
|
||||
Expanded(child: value),
|
||||
);
|
||||
}
|
||||
return Container(
|
||||
padding: EdgeInsets.only(top: 8, bottom: 8),
|
||||
child: row,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class RuntimeError extends Error{
|
||||
class RuntimeError extends Error {
|
||||
final int code;
|
||||
final String message;
|
||||
RuntimeError(this.message, {this.code});
|
||||
String toString() => "Runtime Error: $message";
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user