├── .gitignore ├── .metadata ├── CHANGELOG.md ├── LICENSE ├── README-jp.md ├── README-zh.md ├── README.md ├── art ├── 1.jpg ├── 2.jpg ├── 3.jpg ├── 4.jpg └── 5.jpg ├── lib ├── flutter_interceptor.dart └── src │ ├── interceptor │ └── ui_net_interceptor.dart │ ├── interceptor_localizations.dart │ ├── model │ └── http_transaction.dart │ ├── tools │ ├── copy_clipboard.dart │ ├── interceptor_manager.dart │ ├── json_utils.dart │ └── time_utils.dart │ └── ui │ ├── interceptor_detail.dart │ ├── interceptor_draggable.dart │ └── simple_interceptor.dart ├── pubspec.lock ├── pubspec.yaml └── test └── flutter_interceptor_test.dart /.gitignore: -------------------------------------------------------------------------------- 1 | # Miscellaneous 2 | *.class 3 | *.log 4 | *.pyc 5 | *.swp 6 | .DS_Store 7 | .atom/ 8 | .buildlog/ 9 | .history 10 | .svn/ 11 | 12 | # IntelliJ related 13 | *.iml 14 | *.ipr 15 | *.iws 16 | .idea/ 17 | 18 | # The .vscode folder contains launch configuration and tasks you configure in 19 | # VS Code which you may wish to be included in version control, so this line 20 | # is commented out by default. 21 | #.vscode/ 22 | 23 | # Flutter/Dart/Pub related 24 | **/doc/api/ 25 | .dart_tool/ 26 | .flutter-plugins 27 | .flutter-plugins-dependencies 28 | .packages 29 | .pub-cache/ 30 | .pub/ 31 | build/ 32 | 33 | # Android related 34 | **/android/**/gradle-wrapper.jar 35 | **/android/.gradle 36 | **/android/captures/ 37 | **/android/gradlew 38 | **/android/gradlew.bat 39 | **/android/local.properties 40 | **/android/**/GeneratedPluginRegistrant.java 41 | 42 | # iOS/XCode related 43 | **/ios/**/*.mode1v3 44 | **/ios/**/*.mode2v3 45 | **/ios/**/*.moved-aside 46 | **/ios/**/*.pbxuser 47 | **/ios/**/*.perspectivev3 48 | **/ios/**/*sync/ 49 | **/ios/**/.sconsign.dblite 50 | **/ios/**/.tags* 51 | **/ios/**/.vagrant/ 52 | **/ios/**/DerivedData/ 53 | **/ios/**/Icon? 54 | **/ios/**/Pods/ 55 | **/ios/**/.symlinks/ 56 | **/ios/**/profile 57 | **/ios/**/xcuserdata 58 | **/ios/.generated/ 59 | **/ios/Flutter/App.framework 60 | **/ios/Flutter/Flutter.framework 61 | **/ios/Flutter/Flutter.podspec 62 | **/ios/Flutter/Generated.xcconfig 63 | **/ios/Flutter/ephemeral 64 | **/ios/Flutter/app.flx 65 | **/ios/Flutter/app.zip 66 | **/ios/Flutter/flutter_assets/ 67 | **/ios/Flutter/flutter_export_environment.sh 68 | **/ios/ServiceDefinitions.json 69 | **/ios/Runner/GeneratedPluginRegistrant.* 70 | 71 | # Exceptions to above rules. 72 | !**/ios/**/default.mode1v3 73 | !**/ios/**/default.mode2v3 74 | !**/ios/**/default.pbxuser 75 | !**/ios/**/default.perspectivev3 76 | -------------------------------------------------------------------------------- /.metadata: -------------------------------------------------------------------------------- 1 | # This file tracks properties of this Flutter project. 2 | # Used by Flutter tool to assess capabilities and perform upgrades etc. 3 | # 4 | # This file should be version controlled and should not be manually edited. 5 | 6 | version: 7 | revision: f4abaa0735eba4dfd8f33f73363911d63931fe03 8 | channel: stable 9 | 10 | project_type: package 11 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## 0.0.1 2 | 3 | - First version of the new flutter_interceptor 4 | 5 | ## 0.0.2 6 | 7 | - add language japanese,english 8 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 zhonghua 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README-jp.md: -------------------------------------------------------------------------------- 1 | ## flutter_interceptor 2 | ## flutter dio 迎撃器 3 | 4 | ### 始める 5 | 6 | ```dart 7 | dependencies: 8 | flutter_interceptor: ^0.0.2 9 | ``` 10 | 11 | dio 迎撃器を追加 12 | 13 | ```dart 14 | _dio.interceptors.add(UiNetInterceptor()) 15 | ``` 16 | 17 | ページに浮動フォームを挿入します。 18 | 19 | ```dart 20 | Overlay.of(context)?.insert(InterceptorDraggable()); 21 | ``` 22 | 23 | ローカライズ 24 | 25 | ```dart 26 | 27 | InterceptorLocalizations.delegate 28 | ``` 29 | 30 | 機能紹介: 31 | 32 | 1、可視化を要求する 33 | 34 | 2、要求内容をコピーできます。 35 | 36 | 統合後の効果は図のようです。 37 | 38 | ![](art/1.jpg) 39 | 40 | ![](art/2.jpg) 41 | 42 | ![](art/3.jpg) 43 | 44 | ![](art/4.jpg) 45 | 46 | ![](art/5.jpg) 47 | 48 | -------------------------------------------------------------------------------- /README-zh.md: -------------------------------------------------------------------------------- 1 | ## flutter_interceptor 2 | ## flutter dio 拦截器 3 | 4 | ### 开始集成 5 | 6 | ```dart 7 | dependencies: 8 | flutter_interceptor: ^0.0.2 9 | ``` 10 | 11 | dio添加拦截器 12 | 13 | ```dart 14 | _dio.interceptors.add(UiNetInterceptor()) 15 | ``` 16 | 17 | 页面插入浮动窗体 18 | 19 | ```dart 20 | Overlay.of(context)?.insert(InterceptorDraggable()); 21 | ``` 22 | 23 | 本地化 24 | 25 | ```dart 26 | 27 | InterceptorLocalizations.delegate 28 | ``` 29 | 30 | 功能介绍: 31 | 1、请求可视化 32 | 2、可以复制请求内容 33 | 34 | 集成后的效果如图 35 | 36 | ![](art/1.jpg) 37 | 38 | ![](art/2.jpg) 39 | 40 | ![](art/3.jpg) 41 | 42 | ![](art/4.jpg) 43 | 44 | ![](art/5.jpg) 45 | 46 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # flutter_interceptor 2 | 3 | flutter dio interceptor 4 | 5 | ## Getting Started 6 | 7 | ```dart 8 | dependencies: 9 | flutter_interceptor: ^0.0.2 10 | ``` 11 | 12 | dio add interceptor 13 | 14 | ```dart 15 | _dio.interceptors.add(UiNetInterceptor()) 16 | ``` 17 | 18 | Page insert floating form 19 | 20 | ```dart 21 | Overlay.of(context)?.insert(InterceptorDraggable()); 22 | ``` 23 | 24 | local 25 | 26 | ```dart 27 | 28 | InterceptorLocalizations.delegate 29 | ``` 30 | 31 | Function introduction: 32 | 33 | 1. Request visualization 34 | 35 | 2. You can copy the requested content 36 | 37 | The effect after integration is shown in the figure 38 | 39 | ![](art/1.jpg) 40 | 41 | ![](art/2.jpg) 42 | 43 | ![](art/3.jpg) 44 | 45 | ![](art/4.jpg) 46 | 47 | ![](art/5.jpg) 48 | 49 | -------------------------------------------------------------------------------- /art/1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smartbackme/flutter_interceptor/f6089e1d418ed232a9da84207ef9631c8e42bb43/art/1.jpg -------------------------------------------------------------------------------- /art/2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smartbackme/flutter_interceptor/f6089e1d418ed232a9da84207ef9631c8e42bb43/art/2.jpg -------------------------------------------------------------------------------- /art/3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smartbackme/flutter_interceptor/f6089e1d418ed232a9da84207ef9631c8e42bb43/art/3.jpg -------------------------------------------------------------------------------- /art/4.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smartbackme/flutter_interceptor/f6089e1d418ed232a9da84207ef9631c8e42bb43/art/4.jpg -------------------------------------------------------------------------------- /art/5.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smartbackme/flutter_interceptor/f6089e1d418ed232a9da84207ef9631c8e42bb43/art/5.jpg -------------------------------------------------------------------------------- /lib/flutter_interceptor.dart: -------------------------------------------------------------------------------- 1 | library flutter_interceptor; 2 | 3 | export 'package:flutter_interceptor/src/ui/interceptor_draggable.dart'; 4 | export 'package:flutter_interceptor/src/interceptor/ui_net_interceptor.dart'; 5 | export 'package:flutter_interceptor/src/interceptor_localizations.dart'; -------------------------------------------------------------------------------- /lib/src/interceptor/ui_net_interceptor.dart: -------------------------------------------------------------------------------- 1 | import 'dart:convert'; 2 | 3 | import 'package:dio/dio.dart'; 4 | import 'package:flutter_interceptor/src/model/http_transaction.dart'; 5 | import 'dart:convert' as convert; 6 | 7 | import 'package:flutter_interceptor/src/tools/interceptor_manager.dart'; 8 | 9 | class UiNetInterceptor extends Interceptor { 10 | 11 | 12 | @override 13 | void onRequest(RequestOptions options, RequestInterceptorHandler handler) { 14 | InterceptorManager.instance.onRequestTime(options.hashCode, DateTime.now()); 15 | super.onRequest(options, handler); 16 | } 17 | 18 | @override 19 | void onResponse(Response response, ResponseInterceptorHandler handler) async { 20 | InterceptorManager.instance.onResponseTime(response.requestOptions.hashCode, DateTime.now()); 21 | InterceptorManager.instance.onSave(setData(response, response.requestOptions)); 22 | handler.next(response); 23 | } 24 | 25 | @override 26 | void onError(DioError err, ErrorInterceptorHandler handler) async { 27 | 28 | if (err.response != null) { 29 | InterceptorManager.instance.onResponseTime(err.response!.requestOptions.hashCode, DateTime.now()); 30 | InterceptorManager.instance.onSave(setData(err.response!, err.response!.requestOptions)..error = true); 31 | }else{ 32 | InterceptorManager.instance.onSave(HttpTransaction() 33 | ..id = err.requestOptions.hashCode 34 | ..requestTime = InterceptorManager.instance.requestTime[err.requestOptions.hashCode] 35 | ..responseTime = InterceptorManager.instance.responseTime[err.requestOptions.hashCode] 36 | ..uri = err.requestOptions.uri 37 | ..error = true); 38 | } 39 | handler.next(err); 40 | } 41 | 42 | HttpTransaction setData(Response response,RequestOptions options){ 43 | return HttpTransaction() 44 | ..id = options.hashCode 45 | ..uri = options.uri 46 | ..statusCode = response.statusCode 47 | ..method = options.method 48 | ..responseType = options.responseType.toString() 49 | ..followRedirects = options.followRedirects 50 | ..connectTimeout = options.connectTimeout 51 | ..sendTimeout = options.sendTimeout 52 | ..receiveTimeout = options.receiveTimeout 53 | ..receiveDataWhenStatusError = options.receiveDataWhenStatusError 54 | // ..extra = convert.jsonEncode(options.extra) 55 | ..extra = options.extra 56 | ..requestBody = stringifyMessage(options.data) 57 | // ..requestHeaders = convert.jsonEncode(options.headers) 58 | ..requestHeaders = options.headers 59 | ..redirect = response.isRedirect 60 | ..realUri = response.realUri 61 | // ..responseHeaders = convert.jsonEncode(response.headers) 62 | ..responseHeaders = response.headers.map 63 | ..responseBody = response.toString() 64 | ..requestTime = InterceptorManager.instance.requestTime[options.hashCode] 65 | ..responseTime = InterceptorManager.instance.responseTime[options.hashCode] 66 | ..duration = InterceptorManager.instance.responseTime[options.hashCode]!.millisecondsSinceEpoch-InterceptorManager.instance.requestTime[options.hashCode]!.millisecondsSinceEpoch; 67 | } 68 | 69 | String stringifyMessage(dynamic message) { 70 | final finalMessage = message is Function ? message() : message; 71 | if (finalMessage is Map || finalMessage is Iterable) { 72 | return convert.jsonEncode(finalMessage); 73 | } else { 74 | return finalMessage.toString(); 75 | } 76 | } 77 | } -------------------------------------------------------------------------------- /lib/src/interceptor_localizations.dart: -------------------------------------------------------------------------------- 1 | 2 | 3 | import 'package:flutter/foundation.dart'; 4 | import 'package:flutter/material.dart'; 5 | 6 | class InterceptorLocalizations { 7 | 8 | final Locale locale; 9 | 10 | InterceptorLocalizations(this.locale); 11 | 12 | Map values = { 13 | 'en': EnInterceptorString(), 14 | 'zh': ChInterceptorString(), 15 | 'ja': JpInterceptorString(), 16 | }; 17 | 18 | InterceptorString? get currentLocalization { 19 | if (values.containsKey(locale.languageCode)) { 20 | return values[locale.languageCode]; 21 | } 22 | return values["en"]; 23 | } 24 | 25 | static const InterceptorLocalizationsDelegate delegate = InterceptorLocalizationsDelegate(); 26 | 27 | //为了使用方便,我们定义一个静态方法 28 | static InterceptorLocalizations? of(BuildContext context) { 29 | return Localizations.of(context, InterceptorLocalizations); 30 | } 31 | 32 | static InterceptorString getInterceptorString(BuildContext context) { 33 | return Localizations.of(context, InterceptorLocalizations)?.currentLocalization??EnInterceptorString(); 34 | } 35 | 36 | 37 | } 38 | 39 | 40 | class InterceptorLocalizationsDelegate extends LocalizationsDelegate { 41 | 42 | const InterceptorLocalizationsDelegate(); 43 | ///是否支持某个Local 44 | @override 45 | bool isSupported(Locale locale) { 46 | return [ 47 | 'en', 48 | 'zh', 49 | 'ja', 50 | ].contains(locale.languageCode); 51 | } 52 | @override 53 | Future load(Locale locale) { 54 | return SynchronousFuture( 55 | InterceptorLocalizations(locale)); 56 | } 57 | 58 | 59 | @override 60 | bool shouldReload(covariant LocalizationsDelegate old) => false; 61 | 62 | } 63 | 64 | 65 | 66 | abstract class InterceptorString { 67 | 68 | 69 | 70 | String? simpleInterception; 71 | 72 | String? summary; 73 | 74 | String? request; 75 | String? response; 76 | String? copy; 77 | String? method; 78 | String? requestTime; 79 | String? responseTime; 80 | String? duration; 81 | 82 | } 83 | 84 | /// Simplified Chinese 85 | class ChInterceptorString implements InterceptorString { 86 | 87 | @override 88 | String? copy = "复制请求"; 89 | 90 | @override 91 | String? duration = "持续时间"; 92 | 93 | @override 94 | String? method = "方法"; 95 | 96 | @override 97 | String? request = "请求"; 98 | 99 | @override 100 | String? requestTime = "请求时间"; 101 | 102 | @override 103 | String? response = "响应"; 104 | 105 | @override 106 | String? responseTime = "响应时间"; 107 | 108 | @override 109 | String? simpleInterception = "简单拦截"; 110 | 111 | @override 112 | String? summary = "概述"; 113 | 114 | 115 | } 116 | 117 | // /// Traditional Chinese 118 | // class TChInterceptorString implements InterceptorString { 119 | // @override 120 | // String? libDebugConsole = "調試模式:在開發階段,為了調試及測試方便,提供測試階段的環境切換,此頁面發佈後的線上環境不顯示。"; 121 | // 122 | // @override 123 | // String? libDebugInterception = "控制台"; 124 | // 125 | // @override 126 | // String? libDebugModeIntroduce = "網絡攔截"; 127 | // 128 | // } 129 | 130 | /// English 131 | class EnInterceptorString implements InterceptorString { 132 | 133 | @override 134 | String? copy = "copy"; 135 | 136 | @override 137 | String? duration = "duration"; 138 | 139 | @override 140 | String? method = "method"; 141 | 142 | @override 143 | String? request = "request"; 144 | 145 | @override 146 | String? requestTime = "Request time"; 147 | 148 | @override 149 | String? response = "response"; 150 | 151 | @override 152 | String? responseTime = "Response time"; 153 | 154 | @override 155 | String? simpleInterception = "Simple interception"; 156 | 157 | @override 158 | String? summary = "summary"; 159 | 160 | 161 | } 162 | 163 | /// Japanese 164 | class JpInterceptorString implements InterceptorString { 165 | 166 | @override 167 | String? copy = "要求のコピー"; 168 | 169 | @override 170 | String? duration = "持続時間"; 171 | 172 | @override 173 | String? method = "方法"; 174 | 175 | @override 176 | String? request = "リクエスト"; 177 | 178 | @override 179 | String? requestTime = "リクエスト時間"; 180 | 181 | @override 182 | String? response = "応答"; 183 | 184 | @override 185 | String? responseTime = "レスポンス時間"; 186 | 187 | @override 188 | String? simpleInterception = "単純ブロック"; 189 | 190 | @override 191 | String? summary = "概要"; 192 | 193 | } -------------------------------------------------------------------------------- /lib/src/model/http_transaction.dart: -------------------------------------------------------------------------------- 1 | import 'dart:collection'; 2 | import 'dart:core'; 3 | 4 | import 'dart:convert' as convert; 5 | 6 | import 'package:flutter_interceptor/src/tools/json_utils.dart'; 7 | 8 | 9 | enum Status { 10 | Requested, 11 | Complete, 12 | Failed 13 | } 14 | 15 | class HttpTransaction extends LinkedListEntry{ 16 | String? host; 17 | String? path ; 18 | String? scheme; 19 | Uri? muri; 20 | int? id; 21 | DateTime? requestTime; 22 | DateTime? responseTime; 23 | int? duration; 24 | 25 | 26 | int? statusCode; 27 | bool? redirect; 28 | String? responseBody; 29 | Map? responseHeaders; 30 | Uri? realUri; 31 | 32 | bool error = false; 33 | 34 | String? method; 35 | bool? followRedirects; 36 | 37 | String? responseType; 38 | int? connectTimeout; 39 | int? sendTimeout; 40 | int? receiveTimeout; 41 | bool? receiveDataWhenStatusError; 42 | String? requestBody; 43 | Map? requestHeaders; 44 | Map? extra; 45 | 46 | Status get status{ 47 | if(error){ 48 | return Status.Failed; 49 | }else if(statusCode==null){ 50 | return Status.Requested; 51 | }else { 52 | return Status.Complete; 53 | } 54 | } 55 | 56 | set uri(Uri uri){ 57 | muri = uri; 58 | host = uri.host; 59 | path = uri.path+ (uri.hasQuery?"?${uri.query}":""); 60 | scheme = uri.scheme; 61 | } 62 | 63 | @override 64 | String toString() { 65 | return 'url:${muri.toString()}\nmethod:$method\nrequestTime:$requestTime\nresponseTime:$responseTime\n' 66 | 'duration:${duration ?? 0}ms\n' 67 | 'statusCode:$statusCode' 68 | '\nrequestHeaders:${convert.jsonEncode(requestHeaders)}\n$requestBody' 69 | '\nresponseHeaders:${convert.jsonEncode(responseHeaders)}\n${json(responseBody)}'; 70 | } 71 | 72 | // Map? getRequestHeaders() { 73 | // if(requestHeaders!=null){ 74 | // Map header = convert.jsonDecode(requestHeaders!); 75 | // return header; 76 | // } 77 | // return null; 78 | // } 79 | // 80 | // Map? getResponseHeaders() { 81 | // if(responseHeaders!=null){ 82 | // Map header = convert.jsonDecode(responseHeaders!); 83 | // return header; 84 | // } 85 | // return null; 86 | // } 87 | // 88 | // Map? getExtra() { 89 | // if(extra!=null){ 90 | // Map header = convert.jsonDecode(extra!); 91 | // return header; 92 | // } 93 | // return null; 94 | // } 95 | 96 | } -------------------------------------------------------------------------------- /lib/src/tools/copy_clipboard.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter/services.dart'; 3 | 4 | ///复制到粘贴板 5 | copyClipboard(BuildContext context, String? value) { 6 | var snackBar = SnackBar(content: Text('copy $value to clipboard')); 7 | ScaffoldMessenger.of(context).showSnackBar(snackBar); 8 | Clipboard.setData(ClipboardData(text: value)); 9 | } 10 | -------------------------------------------------------------------------------- /lib/src/tools/interceptor_manager.dart: -------------------------------------------------------------------------------- 1 | 2 | import 'dart:collection'; 3 | 4 | import 'package:flutter_interceptor/src/model/http_transaction.dart'; 5 | 6 | 7 | class InterceptorManager{ 8 | ///请求日志存储 9 | late LinkedList transactionList; 10 | 11 | late LinkedHashMap requestTime; 12 | late LinkedHashMap responseTime; 13 | 14 | ///存储请求最大数 15 | int maxCount = 50; 16 | 17 | InterceptorManager._(){ 18 | transactionList = LinkedList(); 19 | requestTime = LinkedHashMap(); 20 | responseTime = LinkedHashMap(); 21 | } 22 | 23 | ///The singleton for [InterceptorManager] 24 | static final InterceptorManager _instance = InterceptorManager._(); 25 | 26 | static InterceptorManager get instance { 27 | return _instance; 28 | } 29 | 30 | void onSave(HttpTransaction httpTransaction) { 31 | if(transactionList.length>=maxCount){ 32 | transactionList.remove(transactionList.first); 33 | } 34 | transactionList.add(httpTransaction); 35 | } 36 | 37 | void onResponseTime(int id,DateTime dateTime) { 38 | if(responseTime.length>=maxCount){ 39 | responseTime.remove(responseTime.keys.first); 40 | } 41 | responseTime[id] = dateTime; 42 | } 43 | 44 | void onRequestTime(int id,DateTime dateTime) { 45 | if(requestTime.length>=maxCount){ 46 | requestTime.remove(requestTime.keys.first); 47 | } 48 | requestTime[id] = dateTime; 49 | } 50 | 51 | ///日志清除 52 | void clear() { 53 | transactionList.clear(); 54 | requestTime.clear(); 55 | responseTime.clear(); 56 | } 57 | } -------------------------------------------------------------------------------- /lib/src/tools/json_utils.dart: -------------------------------------------------------------------------------- 1 | 2 | import 'dart:convert'; 3 | 4 | String json(String? json) { 5 | if (json==null||json.isEmpty) { 6 | return ""; 7 | }else{ 8 | var j = json.trim(); 9 | try{ 10 | if (j.startsWith("{")) { 11 | Map decode = JsonCodec().decode(json); 12 | var encoder = JsonEncoder.withIndent(' '); 13 | return encoder.convert(decode); 14 | } 15 | if (j.startsWith("[")) { 16 | List decode = JsonCodec().decode(json); 17 | var encoder = JsonEncoder.withIndent(' '); 18 | return encoder.convert(decode); 19 | } 20 | }on Exception catch (e) { 21 | return json; 22 | } 23 | return json; 24 | } 25 | } -------------------------------------------------------------------------------- /lib/src/tools/time_utils.dart: -------------------------------------------------------------------------------- 1 | ///format(2018.09.08 11:22:333) 2 | String getTimeStr(DateTime dateTime) { 3 | return "${twoNum(dateTime.year)}.${twoNum(dateTime.month)}.${twoNum(dateTime.day)}-" 4 | "${twoNum(dateTime.hour)}:${twoNum(dateTime.minute)}:${twoNum(dateTime.second)}:${dateTime.millisecond}"; 5 | } 6 | 7 | ///format(11:22:333) 8 | String getTimeStr1(DateTime dateTime) { 9 | return "${twoNum(dateTime.hour)}:${twoNum(dateTime.minute)}:${twoNum(dateTime.second)}"; 10 | } 11 | 12 | ///转成两位数 13 | String twoNum(int num) { 14 | return num > 9 ? num.toString() : "0$num"; 15 | } 16 | -------------------------------------------------------------------------------- /lib/src/ui/interceptor_detail.dart: -------------------------------------------------------------------------------- 1 | 2 | import 'package:flutter/material.dart'; 3 | import 'package:flutter_interceptor/src/interceptor_localizations.dart'; 4 | import 'dart:convert' as convert; 5 | 6 | import 'package:flutter_interceptor/src/model/http_transaction.dart'; 7 | import 'package:flutter_interceptor/src/tools/copy_clipboard.dart'; 8 | import 'package:flutter_interceptor/src/tools/json_utils.dart'; 9 | 10 | class InterceptorDetail extends StatefulWidget { 11 | 12 | final HttpTransaction httpTransaction; 13 | InterceptorDetail({required this.httpTransaction}); 14 | 15 | @override 16 | State createState() => _InterceptorDetailState(); 17 | } 18 | 19 | class _InterceptorDetailState extends State { 20 | 21 | 22 | @override 23 | Widget build(BuildContext context) { 24 | var local = InterceptorLocalizations.getInterceptorString(context); 25 | return DefaultTabController( 26 | length: 3, 27 | child: Scaffold( 28 | appBar: AppBar( 29 | title: Text("${widget.httpTransaction.method} ${widget.httpTransaction.path}"), 30 | elevation: 0, 31 | bottom: TabBar( 32 | unselectedLabelColor: Colors.black38, 33 | indicatorColor: Colors.white, 34 | indicatorSize: TabBarIndicatorSize.label, 35 | indicatorWeight: 3.0, 36 | tabs: [ 37 | Text(local.summary!,style: TextStyle(fontSize: 15,color: Colors.white),), 38 | Text(local.request!,style: TextStyle(fontSize: 15,color: Colors.white),), 39 | Text(local.response!,style: TextStyle(fontSize: 15,color: Colors.white),), 40 | ], 41 | ), 42 | actions: [ 43 | PopupMenuButton( 44 | icon: Icon(Icons.share), 45 | onSelected: (value) { 46 | if(value == 'text'){ 47 | }else{ 48 | } 49 | }, 50 | itemBuilder: (context) => [ 51 | PopupMenuItem( 52 | child: TextButton(child: Text(local.copy!),onPressed: (){ 53 | copyClipboard(context,widget.httpTransaction.toString()); 54 | },), 55 | value: 'text', 56 | 57 | ), 58 | ], 59 | ) 60 | ], 61 | ), 62 | body: TabBarView( 63 | children: [ 64 | ContentDetialPage(httpTransaction: widget.httpTransaction,page : DetailPage.Content), 65 | ContentDetialPage(httpTransaction: widget.httpTransaction,page : DetailPage.Request), 66 | ContentDetialPage(httpTransaction: widget.httpTransaction,page : DetailPage.Resopnse), 67 | ], 68 | ), 69 | ), 70 | ); 71 | } 72 | } 73 | 74 | enum DetailPage { 75 | Request, 76 | Resopnse, 77 | Content 78 | } 79 | 80 | class ContentDetialPage extends StatefulWidget { 81 | final HttpTransaction httpTransaction; 82 | final DetailPage page; 83 | ContentDetialPage({required this.httpTransaction,required this.page}); 84 | 85 | @override 86 | State createState() => _ContentDetialPageState(); 87 | } 88 | 89 | class _ContentDetialPageState extends State { 90 | @override 91 | Widget build(BuildContext context) { 92 | var local = InterceptorLocalizations.getInterceptorString(context); 93 | 94 | switch(widget.page){ 95 | case DetailPage.Content: 96 | return SingleChildScrollView( 97 | child: Column(children: [ 98 | Row(children: [Text("URL:"),Expanded(child: Text("${widget.httpTransaction.muri.toString()}",textAlign: TextAlign.left,))],), 99 | Row(children: [Text("${local.method!}:"),Text("${widget.httpTransaction.method}")],), 100 | Row(children: [Text("${local.requestTime!}:"),Text("${widget.httpTransaction.requestTime}")],), 101 | Row(children: [Text("${local.responseTime!}:"),Text("${widget.httpTransaction.responseTime}")],), 102 | Row(children: [Text("${local.duration!}:"),Text("${widget.httpTransaction.duration}ms")],)],), 103 | ); 104 | case DetailPage.Request: 105 | List list = []; 106 | widget.httpTransaction.requestHeaders?.forEach((key, value) { 107 | list.add(Row(children: [Text("$key:"),Expanded(child:Text(convert.jsonEncode(value)))],crossAxisAlignment: CrossAxisAlignment.start,)); 108 | }); 109 | list.add(Padding(padding: EdgeInsets.only(top: 25),child: Text(widget.httpTransaction.requestBody??""))); 110 | return SingleChildScrollView( 111 | child: Column(children: 112 | list,), 113 | ); 114 | case DetailPage.Resopnse: 115 | List list = []; 116 | widget.httpTransaction.responseHeaders?.forEach((key, value) { 117 | list.add(Row(children: [Text("$key:"),Expanded(child:Text(convert.jsonEncode(value)))],crossAxisAlignment: CrossAxisAlignment.start,)); 118 | }); 119 | list.add(Padding(padding: EdgeInsets.only(top: 25),child: Text(json(widget.httpTransaction.responseBody)),)); 120 | return SingleChildScrollView( 121 | child: Column(children: 122 | list,), 123 | ); 124 | } 125 | } 126 | } 127 | -------------------------------------------------------------------------------- /lib/src/ui/interceptor_draggable.dart: -------------------------------------------------------------------------------- 1 | 2 | import 'package:flutter/cupertino.dart'; 3 | import 'package:flutter/material.dart'; 4 | 5 | import 'simple_interceptor.dart'; 6 | 7 | class InterceptorDraggable extends StatefulWidget{ 8 | @override 9 | State createState() =>_InterceptorDraggableState(); 10 | } 11 | 12 | class _InterceptorDraggableState extends State{ 13 | double left = 100; 14 | double top = 100; 15 | double btnSize = 50; 16 | late double screenWidth; 17 | late double screenHeight; 18 | 19 | @override 20 | Widget build(BuildContext context) { 21 | screenHeight = MediaQuery.of(context).size.height; 22 | screenWidth = MediaQuery.of(context).size.width; 23 | ///计算偏移量限制 24 | if (left < 1) { 25 | left = 1; 26 | } 27 | if (left > screenWidth - btnSize) { 28 | left = screenWidth - btnSize; 29 | } 30 | 31 | if (top < 1) { 32 | top = 1; 33 | } 34 | if (top > screenHeight - btnSize) { 35 | top = screenHeight - btnSize; 36 | } 37 | return GestureDetector( 38 | onTap: (){ 39 | Navigator.of(context).push( 40 | MaterialPageRoute( 41 | builder: (context) => SimpleInterceptor(), 42 | ), 43 | ); 44 | }, 45 | onPanUpdate: _dragUpdate, 46 | child:Container( 47 | alignment: Alignment.topLeft, 48 | margin: EdgeInsets.only(left: left, top: top), 49 | child: SizedBox( 50 | width: btnSize, 51 | height: btnSize, 52 | child: CircleAvatar( 53 | backgroundColor: Color(0x5f6495ed), 54 | child: Text( 55 | "Http", 56 | textAlign: TextAlign.center, 57 | style: TextStyle( 58 | color: Colors.white 59 | ), 60 | ), 61 | ), 62 | ), 63 | ), 64 | ); 65 | } 66 | 67 | void _dragUpdate(DragUpdateDetails detail) { 68 | Offset offset = detail.delta; 69 | left = left + offset.dx; 70 | top = top + offset.dy; 71 | setState(() { 72 | 73 | }); 74 | } 75 | } -------------------------------------------------------------------------------- /lib/src/ui/simple_interceptor.dart: -------------------------------------------------------------------------------- 1 | 2 | import 'package:flutter/material.dart'; 3 | import 'package:flutter/widgets.dart'; 4 | import 'package:flutter_interceptor/src/interceptor_localizations.dart'; 5 | import 'package:flutter_interceptor/src/model/http_transaction.dart'; 6 | import 'package:flutter_interceptor/src/tools/interceptor_manager.dart'; 7 | import 'package:flutter_interceptor/src/tools/time_utils.dart'; 8 | 9 | import 'interceptor_detail.dart'; 10 | 11 | class SimpleInterceptor extends StatefulWidget{ 12 | @override 13 | State createState() =>_SimpleInterceptorState(); 14 | 15 | } 16 | 17 | class _SimpleInterceptorState extends State{ 18 | @override 19 | Widget build(BuildContext context) { 20 | return Scaffold( 21 | appBar: AppBar( 22 | title: Text(InterceptorLocalizations.getInterceptorString(context).simpleInterception!), 23 | elevation: 0, 24 | actions: [ 25 | IconButton( 26 | icon: const Icon(Icons.delete), 27 | // tooltip: 'search', 28 | onPressed:() { 29 | InterceptorManager.instance.clear(); 30 | setState(() { 31 | }); 32 | }, 33 | ), 34 | ], 35 | ), 36 | 37 | body: SafeArea( 38 | child: ListView( 39 | children: InterceptorManager.instance.transactionList.map((httpTransaction){ 40 | return SimpleInterceptorCell(httpTransaction: httpTransaction); 41 | }).toList(), 42 | ), 43 | ), 44 | ); 45 | } 46 | 47 | } 48 | 49 | class SimpleInterceptorCell extends StatelessWidget { 50 | final HttpTransaction httpTransaction; 51 | SimpleInterceptorCell({required this.httpTransaction}) ; 52 | 53 | Color statusGetColor(){ 54 | if(httpTransaction.status == Status.Failed){ 55 | return Color(0xffF44336); 56 | } 57 | if(httpTransaction.status == Status.Requested){ 58 | return Color(0xff9E9E9E); 59 | } 60 | if(httpTransaction.statusCode! >= 500){ 61 | return Color(0xffB71C1C); 62 | } 63 | if(httpTransaction.statusCode! >= 400){ 64 | return Color(0xffFF9800); 65 | } 66 | if(httpTransaction.statusCode! >= 300){ 67 | return Color(0xff0D47A1); 68 | } 69 | return Color(0xff212121); 70 | } 71 | 72 | @override 73 | Widget build(BuildContext context) { 74 | 75 | return InkWell( 76 | onTap: (){ 77 | Navigator.of(context).push( 78 | MaterialPageRoute(builder: (context) => InterceptorDetail(httpTransaction : httpTransaction)), 79 | ); 80 | }, 81 | child: Stack( 82 | children: [ 83 | //内容布局 84 | Container( 85 | padding: EdgeInsets.all(10), 86 | child: Row( 87 | crossAxisAlignment: CrossAxisAlignment.start, 88 | children: [ 89 | Text(httpTransaction.statusCode.toString(),style: TextStyle( 90 | fontSize: 17, 91 | color: statusGetColor(), 92 | ),), 93 | SizedBox(width: 10,), 94 | Expanded( 95 | child:Column( 96 | crossAxisAlignment: CrossAxisAlignment.start, 97 | children: [ 98 | Text("${httpTransaction.method} ${httpTransaction.path}",style: TextStyle( 99 | fontSize: 17, 100 | color: statusGetColor(), 101 | ),textAlign: TextAlign.left,), 102 | SizedBox(height: 10,), 103 | Text(httpTransaction.host??"",style: TextStyle( 104 | fontSize: 13, 105 | color: Colors.grey, 106 | ),), 107 | SizedBox(height: 10,), 108 | Flex( 109 | direction: Axis.horizontal, 110 | children: [ 111 | Expanded(child:Text(getTimeStr1(httpTransaction.requestTime!),style: TextStyle(color: Colors.grey,fontSize: 13),),flex: 1,), 112 | Expanded(child:Text("${httpTransaction.duration}ms",style: TextStyle(color: Colors.grey,fontSize: 13),),flex: 1,), 113 | ], 114 | ), 115 | SizedBox(height: 10,), 116 | ], 117 | ), 118 | ), 119 | 120 | ], 121 | ), 122 | ), 123 | //线 124 | Positioned( 125 | child: Container( 126 | height: 1, 127 | color: Colors.grey, 128 | ), 129 | bottom: 1, 130 | left: 0, 131 | right: 0, 132 | ), 133 | ], 134 | ), 135 | ); 136 | } 137 | } 138 | -------------------------------------------------------------------------------- /pubspec.lock: -------------------------------------------------------------------------------- 1 | # Generated by pub 2 | # See https://dart.dev/tools/pub/glossary#lockfile 3 | packages: 4 | async: 5 | dependency: transitive 6 | description: 7 | name: async 8 | url: "https://pub.flutter-io.cn" 9 | source: hosted 10 | version: "2.6.1" 11 | boolean_selector: 12 | dependency: transitive 13 | description: 14 | name: boolean_selector 15 | url: "https://pub.flutter-io.cn" 16 | source: hosted 17 | version: "2.1.0" 18 | characters: 19 | dependency: transitive 20 | description: 21 | name: characters 22 | url: "https://pub.flutter-io.cn" 23 | source: hosted 24 | version: "1.1.0" 25 | charcode: 26 | dependency: transitive 27 | description: 28 | name: charcode 29 | url: "https://pub.flutter-io.cn" 30 | source: hosted 31 | version: "1.2.0" 32 | clock: 33 | dependency: transitive 34 | description: 35 | name: clock 36 | url: "https://pub.flutter-io.cn" 37 | source: hosted 38 | version: "1.1.0" 39 | collection: 40 | dependency: transitive 41 | description: 42 | name: collection 43 | url: "https://pub.flutter-io.cn" 44 | source: hosted 45 | version: "1.15.0" 46 | dio: 47 | dependency: "direct main" 48 | description: 49 | name: dio 50 | url: "https://pub.flutter-io.cn" 51 | source: hosted 52 | version: "4.0.1" 53 | fake_async: 54 | dependency: transitive 55 | description: 56 | name: fake_async 57 | url: "https://pub.flutter-io.cn" 58 | source: hosted 59 | version: "1.2.0" 60 | flutter: 61 | dependency: "direct main" 62 | description: flutter 63 | source: sdk 64 | version: "0.0.0" 65 | flutter_test: 66 | dependency: "direct dev" 67 | description: flutter 68 | source: sdk 69 | version: "0.0.0" 70 | http_parser: 71 | dependency: transitive 72 | description: 73 | name: http_parser 74 | url: "https://pub.flutter-io.cn" 75 | source: hosted 76 | version: "4.0.0" 77 | matcher: 78 | dependency: transitive 79 | description: 80 | name: matcher 81 | url: "https://pub.flutter-io.cn" 82 | source: hosted 83 | version: "0.12.10" 84 | meta: 85 | dependency: transitive 86 | description: 87 | name: meta 88 | url: "https://pub.flutter-io.cn" 89 | source: hosted 90 | version: "1.3.0" 91 | path: 92 | dependency: transitive 93 | description: 94 | name: path 95 | url: "https://pub.flutter-io.cn" 96 | source: hosted 97 | version: "1.8.0" 98 | sky_engine: 99 | dependency: transitive 100 | description: flutter 101 | source: sdk 102 | version: "0.0.99" 103 | source_span: 104 | dependency: transitive 105 | description: 106 | name: source_span 107 | url: "https://pub.flutter-io.cn" 108 | source: hosted 109 | version: "1.8.1" 110 | stack_trace: 111 | dependency: transitive 112 | description: 113 | name: stack_trace 114 | url: "https://pub.flutter-io.cn" 115 | source: hosted 116 | version: "1.10.0" 117 | stream_channel: 118 | dependency: transitive 119 | description: 120 | name: stream_channel 121 | url: "https://pub.flutter-io.cn" 122 | source: hosted 123 | version: "2.1.0" 124 | string_scanner: 125 | dependency: transitive 126 | description: 127 | name: string_scanner 128 | url: "https://pub.flutter-io.cn" 129 | source: hosted 130 | version: "1.1.0" 131 | term_glyph: 132 | dependency: transitive 133 | description: 134 | name: term_glyph 135 | url: "https://pub.flutter-io.cn" 136 | source: hosted 137 | version: "1.2.0" 138 | test_api: 139 | dependency: transitive 140 | description: 141 | name: test_api 142 | url: "https://pub.flutter-io.cn" 143 | source: hosted 144 | version: "0.3.0" 145 | typed_data: 146 | dependency: transitive 147 | description: 148 | name: typed_data 149 | url: "https://pub.flutter-io.cn" 150 | source: hosted 151 | version: "1.3.0" 152 | vector_math: 153 | dependency: transitive 154 | description: 155 | name: vector_math 156 | url: "https://pub.flutter-io.cn" 157 | source: hosted 158 | version: "2.1.0" 159 | sdks: 160 | dart: ">=2.12.0 <3.0.0" 161 | flutter: ">=1.17.0" 162 | -------------------------------------------------------------------------------- /pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: flutter_interceptor 2 | description: flutter dio interceptor 3 | version: 0.0.2 4 | homepage: https://github.com/smartbackme/flutter_interceptor 5 | 6 | environment: 7 | sdk: ">=2.12.0 <3.0.0" 8 | flutter: ">=1.17.0" 9 | 10 | dependencies: 11 | flutter: 12 | sdk: flutter 13 | dio: ^4.0.0 14 | 15 | 16 | dev_dependencies: 17 | flutter_test: 18 | sdk: flutter 19 | 20 | # For information on the generic Dart part of this file, see the 21 | # following page: https://dart.dev/tools/pub/pubspec 22 | 23 | # The following section is specific to Flutter. 24 | flutter: 25 | 26 | # To add assets to your package, add an assets section, like this: 27 | # assets: 28 | # - images/a_dot_burr.jpeg 29 | # - images/a_dot_ham.jpeg 30 | # 31 | # For details regarding assets in packages, see 32 | # https://flutter.dev/assets-and-images/#from-packages 33 | # 34 | # An image asset can refer to one or more resolution-specific "variants", see 35 | # https://flutter.dev/assets-and-images/#resolution-aware. 36 | 37 | # To add custom fonts to your package, add a fonts section here, 38 | # in this "flutter" section. Each entry in this list should have a 39 | # "family" key with the font family name, and a "fonts" key with a 40 | # list giving the asset and other descriptors for the font. For 41 | # example: 42 | # fonts: 43 | # - family: Schyler 44 | # fonts: 45 | # - asset: fonts/Schyler-Regular.ttf 46 | # - asset: fonts/Schyler-Italic.ttf 47 | # style: italic 48 | # - family: Trajan Pro 49 | # fonts: 50 | # - asset: fonts/TrajanPro.ttf 51 | # - asset: fonts/TrajanPro_Bold.ttf 52 | # weight: 700 53 | # 54 | # For details regarding fonts in packages, see 55 | # https://flutter.dev/custom-fonts/#from-packages 56 | -------------------------------------------------------------------------------- /test/flutter_interceptor_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter_test/flutter_test.dart'; 2 | 3 | import 'package:flutter_interceptor/flutter_interceptor.dart'; 4 | 5 | void main() { 6 | 7 | } 8 | --------------------------------------------------------------------------------