backup. before shop update

This commit is contained in:
2021-08-31 13:28:33 -04:00
parent c378a6203c
commit 808ffa3211
292 changed files with 51551 additions and 695 deletions

View File

@@ -0,0 +1,4 @@
void configureApp() {
}

View File

@@ -0,0 +1,6 @@
import 'package:flutter_web_plugins/flutter_web_plugins.dart';
void configureApp() {
setUrlStrategy(PathUrlStrategy());
}

View 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();
}
}

View File

@@ -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
View 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',
);
}
}

View 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.debugFillDescriptiondescription`来启动方法。
@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');
}
}
}

View 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);
}

View 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
}
}

View File

@@ -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();
}
}

View File

@@ -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;
}
}

View File

@@ -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";
}
}