Sfoglia il codice sorgente

1、Android混合代码返回多次的修复。

2、网络工具类
3、日志类
tags/0.0.1
PH2 4 anni fa
parent
commit
89d181c706
14 ha cambiato i file con 1269 aggiunte e 224 eliminazioni
  1. +23
    -20
      android/src/main/java/cn/zhios/zhiying_comm/ZhiyingCommPlugin.java
  2. +7
    -0
      android/src/main/java/cn/zhios/zhiying_comm/ZhiyingFlutterCommNative.java
  3. +19
    -0
      lib/util/empty_util.dart
  4. +0
    -1
      lib/util/encode_util.dart
  5. +8
    -0
      lib/util/global_config.dart
  6. +362
    -0
      lib/util/log/let_log.dart
  7. +246
    -0
      lib/util/log/log_widget.dart
  8. +321
    -0
      lib/util/log/net_widget.dart
  9. +12
    -6
      lib/util/native_util.dart
  10. +225
    -197
      lib/util/net_util.dart
  11. +30
    -0
      lib/util/shared_prefe_util.dart
  12. +10
    -0
      lib/util/time_util.dart
  13. +1
    -0
      lib/zhiying_comm.dart
  14. +5
    -0
      pubspec.yaml

+ 23
- 20
android/src/main/java/cn/zhios/zhiying_comm/ZhiyingCommPlugin.java Vedi File

@@ -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) {
}
}

+ 7
- 0
android/src/main/java/cn/zhios/zhiying_comm/ZhiyingFlutterCommNative.java Vedi File

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

}
}

+ 19
- 0
lib/util/empty_util.dart Vedi File

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

+ 0
- 1
lib/util/encode_util.dart Vedi File

@@ -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 {


+ 8
- 0
lib/util/global_config.dart Vedi File

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

+ 362
- 0
lib/util/log/let_log.dart Vedi File

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

+ 246
- 0
lib/util/log/log_widget.dart Vedi File

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

+ 321
- 0
lib/util/log/net_widget.dart Vedi File

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

+ 12
- 6
lib/util/native_util.dart Vedi File

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


+ 225
- 197
lib/util/net_util.dart Vedi File

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

+ 30
- 0
lib/util/shared_prefe_util.dart Vedi File

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

+ 10
- 0
lib/util/time_util.dart Vedi File

@@ -0,0 +1,10 @@

class TimeUtil{

/// 获取时间戳,单位:秒
static String getNowTime() {
int timestamp = (DateTime.now().millisecondsSinceEpoch / 1000).floor();
return "$timestamp";
}

}

+ 1
- 0
lib/zhiying_comm.dart Vedi File

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


+ 5
- 0
pubspec.yaml Vedi File

@@ -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:


Caricamento…
Annulla
Salva