first
This commit is contained in:
11
lib/common/base/base_model.dart
Normal file
11
lib/common/base/base_model.dart
Normal file
@@ -0,0 +1,11 @@
|
||||
|
||||
import '../utils/func_tools.dart';
|
||||
|
||||
class BaseModel {
|
||||
/// 发生错误时错误信息, 有可能会被后面的请求覆盖掉
|
||||
String? msg;
|
||||
|
||||
String? getMsg() {
|
||||
return isEmptyString(msg) ? '' : msg;
|
||||
}
|
||||
}
|
||||
15
lib/common/base/provider.dart
Normal file
15
lib/common/base/provider.dart
Normal file
@@ -0,0 +1,15 @@
|
||||
import 'package:cashier_reserve/common/base/ui_model.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
export 'package:provider/provider.dart';
|
||||
|
||||
class MyProvider<T> {
|
||||
static T of<T>(BuildContext context, {bool listen = true}) {
|
||||
T provider = Provider.of<T>(context, listen: listen);
|
||||
if (provider is BaseUIModel) {
|
||||
BaseUIModel viewModel = provider;
|
||||
viewModel.context = context;
|
||||
}
|
||||
return provider;
|
||||
}
|
||||
}
|
||||
129
lib/common/base/ui.dart
Normal file
129
lib/common/base/ui.dart
Normal file
@@ -0,0 +1,129 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
import '../push/push.dart';
|
||||
import './ui_model.dart';
|
||||
import './widgets.dart';
|
||||
|
||||
export 'package:flutter/material.dart';
|
||||
|
||||
class BaseUIController extends StatefulWidget {
|
||||
final BaseUI stateWidget;
|
||||
|
||||
const BaseUIController({super.key, required this.stateWidget});
|
||||
|
||||
@override
|
||||
State<BaseUIController> createState() {
|
||||
// ignore: no_logic_in_create_state
|
||||
return stateWidget;
|
||||
}
|
||||
}
|
||||
|
||||
abstract class BaseUI extends State<BaseUIController>
|
||||
with AutomaticKeepAliveClientMixin {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
super.build(context);
|
||||
final provider = getProvider(context, listen: true);
|
||||
return Stack(
|
||||
children: [
|
||||
Scaffold(
|
||||
backgroundColor: getBackgroundColor(),
|
||||
appBar: getAppBar(context),
|
||||
body: _buildBody(context),
|
||||
bottomNavigationBar: getBottomNavigationBar(context),
|
||||
floatingActionButton: getFloatActionButton(context),
|
||||
),
|
||||
if (provider.showProgressHUD)
|
||||
Material(
|
||||
color: Colors.black.withAlpha(50),
|
||||
child: Center(
|
||||
child: Card(
|
||||
child: Container(
|
||||
width: 100,
|
||||
height: 100,
|
||||
color: const Color(0xaa000000),
|
||||
child: const Center(
|
||||
child: SizedBox(
|
||||
width: 40,
|
||||
height: 40,
|
||||
child: CircularProgressIndicator(),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
dismissKeyboard(BuildContext context) {
|
||||
FocusScope.of(context).requestFocus(FocusNode());
|
||||
}
|
||||
|
||||
Widget? getFloatActionButton(BuildContext context) {
|
||||
return null;
|
||||
}
|
||||
|
||||
Widget? getBottomNavigationBar(BuildContext context) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@protected
|
||||
BaseUIModel getProvider(BuildContext context, {bool listen = true});
|
||||
|
||||
@protected
|
||||
String? getTitleStr(BuildContext context);
|
||||
|
||||
Future pushToPage<T extends BaseUIModel>(
|
||||
BuildContext context, BaseUIModel model) {
|
||||
return YJPush.pushWidget(
|
||||
context,
|
||||
getBuild<T>(model),
|
||||
);
|
||||
}
|
||||
|
||||
Widget getBuild<T extends BaseUIModel>(BaseUIModel model) {
|
||||
return ChangeNotifierProvider(
|
||||
create: (BuildContext context) {
|
||||
return model as T;
|
||||
},
|
||||
child: BaseUIController(
|
||||
stateWidget: this,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
AppBar? getAppBar(BuildContext context) {
|
||||
return makeAppbar(context, getTitleStr(context));
|
||||
}
|
||||
|
||||
Color getBackgroundColor() {
|
||||
return appBackGroundColor;
|
||||
}
|
||||
|
||||
bool hiddenBackBtn() {
|
||||
return false;
|
||||
}
|
||||
|
||||
Widget _buildBody(BuildContext context) {
|
||||
return GestureDetector(
|
||||
child: Container(
|
||||
color: getBackgroundColor(),
|
||||
width: MediaQuery.of(context).size.width,
|
||||
height: MediaQuery.of(context).size.height,
|
||||
child: buildBody(context),
|
||||
),
|
||||
onTap: () {
|
||||
dismissKeyboard(context);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
@protected
|
||||
Widget buildBody(BuildContext context);
|
||||
|
||||
@override
|
||||
bool get wantKeepAlive => false;
|
||||
}
|
||||
46
lib/common/base/ui_model.dart
Normal file
46
lib/common/base/ui_model.dart
Normal file
@@ -0,0 +1,46 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class BaseUIModel with ChangeNotifier {
|
||||
static Function? staticNotifyListeners;
|
||||
BuildContext? context;
|
||||
bool showProgressHUD = false;
|
||||
|
||||
bool initialPage = true;
|
||||
|
||||
/// 判断页面是否被销毁
|
||||
bool disposed = false;
|
||||
|
||||
dismissKeyboard(BuildContext? context) {
|
||||
if (context == null) {
|
||||
return;
|
||||
}
|
||||
FocusScope.of(context).requestFocus(FocusNode());
|
||||
}
|
||||
|
||||
showLoadingHud() {
|
||||
showProgressHUD = true;
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
dismissLoadingHud() {
|
||||
showProgressHUD = false;
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
@override
|
||||
void notifyListeners() {
|
||||
if (!hasListeners) {
|
||||
return;
|
||||
}
|
||||
if (disposed) {
|
||||
return;
|
||||
}
|
||||
super.notifyListeners();
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
disposed = true;
|
||||
super.dispose();
|
||||
}
|
||||
}
|
||||
483
lib/common/base/widgets.dart
Normal file
483
lib/common/base/widgets.dart
Normal file
@@ -0,0 +1,483 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:cached_network_image/cached_network_image.dart';
|
||||
import 'package:cashier_reserve/common/base/ui.dart';
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_localizations/flutter_localizations.dart';
|
||||
|
||||
/// 应用主体背景颜色
|
||||
const Color appBackGroundColor = Color.fromARGB(255, 247, 250, 255);
|
||||
|
||||
/// 主色调
|
||||
const Color mdMainColor = Color(0xff2854BC);
|
||||
|
||||
/// fix for Flutter 2.5
|
||||
/// https://github.com/flutter/flutter/issues/77904
|
||||
Brightness? getAppBrightness(bool isLight) {
|
||||
if (kIsWeb) {
|
||||
return null;
|
||||
}
|
||||
if (Platform.isIOS) {
|
||||
return isLight ? Brightness.dark : Brightness.light;
|
||||
}
|
||||
return isLight ? Brightness.light : Brightness.dark;
|
||||
}
|
||||
|
||||
/// 微信风格Appbar
|
||||
AppBar makeWechatStyleAppbar(BuildContext context, String title,
|
||||
{List<Widget>? actions,
|
||||
final Color backGroundColor = Colors.white,
|
||||
final Color textColor = Colors.black,
|
||||
final Brightness brightness = Brightness.light,
|
||||
bool centerTitle = false,
|
||||
PreferredSizeWidget? bottom}) {
|
||||
return AppBar(
|
||||
backgroundColor: backGroundColor,
|
||||
elevation: 0,
|
||||
centerTitle: centerTitle,
|
||||
systemOverlayStyle: SystemUiOverlayStyle(
|
||||
statusBarColor: Colors.transparent,
|
||||
statusBarIconBrightness: getAppBrightness(false),
|
||||
statusBarBrightness: getAppBrightness(false)),
|
||||
actions: actions,
|
||||
bottom: bottom,
|
||||
title: Text(
|
||||
title,
|
||||
style: TextStyle(
|
||||
fontSize: 17, color: textColor, fontWeight: FontWeight.bold),
|
||||
),
|
||||
leading: IconButton(
|
||||
iconSize: 18,
|
||||
tooltip: "返回",
|
||||
color: Colors.black,
|
||||
icon: Icon(
|
||||
Icons.arrow_back_ios,
|
||||
color: textColor,
|
||||
),
|
||||
onPressed: () {
|
||||
Navigator.pop(context);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/// 通用Appbar
|
||||
AppBar makeAppbar(BuildContext context, String? title,
|
||||
{List<Widget>? actions,
|
||||
Color? backgroundColor,
|
||||
PreferredSizeWidget? bottom,
|
||||
final bool showBack = true,
|
||||
Widget? titleWidget,
|
||||
Widget? backWidget,
|
||||
VoidCallback? onBack,
|
||||
Key? key}) {
|
||||
backgroundColor ??= mdMainColor;
|
||||
return AppBar(
|
||||
key: key,
|
||||
backgroundColor: backgroundColor,
|
||||
elevation: 0,
|
||||
centerTitle: true,
|
||||
actions: actions,
|
||||
bottom: bottom,
|
||||
systemOverlayStyle: SystemUiOverlayStyle(
|
||||
statusBarColor: Colors.transparent,
|
||||
statusBarIconBrightness: getAppBrightness(false),
|
||||
statusBarBrightness: getAppBrightness(false)),
|
||||
title: titleWidget ??
|
||||
Text(
|
||||
title!,
|
||||
style: const TextStyle(
|
||||
fontSize: 17, color: Colors.white, fontWeight: FontWeight.bold),
|
||||
),
|
||||
leading: showBack
|
||||
? (backWidget ??
|
||||
IconButton(
|
||||
iconSize: 18,
|
||||
tooltip: "返回",
|
||||
color: Colors.white,
|
||||
splashColor: Colors.black,
|
||||
hoverColor: Colors.black,
|
||||
icon: const Icon(Icons.arrow_back_ios),
|
||||
onPressed: onBack ??
|
||||
() {
|
||||
Navigator.pop(context);
|
||||
},
|
||||
))
|
||||
: null,
|
||||
);
|
||||
}
|
||||
|
||||
/// ListView 无感上拉加载
|
||||
class ListViewPullUpLoadMore {
|
||||
final ScrollController scrollController;
|
||||
final Future<void> Function() onLoadMore;
|
||||
|
||||
bool _isLoading = false;
|
||||
|
||||
ListViewPullUpLoadMore(this.scrollController, {required this.onLoadMore}) {
|
||||
scrollController.addListener(() {
|
||||
if (_isLoading) {
|
||||
return;
|
||||
}
|
||||
if (scrollController.offset ==
|
||||
scrollController.position.maxScrollExtent) {
|
||||
/// 到底部,添加加载中标记,触发回调
|
||||
_isLoading = true;
|
||||
onLoadMore().then((_) {
|
||||
/// 加载完毕,标记复位
|
||||
_isLoading = false;
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/// 绘制通用下划线
|
||||
Widget makeDivider(BuildContext? context,
|
||||
{Color? color, EdgeInsetsGeometry? padding, double? height}) {
|
||||
color ??= Colors.black.withAlpha(50);
|
||||
return Padding(
|
||||
padding: padding ??
|
||||
const EdgeInsets.only(left: 18.5, right: 18.5, top: 5, bottom: 5),
|
||||
child: Divider(
|
||||
color: color,
|
||||
height: height ?? 0.5,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/// 卡片容器
|
||||
Widget makeCardContainer(
|
||||
{required BuildContext context,
|
||||
required Widget child,
|
||||
Key? key,
|
||||
EdgeInsetsGeometry? padding,
|
||||
EdgeInsetsGeometry? paddingIn,
|
||||
BorderRadius? borderRadius,
|
||||
Color? color,
|
||||
double? elevation,
|
||||
double? width,
|
||||
bool nullWidth = false}) {
|
||||
return SizedBox(
|
||||
key: key,
|
||||
width: nullWidth ? null : (width ?? MediaQuery.of(context).size.width),
|
||||
child: Padding(
|
||||
padding: padding ?? const EdgeInsets.only(left: 8, right: 8),
|
||||
child: Card(
|
||||
color: color,
|
||||
elevation: elevation ?? 0,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: borderRadius ?? BorderRadius.circular(0),
|
||||
),
|
||||
child: Padding(
|
||||
padding:
|
||||
paddingIn ?? const EdgeInsets.only(top: 10, bottom: 10),
|
||||
child: child,
|
||||
))));
|
||||
}
|
||||
|
||||
/// 通用页面加载动画
|
||||
Widget makeLoadBody(BuildContext context) {
|
||||
return const Center(
|
||||
child: CircularProgressIndicator(),
|
||||
);
|
||||
}
|
||||
|
||||
/// 通用网络图片
|
||||
Widget makeNetImage(BuildContext context, String? imageUrl,
|
||||
{BoxFit? fit, double? height, double? width}) {
|
||||
imageUrl ??= "https://image.xiaomfzs.com/xmf-app/user_icon.png";
|
||||
|
||||
if (kIsWeb) {
|
||||
return Image.network(imageUrl, fit: fit, height: height, width: width);
|
||||
}
|
||||
|
||||
return CachedNetworkImage(
|
||||
height: height,
|
||||
width: width,
|
||||
imageUrl: imageUrl,
|
||||
fit: fit,
|
||||
placeholder: (context, url) {
|
||||
return Container(
|
||||
color: Colors.black.withAlpha(50),
|
||||
child: Center(
|
||||
child: Image.asset("images/loading.png"),
|
||||
));
|
||||
},
|
||||
errorWidget: (BuildContext context, String url, dynamic error) {
|
||||
return const Icon(Icons.error);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
/// 通用头像
|
||||
Widget makeUserAvatar(BuildContext context, String? imgUrl,
|
||||
{final double size = 46}) {
|
||||
return SizedBox(
|
||||
width: size,
|
||||
height: size,
|
||||
child: ClipRRect(
|
||||
borderRadius: BorderRadius.circular(10000.0),
|
||||
child: makeNetImage(context, imgUrl, fit: BoxFit.fill),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/// 底部按钮边距容器
|
||||
Widget makeNavButtonPadding(Widget? widget) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(bottom: 14),
|
||||
child: widget,
|
||||
);
|
||||
}
|
||||
|
||||
/// 底部按钮
|
||||
Widget makeBottomNavButton(String text,
|
||||
{required VoidCallback? onTap,
|
||||
Color color = mdMainColor,
|
||||
EdgeInsetsGeometry? padding,
|
||||
TextStyle? textStyle,
|
||||
double? elevation}) {
|
||||
return Padding(
|
||||
padding: padding ?? const EdgeInsets.only(left: 20, right: 20),
|
||||
child: ElevatedButton(
|
||||
style: TextButton.styleFrom(
|
||||
backgroundColor: color, elevation: elevation ?? 1),
|
||||
onPressed: onTap,
|
||||
child: Text(
|
||||
text,
|
||||
style: textStyle ??
|
||||
const TextStyle(color: Colors.white, fontWeight: FontWeight.bold),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/// 收起键盘容器
|
||||
Widget makeDismissKeyboardContainer(BuildContext context, Widget child,
|
||||
{VoidCallback? onDismissed}) {
|
||||
return GestureDetector(
|
||||
onTap: () {
|
||||
FocusScope.of(context).requestFocus(FocusNode());
|
||||
if (onDismissed != null) {
|
||||
onDismissed();
|
||||
}
|
||||
},
|
||||
child: child,
|
||||
);
|
||||
}
|
||||
|
||||
/// 底部弹出按钮
|
||||
Future<String?> showBottomMenu(
|
||||
BuildContext context, Map<String, String> items) async {
|
||||
return await showCupertinoModalPopup<String?>(
|
||||
context: context,
|
||||
builder: (ctx) {
|
||||
return CupertinoActionSheet(
|
||||
cancelButton: CupertinoActionSheetAction(
|
||||
onPressed: () {
|
||||
Navigator.pop(context);
|
||||
},
|
||||
child: const Text(
|
||||
'取消',
|
||||
style: TextStyle(fontSize: 14, color: Colors.red),
|
||||
),
|
||||
),
|
||||
actions: <Widget>[
|
||||
for (var kv in (items.entries))
|
||||
CupertinoActionSheetAction(
|
||||
onPressed: () {
|
||||
Navigator.pop(context, kv.key);
|
||||
},
|
||||
child: Text(
|
||||
kv.value,
|
||||
style: const TextStyle(fontSize: 14, color: Colors.black),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
/// 日期选择器
|
||||
Future<DateTime?> showMyDatePicker(
|
||||
BuildContext context, CupertinoDatePickerMode mode,
|
||||
{DateTime? defaultTime, DateTime? minTime, DateTime? maxTime}) async {
|
||||
final DateTime? date = await showModalBottomSheet<DateTime>(
|
||||
context: context,
|
||||
builder: (context) {
|
||||
DateTime pickedDate = DateTime.now();
|
||||
return SizedBox(
|
||||
height: 250,
|
||||
child: Column(
|
||||
children: <Widget>[
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: <Widget>[
|
||||
CupertinoButton(
|
||||
child: const Text('取消'),
|
||||
onPressed: () {
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
),
|
||||
CupertinoButton(
|
||||
child: const Text('确认'),
|
||||
onPressed: () {
|
||||
Navigator.pop(context, pickedDate);
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
const Divider(
|
||||
height: 0,
|
||||
thickness: 1,
|
||||
),
|
||||
Expanded(
|
||||
child: CupertinoApp(
|
||||
debugShowCheckedModeBanner: false,
|
||||
locale: const Locale('zh', 'CN'),
|
||||
localizationsDelegates: const [
|
||||
GlobalCupertinoLocalizations.delegate,
|
||||
],
|
||||
supportedLocales: const [
|
||||
Locale('zh', 'CN'),
|
||||
Locale('en', 'US'),
|
||||
],
|
||||
home: CupertinoDatePicker(
|
||||
mode: mode,
|
||||
initialDateTime: defaultTime ?? DateTime.now(),
|
||||
minimumDate: minTime,
|
||||
maximumDate: maxTime,
|
||||
onDateTimeChanged: (DateTime dateTime) {
|
||||
pickedDate = dateTime;
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
return date;
|
||||
}
|
||||
|
||||
/// appBar Search Widget
|
||||
PreferredSizeWidget makeAppBarSearchWidget(
|
||||
BuildContext context,
|
||||
String tipText,
|
||||
ValueChanged<String> onSubmitted, {
|
||||
double height = 56,
|
||||
double? width,
|
||||
TextInputType? textInputType,
|
||||
List<TextInputFormatter>? inputFormatters,
|
||||
ValueChanged<String>? onChanged,
|
||||
TextEditingController? controller,
|
||||
}) {
|
||||
return PreferredSize(
|
||||
child: SizedBox(
|
||||
height: height,
|
||||
child: Padding(
|
||||
padding:
|
||||
const EdgeInsets.only(top: 5, left: 10, right: 10, bottom: 10),
|
||||
child: Container(
|
||||
width: width ?? MediaQuery.of(context).size.width,
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white.withAlpha(100),
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
Container(
|
||||
margin: const EdgeInsets.only(left: 15),
|
||||
child: const Icon(
|
||||
Icons.search,
|
||||
color: Colors.black45,
|
||||
size: 20,
|
||||
),
|
||||
),
|
||||
Container(
|
||||
margin: const EdgeInsets.only(left: 5),
|
||||
width: MediaQuery.of(context).size.width -
|
||||
15 * 2 -
|
||||
15 -
|
||||
20 -
|
||||
5 -
|
||||
(width ?? 0),
|
||||
child: TextField(
|
||||
controller: controller,
|
||||
textInputAction: TextInputAction.search,
|
||||
onSubmitted: onSubmitted,
|
||||
keyboardType: textInputType,
|
||||
inputFormatters: inputFormatters,
|
||||
onChanged: onChanged,
|
||||
decoration: InputDecoration(
|
||||
hintText: tipText,
|
||||
hintStyle: const TextStyle(
|
||||
fontSize: 15, color: Colors.black45),
|
||||
border: InputBorder.none,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
)),
|
||||
),
|
||||
),
|
||||
preferredSize: const Size(0, 42));
|
||||
}
|
||||
|
||||
Widget makeSearchWidget(
|
||||
BuildContext context, String hintText, ValueChanged<String> onSubmitted) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(top: 5, left: 0, right: 0, bottom: 14),
|
||||
child: Container(
|
||||
height: 42,
|
||||
width: MediaQuery.of(context).size.width,
|
||||
decoration: BoxDecoration(
|
||||
color: const Color.fromARGB(255, 247, 250, 255),
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
Container(
|
||||
margin: const EdgeInsets.only(left: 15),
|
||||
child: const Icon(
|
||||
Icons.search,
|
||||
color: Colors.black45,
|
||||
size: 20,
|
||||
),
|
||||
),
|
||||
Container(
|
||||
margin: const EdgeInsets.only(left: 5),
|
||||
width: MediaQuery.of(context).size.width - 15 * 2 - 82,
|
||||
child: TextField(
|
||||
textInputAction: TextInputAction.search,
|
||||
onSubmitted: onSubmitted,
|
||||
onChanged: (str) {
|
||||
if (str == "") {
|
||||
onSubmitted("");
|
||||
}
|
||||
},
|
||||
decoration: InputDecoration(
|
||||
hintText: hintText,
|
||||
hintStyle:
|
||||
const TextStyle(fontSize: 15, color: Colors.black45),
|
||||
border: InputBorder.none,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
)),
|
||||
);
|
||||
}
|
||||
|
||||
class AlwaysDisabledFocusNode extends FocusNode {
|
||||
@override
|
||||
bool get hasFocus => false;
|
||||
}
|
||||
54
lib/common/channel/channel_event.dart
Normal file
54
lib/common/channel/channel_event.dart
Normal file
@@ -0,0 +1,54 @@
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:cashier_reserve/common/channel/model/call_status_change_model.dart';
|
||||
import 'package:cashier_reserve/common/print/print.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
|
||||
import '../manager/event_manager.dart';
|
||||
import 'model/call_log_model.dart';
|
||||
import 'names.dart';
|
||||
|
||||
class MyEventChannel {
|
||||
static void startListener() {
|
||||
onGetCallLogResult();
|
||||
onCallStatusChange();
|
||||
}
|
||||
|
||||
static void onGetCallLogResult() {
|
||||
EventChannel channel = EventChannel(getChannelName(kCallLogCallback));
|
||||
channel.receiveBroadcastStream().listen((Object? o) {
|
||||
GetCallLogEvent event = GetCallLogEvent();
|
||||
if (o is String) {
|
||||
event.isSuccess = true;
|
||||
List<dynamic> list = json.decode(o);
|
||||
List<CallLogModel> callLogs = [];
|
||||
for (var item in list) {
|
||||
callLogs.add(CallLogModel.fromJson(item));
|
||||
if (callLogs.length >= 100) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
event.callLogs = callLogs.reversed.toList();
|
||||
} else {
|
||||
event.isSuccess = false;
|
||||
}
|
||||
EventManager.postEvent(event);
|
||||
}, onError: (Object error) {
|
||||
yjPrint("onGetCallLogResult error");
|
||||
});
|
||||
}
|
||||
|
||||
static void onCallStatusChange() {
|
||||
EventChannel channel = EventChannel(getChannelName(kCallStatusChange));
|
||||
channel.receiveBroadcastStream().listen((Object? o) {
|
||||
yjPrint("onCallStatusChange: $o");
|
||||
if (o is String) {
|
||||
Map<String, dynamic> m = json.decode(o);
|
||||
CallStatusChangeModel model = CallStatusChangeModel.fromJson(m);
|
||||
EventManager.postEvent(CallStatusChangeEvent(model: model));
|
||||
}
|
||||
}, onError: (Object error) {
|
||||
yjPrint("onCallStatusChange error");
|
||||
});
|
||||
}
|
||||
}
|
||||
46
lib/common/channel/channel_manager.dart
Normal file
46
lib/common/channel/channel_manager.dart
Normal file
@@ -0,0 +1,46 @@
|
||||
import 'package:flutter/services.dart';
|
||||
|
||||
import '../print/print.dart';
|
||||
import 'names.dart';
|
||||
|
||||
class ChannelManager {
|
||||
static Future<void> getCallLog(String param) async {
|
||||
MethodChannel channel = MethodChannel(getChannelName(kGetCallLog));
|
||||
try {
|
||||
final result = await channel.invokeMethod(kGetCallLog, param);
|
||||
yjPrint(result);
|
||||
} on PlatformException catch (e) {
|
||||
yjPrint('$kGetCallLog 发生异常:$e');
|
||||
}
|
||||
}
|
||||
|
||||
static Future<void> acceptCall() async {
|
||||
MethodChannel channel = MethodChannel(getChannelName(kAcceptCall));
|
||||
try {
|
||||
final result = await channel.invokeMethod(kAcceptCall);
|
||||
yjPrint(result);
|
||||
} on PlatformException catch (e) {
|
||||
yjPrint('$kAcceptCall 发生异常:$e');
|
||||
}
|
||||
}
|
||||
|
||||
static Future<void> rejectCall() async {
|
||||
MethodChannel channel = MethodChannel(getChannelName(kRejectCall));
|
||||
try {
|
||||
final result = await channel.invokeMethod(kRejectCall);
|
||||
yjPrint(result);
|
||||
} on PlatformException catch (e) {
|
||||
yjPrint('$kRejectCall 发生异常:$e');
|
||||
}
|
||||
}
|
||||
|
||||
static Future<void> endCall() async {
|
||||
MethodChannel channel = MethodChannel(getChannelName(kEndCall));
|
||||
try {
|
||||
final result = await channel.invokeMethod(kEndCall);
|
||||
yjPrint(result);
|
||||
} on PlatformException catch (e) {
|
||||
yjPrint('$kEndCall 发生异常:$e');
|
||||
}
|
||||
}
|
||||
}
|
||||
86
lib/common/channel/model/call_log_model.dart
Normal file
86
lib/common/channel/model/call_log_model.dart
Normal file
@@ -0,0 +1,86 @@
|
||||
import 'dart:convert';
|
||||
|
||||
/// number : "18092171236"
|
||||
/// date : "2024-11-21 09:32:27"
|
||||
/// duration : 0
|
||||
/// type : 5
|
||||
/// name : gong
|
||||
|
||||
CallLogModel callLogModelFromJson(String str) =>
|
||||
CallLogModel.fromJson(json.decode(str));
|
||||
|
||||
String callLogModelToJson(CallLogModel data) => json.encode(data.toJson());
|
||||
|
||||
class CallLogModel {
|
||||
CallLogModel({
|
||||
String? number,
|
||||
String? date,
|
||||
num? duration,
|
||||
num? type,
|
||||
String? name,
|
||||
String? time,
|
||||
}) {
|
||||
_number = number;
|
||||
_date = date;
|
||||
_duration = duration;
|
||||
_type = type;
|
||||
_name = name;
|
||||
_time = time;
|
||||
}
|
||||
|
||||
CallLogModel.fromJson(dynamic json) {
|
||||
_number = json['number'];
|
||||
_date = json['date'];
|
||||
_duration = json['duration'];
|
||||
_type = json['type'];
|
||||
_name = json['name'];
|
||||
_time = json['time'];
|
||||
}
|
||||
|
||||
String? _number;
|
||||
String? _date;
|
||||
num? _duration;
|
||||
num? _type;
|
||||
String? _name;
|
||||
String? _time;
|
||||
|
||||
CallLogModel copyWith({
|
||||
String? number,
|
||||
String? date,
|
||||
num? duration,
|
||||
num? type,
|
||||
String? name,
|
||||
String? time,
|
||||
}) =>
|
||||
CallLogModel(
|
||||
number: number ?? _number,
|
||||
date: date ?? _date,
|
||||
duration: duration ?? _duration,
|
||||
type: type ?? _type,
|
||||
name: name ?? _name,
|
||||
time: time ?? _time,
|
||||
);
|
||||
|
||||
String? get number => _number;
|
||||
|
||||
String? get date => _date;
|
||||
|
||||
num? get duration => _duration;
|
||||
|
||||
num? get type => _type;
|
||||
|
||||
String? get name => _name;
|
||||
|
||||
String? get time => _time;
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
final map = <String, dynamic>{};
|
||||
map['number'] = _number;
|
||||
map['date'] = _date;
|
||||
map['duration'] = _duration;
|
||||
map['type'] = _type;
|
||||
map['name'] = _name;
|
||||
map['time'] = _time;
|
||||
return map;
|
||||
}
|
||||
}
|
||||
54
lib/common/channel/model/call_status_change_model.dart
Normal file
54
lib/common/channel/model/call_status_change_model.dart
Normal file
@@ -0,0 +1,54 @@
|
||||
import 'dart:convert';
|
||||
/// state : "Incoming"
|
||||
/// number : "18020143310"
|
||||
/// name : "gong"
|
||||
/// region : "江苏省 南京市"
|
||||
|
||||
CallStatusChangeModel callStatusChangeModelFromJson(String str) => CallStatusChangeModel.fromJson(json.decode(str));
|
||||
String callStatusChangeModelToJson(CallStatusChangeModel data) => json.encode(data.toJson());
|
||||
class CallStatusChangeModel {
|
||||
CallStatusChangeModel({
|
||||
String? state,
|
||||
String? number,
|
||||
String? name,
|
||||
String? region,}){
|
||||
_state = state;
|
||||
_number = number;
|
||||
_name = name;
|
||||
_region = region;
|
||||
}
|
||||
|
||||
CallStatusChangeModel.fromJson(dynamic json) {
|
||||
_state = json['state'];
|
||||
_number = json['number'];
|
||||
_name = json['name'];
|
||||
_region = json['region'];
|
||||
}
|
||||
String? _state;
|
||||
String? _number;
|
||||
String? _name;
|
||||
String? _region;
|
||||
CallStatusChangeModel copyWith({ String? state,
|
||||
String? number,
|
||||
String? name,
|
||||
String? region,
|
||||
}) => CallStatusChangeModel( state: state ?? _state,
|
||||
number: number ?? _number,
|
||||
name: name ?? _name,
|
||||
region: region ?? _region,
|
||||
);
|
||||
String? get state => _state;
|
||||
String? get number => _number;
|
||||
String? get name => _name;
|
||||
String? get region => _region;
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
final map = <String, dynamic>{};
|
||||
map['state'] = _state;
|
||||
map['number'] = _number;
|
||||
map['name'] = _name;
|
||||
map['region'] = _region;
|
||||
return map;
|
||||
}
|
||||
|
||||
}
|
||||
14
lib/common/channel/names.dart
Normal file
14
lib/common/channel/names.dart
Normal file
@@ -0,0 +1,14 @@
|
||||
const String kChannelBaseName = 'com.czg.cashier_reserve/';
|
||||
|
||||
const String kGetCallLog = 'getCallLog';
|
||||
|
||||
const String kCallLogCallback = 'callLogCallback';
|
||||
|
||||
const String kCallStatusChange = 'callStatusChange';
|
||||
const String kAcceptCall = 'acceptCall';
|
||||
const String kEndCall = 'endCall';
|
||||
const String kRejectCall = 'rejectCall';
|
||||
|
||||
String getChannelName(name) {
|
||||
return kChannelBaseName + name;
|
||||
}
|
||||
45
lib/common/encrypt/pwd.dart
Normal file
45
lib/common/encrypt/pwd.dart
Normal file
@@ -0,0 +1,45 @@
|
||||
|
||||
import 'package:cashier_reserve/common/print/print.dart';
|
||||
import 'package:encrypt/encrypt.dart';
|
||||
import 'package:pointycastle/asymmetric/api.dart';
|
||||
|
||||
class PwdEncrypt {
|
||||
static const _rsaPubKey =
|
||||
"MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBANL378k3RiZHWx5AfJqdH9xRNBmD9wGD2iRe41HdTNF8RUhNnHit5NpMNtGL0NPTSSpPjjI1kJfVorRvaQerUgkCAwEAAQ==";
|
||||
|
||||
static String encrypt(String pwd) {
|
||||
return _encryptString(publicKeyString: _rsaPubKey, plainText: pwd);
|
||||
}
|
||||
|
||||
/// 利用公钥进行加密
|
||||
static String _encryptString(
|
||||
{required String publicKeyString, required String plainText}) {
|
||||
String key = '-----BEGIN PUBLIC KEY-----\n\r';
|
||||
int length = 0;
|
||||
const baseLen = 64;
|
||||
while (length < publicKeyString.length) {
|
||||
bool over = length + baseLen > publicKeyString.length;
|
||||
key += publicKeyString.substring(
|
||||
length, over ? publicKeyString.length : length + baseLen);
|
||||
key += '\n\r';
|
||||
length += baseLen;
|
||||
}
|
||||
key += '-----END PUBLIC KEY-----';
|
||||
|
||||
yjPrint(key);
|
||||
|
||||
final publicKey = _parsePublicKeyFromPem(key);
|
||||
|
||||
final encrypt = Encrypter(RSA(publicKey: publicKey));
|
||||
|
||||
final encryptedText = encrypt.encrypt(plainText);
|
||||
|
||||
return encryptedText.base64;
|
||||
}
|
||||
|
||||
/// 通过PEM字符串解析公钥字符串
|
||||
static RSAPublicKey _parsePublicKeyFromPem(String pemString) {
|
||||
final key = RSAKeyParser().parse(pemString);
|
||||
return key as RSAPublicKey;
|
||||
}
|
||||
}
|
||||
182
lib/common/local/cupertino_localizations_delegate.dart
Normal file
182
lib/common/local/cupertino_localizations_delegate.dart
Normal file
@@ -0,0 +1,182 @@
|
||||
import 'package:flutter/foundation.dart' show SynchronousFuture;
|
||||
import 'package:flutter/cupertino.dart';
|
||||
|
||||
class CupertinoLocalizationsDelegate
|
||||
extends LocalizationsDelegate<CupertinoLocalizations> {
|
||||
const CupertinoLocalizationsDelegate();
|
||||
|
||||
@override
|
||||
bool isSupported(Locale locale) =>
|
||||
<String>['en', 'zh'].contains(locale.languageCode);
|
||||
|
||||
@override
|
||||
SynchronousFuture<_DefaultCupertinoLocalizations> load(Locale locale) {
|
||||
return SynchronousFuture<_DefaultCupertinoLocalizations>(
|
||||
_DefaultCupertinoLocalizations(locale.languageCode));
|
||||
}
|
||||
|
||||
@override
|
||||
bool shouldReload(CupertinoLocalizationsDelegate old) => false;
|
||||
}
|
||||
|
||||
class _DefaultCupertinoLocalizations extends CupertinoLocalizations {
|
||||
_DefaultCupertinoLocalizations(this._languageCode);
|
||||
|
||||
final DefaultCupertinoLocalizations _en =
|
||||
const DefaultCupertinoLocalizations();
|
||||
final String _languageCode;
|
||||
|
||||
final Map<String, Map<String, String>> _dict = <String, Map<String, String>>{
|
||||
'en': <String, String>{
|
||||
'alert': 'Alert',
|
||||
'copy': 'Copy',
|
||||
'paste': 'Paste',
|
||||
'cut': 'Cut',
|
||||
'selectAll': 'Select all',
|
||||
'today': 'today'
|
||||
},
|
||||
'zh': <String, String>{
|
||||
'alert': '警告',
|
||||
'copy': '复制',
|
||||
'paste': '粘贴',
|
||||
'cut': '剪切',
|
||||
'selectAll': '选择全部',
|
||||
'today': '今天'
|
||||
}
|
||||
};
|
||||
|
||||
@override
|
||||
String get alertDialogLabel => _get('alert')!;
|
||||
|
||||
@override
|
||||
String get anteMeridiemAbbreviation => _en.anteMeridiemAbbreviation;
|
||||
|
||||
@override
|
||||
String get postMeridiemAbbreviation => _en.postMeridiemAbbreviation;
|
||||
|
||||
@override
|
||||
String get copyButtonLabel => _get('copy')!;
|
||||
|
||||
@override
|
||||
String get cutButtonLabel => _get('cut')!;
|
||||
|
||||
@override
|
||||
String get pasteButtonLabel => _get('paste')!;
|
||||
|
||||
@override
|
||||
String get selectAllButtonLabel => _get('selectAll')!;
|
||||
|
||||
@override
|
||||
DatePickerDateOrder get datePickerDateOrder => _en.datePickerDateOrder;
|
||||
|
||||
@override
|
||||
DatePickerDateTimeOrder get datePickerDateTimeOrder =>
|
||||
_en.datePickerDateTimeOrder;
|
||||
|
||||
|
||||
|
||||
@override
|
||||
String datePickerHour(int hour) => _en.datePickerHour(hour);
|
||||
|
||||
@override
|
||||
String datePickerHourSemanticsLabel(int hour) =>
|
||||
_en.datePickerHourSemanticsLabel(hour);
|
||||
|
||||
@override
|
||||
String datePickerMediumDate(DateTime date) => _en.datePickerMediumDate(date);
|
||||
|
||||
@override
|
||||
String datePickerMinute(int minute) => _en.datePickerMinute(minute);
|
||||
|
||||
@override
|
||||
String datePickerMinuteSemanticsLabel(int minute) =>
|
||||
_en.datePickerMinuteSemanticsLabel(minute);
|
||||
|
||||
@override
|
||||
String datePickerMonth(int monthIndex) => _en.datePickerMonth(monthIndex);
|
||||
|
||||
@override
|
||||
String datePickerYear(int yearIndex) => _en.datePickerYear(yearIndex);
|
||||
|
||||
@override
|
||||
String timerPickerHour(int hour) => _en.timerPickerHour(hour);
|
||||
|
||||
@override
|
||||
String timerPickerHourLabel(int hour) => _en.timerPickerHourLabel(hour);
|
||||
|
||||
@override
|
||||
String timerPickerMinute(int minute) => _en.timerPickerMinute(minute);
|
||||
|
||||
@override
|
||||
String timerPickerMinuteLabel(int minute) =>
|
||||
_en.timerPickerMinuteLabel(minute);
|
||||
|
||||
@override
|
||||
String timerPickerSecond(int second) => _en.timerPickerSecond(second);
|
||||
|
||||
@override
|
||||
String timerPickerSecondLabel(int second) =>
|
||||
_en.timerPickerSecondLabel(second);
|
||||
|
||||
String? _get(String key) {
|
||||
return _dict[_languageCode]![key];
|
||||
}
|
||||
|
||||
@override
|
||||
String get todayLabel => _get("today")!;
|
||||
|
||||
@override
|
||||
String get modalBarrierDismissLabel => _en.modalBarrierDismissLabel;
|
||||
|
||||
@override
|
||||
String tabSemanticsLabel({required int tabIndex, required int tabCount}) {
|
||||
return _en.tabSemanticsLabel(tabIndex: tabIndex, tabCount: tabCount);
|
||||
}
|
||||
|
||||
@override
|
||||
List<String> get timerPickerHourLabels => _en.timerPickerHourLabels;
|
||||
|
||||
@override
|
||||
List<String> get timerPickerMinuteLabels => _en.timerPickerMinuteLabels;
|
||||
|
||||
@override
|
||||
List<String> get timerPickerSecondLabels => _en.timerPickerSecondLabels;
|
||||
|
||||
@override
|
||||
String get searchTextFieldPlaceholderLabel => throw UnimplementedError();
|
||||
|
||||
@override
|
||||
String datePickerDayOfMonth(int dayIndex, [int? weekDay]) {
|
||||
return _en.datePickerDayOfMonth(dayIndex, weekDay);
|
||||
}
|
||||
|
||||
@override
|
||||
// TODO: implement noSpellCheckReplacementsLabel
|
||||
String get noSpellCheckReplacementsLabel => throw UnimplementedError();
|
||||
|
||||
@override
|
||||
String datePickerStandaloneMonth(int monthIndex) {
|
||||
// TODO: implement datePickerStandaloneMonth
|
||||
throw UnimplementedError();
|
||||
}
|
||||
|
||||
@override
|
||||
// TODO: implement lookUpButtonLabel
|
||||
String get lookUpButtonLabel => throw UnimplementedError();
|
||||
|
||||
@override
|
||||
// TODO: implement menuDismissLabel
|
||||
String get menuDismissLabel => throw UnimplementedError();
|
||||
|
||||
@override
|
||||
// TODO: implement searchWebButtonLabel
|
||||
String get searchWebButtonLabel => throw UnimplementedError();
|
||||
|
||||
@override
|
||||
// TODO: implement shareButtonLabel
|
||||
String get shareButtonLabel => throw UnimplementedError();
|
||||
|
||||
@override
|
||||
// TODO: implement clearButtonLabel
|
||||
String get clearButtonLabel => throw UnimplementedError();
|
||||
}
|
||||
52
lib/common/local/timeago.dart
Normal file
52
lib/common/local/timeago.dart
Normal file
@@ -0,0 +1,52 @@
|
||||
import 'package:timeago/timeago.dart';
|
||||
|
||||
/// Chinese-China messages
|
||||
class ZhCnMessages implements LookupMessages {
|
||||
@override
|
||||
String prefixAgo() => '';
|
||||
|
||||
@override
|
||||
String prefixFromNow() => '';
|
||||
|
||||
@override
|
||||
String suffixAgo() => '前';
|
||||
|
||||
@override
|
||||
String suffixFromNow() => '后';
|
||||
|
||||
@override
|
||||
String lessThanOneMinute(int seconds) => '不到一分钟';
|
||||
|
||||
@override
|
||||
String aboutAMinute(int minutes) => '约 1 分钟';
|
||||
|
||||
@override
|
||||
String minutes(int minutes) => '$minutes 分钟';
|
||||
|
||||
@override
|
||||
String aboutAnHour(int minutes) => '约 1 小时';
|
||||
|
||||
@override
|
||||
String hours(int hours) => '约 $hours 小时';
|
||||
|
||||
@override
|
||||
String aDay(int hours) => '约 1 天';
|
||||
|
||||
@override
|
||||
String days(int days) => '约 $days 日';
|
||||
|
||||
@override
|
||||
String aboutAMonth(int days) => '约 1 个月';
|
||||
|
||||
@override
|
||||
String months(int months) => '约 $months 月';
|
||||
|
||||
@override
|
||||
String aboutAYear(int year) => '约 1 年';
|
||||
|
||||
@override
|
||||
String years(int years) => '约 $years 年';
|
||||
|
||||
@override
|
||||
String wordSeparator() => '';
|
||||
}
|
||||
129
lib/common/manager/app_manager.dart
Normal file
129
lib/common/manager/app_manager.dart
Normal file
@@ -0,0 +1,129 @@
|
||||
import 'package:cashier_reserve/common/print/print.dart';
|
||||
import 'package:cashier_reserve/common/push/push.dart';
|
||||
import 'package:cashier_reserve/data_model/login/login_result.dart';
|
||||
import 'package:cashier_reserve/login/login_view.dart';
|
||||
import 'package:cashier_reserve/model/reserve_model.dart';
|
||||
import 'package:cashier_reserve/model/version_model.dart';
|
||||
import 'package:cashier_reserve/update_version/update_version_view.dart';
|
||||
import 'package:easy_refresh/easy_refresh.dart';
|
||||
import 'package:package_info_plus/package_info_plus.dart';
|
||||
|
||||
import '../base/ui.dart';
|
||||
import '../channel/channel_event.dart';
|
||||
import '../utils/func_tools.dart';
|
||||
import 'hive_manager.dart';
|
||||
|
||||
class AppManager {
|
||||
static BuildContext? globalContext;
|
||||
static bool _isAlertLogin = false;
|
||||
|
||||
static String _smsContent = "";
|
||||
|
||||
static Future<void> initThirdPackage() async {
|
||||
MyEventChannel.startListener();
|
||||
|
||||
await HiveManager.initHive();
|
||||
|
||||
EasyRefresh.defaultHeaderBuilder = () => const ClassicHeader(
|
||||
dragText: "下拉刷新",
|
||||
readyText: "释放刷新",
|
||||
armedText: "正在刷新",
|
||||
processingText: "正在刷新",
|
||||
processedText: "刷新完成",
|
||||
noMoreText: "没有更多数据了",
|
||||
failedText: "刷新失败",
|
||||
messageText: "上次刷新时间:%T",
|
||||
showText: true,
|
||||
showMessage: true,
|
||||
iconDimension: 30,
|
||||
spacing: 10,
|
||||
);
|
||||
}
|
||||
|
||||
static void setGlobalContext(BuildContext context) {
|
||||
globalContext = context;
|
||||
}
|
||||
|
||||
static bool isLogin() {
|
||||
String token = HiveManager.getUserToken();
|
||||
if (isEmptyString(token)) {
|
||||
gotoLogin();
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static void gotoLogin() {
|
||||
if (_isAlertLogin) {
|
||||
return;
|
||||
}
|
||||
_isAlertLogin = true;
|
||||
|
||||
while (Navigator.of(globalContext!).canPop()) {
|
||||
Navigator.of(globalContext!).pop();
|
||||
}
|
||||
|
||||
YJPush.presentWidget(globalContext!, const LoginView());
|
||||
}
|
||||
|
||||
static void checkAppVersion() async {
|
||||
final res = await VersionModel.requestNewVersionInfo();
|
||||
yjPrint(res);
|
||||
|
||||
if (res == null || res.version == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
PackageInfo packageInfo = await PackageInfo.fromPlatform();
|
||||
yjPrint("version == ${packageInfo.version}");
|
||||
|
||||
List<String> serverList = res.version!.split(".");
|
||||
List<String> localList = packageInfo.version.split(".");
|
||||
|
||||
if (serverList.length != 3 || localList.length != 3) {
|
||||
return;
|
||||
}
|
||||
|
||||
int serverVersion = int.parse(serverList[0]) * 10000 + int.parse(serverList[1]) * 100 + int.parse(serverList[2]);
|
||||
int localVersion = int.parse(localList[0]) * 10000 + int.parse(localList[1]) * 100 + int.parse(localList[2]);
|
||||
|
||||
if (serverVersion <= localVersion) {
|
||||
return;
|
||||
}
|
||||
|
||||
yjPrint("serverVersion == $serverVersion, localVersion == $localVersion");
|
||||
|
||||
YJPush.presentWidget(globalContext!, UpdateVersionView(versionModel: res,));
|
||||
}
|
||||
|
||||
static void disposeLoginWidget() {
|
||||
_isAlertLogin = false;
|
||||
}
|
||||
|
||||
static bool isShowLoginView() {
|
||||
return _isAlertLogin;
|
||||
}
|
||||
|
||||
static String getUserToken() {
|
||||
return HiveManager.getUserToken();
|
||||
}
|
||||
|
||||
static void loginSuccess(LoginResult? r) {
|
||||
HiveManager.setUserToken(r?.token ?? '');
|
||||
HiveManager.setShopId((r?.shopId ?? '').toString());
|
||||
HiveManager.setShopName(r?.shopName ?? '');
|
||||
HiveManager.setShopLogo(r?.logo ?? '');
|
||||
HiveManager.setUserInfo(r?.user?.toString() ?? '');
|
||||
|
||||
disposeLoginWidget();
|
||||
Navigator.of(globalContext!).pop();
|
||||
}
|
||||
|
||||
static Future<String> loadReserveSms() async {
|
||||
if (_smsContent.isNotEmpty) {
|
||||
return _smsContent;
|
||||
}
|
||||
_smsContent = await ReserveModel.getReserveSms();
|
||||
return _smsContent;
|
||||
}
|
||||
}
|
||||
69
lib/common/manager/event_manager.dart
Normal file
69
lib/common/manager/event_manager.dart
Normal file
@@ -0,0 +1,69 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:cashier_reserve/common/channel/model/call_log_model.dart';
|
||||
import 'package:cashier_reserve/common/channel/model/call_status_change_model.dart';
|
||||
import 'package:event_bus/event_bus.dart';
|
||||
|
||||
|
||||
class EventManager {
|
||||
static EventBus? _eventBus;
|
||||
|
||||
static final Map<dynamic, List<StreamSubscription>> _eventMap = {};
|
||||
|
||||
static EventBus? get eventBus => getEventBus();
|
||||
|
||||
static EventBus? getEventBus() {
|
||||
_eventBus ??= EventBus();
|
||||
return _eventBus;
|
||||
}
|
||||
|
||||
static void postEvent(MyEvent event) {
|
||||
getEventBus()!.fire(event);
|
||||
}
|
||||
|
||||
static void addListener<T>(dynamic widget, void Function(T event) onData) {
|
||||
StreamSubscription event = EventManager.eventBus!.on<T>().listen((T e) {
|
||||
onData(e);
|
||||
});
|
||||
List<StreamSubscription>? list = _eventMap[widget];
|
||||
list ??= [];
|
||||
list.add(event);
|
||||
_eventMap[widget] = list;
|
||||
}
|
||||
|
||||
static void cancelListener(dynamic widget) {
|
||||
List<StreamSubscription>? list = _eventMap[widget];
|
||||
if (list == null) {
|
||||
return;
|
||||
}
|
||||
for (var event in list) {
|
||||
event.cancel();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class MyEvent {
|
||||
String name = '';
|
||||
}
|
||||
|
||||
class GetCallLogEvent extends MyEvent {
|
||||
List<CallLogModel> callLogs;
|
||||
bool isLoadMore = false;
|
||||
bool isSuccess = false;
|
||||
|
||||
GetCallLogEvent({this.callLogs = const [], this.isLoadMore = false, this.isSuccess = false});
|
||||
}
|
||||
|
||||
class CallStatusChangeEvent extends MyEvent {
|
||||
/// state = IncomingNumberReceived 来电接听
|
||||
/// state = OutGoing 呼出
|
||||
/// state = End 通话结束
|
||||
/// state = Incoming 来电
|
||||
CallStatusChangeModel model;
|
||||
|
||||
CallStatusChangeEvent({required this.model});
|
||||
}
|
||||
|
||||
class CallReceivedEvent extends MyEvent {
|
||||
CallReceivedEvent();
|
||||
}
|
||||
61
lib/common/manager/hive_manager.dart
Normal file
61
lib/common/manager/hive_manager.dart
Normal file
@@ -0,0 +1,61 @@
|
||||
import 'package:hive/hive.dart';
|
||||
import 'package:path_provider/path_provider.dart';
|
||||
|
||||
class HiveManager {
|
||||
static Box? _userInfoBox;
|
||||
|
||||
static Future<void> initHive() async {
|
||||
final dir = await getApplicationDocumentsDirectory();
|
||||
Hive.defaultDirectory = dir.path;
|
||||
|
||||
_userInfoBox = Hive.box();
|
||||
}
|
||||
|
||||
static void clearLoginInfo() {
|
||||
_userInfoBox?.delete('token');
|
||||
_userInfoBox?.delete('shopId');
|
||||
_userInfoBox?.delete('shopName');
|
||||
_userInfoBox?.delete('shopLogo');
|
||||
_userInfoBox?.delete('userInfo');
|
||||
}
|
||||
|
||||
static void setUserToken(String token) {
|
||||
_userInfoBox?.put('token', token);
|
||||
}
|
||||
|
||||
static String getUserToken() {
|
||||
return _userInfoBox?.get('token') ?? '';
|
||||
}
|
||||
|
||||
static void setShopId(String shopId) {
|
||||
_userInfoBox?.put('shopId', shopId);
|
||||
}
|
||||
|
||||
static String getShopId() {
|
||||
return _userInfoBox?.get('shopId') ?? '';
|
||||
}
|
||||
|
||||
static void setShopName(String shopName) {
|
||||
_userInfoBox?.put('shopName', shopName);
|
||||
}
|
||||
|
||||
static String getShopName() {
|
||||
return _userInfoBox?.get('shopName') ?? '';
|
||||
}
|
||||
|
||||
static void setShopLogo(String shopLogo) {
|
||||
_userInfoBox?.put('shopLogo', shopLogo);
|
||||
}
|
||||
|
||||
static String getShopLogo() {
|
||||
return _userInfoBox?.get('shopLogo') ?? '';
|
||||
}
|
||||
|
||||
static void setUserInfo(String userInfo) {
|
||||
_userInfoBox?.put('userInfo', userInfo);
|
||||
}
|
||||
|
||||
static String getUserInfo() {
|
||||
return _userInfoBox?.get('userInfo') ?? '';
|
||||
}
|
||||
}
|
||||
10
lib/common/print/print.dart
Normal file
10
lib/common/print/print.dart
Normal file
@@ -0,0 +1,10 @@
|
||||
|
||||
import 'package:flutter/foundation.dart';
|
||||
|
||||
const bool inProduction = !kDebugMode;
|
||||
|
||||
void yjPrint(Object? object) {
|
||||
if (kDebugMode) {
|
||||
print(object);
|
||||
}
|
||||
}
|
||||
87
lib/common/push/push.dart
Normal file
87
lib/common/push/push.dart
Normal file
@@ -0,0 +1,87 @@
|
||||
import 'package:flutter/cupertino.dart';
|
||||
|
||||
class YJPush {
|
||||
static Future presentWidget(BuildContext context, Widget widget) {
|
||||
return Navigator.of(context).push(PageRouteBuilder(
|
||||
pageBuilder: (BuildContext context, Animation<double> animation,
|
||||
Animation<double> secondaryAnimation) {
|
||||
return widget;
|
||||
},
|
||||
transitionsBuilder: (
|
||||
BuildContext context,
|
||||
Animation<double> animation,
|
||||
Animation<double> secondaryAnimation,
|
||||
Widget child,
|
||||
) {
|
||||
// 添加一个平移动画
|
||||
return YJPush.createTransition(animation, child);
|
||||
},
|
||||
opaque: false,
|
||||
barrierDismissible: true));
|
||||
}
|
||||
|
||||
static void presentWidgetNoAnimation(BuildContext context, Widget widget) {
|
||||
Navigator.of(context).push(PageRouteBuilder(
|
||||
pageBuilder: (BuildContext context, Animation<double> animation,
|
||||
Animation<double> secondaryAnimation) {
|
||||
return widget;
|
||||
},
|
||||
transitionsBuilder: (
|
||||
BuildContext context,
|
||||
Animation<double> animation,
|
||||
Animation<double> secondaryAnimation,
|
||||
Widget child,
|
||||
) {
|
||||
// 添加一个平移动画
|
||||
return YJPush.createNoAnimationTransition(animation, child);
|
||||
},
|
||||
opaque: false,
|
||||
barrierDismissible: true));
|
||||
}
|
||||
|
||||
/// 创建一个平移变换
|
||||
/// 跳转过去查看源代码,可以看到有各种各样定义好的变换
|
||||
static SlideTransition createTransition(
|
||||
Animation<double> animation, Widget child) {
|
||||
return SlideTransition(
|
||||
position: Tween<Offset>(
|
||||
begin: const Offset(0.0, 1.0),
|
||||
end: const Offset(0.0, 0.0),
|
||||
).animate(animation),
|
||||
child: child, // child is the value returned by pageBuilder
|
||||
);
|
||||
}
|
||||
|
||||
/// 创建一个平移变换 没有动画
|
||||
static SlideTransition createNoAnimationTransition(
|
||||
Animation<double> animation, Widget child) {
|
||||
return SlideTransition(
|
||||
position: Tween<Offset>(
|
||||
begin: const Offset(0.0, 0.0),
|
||||
end: const Offset(0.0, 0.0),
|
||||
).animate(animation),
|
||||
child: child, // child is the value returned by pageBuilder
|
||||
);
|
||||
}
|
||||
|
||||
static Future pushWidget(BuildContext context, Widget widget) async {
|
||||
Route route = CupertinoPageRoute(builder: (context) => widget);
|
||||
return await Navigator.push(context, route);
|
||||
}
|
||||
|
||||
static Future pushAndRemoveWidget(BuildContext context, Widget widget) async {
|
||||
Route route = CupertinoPageRoute(builder: (context) => widget);
|
||||
return await Navigator.pushAndRemoveUntil(context, route, (route) => false);
|
||||
}
|
||||
|
||||
static SlideTransition createHTransition(
|
||||
Animation<double> animation, Widget child) {
|
||||
return SlideTransition(
|
||||
position: Tween<Offset>(
|
||||
begin: const Offset(1.0, 0.0),
|
||||
end: const Offset(0.0, 0.0),
|
||||
).animate(animation),
|
||||
child: child, // child is the value returned by pageBuilder
|
||||
);
|
||||
}
|
||||
}
|
||||
115
lib/common/request/request_manager.dart
Normal file
115
lib/common/request/request_manager.dart
Normal file
@@ -0,0 +1,115 @@
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:cashier_reserve/common/manager/app_manager.dart';
|
||||
import 'package:cashier_reserve/common/manager/hive_manager.dart';
|
||||
import 'package:cashier_reserve/common/print/print.dart';
|
||||
import 'package:dio/dio.dart';
|
||||
|
||||
import '../utils/utils.dart';
|
||||
|
||||
const String kBaseUrl = 'http://192.168.1.31';
|
||||
// const String kBaseUrl = 'https://cashier.sxczgkj.com';
|
||||
|
||||
const kSuccessCode = 200;
|
||||
const kNeedLoginCode = 401;
|
||||
|
||||
const Map<String, dynamic> _emptyMap = {};
|
||||
|
||||
class RequestManager {
|
||||
/// HttpClient
|
||||
static final Dio _c = Dio(BaseOptions(
|
||||
baseUrl: kBaseUrl,
|
||||
connectTimeout: const Duration(milliseconds: 5000),
|
||||
receiveTimeout: const Duration(milliseconds: 5000)));
|
||||
|
||||
/// GET
|
||||
static Future<dynamic> get(String url,
|
||||
{Map<String, dynamic> params = _emptyMap, bool catchError = true}) {
|
||||
if (url.contains("?")) {
|
||||
url += "&shopId=${HiveManager.getShopId()}";
|
||||
} else {
|
||||
url += "?shopId=${HiveManager.getShopId()}";
|
||||
}
|
||||
|
||||
// 处理传入的其他参数
|
||||
if (params.isNotEmpty) {
|
||||
String paramString = Uri(queryParameters: params).query;
|
||||
url += "&$paramString";
|
||||
}
|
||||
|
||||
return _doRequest("GET", url, catchError: catchError);
|
||||
}
|
||||
|
||||
/// DELETE
|
||||
static Future<dynamic> delete(String url, {bool catchError = true}) {
|
||||
return _doRequest("DELETE", url, catchError: catchError);
|
||||
}
|
||||
|
||||
/// POST
|
||||
static Future<dynamic> post(String url, Map<String, dynamic>? body,
|
||||
{bool catchError = true}) {
|
||||
if (body != null) {
|
||||
body["shopId"] = HiveManager.getShopId();
|
||||
} else {
|
||||
body = {"shopId": HiveManager.getShopId()};
|
||||
}
|
||||
return _doRequest("POST", url, body: body, catchError: catchError);
|
||||
}
|
||||
|
||||
/// PUT
|
||||
static Future<dynamic> put(String url, Map<String, dynamic>? body,
|
||||
{bool catchError = true}) {
|
||||
if (body != null) {
|
||||
body["shopId"] = HiveManager.getShopId();
|
||||
} else {
|
||||
body = {"shopId": HiveManager.getShopId()};
|
||||
}
|
||||
return _doRequest("PUT", url, body: body, catchError: catchError);
|
||||
}
|
||||
|
||||
static Future<dynamic> _doRequest(String method, String url,
|
||||
{Map<String, dynamic>? body, required bool catchError}) async {
|
||||
yjPrint("[RequestManager req]: $method 【$url】body === $body");
|
||||
|
||||
try {
|
||||
final resp = await _c.request(url,
|
||||
data: body,
|
||||
options: Options(method: method, headers: {
|
||||
"token": AppManager.getUserToken(),
|
||||
"platformtype":'telephone'
|
||||
}));
|
||||
yjPrint("[RequestManager resp]: $method 【$url】body === $resp");
|
||||
|
||||
if (catchError) {
|
||||
if (resp.statusCode == kNeedLoginCode) {
|
||||
AppManager.gotoLogin();
|
||||
return null;
|
||||
}
|
||||
if (resp.statusCode != kSuccessCode) {
|
||||
_alertError("提示", resp.data ?? "未知错误");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
return resp.data["data"];
|
||||
} catch (e) {
|
||||
yjPrint("[RequestManager error]: $method 【$url】error === $e");
|
||||
// _alertError("网络错误", "请检查您的网络连接!");
|
||||
if (e is DioException) {
|
||||
DioException de = e;
|
||||
if (de.response?.statusCode == kNeedLoginCode) {
|
||||
AppManager.gotoLogin();
|
||||
return null;
|
||||
}
|
||||
if (de.response?.data is Map) {
|
||||
final data = de.response!.data as Map;
|
||||
Utils.toast(data["message"], AppManager.globalContext!);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
static _alertError(String title, String errorText) {
|
||||
Utils.alert(AppManager.globalContext!, errorText, title: title);
|
||||
}
|
||||
}
|
||||
8
lib/common/utils/func_tools.dart
Normal file
8
lib/common/utils/func_tools.dart
Normal file
@@ -0,0 +1,8 @@
|
||||
|
||||
|
||||
bool isEmptyString(String? str) {
|
||||
if (str == null || str.isEmpty) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
47
lib/common/utils/utils.dart
Normal file
47
lib/common/utils/utils.dart
Normal file
@@ -0,0 +1,47 @@
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:fluttertoast/fluttertoast.dart';
|
||||
|
||||
import 'func_tools.dart';
|
||||
|
||||
class Utils {
|
||||
///大陆手机号码11位数,匹配格式:前三位固定格式+后8位任意数
|
||||
static bool isPhone(String phone) {
|
||||
return RegExp('^1\\d{10}\$').hasMatch(phone);
|
||||
}
|
||||
|
||||
static void toast(String? text, BuildContext? context) {
|
||||
if (isEmptyString(text)) {
|
||||
return;
|
||||
}
|
||||
Fluttertoast.showToast(
|
||||
msg: "$text",
|
||||
toastLength: Toast.LENGTH_SHORT,
|
||||
gravity: ToastGravity.CENTER,
|
||||
timeInSecForIosWeb: 1,
|
||||
// backgroundColor: Colors.red,
|
||||
// textColor: Colors.white,
|
||||
fontSize: 16.0);
|
||||
}
|
||||
|
||||
|
||||
|
||||
static Future alert(BuildContext context, String? content, {String? title}) {
|
||||
return showCupertinoDialog(
|
||||
context: context,
|
||||
builder: (BuildContext context) {
|
||||
return CupertinoAlertDialog(
|
||||
title: Text(title ?? '提示'),
|
||||
content: Text(content!),
|
||||
actions: <Widget>[
|
||||
TextButton(
|
||||
child: const Text('确定'),
|
||||
onPressed: () {
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
),
|
||||
],
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user