mirror of
https://github.com/foss42/apidash.git
synced 2025-08-06 13:51:20 +08:00
Merge branch 'main' into add-feature-22
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@ -50,6 +50,7 @@ macos/
|
||||
windows/
|
||||
web/
|
||||
ios/
|
||||
android/
|
||||
.vscode/*
|
||||
icons/
|
||||
coverage/*
|
||||
|
@ -138,7 +138,7 @@ Here is the complete list of mimetypes that can be directly previewed in API Das
|
||||
| Image | `image/portable-anymap` | `.pnm` | |
|
||||
| Image | `image/png` | `.png` | |
|
||||
| Image | `image/sgi` | `.sgi` | |
|
||||
| Image | `image/svg+xml` | `.svg` | Partial support. See issue https://github.com/foss42/apidash/issues/20 |
|
||||
| Image | `image/svg+xml` | `.svg` | |
|
||||
| Image | `image/tiff` | `.tiff` | |
|
||||
| Image | `image/targa` | `.tga` | |
|
||||
| Image | `image/vnd.wap.wbmp` | `.wbmp` | |
|
||||
|
26
lib/app.dart
26
lib/app.dart
@ -1,10 +1,10 @@
|
||||
import 'package:apidash/widgets/window_caption.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:window_manager/window_manager.dart';
|
||||
import 'package:window_manager/window_manager.dart' hide WindowCaption;
|
||||
import 'providers/providers.dart';
|
||||
import 'screens/screens.dart';
|
||||
import 'consts.dart'
|
||||
show kIsLinux, kFontFamily, kFontFamilyFallback, kColorSchemeSeed;
|
||||
import 'consts.dart';
|
||||
|
||||
class App extends ConsumerStatefulWidget {
|
||||
const App({super.key});
|
||||
@ -64,6 +64,7 @@ class _DashAppState extends ConsumerState<DashApp> {
|
||||
return MaterialApp(
|
||||
debugShowCheckedModeBanner: false,
|
||||
theme: ThemeData(
|
||||
visualDensity: VisualDensity.adaptivePlatformDensity,
|
||||
fontFamily: kFontFamily,
|
||||
fontFamilyFallback: kFontFamilyFallback,
|
||||
colorSchemeSeed: kColorSchemeSeed,
|
||||
@ -78,7 +79,24 @@ class _DashAppState extends ConsumerState<DashApp> {
|
||||
brightness: Brightness.dark,
|
||||
),
|
||||
themeMode: isDarkMode ? ThemeMode.dark : ThemeMode.light,
|
||||
home: kIsLinux ? const Dashboard() : const App(),
|
||||
home: kIsMobile
|
||||
? const MobileDashboard(
|
||||
title: 'Requests',
|
||||
scaffoldBody: CollectionPane(),
|
||||
)
|
||||
: Stack(
|
||||
children: [
|
||||
kIsLinux ? const Dashboard() : const App(),
|
||||
if (kIsWindows)
|
||||
SizedBox(
|
||||
height: 29,
|
||||
child: WindowCaption(
|
||||
backgroundColor: Colors.transparent,
|
||||
brightness: isDarkMode ? Brightness.dark : Brightness.light,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -16,6 +16,10 @@ final kIsApple = !kIsWeb && (Platform.isIOS || Platform.isMacOS);
|
||||
final kIsDesktop =
|
||||
!kIsWeb && (Platform.isMacOS || Platform.isWindows || Platform.isLinux);
|
||||
|
||||
final kIsIOS = !kIsWeb && Platform.isIOS;
|
||||
final kIsAndroid = !kIsWeb && Platform.isAndroid;
|
||||
final kIsMobile = !kIsWeb && (Platform.isIOS || Platform.isAndroid);
|
||||
|
||||
final kColorTransparentState =
|
||||
MaterialStateProperty.all<Color>(Colors.transparent);
|
||||
const kColorTransparent = Colors.transparent;
|
||||
@ -338,7 +342,7 @@ const Map<String, Map<String, List<ResponseBodyView>>>
|
||||
},
|
||||
kTypeImage: {
|
||||
kSubTypeDefaultViewOptions: kPreviewBodyViewOptions,
|
||||
kSubTypeSvg: kCodeRawBodyViewOptions,
|
||||
kSubTypeSvg: kPreviewRawBodyViewOptions,
|
||||
},
|
||||
kTypeAudio: {
|
||||
kSubTypeDefaultViewOptions: kPreviewBodyViewOptions,
|
||||
@ -455,6 +459,9 @@ const kUnexpectedRaiseIssue =
|
||||
const kImageError =
|
||||
"There seems to be an issue rendering this image. Please raise an issue in API Dash GitHub repo so that we can resolve it.";
|
||||
|
||||
const kSvgError =
|
||||
"There seems to be an issue rendering this SVG image. Please raise an issue in API Dash GitHub repo so that we can resolve it.";
|
||||
|
||||
const kPdfError =
|
||||
"There seems to be an issue rendering this pdf. Please raise an issue in API Dash GitHub repo so that we can resolve it.";
|
||||
|
||||
|
@ -2,7 +2,7 @@ import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:google_fonts/google_fonts.dart';
|
||||
import 'services/services.dart';
|
||||
import 'consts.dart' show kIsLinux;
|
||||
import 'consts.dart' show kIsLinux, kIsMacOS, kIsWindows;
|
||||
import 'app.dart';
|
||||
|
||||
void main() async {
|
||||
@ -11,7 +11,8 @@ void main() async {
|
||||
await openBoxes();
|
||||
if (kIsLinux) {
|
||||
await setupInitialWindow();
|
||||
} else {
|
||||
}
|
||||
if (kIsMacOS || kIsWindows) {
|
||||
var win = getInitialSize();
|
||||
await setupWindow(sz: win.$1, off: win.$2);
|
||||
}
|
||||
|
@ -27,7 +27,7 @@ class _RequestEditorPaneState extends ConsumerState<RequestEditorPane> {
|
||||
return const RequestEditorDefault();
|
||||
} else {
|
||||
return Padding(
|
||||
padding: kIsMacOS ? kPt24o8 : kP8,
|
||||
padding: kIsMacOS || kIsWindows ? kPt24o8 : kP8,
|
||||
child: const Column(
|
||||
children: [
|
||||
EditorPaneRequestURLCard(),
|
||||
|
83
lib/screens/mobile/dashboard.dart
Normal file
83
lib/screens/mobile/dashboard.dart
Normal file
@ -0,0 +1,83 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import '../intro_page.dart';
|
||||
import '../settings_page.dart';
|
||||
import '../home_page/collection_pane.dart';
|
||||
|
||||
class MobileDashboard extends ConsumerStatefulWidget {
|
||||
const MobileDashboard(
|
||||
{required this.scaffoldBody, required this.title, super.key});
|
||||
|
||||
final Widget scaffoldBody;
|
||||
final String title;
|
||||
|
||||
@override
|
||||
ConsumerState<MobileDashboard> createState() => _MobileDashboardState();
|
||||
}
|
||||
|
||||
class _MobileDashboardState extends ConsumerState<MobileDashboard> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: Text(widget.title),
|
||||
),
|
||||
drawer: Drawer(
|
||||
child: ListView(
|
||||
padding: EdgeInsets.zero,
|
||||
children: [
|
||||
const SizedBox(
|
||||
height: 70,
|
||||
),
|
||||
ListTile(
|
||||
title: const Text('Home'),
|
||||
leading: const Icon(Icons.home_outlined),
|
||||
onTap: () {
|
||||
Navigator.of(context).pushAndRemoveUntil(
|
||||
MaterialPageRoute(
|
||||
builder: (context) => const MobileDashboard(
|
||||
title: 'Home',
|
||||
scaffoldBody: IntroPage(),
|
||||
),
|
||||
),
|
||||
(Route<dynamic> route) => false);
|
||||
},
|
||||
),
|
||||
ListTile(
|
||||
title: const Text('Requests'),
|
||||
leading: const Icon(Icons.auto_awesome_mosaic_outlined),
|
||||
onTap: () {
|
||||
Navigator.of(context).pushAndRemoveUntil(
|
||||
MaterialPageRoute(
|
||||
builder: (context) => const MobileDashboard(
|
||||
title: 'Requests',
|
||||
scaffoldBody: CollectionPane(),
|
||||
),
|
||||
),
|
||||
(Route<dynamic> route) => false);
|
||||
},
|
||||
),
|
||||
ListTile(
|
||||
title: const Text('Settings'),
|
||||
leading: const Icon(Icons.settings_outlined),
|
||||
onTap: () {
|
||||
Navigator.of(context).pushAndRemoveUntil(
|
||||
MaterialPageRoute(
|
||||
builder: (context) => const MobileDashboard(
|
||||
title: 'Settings',
|
||||
scaffoldBody: SettingsPage(),
|
||||
),
|
||||
),
|
||||
(Route<dynamic> route) => false);
|
||||
},
|
||||
),
|
||||
const Divider(),
|
||||
],
|
||||
),
|
||||
),
|
||||
body: SafeArea(
|
||||
child: widget.scaffoldBody,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
1
lib/screens/mobile/mobile.dart
Normal file
1
lib/screens/mobile/mobile.dart
Normal file
@ -0,0 +1 @@
|
||||
export 'dashboard.dart';
|
@ -1 +1,3 @@
|
||||
export "dashboard.dart";
|
||||
export 'dashboard.dart';
|
||||
export 'mobile/mobile.dart';
|
||||
export 'home_page/collection_pane.dart';
|
||||
|
@ -27,9 +27,11 @@ class _SettingsPageState extends ConsumerState<SettingsPage> {
|
||||
padding: kPh20t40,
|
||||
shrinkWrap: true,
|
||||
children: [
|
||||
Text("Settings",
|
||||
style: Theme.of(context).textTheme.headlineLarge),
|
||||
const Divider(),
|
||||
kIsDesktop
|
||||
? Text("Settings",
|
||||
style: Theme.of(context).textTheme.headlineLarge)
|
||||
: const SizedBox.shrink(),
|
||||
kIsDesktop ? const Divider() : const SizedBox.shrink(),
|
||||
SwitchListTile(
|
||||
contentPadding: EdgeInsets.zero,
|
||||
hoverColor: kColorTransparent,
|
||||
|
@ -62,7 +62,7 @@ Future<void> setupWindow({Size? sz, Offset? off, bool center = false}) async {
|
||||
minimumSize: kMinWindowSize,
|
||||
skipTaskbar: false,
|
||||
title: kWindowTitle,
|
||||
titleBarStyle: kIsMacOS ? TitleBarStyle.hidden : null,
|
||||
titleBarStyle: kIsMacOS || kIsWindows ? TitleBarStyle.hidden : null,
|
||||
);
|
||||
if (off != null) {
|
||||
await windowManager.setPosition(off);
|
||||
|
@ -1,11 +1,13 @@
|
||||
import 'dart:convert';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'error_message.dart';
|
||||
import 'package:apidash/consts.dart';
|
||||
import 'package:printing/printing.dart';
|
||||
import 'package:flutter_svg/flutter_svg.dart';
|
||||
import 'package:vector_graphics_compiler/vector_graphics_compiler.dart';
|
||||
import 'error_message.dart';
|
||||
import 'uint8_audio_player.dart';
|
||||
import 'json_previewer.dart';
|
||||
import '../consts.dart';
|
||||
|
||||
class Previewer extends StatefulWidget {
|
||||
const Previewer({
|
||||
@ -40,6 +42,18 @@ class _PreviewerState extends State<Previewer> {
|
||||
// pass
|
||||
}
|
||||
}
|
||||
if (widget.type == kTypeImage && widget.subtype == kSubTypeSvg) {
|
||||
final String rawSvg = widget.body;
|
||||
try {
|
||||
parseWithoutOptimizers(rawSvg);
|
||||
var svgImg = SvgPicture.string(
|
||||
rawSvg,
|
||||
);
|
||||
return svgImg;
|
||||
} catch (e) {
|
||||
return const ErrorMessage(message: kSvgError);
|
||||
}
|
||||
}
|
||||
if (widget.type == kTypeImage) {
|
||||
return Image.memory(
|
||||
widget.bytes,
|
||||
|
98
lib/widgets/window_caption.dart
Normal file
98
lib/widgets/window_caption.dart
Normal file
@ -0,0 +1,98 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:window_manager/window_manager.dart';
|
||||
|
||||
const double kWindowCaptionHeight = 30;
|
||||
|
||||
class WindowCaption extends StatefulWidget {
|
||||
const WindowCaption({
|
||||
super.key,
|
||||
this.backgroundColor,
|
||||
this.brightness,
|
||||
});
|
||||
|
||||
final Color? backgroundColor;
|
||||
final Brightness? brightness;
|
||||
|
||||
@override
|
||||
State<WindowCaption> createState() => _WindowCaptionState();
|
||||
}
|
||||
|
||||
class _WindowCaptionState extends State<WindowCaption> with WindowListener {
|
||||
@override
|
||||
void initState() {
|
||||
windowManager.addListener(this);
|
||||
super.initState();
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
windowManager.removeListener(this);
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: GestureDetector(
|
||||
behavior: HitTestBehavior.translucent,
|
||||
onPanStart: (details) {
|
||||
windowManager.startDragging();
|
||||
},
|
||||
child: const SizedBox(
|
||||
height: double.infinity,
|
||||
),
|
||||
),
|
||||
),
|
||||
WindowCaptionButton.minimize(
|
||||
brightness: widget.brightness,
|
||||
onPressed: () async {
|
||||
bool isMinimized = await windowManager.isMinimized();
|
||||
if (isMinimized) {
|
||||
windowManager.restore();
|
||||
} else {
|
||||
windowManager.minimize();
|
||||
}
|
||||
},
|
||||
),
|
||||
FutureBuilder<bool>(
|
||||
future: windowManager.isMaximized(),
|
||||
builder: (BuildContext context, AsyncSnapshot<bool> snapshot) {
|
||||
if (snapshot.data == true) {
|
||||
return WindowCaptionButton.unmaximize(
|
||||
brightness: widget.brightness,
|
||||
onPressed: () {
|
||||
windowManager.unmaximize();
|
||||
},
|
||||
);
|
||||
}
|
||||
return WindowCaptionButton.maximize(
|
||||
brightness: widget.brightness,
|
||||
onPressed: () {
|
||||
windowManager.maximize();
|
||||
},
|
||||
);
|
||||
},
|
||||
),
|
||||
WindowCaptionButton.close(
|
||||
brightness: widget.brightness,
|
||||
onPressed: () {
|
||||
windowManager.close();
|
||||
},
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
void onWindowMaximize() {
|
||||
setState(() {});
|
||||
}
|
||||
|
||||
@override
|
||||
void onWindowUnmaximize() {
|
||||
setState(() {});
|
||||
}
|
||||
}
|
32
pubspec.lock
32
pubspec.lock
@ -374,6 +374,14 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.4.4"
|
||||
flutter_svg:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: flutter_svg
|
||||
sha256: d39e7f95621fc84376bc0f7d504f05c3a41488c562f4a8ad410569127507402c
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.0.9"
|
||||
flutter_test:
|
||||
dependency: "direct dev"
|
||||
description: flutter
|
||||
@ -1174,6 +1182,30 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "4.1.0"
|
||||
vector_graphics:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: vector_graphics
|
||||
sha256: "0f0c746dd2d6254a0057218ff980fc7f5670fd0fcf5e4db38a490d31eed4ad43"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.1.9+1"
|
||||
vector_graphics_codec:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: vector_graphics_codec
|
||||
sha256: "0edf6d630d1bfd5589114138ed8fada3234deacc37966bec033d3047c29248b7"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.1.9+1"
|
||||
vector_graphics_compiler:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: vector_graphics_compiler
|
||||
sha256: d24333727332d9bd20990f1483af4e09abdb9b1fc7c3db940b56ab5c42790c26
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.1.9+1"
|
||||
vector_math:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -49,6 +49,8 @@ dependencies:
|
||||
url: https://github.com/foss42/json_data_explorer.git
|
||||
version: ^0.1.1
|
||||
scrollable_positioned_list: ^0.2.3
|
||||
flutter_svg: ^2.0.9
|
||||
vector_graphics_compiler: ^1.1.9+1
|
||||
json_text_field: ^1.0.0
|
||||
|
||||
dev_dependencies:
|
||||
|
@ -268,7 +268,7 @@ void main() {
|
||||
test('Testing getResponseBodyViewOptions for image/svg+xml', () {
|
||||
MediaType mediaType5 = MediaType("image", "svg+xml");
|
||||
var result5 = getResponseBodyViewOptions(mediaType5);
|
||||
expect(result5.$1, kCodeRawBodyViewOptions);
|
||||
expect(result5.$1, kPreviewRawBodyViewOptions);
|
||||
expect(result5.$2, "xml");
|
||||
});
|
||||
test('Testing getResponseBodyViewOptions for application/xhtml+xml', () {
|
||||
|
@ -4,6 +4,7 @@ import 'package:apidash/widgets/widgets.dart';
|
||||
import 'package:apidash/consts.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:printing/printing.dart' show PdfPreview;
|
||||
import 'package:flutter_svg/flutter_svg.dart' show SvgPicture;
|
||||
import '../test_consts.dart';
|
||||
|
||||
void main() {
|
||||
@ -169,4 +170,65 @@ void main() {
|
||||
await tester.pumpAndSettle();
|
||||
expect(find.text(kAudioError), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('Testing when type/subtype is image/svg+xml', (tester) async {
|
||||
String rawSvg =
|
||||
"""<svg xmlns="http://www.w3.org/2000/svg" version="1.1" viewBox="0 0 166 202">
|
||||
<defs>
|
||||
<linearGradient id="triangleGradient">
|
||||
<stop offset="20%" stop-color="#000000" stop-opacity=".55" />
|
||||
<stop offset="85%" stop-color="#616161" stop-opacity=".01" />
|
||||
</linearGradient>
|
||||
<linearGradient id="rectangleGradient" x1="0%" x2="0%" y1="0%" y2="100%">
|
||||
<stop offset="20%" stop-color="#000000" stop-opacity=".15" />
|
||||
<stop offset="85%" stop-color="#616161" stop-opacity=".01" />
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<path fill="#42A5F5" fill-opacity=".8" d="M37.7 128.9 9.8 101 100.4 10.4 156.2 10.4"/>
|
||||
<path fill="#42A5F5" fill-opacity=".8" d="M156.2 94 100.4 94 79.5 114.9 107.4 142.8"/>
|
||||
<path fill="#0D47A1" d="M79.5 170.7 100.4 191.6 156.2 191.6 156.2 191.6 107.4 142.8"/>
|
||||
<g transform="matrix(0.7071, -0.7071, 0.7071, 0.7071, -77.667, 98.057)">
|
||||
<rect width="39.4" height="39.4" x="59.8" y="123.1" fill="#42A5F5" />
|
||||
<rect width="39.4" height="5.5" x="59.8" y="162.5" fill="url(#rectangleGradient)" />
|
||||
</g>
|
||||
<path d="M79.5 170.7 120.9 156.4 107.4 142.8" fill="url(#triangleGradient)" />
|
||||
</svg>""";
|
||||
|
||||
await tester.pumpWidget(
|
||||
MaterialApp(
|
||||
title: 'Previewer',
|
||||
home: Scaffold(
|
||||
body: Previewer(
|
||||
type: 'image',
|
||||
subtype: 'svg+xml',
|
||||
bytes: Uint8List.fromList([]),
|
||||
body: rawSvg,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
expect(find.text(kSvgError), findsNothing);
|
||||
expect(find.byType(SvgPicture), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('Testing when type/subtype is image/svg+xml corrupted',
|
||||
(tester) async {
|
||||
String rawSvg = "rwsjhdws";
|
||||
await tester.pumpWidget(
|
||||
MaterialApp(
|
||||
title: 'Previewer',
|
||||
home: Scaffold(
|
||||
body: Previewer(
|
||||
type: 'image',
|
||||
subtype: 'svg+xml',
|
||||
bytes: Uint8List.fromList([]),
|
||||
body: rawSvg,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
expect(find.text(kSvgError), findsOneWidget);
|
||||
});
|
||||
}
|
||||
|
Reference in New Issue
Block a user