@@ -1,6 +1,7 @@ | |||||
package cn.zhios.zhiying_comm; | package cn.zhios.zhiying_comm; | ||||
import androidx.annotation.NonNull; | import androidx.annotation.NonNull; | ||||
import io.flutter.embedding.engine.plugins.FlutterPlugin; | import io.flutter.embedding.engine.plugins.FlutterPlugin; | ||||
import io.flutter.plugin.common.MethodCall; | import io.flutter.plugin.common.MethodCall; | ||||
import io.flutter.plugin.common.MethodChannel; | import io.flutter.plugin.common.MethodChannel; | ||||
@@ -8,32 +9,34 @@ import io.flutter.plugin.common.MethodChannel.MethodCallHandler; | |||||
import io.flutter.plugin.common.MethodChannel.Result; | import io.flutter.plugin.common.MethodChannel.Result; | ||||
import io.flutter.plugin.common.PluginRegistry.Registrar; | import io.flutter.plugin.common.PluginRegistry.Registrar; | ||||
/** ZhiyingCommPlugin */ | |||||
/** | |||||
* ZhiyingCommPlugin | |||||
*/ | |||||
public class ZhiyingCommPlugin implements FlutterPlugin { | public class ZhiyingCommPlugin implements FlutterPlugin { | ||||
@Override | |||||
public void onAttachedToEngine(@NonNull FlutterPluginBinding flutterPluginBinding) { | |||||
@Override | |||||
public void onAttachedToEngine(@NonNull FlutterPluginBinding flutterPluginBinding) { | |||||
// final MethodChannel channel = new MethodChannel(flutterPluginBinding.getFlutterEngine().getDartExecutor(), "zhiying_comm"); | // final MethodChannel channel = new MethodChannel(flutterPluginBinding.getFlutterEngine().getDartExecutor(), "zhiying_comm"); | ||||
// channel.setMethodCallHandler(new ZhiyingCommPlugin()); | // channel.setMethodCallHandler(new ZhiyingCommPlugin()); | ||||
ZhiyingFlutterCommNative.getInstance().registerWith(flutterPluginBinding.getFlutterEngine().getDartExecutor()); | |||||
} | |||||
ZhiyingFlutterCommNative.getInstance().registerWith(flutterPluginBinding.getFlutterEngine().getDartExecutor()); | |||||
} | |||||
// This static function is optional and equivalent to onAttachedToEngine. It supports the old | |||||
// pre-Flutter-1.12 Android projects. You are encouraged to continue supporting | |||||
// plugin registration via this function while apps migrate to use the new Android APIs | |||||
// post-flutter-1.12 via https://flutter.dev/go/android-project-migration. | |||||
// | |||||
// It is encouraged to share logic between onAttachedToEngine and registerWith to keep | |||||
// them functionally equivalent. Only one of onAttachedToEngine or registerWith will be called | |||||
// depending on the user's project. onAttachedToEngine or registerWith must both be defined | |||||
// in the same class. | |||||
public static void registerWith(Registrar registrar) { | |||||
// This static function is optional and equivalent to onAttachedToEngine. It supports the old | |||||
// pre-Flutter-1.12 Android projects. You are encouraged to continue supporting | |||||
// plugin registration via this function while apps migrate to use the new Android APIs | |||||
// post-flutter-1.12 via https://flutter.dev/go/android-project-migration. | |||||
// | |||||
// It is encouraged to share logic between onAttachedToEngine and registerWith to keep | |||||
// them functionally equivalent. Only one of onAttachedToEngine or registerWith will be called | |||||
// depending on the user's project. onAttachedToEngine or registerWith must both be defined | |||||
// in the same class. | |||||
public static void registerWith(Registrar registrar) { | |||||
// final MethodChannel channel = new MethodChannel(registrar.messenger(), "zhiying_comm"); | // final MethodChannel channel = new MethodChannel(registrar.messenger(), "zhiying_comm"); | ||||
// channel.setMethodCallHandler(new ZhiyingCommPlugin()); | // channel.setMethodCallHandler(new ZhiyingCommPlugin()); | ||||
ZhiyingFlutterCommNative.getInstance().registerWith(registrar.messenger()); | |||||
} | |||||
ZhiyingFlutterCommNative.getInstance().registerWith(registrar.messenger()); | |||||
} | |||||
@Override | |||||
public void onDetachedFromEngine(@NonNull FlutterPluginBinding binding) { | |||||
} | |||||
@Override | |||||
public void onDetachedFromEngine(@NonNull FlutterPluginBinding binding) { | |||||
} | |||||
} | } |
@@ -67,6 +67,7 @@ public class ZhiyingFlutterCommNative implements MethodCallHandler { | |||||
Map map = new HashMap<String, Object>(); | Map map = new HashMap<String, Object>(); | ||||
map.put("success", "1"); | map.put("success", "1"); | ||||
result.success(map); | result.success(map); | ||||
return; | |||||
} | } | ||||
/* 跳转原生页面(非公共跳转) */ | /* 跳转原生页面(非公共跳转) */ | ||||
@@ -77,6 +78,7 @@ public class ZhiyingFlutterCommNative implements MethodCallHandler { | |||||
Map map = new HashMap<String, Object>(); | Map map = new HashMap<String, Object>(); | ||||
map.put("success", "1"); | map.put("success", "1"); | ||||
result.success(map); | result.success(map); | ||||
return; | |||||
} | } | ||||
/* 获取设置 */ | /* 获取设置 */ | ||||
@@ -86,6 +88,7 @@ public class ZhiyingFlutterCommNative implements MethodCallHandler { | |||||
map = nat.getSetting(); | map = nat.getSetting(); | ||||
} | } | ||||
result.success(map); | result.success(map); | ||||
return; | |||||
} | } | ||||
/* 调用原生方法 */ | /* 调用原生方法 */ | ||||
@@ -100,10 +103,14 @@ public class ZhiyingFlutterCommNative implements MethodCallHandler { | |||||
}); | }); | ||||
} else { | } else { | ||||
result.success(ZhiyingFlutterCommNativeResult.notImp().toMap()); | result.success(ZhiyingFlutterCommNativeResult.notImp().toMap()); | ||||
return; | |||||
} | } | ||||
} else { | } else { | ||||
result.notImplemented(); | result.notImplemented(); | ||||
return; | |||||
} | } | ||||
} | } | ||||
} | } |
@@ -0,0 +1,19 @@ | |||||
/// 判空工具类 | |||||
class EmptyUtil { | |||||
/// 判断是否为空,object的类型可以为:String Map List | |||||
static bool isEmpty(Object object) { | |||||
if (null == object) return true; | |||||
if (object is String && object.isEmpty) { | |||||
return true; | |||||
} | |||||
if (object is List && object.isEmpty) { | |||||
return true; | |||||
} | |||||
if (object is Map && object.isEmpty) { | |||||
return true; | |||||
} | |||||
return false; | |||||
} | |||||
} |
@@ -1,7 +1,6 @@ | |||||
import 'package:crypto/crypto.dart'; | import 'package:crypto/crypto.dart'; | ||||
import 'dart:convert'; | import 'dart:convert'; | ||||
import 'dart:io'; | import 'dart:io'; | ||||
import 'dart:typed_data'; | |||||
import 'package:flutter_native_image/flutter_native_image.dart'; | import 'package:flutter_native_image/flutter_native_image.dart'; | ||||
class EncodeUtil { | class EncodeUtil { | ||||
@@ -0,0 +1,8 @@ | |||||
class GlobalConfig { | |||||
/// ====================== 后端json返回的key ====================== /// | |||||
static final String HTTP_RESPONSE_KEY_CODE = 'code'; | |||||
static final String HTTP_RESPONSE_KEY_MSG = 'msg'; | |||||
static final String HTTP_RESPONSE_KEY_DATA = 'data'; | |||||
/// 成功返回的CODE值 | |||||
static final int RESPONSE_SUCCESS_CODE = 200; | |||||
} |
@@ -0,0 +1,362 @@ | |||||
library let_log; | |||||
import 'dart:convert'; | |||||
import 'package:flutter/material.dart'; | |||||
import 'package:flutter/services.dart'; | |||||
part 'log_widget.dart'; | |||||
part 'net_widget.dart'; | |||||
enum _Type { log, debug, warn, error } | |||||
List<String> _printNames = ["😄", "🐛", "❗", "❌", "⬆️", "⬇️"]; | |||||
List<String> _tabNames = ["[Log]", "[Debug]", "[Warn]", "[Error]"]; | |||||
final RegExp _tabReg = RegExp(r"\[|\]"); | |||||
String _getTabName(int index) { | |||||
return _tabNames[index].replaceAll(_tabReg, ""); | |||||
} | |||||
class _Config { | |||||
/// Whether to display the log in reverse order | |||||
bool reverse = false; | |||||
/// Whether or not to print logs in the ide | |||||
bool printNet = true; | |||||
/// Whether or not to print net logs in the ide | |||||
bool printLog = true; | |||||
/// Maximum number of logs, larger than this number, will be cleaned up, default value 500 | |||||
int maxLimit = 500; | |||||
/// Set the names in ide print. | |||||
void setPrintNames({ | |||||
String log, | |||||
String debug, | |||||
String warn, | |||||
String error, | |||||
String request, | |||||
String response, | |||||
}) { | |||||
_printNames = [ | |||||
log ?? "[Log]", | |||||
debug ?? "[Debug]", | |||||
warn ?? "[Warn]", | |||||
error ?? "[Error]", | |||||
request ?? "[Req]", | |||||
response ?? "[Res]", | |||||
]; | |||||
} | |||||
/// Set the names in the app. | |||||
void setTabNames({ | |||||
String log, | |||||
String debug, | |||||
String warn, | |||||
String error, | |||||
String request, | |||||
String response, | |||||
}) { | |||||
_tabNames = [ | |||||
log ?? "[Log]", | |||||
debug ?? "[Debug]", | |||||
warn ?? "[Warn]", | |||||
error ?? "[Error]", | |||||
]; | |||||
} | |||||
} | |||||
class Logger extends StatelessWidget { | |||||
@override | |||||
Widget build(BuildContext context) { | |||||
return DefaultTabController( | |||||
length: 2, | |||||
child: Scaffold( | |||||
appBar: AppBar( | |||||
title: const TabBar( | |||||
tabs: [ | |||||
Tab(child: Text("Log")), | |||||
Tab(child: Text("Net")), | |||||
], | |||||
), | |||||
elevation: 0, | |||||
), | |||||
body: const TabBarView( | |||||
children: [ | |||||
LogWidget(), | |||||
NetWidget(), | |||||
], | |||||
), | |||||
), | |||||
); | |||||
} | |||||
static bool enabled = true; | |||||
static _Config config = _Config(); | |||||
/// Logging | |||||
static void log(Object message, [Object detail]) { | |||||
if (enabled) _Log.add(_Type.log, message, detail); | |||||
} | |||||
/// Record debug information | |||||
static void debug(Object message, [Object detail]) { | |||||
if (enabled) _Log.add(_Type.debug, message, detail); | |||||
} | |||||
/// Record warnning information | |||||
static void warn(Object message, [Object detail]) { | |||||
if (enabled) _Log.add(_Type.warn, message, detail); | |||||
} | |||||
/// Record error information | |||||
static void error(Object message, [Object detail]) { | |||||
if (enabled) _Log.add(_Type.error, message, detail); | |||||
} | |||||
/// Start recording time | |||||
static void time(Object key) { | |||||
assert(key != null); | |||||
if (enabled) _Log.time(key); | |||||
} | |||||
/// End of record time | |||||
static void endTime(Object key) { | |||||
assert(key != null); | |||||
if (enabled) _Log.endTime(key); | |||||
} | |||||
/// Clearance log | |||||
static void clear() { | |||||
_Log.clear(); | |||||
} | |||||
/// Recording network information | |||||
static void net(String api, | |||||
{String type = "请求接口", int status = 100, Object data}) { | |||||
assert(api != null); | |||||
if (enabled) _Net.request(api, type, status, data); | |||||
} | |||||
/// End of record network information, with statistics on duration and size. | |||||
static void endNet(String api, | |||||
{int status = 200, Object data, Object headers, String type}) { | |||||
assert(api != null); | |||||
if (enabled) _Net.response(api, status, data, headers, type); | |||||
} | |||||
} | |||||
class _Log { | |||||
static final List<_Log> list = []; | |||||
static final ValueNotifier<int> length = ValueNotifier(0); | |||||
static final Map<Object, Object> _map = {}; | |||||
final _Type type; | |||||
final String message; | |||||
final String detail; | |||||
final DateTime start; | |||||
const _Log({this.type, this.message, this.detail, this.start}); | |||||
String get typeName { | |||||
return _printNames[type.index]; | |||||
} | |||||
String get tabName { | |||||
return _tabNames[type.index]; | |||||
} | |||||
bool contains(String keyword) { | |||||
if (keyword.isEmpty) return true; | |||||
return message != null && message.contains(keyword) || | |||||
detail != null && detail.contains(keyword); | |||||
} | |||||
@override | |||||
String toString() { | |||||
final StringBuffer sb = StringBuffer(); | |||||
sb.writeln("Message: $message"); | |||||
sb.writeln("Time: $start"); | |||||
if (detail != null && detail.length > 100) { | |||||
sb.writeln("Detail: "); | |||||
sb.writeln(detail); | |||||
} else { | |||||
sb.writeln("Detail: $detail"); | |||||
} | |||||
return sb.toString(); | |||||
} | |||||
static void add(_Type type, Object value, Object detail) { | |||||
final log = _Log( | |||||
type: type, | |||||
message: value?.toString(), | |||||
detail: detail?.toString(), | |||||
start: DateTime.now(), | |||||
); | |||||
list.add(log); | |||||
_clearWhenTooMuch(); | |||||
length.value++; | |||||
if (Logger.config.printLog) { | |||||
debugPrint( | |||||
"${log.typeName} ${log.message}${log.detail == null ? '' : '\n${log.detail}'}\n--------------------------------"); | |||||
} | |||||
} | |||||
static void _clearWhenTooMuch() { | |||||
if (list.length > Logger.config.maxLimit) { | |||||
list.removeRange(0, (Logger.config.maxLimit * 0.2).ceil()); | |||||
} | |||||
} | |||||
static void time(Object key) { | |||||
_map[key] = DateTime.now(); | |||||
} | |||||
static void endTime(Object key) { | |||||
final data = _map[key]; | |||||
if (data != null) { | |||||
_map.remove(key); | |||||
final spend = DateTime.now().difference(data).inMilliseconds; | |||||
_Log.add(_Type.log, '$key: $spend ms', null); | |||||
} | |||||
} | |||||
static void clear() { | |||||
list.clear(); | |||||
_map.clear(); | |||||
length.value = 0; | |||||
} | |||||
} | |||||
class _Net extends ChangeNotifier { | |||||
static const all = "All"; | |||||
static final List<_Net> list = []; | |||||
static final ValueNotifier<int> length = ValueNotifier(0); | |||||
static final Map<String, _Net> _map = {}; | |||||
static final List<String> types = [all]; | |||||
static final ValueNotifier<int> typeLength = ValueNotifier(1); | |||||
final String api; | |||||
final String req; | |||||
final DateTime start; | |||||
String type; | |||||
int status = 100; | |||||
int spend = 0; | |||||
String res; | |||||
String headers; | |||||
bool showDetail = false; | |||||
int _reqSize = -1; | |||||
int _resSize = -1; | |||||
_Net({ | |||||
this.api, | |||||
this.type, | |||||
this.req, | |||||
this.headers, | |||||
this.start, | |||||
this.res, | |||||
this.spend = 0, | |||||
this.status = 100, | |||||
}); | |||||
int getReqSize() { | |||||
if (_reqSize > -1) return _reqSize; | |||||
if (req != null && req.isNotEmpty) { | |||||
try { | |||||
return _reqSize = utf8.encode(req).length; | |||||
} catch (e) { | |||||
// print(e); | |||||
} | |||||
} | |||||
return 0; | |||||
} | |||||
int getResSize() { | |||||
if (_resSize > -1) return _resSize; | |||||
if (res != null && res.isNotEmpty) { | |||||
try { | |||||
return _resSize = utf8.encode(res).length; | |||||
} catch (e) { | |||||
// print(e); | |||||
} | |||||
} | |||||
return 0; | |||||
} | |||||
bool contains(String keyword) { | |||||
if (keyword.isEmpty) return true; | |||||
return api.contains(keyword) || | |||||
req != null && req.contains(keyword) || | |||||
res != null && res.contains(keyword); | |||||
} | |||||
@override | |||||
String toString() { | |||||
final StringBuffer sb = StringBuffer(); | |||||
sb.writeln("[$status] $api"); | |||||
sb.writeln("Start: $start"); | |||||
sb.writeln("Spend: $spend ms"); | |||||
sb.writeln("Headers: $headers"); | |||||
sb.writeln("Request: $req"); | |||||
sb.writeln("Response: $res"); | |||||
return sb.toString(); | |||||
} | |||||
static void request(String api, String type, int status, Object data) { | |||||
final net = _Net( | |||||
api: api, | |||||
type: type, | |||||
status: status, | |||||
req: data?.toString(), | |||||
start: DateTime.now(), | |||||
); | |||||
list.add(net); | |||||
_map[api] = net; | |||||
if (type != null && type != "" && !types.contains(type)) { | |||||
types.add(type); | |||||
typeLength.value++; | |||||
} | |||||
_clearWhenTooMuch(); | |||||
length.value++; | |||||
if (Logger.config.printNet) { | |||||
debugPrint( | |||||
"${_printNames[4]} ${type == null ? '' : '$type: '}${net.api}${net.req == null ? '' : '\n${_printNames[0]} 请求数据: ${net.req}'}\n--------------------------------"); | |||||
} | |||||
} | |||||
static void _clearWhenTooMuch() { | |||||
if (list.length > Logger.config.maxLimit) { | |||||
list.removeRange(0, (Logger.config.maxLimit * 0.2).ceil()); | |||||
} | |||||
} | |||||
static void response( | |||||
String api, int status, Object data, Object headers, String type) { | |||||
_Net net = _map[api]; | |||||
if (net != null) { | |||||
_map.remove(net); | |||||
net.spend = DateTime.now().difference(net.start).inMilliseconds; | |||||
net.status = status; | |||||
net.headers = headers?.toString(); | |||||
net.res = data?.toString(); | |||||
length.notifyListeners(); | |||||
} else { | |||||
net = _Net(api: api, start: DateTime.now(), type: type); | |||||
net.status = status; | |||||
net.headers = headers?.toString(); | |||||
net.res = data?.toString(); | |||||
list.add(net); | |||||
_clearWhenTooMuch(); | |||||
length.value++; | |||||
} | |||||
if (Logger.config.printNet) { | |||||
debugPrint( | |||||
"${_printNames[5]} ${net.type == null ? '' : '${net.type}: '}${net.api}${net.res == null ? '' : '\n${_printNames[0]} 响应数据: ${net.res}'}\nSpend: ${net.spend} ms\n--------------------------------"); | |||||
} | |||||
} | |||||
static void clear() { | |||||
list.clear(); | |||||
_map.clear(); | |||||
length.value = 0; | |||||
} | |||||
} |
@@ -0,0 +1,246 @@ | |||||
part of let_log; | |||||
class LogWidget extends StatefulWidget { | |||||
const LogWidget({Key key}) : super(key: key); | |||||
@override | |||||
_LogWidgetState createState() => _LogWidgetState(); | |||||
} | |||||
class _LogWidgetState extends State<LogWidget> { | |||||
bool _showSearch = false; | |||||
String _keyword = ""; | |||||
TextEditingController _textController; | |||||
ScrollController _scrollController; | |||||
FocusNode _focusNode; | |||||
bool _goDown = true; | |||||
@override | |||||
void initState() { | |||||
_textController = TextEditingController(text: _keyword); | |||||
_scrollController = ScrollController(); | |||||
_focusNode = FocusNode(); | |||||
super.initState(); | |||||
} | |||||
@override | |||||
void dispose() { | |||||
_textController.dispose(); | |||||
_scrollController.dispose(); | |||||
super.dispose(); | |||||
} | |||||
@override | |||||
Widget build(BuildContext context) { | |||||
return Scaffold( | |||||
body: Column( | |||||
crossAxisAlignment: CrossAxisAlignment.start, | |||||
children: <Widget>[ | |||||
_buildTools(), | |||||
Expanded( | |||||
child: ValueListenableBuilder<int>( | |||||
valueListenable: _Log.length, | |||||
builder: (context, value, child) { | |||||
List<_Log> logs = _Log.list; | |||||
if (_selectTypes.length < 4 || _keyword.isNotEmpty) { | |||||
logs = _Log.list.where((test) { | |||||
return _selectTypes.contains(test.type) && | |||||
test.contains(_keyword); | |||||
}).toList(); | |||||
} | |||||
final len = logs.length; | |||||
return ListView.separated( | |||||
itemBuilder: (context, index) { | |||||
final item = Logger.config.reverse | |||||
? logs[len - index - 1] | |||||
: logs[index]; | |||||
final color = _getColor(item.type, context); | |||||
return _buildItem(item, color); | |||||
}, | |||||
itemCount: len, | |||||
controller: _scrollController, | |||||
separatorBuilder: (context, index) { | |||||
return const Divider( | |||||
height: 10, | |||||
thickness: 0.5, | |||||
color: Color(0xFFE0E0E0), | |||||
); | |||||
}, | |||||
); | |||||
}, | |||||
), | |||||
), | |||||
], | |||||
), | |||||
floatingActionButton: FloatingActionButton( | |||||
onPressed: () { | |||||
if (_goDown) { | |||||
_scrollController.animateTo( | |||||
_scrollController.position.maxScrollExtent * 2, | |||||
curve: Curves.easeOut, | |||||
duration: const Duration(milliseconds: 300), | |||||
); | |||||
} else { | |||||
_scrollController.animateTo( | |||||
0, | |||||
curve: Curves.easeOut, | |||||
duration: const Duration(milliseconds: 300), | |||||
); | |||||
} | |||||
_goDown = !_goDown; | |||||
setState(() {}); | |||||
}, | |||||
mini: true, | |||||
child: Icon( | |||||
_goDown ? Icons.arrow_downward : Icons.arrow_upward, | |||||
), | |||||
), | |||||
); | |||||
} | |||||
Widget _buildItem(_Log item, Color color) { | |||||
return InkWell( | |||||
onTap: () { | |||||
final ClipboardData data = ClipboardData(text: item.toString()); | |||||
Clipboard.setData(data); | |||||
showDialog( | |||||
context: context, | |||||
barrierDismissible: true, | |||||
builder: (context) { | |||||
return const Center( | |||||
child: Material( | |||||
color: Colors.transparent, | |||||
child: Text( | |||||
"copy success!", | |||||
style: TextStyle(color: Colors.white, fontSize: 30), | |||||
), | |||||
), | |||||
); | |||||
}, | |||||
); | |||||
}, | |||||
child: Padding( | |||||
padding: const EdgeInsets.fromLTRB(16, 8, 16, 8), | |||||
child: Column( | |||||
crossAxisAlignment: CrossAxisAlignment.start, | |||||
children: [ | |||||
Text( | |||||
"${item.tabName} ${item.message} (${item.start.hour}:${item.start.minute}:${item.start.second}:${item.start.millisecond})", | |||||
style: TextStyle(fontSize: 16, color: color), | |||||
), | |||||
if (item.detail != null) | |||||
Padding( | |||||
padding: const EdgeInsets.only(top: 8), | |||||
child: Text( | |||||
item.detail, | |||||
style: TextStyle(fontSize: 14, color: color), | |||||
overflow: TextOverflow.ellipsis, | |||||
maxLines: 20, | |||||
), | |||||
) | |||||
], | |||||
), | |||||
), | |||||
); | |||||
} | |||||
Color _getColor(_Type type, BuildContext context) { | |||||
switch (type) { | |||||
case _Type.debug: | |||||
return Colors.blue; | |||||
case _Type.warn: | |||||
return const Color(0xFFF57F17); | |||||
case _Type.error: | |||||
return Colors.red; | |||||
default: | |||||
return Theme.of(context).textTheme.body1.color; | |||||
} | |||||
} | |||||
final List<_Type> _selectTypes = [ | |||||
_Type.log, | |||||
_Type.debug, | |||||
_Type.warn, | |||||
_Type.error | |||||
]; | |||||
Widget _buildTools() { | |||||
final List<ChoiceChip> arr = []; | |||||
_Type.values.forEach((f) { | |||||
arr.add( | |||||
ChoiceChip( | |||||
label: Text( | |||||
_getTabName(f.index), | |||||
style: const TextStyle(fontSize: 14), | |||||
), | |||||
selectedColor: const Color(0xFFCBE2F6), | |||||
selected: _selectTypes.contains(f), | |||||
onSelected: (value) { | |||||
_selectTypes.contains(f) | |||||
? _selectTypes.remove(f) | |||||
: _selectTypes.add(f); | |||||
setState(() {}); | |||||
}, | |||||
), | |||||
); | |||||
}); | |||||
return Padding( | |||||
padding: const EdgeInsets.fromLTRB(16, 5, 0, 5), | |||||
child: AnimatedCrossFade( | |||||
crossFadeState: | |||||
_showSearch ? CrossFadeState.showSecond : CrossFadeState.showFirst, | |||||
duration: const Duration(milliseconds: 300), | |||||
firstChild: Row( | |||||
children: [ | |||||
Expanded( | |||||
child: Wrap( | |||||
spacing: 5, | |||||
children: arr, | |||||
), | |||||
), | |||||
const IconButton( | |||||
icon: Icon(Icons.clear), | |||||
onPressed: _Log.clear, | |||||
), | |||||
IconButton( | |||||
icon: _keyword.isEmpty | |||||
? const Icon(Icons.search) | |||||
: const Icon(Icons.filter_1), | |||||
onPressed: () { | |||||
_showSearch = true; | |||||
setState(() {}); | |||||
_focusNode.requestFocus(); | |||||
}, | |||||
), | |||||
], | |||||
), | |||||
secondChild: Row( | |||||
children: [ | |||||
Expanded( | |||||
child: SizedBox( | |||||
height: 36, | |||||
child: TextField( | |||||
decoration: const InputDecoration( | |||||
border: OutlineInputBorder(), | |||||
contentPadding: EdgeInsets.all(6), | |||||
), | |||||
controller: _textController, | |||||
focusNode: _focusNode, | |||||
), | |||||
), | |||||
), | |||||
IconButton( | |||||
icon: const Icon(Icons.search), | |||||
onPressed: () { | |||||
_showSearch = false; | |||||
_keyword = _textController.text; | |||||
setState(() {}); | |||||
}, | |||||
), | |||||
], | |||||
), | |||||
), | |||||
); | |||||
} | |||||
} |
@@ -0,0 +1,321 @@ | |||||
part of let_log; | |||||
class NetWidget extends StatefulWidget { | |||||
const NetWidget({Key key}) : super(key: key); | |||||
@override | |||||
_NetWidgetState createState() => _NetWidgetState(); | |||||
} | |||||
class _NetWidgetState extends State<NetWidget> { | |||||
bool _showSearch = false; | |||||
String _keyword = ""; | |||||
TextEditingController _textController; | |||||
ScrollController _scrollController; | |||||
FocusNode _focusNode; | |||||
bool _goDown = true; | |||||
@override | |||||
void initState() { | |||||
_textController = TextEditingController(text: _keyword); | |||||
_scrollController = ScrollController(); | |||||
_focusNode = FocusNode(); | |||||
super.initState(); | |||||
} | |||||
@override | |||||
void dispose() { | |||||
_textController.dispose(); | |||||
_scrollController.dispose(); | |||||
super.dispose(); | |||||
} | |||||
@override | |||||
Widget build(BuildContext context) { | |||||
return Scaffold( | |||||
body: Column( | |||||
crossAxisAlignment: CrossAxisAlignment.start, | |||||
children: <Widget>[ | |||||
ValueListenableBuilder<int>( | |||||
valueListenable: _Net.typeLength, | |||||
builder: (context, value, child) { | |||||
return _buildTools(); | |||||
}, | |||||
), | |||||
Expanded( | |||||
child: ValueListenableBuilder<int>( | |||||
valueListenable: _Net.length, | |||||
builder: (context, value, child) { | |||||
List<_Net> logs = _Net.list; | |||||
if (!_selectTypes.contains(_Net.all)) { | |||||
logs = _Net.list.where((test) { | |||||
return _selectTypes.contains(test.type) && | |||||
test.contains(_keyword); | |||||
}).toList(); | |||||
} else if (_keyword.isNotEmpty) { | |||||
logs = _Net.list.where((test) { | |||||
return test.contains(_keyword); | |||||
}).toList(); | |||||
} | |||||
final len = logs.length; | |||||
return ListView.separated( | |||||
itemBuilder: (context, index) { | |||||
final item = Logger.config.reverse | |||||
? logs[len - index - 1] | |||||
: logs[index]; | |||||
return _buildItem(item, context); | |||||
}, | |||||
itemCount: len, | |||||
controller: _scrollController, | |||||
reverse: Logger.config.reverse, | |||||
separatorBuilder: (context, index) { | |||||
return const Divider( | |||||
height: 10, | |||||
thickness: 0.5, | |||||
color: Color(0xFFE0E0E0), | |||||
); | |||||
}, | |||||
); | |||||
}, | |||||
), | |||||
), | |||||
], | |||||
), | |||||
floatingActionButton: FloatingActionButton( | |||||
onPressed: () { | |||||
if (_goDown) { | |||||
_scrollController.animateTo( | |||||
_scrollController.position.maxScrollExtent * 2, | |||||
curve: Curves.easeOut, | |||||
duration: const Duration(milliseconds: 300), | |||||
); | |||||
} else { | |||||
_scrollController.animateTo( | |||||
0, | |||||
curve: Curves.easeOut, | |||||
duration: const Duration(milliseconds: 300), | |||||
); | |||||
} | |||||
_goDown = !_goDown; | |||||
setState(() {}); | |||||
}, | |||||
mini: true, | |||||
child: Icon( | |||||
_goDown ? Icons.arrow_downward : Icons.arrow_upward, | |||||
), | |||||
), | |||||
); | |||||
} | |||||
Widget _buildItem(_Net item, context) { | |||||
final color = _getColor(item.status); | |||||
return InkWell( | |||||
onTap: () { | |||||
item.showDetail = !item.showDetail; | |||||
setState(() {}); | |||||
}, | |||||
child: Padding( | |||||
padding: const EdgeInsets.fromLTRB(16, 8, 16, 0), | |||||
child: Row( | |||||
children: [ | |||||
Expanded( | |||||
child: Column( | |||||
crossAxisAlignment: CrossAxisAlignment.start, | |||||
children: [ | |||||
Text( | |||||
"[${item.type}] ${item.api}", | |||||
style: const TextStyle(fontSize: 16), | |||||
), | |||||
if (item.showDetail) | |||||
Padding( | |||||
padding: const EdgeInsets.only(top: 8), | |||||
child: Text( | |||||
"Request: ${item.req ?? ""}", | |||||
maxLines: 100, | |||||
overflow: TextOverflow.ellipsis, | |||||
style: const TextStyle(fontSize: 14), | |||||
), | |||||
), | |||||
if (item.showDetail) | |||||
Padding( | |||||
padding: const EdgeInsets.only(top: 8), | |||||
child: Text( | |||||
"Response: ${item.res ?? ""}", | |||||
maxLines: 100, | |||||
overflow: TextOverflow.ellipsis, | |||||
style: const TextStyle(fontSize: 14), | |||||
), | |||||
), | |||||
if (item.showDetail && item.headers != null) | |||||
Padding( | |||||
padding: const EdgeInsets.only(top: 8), | |||||
child: Text( | |||||
"Headers: ${item.headers ?? ""}", | |||||
maxLines: 100, | |||||
overflow: TextOverflow.ellipsis, | |||||
style: const TextStyle(fontSize: 14), | |||||
), | |||||
), | |||||
Padding( | |||||
padding: const EdgeInsets.only(top: 8, bottom: 8), | |||||
child: Row( | |||||
children: [ | |||||
SizedBox( | |||||
width: 120, | |||||
child: Text( | |||||
"${item.start.hour}:${item.start.minute}:${item.start.second}:${item.start.millisecond}", | |||||
style: const TextStyle(fontSize: 14), | |||||
maxLines: 1, | |||||
), | |||||
), | |||||
SizedBox( | |||||
width: 100, | |||||
child: Text( | |||||
"${item.spend ?? "0"} ms", | |||||
style: const TextStyle(fontSize: 14), | |||||
overflow: TextOverflow.visible, | |||||
maxLines: 1, | |||||
), | |||||
), | |||||
Text( | |||||
"${item.getReqSize()}/${item.getResSize()}B", | |||||
style: const TextStyle(fontSize: 14), | |||||
overflow: TextOverflow.visible, | |||||
maxLines: 1, | |||||
), | |||||
], | |||||
), | |||||
) | |||||
], | |||||
), | |||||
), | |||||
InkWell( | |||||
onTap: () { | |||||
final ClipboardData data = ClipboardData(text: item.toString()); | |||||
Clipboard.setData(data); | |||||
showDialog( | |||||
context: context, | |||||
barrierDismissible: true, | |||||
builder: (context) { | |||||
return const Center( | |||||
child: Material( | |||||
color: Colors.transparent, | |||||
child: Text( | |||||
"copy success!", | |||||
style: TextStyle(color: Colors.white, fontSize: 30), | |||||
), | |||||
), | |||||
); | |||||
}, | |||||
); | |||||
}, | |||||
child: Column( | |||||
mainAxisAlignment: MainAxisAlignment.spaceEvenly, | |||||
children: [ | |||||
Text( | |||||
item.status.toString(), | |||||
style: TextStyle(fontSize: 20, color: color), | |||||
), | |||||
if (item.showDetail) | |||||
const Text( | |||||
"copy", | |||||
style: TextStyle(fontSize: 16, color: Colors.blue), | |||||
), | |||||
], | |||||
), | |||||
), | |||||
], | |||||
), | |||||
), | |||||
); | |||||
} | |||||
Color _getColor(int status) { | |||||
if (status == 200 || status == 0) { | |||||
return Colors.green; | |||||
} else if (status < 200) { | |||||
return Colors.blue; | |||||
} else { | |||||
return Colors.red; | |||||
} | |||||
} | |||||
final List<String> _selectTypes = [_Net.all]; | |||||
Widget _buildTools() { | |||||
final List<ChoiceChip> arr = []; | |||||
_Net.types.forEach((f) { | |||||
arr.add( | |||||
ChoiceChip( | |||||
label: Text(f, style: const TextStyle(fontSize: 14)), | |||||
selectedColor: const Color(0xFFCBE2F6), | |||||
selected: _selectTypes.contains(f), | |||||
onSelected: (value) { | |||||
_selectTypes.contains(f) | |||||
? _selectTypes.remove(f) | |||||
: _selectTypes.add(f); | |||||
setState(() {}); | |||||
}, | |||||
), | |||||
); | |||||
}); | |||||
return Padding( | |||||
padding: const EdgeInsets.fromLTRB(16, 5, 0, 5), | |||||
child: AnimatedCrossFade( | |||||
crossFadeState: | |||||
_showSearch ? CrossFadeState.showSecond : CrossFadeState.showFirst, | |||||
duration: const Duration(milliseconds: 300), | |||||
firstChild: Row( | |||||
children: [ | |||||
Expanded( | |||||
child: Wrap( | |||||
spacing: 5, | |||||
children: arr, | |||||
), | |||||
), | |||||
const IconButton( | |||||
icon: Icon(Icons.clear), | |||||
onPressed: _Net.clear, | |||||
), | |||||
IconButton( | |||||
icon: _keyword.isEmpty | |||||
? const Icon(Icons.search) | |||||
: const Icon(Icons.filter_1), | |||||
onPressed: () { | |||||
_showSearch = true; | |||||
setState(() {}); | |||||
_focusNode.requestFocus(); | |||||
}, | |||||
), | |||||
], | |||||
), | |||||
secondChild: Row( | |||||
children: [ | |||||
Expanded( | |||||
child: SizedBox( | |||||
height: 36, | |||||
child: TextField( | |||||
decoration: const InputDecoration( | |||||
border: OutlineInputBorder(), | |||||
contentPadding: EdgeInsets.all(6), | |||||
), | |||||
controller: _textController, | |||||
focusNode: _focusNode, | |||||
), | |||||
), | |||||
), | |||||
IconButton( | |||||
icon: const Icon(Icons.search), | |||||
onPressed: () { | |||||
_showSearch = false; | |||||
_keyword = _textController.text; | |||||
setState(() {}); | |||||
}, | |||||
), | |||||
], | |||||
), | |||||
), | |||||
); | |||||
} | |||||
} |
@@ -25,8 +25,7 @@ class NativeUtil { | |||||
{Map<String, dynamic> params}) async { | {Map<String, dynamic> params}) async { | ||||
var res; | var res; | ||||
try { | try { | ||||
res = | |||||
await NativeUtil.instance._methodChannel.invokeMethod(method, params); | |||||
res = await NativeUtil.instance._methodChannel.invokeMethod(method, params); | |||||
} catch (e) { | } catch (e) { | ||||
res = {'Failed': e.message}; | res = {'Failed': e.message}; | ||||
} | } | ||||
@@ -63,15 +62,22 @@ class NativeUtil { | |||||
}); | }); | ||||
} | } | ||||
// 跳转原生商品详情 | |||||
static openGoodsDetail(Map<String, dynamic> params) async { | |||||
NativeUtil._invokeChannel('openGoodsDetail', params: params); | |||||
} | |||||
static Future<Map<String, dynamic>> getSetting() async { | static Future<Map<String, dynamic>> getSetting() async { | ||||
return NativeUtil._invokeChannel('getSetting'); | return NativeUtil._invokeChannel('getSetting'); | ||||
} | } | ||||
// static Future<Map<String, dynamic>> onPagePop(bool isTop) async { | |||||
// NativeUtil._invokeChannel('pageDidPop', | |||||
// params: new Map<String, dynamic>.from({'isTop': isTop})); | |||||
// } | |||||
// | |||||
// static Future<Map<String, dynamic>> onPagePush(bool isTop) async { | |||||
// NativeUtil._invokeChannel('pageDidPush', | |||||
// params: new Map<String, dynamic>.from({'isTop': isTop})); | |||||
// } | |||||
static Future<Map<String, dynamic>> invokeMethod( | static Future<Map<String, dynamic>> invokeMethod( | ||||
String method, Map<String, dynamic> params) { | String method, Map<String, dynamic> params) { | ||||
Map<String, dynamic> request = {"method": method, "params": params}; | Map<String, dynamic> request = {"method": method, "params": params}; | ||||
@@ -1,25 +1,29 @@ | |||||
import 'package:dio/dio.dart'; | import 'package:dio/dio.dart'; | ||||
import 'package:dio/adapter.dart'; | import 'package:dio/adapter.dart'; | ||||
import 'package:zhiying_comm/util/empty_util.dart'; | |||||
import 'dart:io'; | import 'dart:io'; | ||||
import 'dart:ui'; | import 'dart:ui'; | ||||
import 'dart:convert'; | import 'dart:convert'; | ||||
import 'package:convert/convert.dart'; | |||||
import 'package:crypto/crypto.dart'; | |||||
import 'package:zhiying_comm/zhiying_comm.dart'; | import 'package:zhiying_comm/zhiying_comm.dart'; | ||||
import 'package:package_info/package_info.dart'; | import 'package:package_info/package_info.dart'; | ||||
import 'package:device_info/device_info.dart'; | import 'package:device_info/device_info.dart'; | ||||
import 'package:fluttertoast/fluttertoast.dart'; | import 'package:fluttertoast/fluttertoast.dart'; | ||||
import 'global_config.dart'; | |||||
import 'shared_prefe_util.dart'; | |||||
import 'time_util.dart'; | |||||
typedef OnSuccess = dynamic Function(dynamic data); | typedef OnSuccess = dynamic Function(dynamic data); | ||||
typedef OnError = dynamic Function(String e); | typedef OnError = dynamic Function(String e); | ||||
typedef OnCache = dynamic Function(dynamic data); | |||||
class NetUtil { | class NetUtil { | ||||
static final String APPKEY = "123"; | |||||
Dio _dio; | Dio _dio; | ||||
static String _apiVersion; | |||||
NetUtil._() {} | |||||
NetUtil._(); | |||||
static NetUtil _instance; | static NetUtil _instance; | ||||
static NetUtil getInstance() { | static NetUtil getInstance() { | ||||
if (_instance == null) { | if (_instance == null) { | ||||
_instance = NetUtil._(); | _instance = NetUtil._(); | ||||
@@ -31,14 +35,14 @@ class NetUtil { | |||||
if (_dio == null) { | if (_dio == null) { | ||||
var setting = await NativeUtil.getSetting(); | var setting = await NativeUtil.getSetting(); | ||||
String domain = setting['domain']; //'http://www.hairuyi.com/'; | String domain = setting['domain']; //'http://www.hairuyi.com/'; | ||||
_config(domain, apiVersion: _apiVersion); | |||||
_config(domain); | |||||
} | } | ||||
return _dio; | return _dio; | ||||
} | } | ||||
// 配置网络请求,基础地址,代理地址,如:192.168.0.123:8888(代理地址生产环境无效) | |||||
// apiVersion 接口版本 | |||||
void _config(String baseUrl, {String proxyUrl, String apiVersion}) { | |||||
/// 配置网络请求,基础地址,代理地址,如:192.168.0.123:8888(代理地址生产环境无效) | |||||
/// apiVersion 接口版本 | |||||
void _config(String baseUrl, {String proxyUrl}) { | |||||
_dio = Dio(BaseOptions( | _dio = Dio(BaseOptions( | ||||
method: "post", | method: "post", | ||||
baseUrl: baseUrl, | baseUrl: baseUrl, | ||||
@@ -49,13 +53,13 @@ class NetUtil { | |||||
_dio.interceptors.add(_NetInterceptors()); | _dio.interceptors.add(_NetInterceptors()); | ||||
_dio.interceptors.add(LogInterceptor()); | _dio.interceptors.add(LogInterceptor()); | ||||
_apiVersion = apiVersion; | |||||
const bool inProduction = const bool.fromEnvironment("dart.vm.product"); | const bool inProduction = const bool.fromEnvironment("dart.vm.product"); | ||||
if (proxyUrl != null && proxyUrl != '' && !inProduction) { | if (proxyUrl != null && proxyUrl != '' && !inProduction) { | ||||
_setProxy(proxyUrl); | _setProxy(proxyUrl); | ||||
} | } | ||||
} | } | ||||
/// 设置代理 | |||||
void _setProxy(String proxyUrl) { | void _setProxy(String proxyUrl) { | ||||
(_dio.httpClientAdapter as DefaultHttpClientAdapter).onHttpClientCreate = | (_dio.httpClientAdapter as DefaultHttpClientAdapter).onHttpClientCreate = | ||||
(HttpClient client) { | (HttpClient client) { | ||||
@@ -67,99 +71,24 @@ class NetUtil { | |||||
}; | }; | ||||
} | } | ||||
/// 统一添加必要的参数 | |||||
static Future<Map<String, dynamic>> signParams( | |||||
Map<String, dynamic> params) async { | |||||
if (_apiVersion != null) { | |||||
params["version"] = _apiVersion; | |||||
} | |||||
if (Platform.isIOS) { | |||||
params["platform"] = "iOS"; | |||||
DeviceInfoPlugin deviceInfo = DeviceInfoPlugin(); | |||||
IosDeviceInfo iosInfo = await deviceInfo.iosInfo; | |||||
params["systemVersion"] = iosInfo.systemVersion; | |||||
} else if (Platform.isAndroid) { | |||||
params["platform"] = "Android"; | |||||
DeviceInfoPlugin deviceInfo = DeviceInfoPlugin(); | |||||
AndroidDeviceInfo androidInfo = await deviceInfo.androidInfo; | |||||
String androidVersion = androidInfo.version.release; | |||||
params["systemVersion"] = "Android $androidVersion"; | |||||
} | |||||
PackageInfo packageInfo = await PackageInfo.fromPlatform(); | |||||
String version = packageInfo.version; //版本号 | |||||
params["appVersion"] = version; | |||||
int width = window.physicalSize.width.floor(); | |||||
int height = window.physicalSize.height.floor(); | |||||
params["resolutionRatio"] = "$width*$height"; | |||||
String token = ''; | |||||
if (token != null && | |||||
token != '' && | |||||
(!params.containsKey('token') || params['token'] == '')) { | |||||
params['token'] = token; | |||||
} | |||||
params["time"] = getNowTime(); | |||||
// 过滤空字段,过滤首尾空格 | |||||
Map<String, dynamic> filters = Map<String, dynamic>(); | |||||
params.forEach((key, value) { | |||||
if (key != '' && value != '') { | |||||
filters[key] = (value is String) ? value.trim() : value; | |||||
} | |||||
}); | |||||
params = filters; | |||||
List<String> list = List<String>(); | |||||
params.forEach((key, value) { | |||||
list.add(key.toString() + value.toString()); | |||||
}); | |||||
params["sign"] = signWithArray(list); | |||||
return params; | |||||
} | |||||
/// 签名 | |||||
static String signWithArray(List<String> params) { | |||||
params.sort(); | |||||
String result = ""; | |||||
result += APPKEY; | |||||
params.forEach((param) { | |||||
result += param; | |||||
}); | |||||
result += APPKEY; | |||||
return generateMd5(result); | |||||
} | |||||
// md5 加密 | |||||
static String generateMd5(String data) { | |||||
var content = new Utf8Encoder().convert(data); | |||||
var digest = md5.convert(content); | |||||
// 这里其实就是 digest.toString() | |||||
return hex.encode(digest.bytes); | |||||
} | |||||
/// 获取时间戳,单位:秒 | |||||
static String getNowTime() { | |||||
int timestamp = (DateTime.now().millisecondsSinceEpoch / 1000).floor(); | |||||
return "$timestamp"; | |||||
} | |||||
/// 同步请求 | |||||
static Future<dynamic> post(String path, | static Future<dynamic> post(String path, | ||||
{Map<String, dynamic> params}) async { | {Map<String, dynamic> params}) async { | ||||
if (params == null) { | if (params == null) { | ||||
params = {}; | params = {}; | ||||
} | } | ||||
// Map<String, dynamic> sign = await signParams(params); | |||||
Map<String, dynamic> sign = params; | |||||
Map<String, dynamic> sign = await signParams(params); | |||||
Response response; | Response response; | ||||
try { | try { | ||||
Dio dio = await NetUtil.getInstance().dio; | Dio dio = await NetUtil.getInstance().dio; | ||||
response = await dio.post(path, data: sign); | response = await dio.post(path, data: sign); | ||||
} on DioError catch (e) { | } on DioError catch (e) { | ||||
_formatError(e); | _formatError(e); | ||||
// Logger.error(e); | |||||
} | } | ||||
if (response == null) { | if (response == null) { | ||||
return {}; | |||||
return null; | |||||
} | } | ||||
if (response.statusCode != 200) { | if (response.statusCode != 200) { | ||||
@@ -169,159 +98,258 @@ class NetUtil { | |||||
Fluttertoast.showToast(msg: response.statusMessage); | Fluttertoast.showToast(msg: response.statusMessage); | ||||
} | } | ||||
// log.e(response.statusMessage); | // log.e(response.statusMessage); | ||||
return {}; | |||||
return null; | |||||
} | } | ||||
var result = response.data; | var result = response.data; | ||||
// if (result['success'] == 0 || result['success'] == '0') { | |||||
// print('error: ' + result['msg']); | |||||
// Fluttertoast.showToast( | |||||
// msg: result['msg'], | |||||
// toastLength: Toast.LENGTH_SHORT, | |||||
// gravity: ToastGravity.BOTTOM, | |||||
// ); | |||||
// return {}; | |||||
// } | |||||
return result; | |||||
} | |||||
if (result[GlobalConfig.HTTP_RESPONSE_KEY_CODE] == | |||||
GlobalConfig.RESPONSE_SUCCESS_CODE || | |||||
result[GlobalConfig.HTTP_RESPONSE_KEY_CODE] == | |||||
'${GlobalConfig.RESPONSE_SUCCESS_CODE}') { | |||||
static Future<dynamic> post2(String path, | |||||
{Map<String, dynamic> params}) async { | |||||
// if (params == null) { | |||||
// params = {}; | |||||
// } | |||||
// Map<String, dynamic> sign = await signParams(params); | |||||
// Response response; | |||||
// try { | |||||
// Dio dio = await NetUtil.getInstance().dio; | |||||
// response = await dio.post(path, data: sign); | |||||
// } on DioError catch (e) { | |||||
// _formatError(e); | |||||
// } | |||||
// if (response == null) { | |||||
// return {}; | |||||
// } | |||||
// if (response.statusCode != 200) { | |||||
// const bool inProduction = const bool.fromEnvironment("dart.vm.product"); | |||||
// if (!inProduction) { | |||||
// Fluttertoast.showToast(msg: response.statusMessage); | |||||
// } | |||||
// log.e(response.statusMessage); | |||||
// return {}; | |||||
// } | |||||
// var result = jsonDecode(response.data); | |||||
// if (result['success'] == 0 || result['success'] == '0') { | |||||
// print('error: ' + result['msg']); | |||||
// Fluttertoast.showToast( | |||||
// msg: result['msg'], | |||||
// toastLength: Toast.LENGTH_SHORT, | |||||
// gravity: ToastGravity.BOTTOM, | |||||
// ); | |||||
// return result; | |||||
// } | |||||
// return result['data']; | |||||
} | |||||
Logger.error(result[GlobalConfig.HTTP_RESPONSE_KEY_MSG]); | |||||
/* | |||||
* error统一处理 | |||||
*/ | |||||
static void _formatError(DioError e) { | |||||
// if (e.type == DioErrorType.CONNECT_TIMEOUT) { | |||||
// log.d('连接超时: ${e.toString()}'); | |||||
// } else if (e.type == DioErrorType.SEND_TIMEOUT) { | |||||
// log.d('请求超时: ${e.toString()}'); | |||||
// } else if (e.type == DioErrorType.RECEIVE_TIMEOUT) { | |||||
// log.d('响应超时: ${e.toString()}'); | |||||
// } else if (e.type == DioErrorType.RESPONSE) { | |||||
// log.d('出现异常: ${e.toString()}'); | |||||
// } else if (e.type == DioErrorType.CANCEL) { | |||||
// log.d('请求取消: ${e.toString()}'); | |||||
// } else { | |||||
// log.d('未知错误: ${e.toString()}'); | |||||
// } | |||||
Fluttertoast.showToast( | |||||
msg: result[GlobalConfig.HTTP_RESPONSE_KEY_MSG], | |||||
toastLength: Toast.LENGTH_SHORT, | |||||
gravity: ToastGravity.BOTTOM, | |||||
); | |||||
} | |||||
return result; | |||||
} | } | ||||
/// 异步请求 | |||||
static void request(String path, | static void request(String path, | ||||
{String method = 'POST', | {String method = 'POST', | ||||
Map<String, dynamic> params, | Map<String, dynamic> params, | ||||
Map<String, File> multiFiles, | Map<String, File> multiFiles, | ||||
OnSuccess onSuccess, | OnSuccess onSuccess, | ||||
OnError onError}) async { | |||||
OnError onError, | |||||
OnCache onCache}) async { | |||||
if (params == null) { | if (params == null) { | ||||
params = {}; | params = {}; | ||||
} | } | ||||
signParams(params).then((sign) async { | |||||
print('params => ${sign.toString()}'); | |||||
// 根据请求参数,获取缓存的Key | |||||
String cacheKey = _getRequestParamsCachedKey(params, path); | |||||
// // 读取缓存回调 | |||||
await _onCallBackCacheData(onCache, cacheKey); | |||||
// 参数签名 | |||||
Map<String, dynamic> sign = await signParams(params); | |||||
Response response; | |||||
try { | |||||
Dio dio = await NetUtil.getInstance().dio; | Dio dio = await NetUtil.getInstance().dio; | ||||
// 文件 | |||||
if (multiFiles != null && multiFiles.length > 0) { | if (multiFiles != null && multiFiles.length > 0) { | ||||
for (String key in multiFiles.keys) { | for (String key in multiFiles.keys) { | ||||
var file = await MultipartFile.fromFile(multiFiles[key].path); | var file = await MultipartFile.fromFile(multiFiles[key].path); | ||||
sign[key] = file; | sign[key] = file; | ||||
} | } | ||||
} | } | ||||
FormData formData = FormData.fromMap(sign); | |||||
response = await dio.request(path, | |||||
data: formData, options: Options(method: method)); | |||||
} on DioError catch (e) { | |||||
_formatError(e); | |||||
} | |||||
FormData formdata = FormData.fromMap(sign); | |||||
return dio.request(path, | |||||
data: formdata, options: Options(method: method)); | |||||
}).then((response) { | |||||
var result = jsonDecode(response.data); | |||||
if (result['success'] == 0 || result['success'] == '0') { | |||||
print('error: ' + result['msg']); | |||||
Fluttertoast.showToast( | |||||
msg: result['msg'], | |||||
toastLength: Toast.LENGTH_SHORT, | |||||
gravity: ToastGravity.BOTTOM, | |||||
); | |||||
if (onError != null) { | |||||
onError(result['msg'] ?? '未知错误'); | |||||
} | |||||
return; | |||||
} | |||||
if (response == null) { | |||||
return null; | |||||
} | |||||
var result = jsonDecode(response.data); | |||||
//TODO 加密? | |||||
if (result[GlobalConfig.HTTP_RESPONSE_KEY_CODE] == | |||||
GlobalConfig.RESPONSE_SUCCESS_CODE || | |||||
result[GlobalConfig.HTTP_RESPONSE_KEY_CODE] == | |||||
'${GlobalConfig.RESPONSE_SUCCESS_CODE}') { | |||||
if (onSuccess != null) { | if (onSuccess != null) { | ||||
onSuccess(result['data']); | |||||
onSuccess(result[GlobalConfig.HTTP_RESPONSE_KEY_DATA]); | |||||
// 缓存返回的数据 | |||||
_setCallBackCacheData(cacheKey, response.data); | |||||
} | |||||
return; | |||||
} | |||||
Logger.error('error: ' + result[GlobalConfig.HTTP_RESPONSE_KEY_MSG]); | |||||
Fluttertoast.showToast( | |||||
msg: result[GlobalConfig.HTTP_RESPONSE_KEY_MSG], | |||||
toastLength: Toast.LENGTH_SHORT, | |||||
gravity: ToastGravity.BOTTOM, | |||||
); | |||||
if (onError != null) { | |||||
onError(result[GlobalConfig.HTTP_RESPONSE_KEY_MSG] ?? '未知错误'); | |||||
} | |||||
return; | |||||
} | |||||
/// 统一添加必要的参数 | |||||
static Future<Map<String, dynamic>> signParams( | |||||
Map<String, dynamic> params) async { | |||||
// 应用信息 | |||||
PackageInfo packageInfo = await PackageInfo.fromPlatform(); | |||||
// 原生传的信息 | |||||
Map setting = await NativeUtil.getSetting(); | |||||
if (Platform.isIOS) { | |||||
IosDeviceInfo iosInfo = await DeviceInfoPlugin().iosInfo; | |||||
// 设备 | |||||
params["platform"] = "ios"; | |||||
// 设备系统版本 | |||||
params["system_version"] = iosInfo?.systemVersion; | |||||
// 设备型号 如:iPhone 11pro | |||||
params['device_model'] = iosInfo?.name; | |||||
// 设备型号,如:MLMF2LL/A | |||||
// params['device_number'] = iosInfo?.model; | |||||
// 设备ID | |||||
params['device_id'] = iosInfo?.identifierForVendor; | |||||
} else if (Platform.isAndroid) { | |||||
AndroidDeviceInfo androidInfo = await DeviceInfoPlugin().androidInfo; | |||||
// 设备 | |||||
params["platform"] = "android"; | |||||
// 设备系统版本 | |||||
params["system_version"] = "Android ${androidInfo?.version?.release}"; | |||||
// 设备型号 如:iPhone 11pro | |||||
params['device_model'] = androidInfo?.model; | |||||
// 设备型号,如:MLMF2LL/A | |||||
// params['device_number'] = androidInfo?.id; | |||||
// 设备Id | |||||
params['device_id'] = androidInfo?.androidId; | |||||
} | |||||
// 应用版本号 | |||||
params["app_version"] = packageInfo.version; | |||||
// 分辨率 | |||||
params["solution"] = | |||||
"${window.physicalSize.width.floor()}*${window.physicalSize.height.floor()}"; | |||||
// 站长ID | |||||
String masterId = setting['master_id']; | |||||
if (null != null && | |||||
masterId != '' && | |||||
(!params.containsKey('master_id') || params['master_id'] == '')) { | |||||
params['master_id'] = masterId; | |||||
} | |||||
// secret_key | |||||
params['secret_key'] = setting['secret_key']; | |||||
// token | |||||
String token = setting['token']; | |||||
if (token != null && | |||||
token != '' && | |||||
(!params.containsKey('token') || params['token'] == '')) { | |||||
params['token'] = token; | |||||
} | |||||
// 当前时间戳:秒 | |||||
params["time"] = TimeUtil.getNowTime(); | |||||
// 过滤空字段,过滤首尾空格 | |||||
Map<String, dynamic> filters = Map<String, dynamic>(); | |||||
params.forEach((key, value) { | |||||
if (key != '' && value != '') { | |||||
filters[key] = (value is String) ? value.trim() : value; | |||||
} | } | ||||
}).catchError((error) { | |||||
onError(error.toString()); | |||||
}); | }); | ||||
params = filters; | |||||
List<String> list = List<String>(); | |||||
params.forEach((key, value) { | |||||
list.add(key.toString() + value.toString()); | |||||
}); | |||||
params["sign"] = signWithArray(list); | |||||
return params; | |||||
} | |||||
/// 获取请求缓存成功的数据 | |||||
static Future<void> _onCallBackCacheData(OnCache onCache, String cacheKey) async { | |||||
// 读取缓存 | |||||
Map<String, dynamic> cacheMap = await SharedPreferencesUtil.getNetCacheResult(cacheKey); | |||||
if (!EmptyUtil.isEmpty(cacheMap) && | |||||
cacheMap.containsKey(GlobalConfig.RESPONSE_SUCCESS_CODE) && | |||||
(cacheMap[GlobalConfig.RESPONSE_SUCCESS_CODE] == | |||||
GlobalConfig.RESPONSE_SUCCESS_CODE || | |||||
cacheMap[GlobalConfig.RESPONSE_SUCCESS_CODE] == | |||||
'${GlobalConfig.RESPONSE_SUCCESS_CODE}') && | |||||
null != cacheMap[GlobalConfig.HTTP_RESPONSE_KEY_DATA]) { | |||||
onCache(cacheMap['data']); | |||||
return; | |||||
} | |||||
return; | |||||
} | |||||
/// 缓存请求成功的数据 | |||||
static void _setCallBackCacheData(String cacheKey, String value) async { | |||||
SharedPreferencesUtil.setNetCacheResult(cacheKey, value); | |||||
} | |||||
/// 根据请求参数,获取缓存的Key | |||||
static String _getRequestParamsCachedKey( | |||||
Map<String, dynamic> map, String path) { | |||||
if (EmptyUtil.isEmpty(map)) { | |||||
return EncodeUtil.generateMd5(path); | |||||
} | |||||
return EncodeUtil.generateMd5(path + map.toString()); | |||||
} | |||||
/// 签名 | |||||
static String signWithArray(List<String> params) { | |||||
// 字母升序 | |||||
params.sort(); | |||||
String result = ""; | |||||
// result += secret_key; | |||||
params.forEach((param) { | |||||
result += param; | |||||
}); | |||||
// result += secret_key; | |||||
return EncodeUtil.generateMd5(result); | |||||
} | |||||
/* | |||||
* error统一处理 | |||||
*/ | |||||
static void _formatError(DioError e) { | |||||
if (e.type == DioErrorType.CONNECT_TIMEOUT) { | |||||
Logger.error('连接超时: ${e.toString()}'); | |||||
} else if (e.type == DioErrorType.SEND_TIMEOUT) { | |||||
Logger.error('请求超时: ${e.toString()}'); | |||||
} else if (e.type == DioErrorType.RECEIVE_TIMEOUT) { | |||||
Logger.error('响应超时: ${e.toString()}'); | |||||
} else if (e.type == DioErrorType.RESPONSE) { | |||||
Logger.error('出现异常: ${e.toString()}'); | |||||
} else if (e.type == DioErrorType.CANCEL) { | |||||
Logger.error('请求取消: ${e.toString()}'); | |||||
} else { | |||||
Logger.error('未知错误: ${e.toString()}'); | |||||
} | |||||
} | } | ||||
} | } | ||||
/** | /** | ||||
* @description: 网络请求拦截器 | * @description: 网络请求拦截器 | ||||
* @param {type} | |||||
* @return: | |||||
* @param {type} | |||||
* @return: | |||||
*/ | */ | ||||
class _NetInterceptors extends InterceptorsWrapper { | class _NetInterceptors extends InterceptorsWrapper { | ||||
@override | @override | ||||
Future onRequest(RequestOptions options) { | Future onRequest(RequestOptions options) { | ||||
print("${options?.method} => ${options?.path}"); | |||||
Logger.net(options?.path, data: options.data.toString()); | |||||
// TODO 加密? | |||||
return super.onRequest(options); | return super.onRequest(options); | ||||
} | } | ||||
@override | @override | ||||
Future onResponse(Response response) { | Future onResponse(Response response) { | ||||
print("response[${response?.statusCode}] => ${response?.data}"); | |||||
Logger.endNet(response.realUri.toString(), data: response?.data?.toString() ?? ''); | |||||
// TODO 解密? | |||||
return super.onResponse(response); | return super.onResponse(response); | ||||
// String data = response?.data; | |||||
// Map<String, dynamic> map = json.decode(data); | |||||
// var success = map['success']; | |||||
// if (success != null && '$success' == '1') { | |||||
// return super.onResponse(response); | |||||
// } else { | |||||
// response.data = response?.data['msg'] ?? '未知错误'; | |||||
// return NetUtil._dio.reject(Response(data: response)); | |||||
// } | |||||
} | } | ||||
@override | @override | ||||
Future onError(DioError err) { | Future onError(DioError err) { | ||||
print("ERROR[${err?.response?.statusCode}] => PATH: ${err?.request?.path}"); | |||||
// Logger.error(err); | |||||
return super.onError(err); | return super.onError(err); | ||||
} | } | ||||
} | } |
@@ -0,0 +1,30 @@ | |||||
import 'dart:convert'; | |||||
import 'package:shared_preferences/shared_preferences.dart'; | |||||
import 'package:zhiying_comm/util/empty_util.dart'; | |||||
class SharedPreferencesUtil { | |||||
static Future<Map<String, dynamic>> getNetCacheResult(String key) async { | |||||
if (!EmptyUtil.isEmpty(key)) { | |||||
SharedPreferences prefs = await SharedPreferences.getInstance(); | |||||
String cacheResult = prefs.getString(key); | |||||
// TODO need解密? | |||||
if (!EmptyUtil.isEmpty(cacheResult)) { | |||||
Map<String, dynamic> map = json.decode(cacheResult); | |||||
if(!EmptyUtil.isEmpty(map)) { | |||||
return map; | |||||
} | |||||
} | |||||
} | |||||
return null; | |||||
} | |||||
static Future<void> setNetCacheResult(String key, String value) async { | |||||
if (!EmptyUtil.isEmpty(key) && !EmptyUtil.isEmpty(value)) { | |||||
SharedPreferences prefs = await SharedPreferences.getInstance(); | |||||
// TODO need加密? | |||||
prefs.setString(key, value); | |||||
} | |||||
return; | |||||
} | |||||
} |
@@ -0,0 +1,10 @@ | |||||
class TimeUtil{ | |||||
/// 获取时间戳,单位:秒 | |||||
static String getNowTime() { | |||||
int timestamp = (DateTime.now().millisecondsSinceEpoch / 1000).floor(); | |||||
return "$timestamp"; | |||||
} | |||||
} |
@@ -13,6 +13,7 @@ export 'util/net_util.dart'; | |||||
export 'util/page_factory.dart'; | export 'util/page_factory.dart'; | ||||
export 'util/widget_factory.dart'; | export 'util/widget_factory.dart'; | ||||
export 'util/router_util.dart'; | export 'util/router_util.dart'; | ||||
export 'util/log/let_log.dart'; | |||||
export 'util/enum_util.dart'; | export 'util/enum_util.dart'; | ||||
export 'util/encode_util.dart'; | export 'util/encode_util.dart'; | ||||
@@ -17,6 +17,11 @@ dependencies: | |||||
flutter_native_image: ^0.0.5 | flutter_native_image: ^0.0.5 | ||||
fluttertoast: ^7.0.4 | fluttertoast: ^7.0.4 | ||||
cached_network_image: ^2.2.0+1 | cached_network_image: ^2.2.0+1 | ||||
# 屏幕适配 | |||||
flutter_screenutil: ^1.1.0 | |||||
# 缓存 | |||||
shared_preferences: ^0.5.10 | |||||
dev_dependencies: | dev_dependencies: | ||||
flutter_test: | flutter_test: | ||||