@@ -1,6 +1,7 @@ | |||
package cn.zhios.zhiying_comm; | |||
import androidx.annotation.NonNull; | |||
import io.flutter.embedding.engine.plugins.FlutterPlugin; | |||
import io.flutter.plugin.common.MethodCall; | |||
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.PluginRegistry.Registrar; | |||
/** ZhiyingCommPlugin */ | |||
/** | |||
* ZhiyingCommPlugin | |||
*/ | |||
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"); | |||
// 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"); | |||
// 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.put("success", "1"); | |||
result.success(map); | |||
return; | |||
} | |||
/* 跳转原生页面(非公共跳转) */ | |||
@@ -77,6 +78,7 @@ public class ZhiyingFlutterCommNative implements MethodCallHandler { | |||
Map map = new HashMap<String, Object>(); | |||
map.put("success", "1"); | |||
result.success(map); | |||
return; | |||
} | |||
/* 获取设置 */ | |||
@@ -86,6 +88,7 @@ public class ZhiyingFlutterCommNative implements MethodCallHandler { | |||
map = nat.getSetting(); | |||
} | |||
result.success(map); | |||
return; | |||
} | |||
/* 调用原生方法 */ | |||
@@ -100,10 +103,14 @@ public class ZhiyingFlutterCommNative implements MethodCallHandler { | |||
}); | |||
} else { | |||
result.success(ZhiyingFlutterCommNativeResult.notImp().toMap()); | |||
return; | |||
} | |||
} else { | |||
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 'dart:convert'; | |||
import 'dart:io'; | |||
import 'dart:typed_data'; | |||
import 'package:flutter_native_image/flutter_native_image.dart'; | |||
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 { | |||
var res; | |||
try { | |||
res = | |||
await NativeUtil.instance._methodChannel.invokeMethod(method, params); | |||
res = await NativeUtil.instance._methodChannel.invokeMethod(method, params); | |||
} catch (e) { | |||
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 { | |||
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( | |||
String method, Map<String, dynamic> params) { | |||
Map<String, dynamic> request = {"method": method, "params": params}; | |||
@@ -1,25 +1,29 @@ | |||
import 'package:dio/dio.dart'; | |||
import 'package:dio/adapter.dart'; | |||
import 'package:zhiying_comm/util/empty_util.dart'; | |||
import 'dart:io'; | |||
import 'dart:ui'; | |||
import 'dart:convert'; | |||
import 'package:convert/convert.dart'; | |||
import 'package:crypto/crypto.dart'; | |||
import 'package:zhiying_comm/zhiying_comm.dart'; | |||
import 'package:package_info/package_info.dart'; | |||
import 'package:device_info/device_info.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 OnError = dynamic Function(String e); | |||
typedef OnCache = dynamic Function(dynamic data); | |||
class NetUtil { | |||
static final String APPKEY = "123"; | |||
Dio _dio; | |||
static String _apiVersion; | |||
NetUtil._() {} | |||
NetUtil._(); | |||
static NetUtil _instance; | |||
static NetUtil getInstance() { | |||
if (_instance == null) { | |||
_instance = NetUtil._(); | |||
@@ -31,14 +35,14 @@ class NetUtil { | |||
if (_dio == null) { | |||
var setting = await NativeUtil.getSetting(); | |||
String domain = setting['domain']; //'http://www.hairuyi.com/'; | |||
_config(domain, apiVersion: _apiVersion); | |||
_config(domain); | |||
} | |||
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( | |||
method: "post", | |||
baseUrl: baseUrl, | |||
@@ -49,13 +53,13 @@ class NetUtil { | |||
_dio.interceptors.add(_NetInterceptors()); | |||
_dio.interceptors.add(LogInterceptor()); | |||
_apiVersion = apiVersion; | |||
const bool inProduction = const bool.fromEnvironment("dart.vm.product"); | |||
if (proxyUrl != null && proxyUrl != '' && !inProduction) { | |||
_setProxy(proxyUrl); | |||
} | |||
} | |||
/// 设置代理 | |||
void _setProxy(String proxyUrl) { | |||
(_dio.httpClientAdapter as DefaultHttpClientAdapter).onHttpClientCreate = | |||
(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, | |||
{Map<String, dynamic> params}) async { | |||
if (params == null) { | |||
params = {}; | |||
} | |||
// Map<String, dynamic> sign = await signParams(params); | |||
Map<String, dynamic> sign = 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); | |||
// Logger.error(e); | |||
} | |||
if (response == null) { | |||
return {}; | |||
return null; | |||
} | |||
if (response.statusCode != 200) { | |||
@@ -169,159 +98,258 @@ class NetUtil { | |||
Fluttertoast.showToast(msg: response.statusMessage); | |||
} | |||
// log.e(response.statusMessage); | |||
return {}; | |||
return null; | |||
} | |||
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, | |||
{String method = 'POST', | |||
Map<String, dynamic> params, | |||
Map<String, File> multiFiles, | |||
OnSuccess onSuccess, | |||
OnError onError}) async { | |||
OnError onError, | |||
OnCache onCache}) async { | |||
if (params == null) { | |||
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; | |||
// 文件 | |||
if (multiFiles != null && multiFiles.length > 0) { | |||
for (String key in multiFiles.keys) { | |||
var file = await MultipartFile.fromFile(multiFiles[key].path); | |||
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) { | |||
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: 网络请求拦截器 | |||
* @param {type} | |||
* @return: | |||
* @param {type} | |||
* @return: | |||
*/ | |||
class _NetInterceptors extends InterceptorsWrapper { | |||
@override | |||
Future onRequest(RequestOptions options) { | |||
print("${options?.method} => ${options?.path}"); | |||
Logger.net(options?.path, data: options.data.toString()); | |||
// TODO 加密? | |||
return super.onRequest(options); | |||
} | |||
@override | |||
Future onResponse(Response response) { | |||
print("response[${response?.statusCode}] => ${response?.data}"); | |||
Logger.endNet(response.realUri.toString(), data: response?.data?.toString() ?? ''); | |||
// TODO 解密? | |||
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 | |||
Future onError(DioError err) { | |||
print("ERROR[${err?.response?.statusCode}] => PATH: ${err?.request?.path}"); | |||
// Logger.error(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/widget_factory.dart'; | |||
export 'util/router_util.dart'; | |||
export 'util/log/let_log.dart'; | |||
export 'util/enum_util.dart'; | |||
export 'util/encode_util.dart'; | |||
@@ -17,6 +17,11 @@ dependencies: | |||
flutter_native_image: ^0.0.5 | |||
fluttertoast: ^7.0.4 | |||
cached_network_image: ^2.2.0+1 | |||
# 屏幕适配 | |||
flutter_screenutil: ^1.1.0 | |||
# 缓存 | |||
shared_preferences: ^0.5.10 | |||
dev_dependencies: | |||
flutter_test: | |||