Merge branch 'main' into add-feature-22

This commit is contained in:
Ashita Prasad
2023-12-23 04:14:57 +05:30
committed by GitHub
17 changed files with 340 additions and 17 deletions

1
.gitignore vendored
View File

@ -50,6 +50,7 @@ macos/
windows/
web/
ios/
android/
.vscode/*
icons/
coverage/*

View File

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

View File

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

View File

@ -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.";

View File

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

View File

@ -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(),

View 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,
),
);
}
}

View File

@ -0,0 +1 @@
export 'dashboard.dart';

View File

@ -1 +1,3 @@
export "dashboard.dart";
export 'dashboard.dart';
export 'mobile/mobile.dart';
export 'home_page/collection_pane.dart';

View File

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

View File

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

View File

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

View 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(() {});
}
}

View File

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

View File

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

View File

@ -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', () {

View File

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