mirror of
https://github.com/foss42/apidash.git
synced 2025-06-03 16:27:06 +08:00
Merge branch 'main' into fix-kotlin-codegen-import-issue
This commit is contained in:
@ -378,6 +378,7 @@ const Map<String, Map<String, List<ResponseBodyView>>>
|
||||
kSubTypeDefaultViewOptions: kRawBodyViewOptions,
|
||||
kSubTypeCss: kCodeRawBodyViewOptions,
|
||||
kSubTypeHtml: kCodeRawBodyViewOptions,
|
||||
kSubTypeCsv: kPreviewRawBodyViewOptions,
|
||||
kSubTypeJavascript: kCodeRawBodyViewOptions,
|
||||
kSubTypeMarkdown: kCodeRawBodyViewOptions,
|
||||
kSubTypeTextXml: kCodeRawBodyViewOptions,
|
||||
@ -495,7 +496,11 @@ const kAudioError =
|
||||
const kRaiseIssue =
|
||||
"\nPlease raise an issue in API Dash GitHub repo so that we can resolve it.";
|
||||
|
||||
const kHintTextUrlCard = "Enter API endpoint like api.foss42.com/country/codes";
|
||||
const kCsvError =
|
||||
"There seems to be an issue rendering this CSV. Please raise an issue in API Dash GitHub repo so that we can resolve it.";
|
||||
|
||||
const kHintTextUrlCard =
|
||||
"Enter API endpoint like api.apidash.dev/country/codes";
|
||||
const kLabelPlusNew = "+ New";
|
||||
const kLabelSend = "Send";
|
||||
const kLabelSending = "Sending..";
|
||||
|
@ -29,6 +29,7 @@ class RequestModel {
|
||||
this.responseStatus,
|
||||
this.message,
|
||||
this.responseModel,
|
||||
this.isWorking = false,
|
||||
});
|
||||
|
||||
final String id;
|
||||
@ -47,6 +48,7 @@ class RequestModel {
|
||||
final int? responseStatus;
|
||||
final String? message;
|
||||
final ResponseModel? responseModel;
|
||||
final bool isWorking;
|
||||
|
||||
List<NameValueModel>? get enabledRequestHeaders =>
|
||||
getEnabledRows(requestHeaders, isHeaderEnabledList);
|
||||
@ -106,6 +108,7 @@ class RequestModel {
|
||||
int? responseStatus,
|
||||
String? message,
|
||||
ResponseModel? responseModel,
|
||||
bool? isWorking,
|
||||
}) {
|
||||
var headers = requestHeaders ?? this.requestHeaders;
|
||||
var params = requestParams ?? this.requestParams;
|
||||
@ -129,6 +132,7 @@ class RequestModel {
|
||||
responseStatus: responseStatus ?? this.responseStatus,
|
||||
message: message ?? this.message,
|
||||
responseModel: responseModel ?? this.responseModel,
|
||||
isWorking: isWorking ?? this.isWorking,
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -156,11 +156,20 @@ class CollectionStateNotifier
|
||||
}
|
||||
|
||||
Future<void> sendRequest(String id) async {
|
||||
ref.read(sentRequestIdStateProvider.notifier).state = id;
|
||||
ref.read(codePaneVisibleStateProvider.notifier).state = false;
|
||||
final defaultUriScheme =
|
||||
ref.read(settingsProvider.select((value) => value.defaultUriScheme));
|
||||
final defaultUriScheme = ref.read(
|
||||
settingsProvider.select(
|
||||
(value) => value.defaultUriScheme,
|
||||
),
|
||||
);
|
||||
|
||||
RequestModel requestModel = state![id]!;
|
||||
|
||||
// set current model's isWorking to true and update state
|
||||
var map = {...state!};
|
||||
map[id] = requestModel.copyWith(isWorking: true);
|
||||
state = map;
|
||||
|
||||
(http.Response?, Duration?, String?)? responseRec = await request(
|
||||
requestModel,
|
||||
defaultUriScheme: defaultUriScheme,
|
||||
@ -172,6 +181,7 @@ class CollectionStateNotifier
|
||||
newRequestModel = requestModel.copyWith(
|
||||
responseStatus: -1,
|
||||
message: responseRec.$3,
|
||||
isWorking: false,
|
||||
);
|
||||
} else {
|
||||
final responseModel = baseResponseModel.fromResponse(
|
||||
@ -183,10 +193,12 @@ class CollectionStateNotifier
|
||||
responseStatus: statusCode,
|
||||
message: kResponseCodeReasons[statusCode],
|
||||
responseModel: responseModel,
|
||||
isWorking: false,
|
||||
);
|
||||
}
|
||||
ref.read(sentRequestIdStateProvider.notifier).state = null;
|
||||
var map = {...state!};
|
||||
|
||||
// update state with response data
|
||||
map = {...state!};
|
||||
map[id] = newRequestModel;
|
||||
state = map;
|
||||
}
|
||||
|
@ -3,7 +3,6 @@ import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
|
||||
final navRailIndexStateProvider = StateProvider<int>((ref) => 0);
|
||||
final selectedIdEditStateProvider = StateProvider<String?>((ref) => null);
|
||||
final sentRequestIdStateProvider = StateProvider<String?>((ref) => null);
|
||||
final codePaneVisibleStateProvider = StateProvider<bool>((ref) => false);
|
||||
final saveDataStateProvider = StateProvider<bool>((ref) => false);
|
||||
final clearDataStateProvider = StateProvider<bool>((ref) => false);
|
||||
|
@ -9,13 +9,14 @@ class ResponsePane extends ConsumerWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final selectedId = ref.watch(selectedIdStateProvider);
|
||||
final sentRequestId = ref.watch(sentRequestIdStateProvider);
|
||||
final isWorking = ref.watch(
|
||||
selectedRequestModelProvider.select((value) => value?.isWorking)) ??
|
||||
false;
|
||||
final responseStatus = ref.watch(
|
||||
selectedRequestModelProvider.select((value) => value?.responseStatus));
|
||||
final message = ref
|
||||
.watch(selectedRequestModelProvider.select((value) => value?.message));
|
||||
if (sentRequestId != null && sentRequestId == selectedId) {
|
||||
if (isWorking) {
|
||||
return const SendingWidget();
|
||||
}
|
||||
if (responseStatus == null) {
|
||||
|
@ -1,4 +1,6 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:apidash/providers/collection_providers.dart';
|
||||
import 'package:apidash/consts.dart';
|
||||
import 'details_card/details_card.dart';
|
||||
import 'url_card.dart';
|
||||
@ -10,6 +12,7 @@ class RequestEditor extends StatelessWidget {
|
||||
Widget build(BuildContext context) {
|
||||
return const Column(
|
||||
children: [
|
||||
RequestEditorTopBar(),
|
||||
EditorPaneRequestURLCard(),
|
||||
kVSpacer10,
|
||||
Expanded(
|
||||
@ -19,3 +22,91 @@ class RequestEditor extends StatelessWidget {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class RequestEditorTopBar extends ConsumerWidget {
|
||||
const RequestEditorTopBar({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final id = ref.watch(selectedIdStateProvider);
|
||||
final name =
|
||||
ref.watch(selectedRequestModelProvider.select((value) => value?.name));
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(
|
||||
left: 12.0,
|
||||
top: 4.0,
|
||||
right: 8.0,
|
||||
bottom: 4.0,
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: Text(
|
||||
name ?? "",
|
||||
style: Theme.of(context).textTheme.bodyMedium,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
maxLines: 1,
|
||||
),
|
||||
),
|
||||
const SizedBox(
|
||||
width: 6,
|
||||
),
|
||||
SizedBox(
|
||||
width: 90,
|
||||
height: 24,
|
||||
child: FilledButton.tonalIcon(
|
||||
style: const ButtonStyle(
|
||||
padding: MaterialStatePropertyAll(EdgeInsets.zero),
|
||||
),
|
||||
onPressed: () {
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (context) {
|
||||
final controller =
|
||||
TextEditingController(text: name ?? "");
|
||||
controller.selection = TextSelection(
|
||||
baseOffset: 0, extentOffset: controller.text.length);
|
||||
return AlertDialog(
|
||||
title: const Text('Rename Request'),
|
||||
content: TextField(
|
||||
autofocus: true,
|
||||
controller: controller,
|
||||
decoration:
|
||||
const InputDecoration(hintText: "Enter new name"),
|
||||
),
|
||||
actions: <Widget>[
|
||||
OutlinedButton(
|
||||
onPressed: () {
|
||||
Navigator.pop(context);
|
||||
},
|
||||
child: const Text('CANCEL')),
|
||||
FilledButton(
|
||||
onPressed: () {
|
||||
final val = controller.text.trim();
|
||||
ref
|
||||
.read(collectionStateNotifierProvider
|
||||
.notifier)
|
||||
.update(id!, name: val);
|
||||
Navigator.pop(context);
|
||||
controller.dispose();
|
||||
},
|
||||
child: const Text('OK')),
|
||||
],
|
||||
);
|
||||
});
|
||||
},
|
||||
icon: const Icon(
|
||||
Icons.edit,
|
||||
size: 12,
|
||||
),
|
||||
label: Text(
|
||||
"Rename",
|
||||
style: Theme.of(context).textTheme.bodySmall,
|
||||
),
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -81,6 +81,11 @@ class URLTextField extends ConsumerWidget {
|
||||
.read(collectionStateNotifierProvider.notifier)
|
||||
.update(selectedId, url: value);
|
||||
},
|
||||
onFieldSubmitted: (value) {
|
||||
ref
|
||||
.read(collectionStateNotifierProvider.notifier)
|
||||
.sendRequest(selectedId);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -93,10 +98,11 @@ class SendButton extends ConsumerWidget {
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final selectedId = ref.watch(selectedIdStateProvider);
|
||||
final sentRequestId = ref.watch(sentRequestIdStateProvider);
|
||||
final isWorking = ref.watch(
|
||||
selectedRequestModelProvider.select((value) => value?.isWorking));
|
||||
|
||||
return SendRequestButton(
|
||||
selectedId: selectedId,
|
||||
sentRequestId: sentRequestId,
|
||||
isWorking: isWorking ?? false,
|
||||
onTap: () {
|
||||
ref
|
||||
.read(collectionStateNotifierProvider.notifier)
|
||||
|
@ -65,7 +65,7 @@ class SettingsPage extends ConsumerWidget {
|
||||
hoverColor: kColorTransparent,
|
||||
title: const Text('Default URI Scheme'),
|
||||
subtitle: Text(
|
||||
'api.foss42.com → ${settings.defaultUriScheme}://api.foss42.com'),
|
||||
'api.apidash.dev → ${settings.defaultUriScheme}://api.apidash.dev'),
|
||||
trailing: DropdownMenu(
|
||||
onSelected: (value) {
|
||||
ref
|
||||
|
@ -43,7 +43,7 @@ String getShortPath(String path) {
|
||||
var f = p.split(path);
|
||||
if (f.length > 2) {
|
||||
f = f.sublist(f.length - 2);
|
||||
return ".../${p.joinAll(f)}";
|
||||
return p.join("...", p.joinAll(f));
|
||||
}
|
||||
return path;
|
||||
}
|
||||
|
@ -2,6 +2,7 @@ Map<String, String> headers = {
|
||||
"Accept": "Specifies the media types that are acceptable for the response.",
|
||||
"Accept-Encoding":
|
||||
"Indicates the encoding methods the client can understand.",
|
||||
"Accept-Charset": "Specifies the character sets that are acceptable.",
|
||||
"Access-Control-Allow-Headers":
|
||||
"Specifies a list of HTTP headers that can be used in an actual request after a preflight request including the Access-Control-Request-Headers header is made.",
|
||||
"Access-Control-Allow-Methods":
|
||||
@ -41,11 +42,16 @@ Map<String, String> headers = {
|
||||
"Cross-Origin-Resource-Policy":
|
||||
"Controls how cross-origin requests for resources are handled.",
|
||||
"Date": "Indicates the date and time at which the message was sent.",
|
||||
"Device-Memory":
|
||||
"Indicates the approximate amount of device memory in gigabytes.",
|
||||
"DNT":
|
||||
"Informs websites whether the user's preference is to opt out of online tracking.",
|
||||
"Expect": "Indicates certain expectations that need to be met by the server.",
|
||||
"Expires":
|
||||
"Contains the date/time after which the response is considered expired",
|
||||
"Forwarded":
|
||||
"Contains information from the client-facing side of proxy servers that is altered or lost when a proxy is involved in the path of the request.",
|
||||
"From": "Contains an Internet email address for a human user who controls the requesting user agent.",
|
||||
"Host": "Specifies the domain name of the server and the port number.",
|
||||
"If-Match":
|
||||
"Used for conditional requests, allows the server to respond based on certain conditions.",
|
||||
@ -57,9 +63,15 @@ Map<String, String> headers = {
|
||||
"Used in conjunction with the Range header to conditionally request a partial resource.",
|
||||
"If-Unmodified-Since":
|
||||
"Used for conditional requests, allows the server to respond based on certain conditions.",
|
||||
"Keep-Alive":
|
||||
"Used to allow the connection to be reused for further requests.",
|
||||
"Location":
|
||||
"Indicates the URL a client should redirect to for further interaction.",
|
||||
"Max-Forwards":
|
||||
"Indicates the remaining number of times a request can be forwarded by proxies.",
|
||||
"Origin": "Specifies the origin of a cross-origin request.",
|
||||
"Proxy-Authorization":
|
||||
"Contains credentials for authenticating a client with a proxy server.",
|
||||
"Range":
|
||||
"Used to request only part of a resource, typically in the context of downloading large files.",
|
||||
"Referer":
|
||||
@ -68,10 +80,14 @@ Map<String, String> headers = {
|
||||
"Specifies how much information the browser should include in the Referer header when navigating to other pages.",
|
||||
"Retry-After":
|
||||
"Informs the client how long it should wait before making another request after a server has responded with a rate-limiting status code.",
|
||||
"Save-Data":
|
||||
"Indicates the client's preference for reduced data usage.",
|
||||
"Server": "Indicates the software used by the origin server.",
|
||||
"Strict-Transport-Security":
|
||||
"Instructs the browser to always use HTTPS for the given domain.",
|
||||
"TE": "Specifies the transfer encodings that are acceptable to the client.",
|
||||
"Upgrade-Insecure-Requests":
|
||||
"Instructs the browser to prefer secure connections when available.",
|
||||
"User-Agent":
|
||||
"Identifies the client software and version making the request.",
|
||||
"Via":
|
||||
|
@ -47,36 +47,37 @@ class CopyButton extends StatelessWidget {
|
||||
class SendRequestButton extends StatelessWidget {
|
||||
const SendRequestButton({
|
||||
super.key,
|
||||
required this.selectedId,
|
||||
required this.sentRequestId,
|
||||
required this.isWorking,
|
||||
required this.onTap,
|
||||
});
|
||||
|
||||
final String? selectedId;
|
||||
final String? sentRequestId;
|
||||
final bool isWorking;
|
||||
final void Function() onTap;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
bool disable = sentRequestId != null;
|
||||
return FilledButton(
|
||||
onPressed: disable ? null : onTap,
|
||||
onPressed: isWorking ? null : onTap,
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Text(
|
||||
disable
|
||||
? (selectedId == sentRequestId ? kLabelSending : kLabelBusy)
|
||||
: kLabelSend,
|
||||
style: kTextStyleButton,
|
||||
),
|
||||
if (!disable) kHSpacer10,
|
||||
if (!disable)
|
||||
const Icon(
|
||||
size: 16,
|
||||
Icons.send,
|
||||
),
|
||||
],
|
||||
children: isWorking
|
||||
? const [
|
||||
Text(
|
||||
kLabelSending,
|
||||
style: kTextStyleButton,
|
||||
),
|
||||
]
|
||||
: const [
|
||||
Text(
|
||||
kLabelSend,
|
||||
style: kTextStyleButton,
|
||||
),
|
||||
kHSpacer10,
|
||||
Icon(
|
||||
size: 16,
|
||||
Icons.send,
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
@ -97,6 +97,9 @@ class SidebarRequestCard extends StatelessWidget {
|
||||
onTapOutsideNameEditor?.call();
|
||||
//FocusScope.of(context).unfocus();
|
||||
},
|
||||
onFieldSubmitted: (value) {
|
||||
onTapOutsideNameEditor?.call();
|
||||
},
|
||||
onChanged: onChangedNameEditor,
|
||||
decoration: const InputDecoration(
|
||||
isCollapsed: true,
|
||||
|
53
lib/widgets/csv_previewer.dart
Normal file
53
lib/widgets/csv_previewer.dart
Normal file
@ -0,0 +1,53 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:csv/csv.dart';
|
||||
import 'error_message.dart';
|
||||
import '../consts.dart';
|
||||
|
||||
class CsvPreviewer extends StatelessWidget {
|
||||
const CsvPreviewer({super.key, required this.body});
|
||||
|
||||
final String body;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
try {
|
||||
final List<List<dynamic>> csvData =
|
||||
const CsvToListConverter().convert(body, eol: '\n');
|
||||
return SingleChildScrollView(
|
||||
scrollDirection: Axis.vertical,
|
||||
child: SingleChildScrollView(
|
||||
scrollDirection: Axis.horizontal,
|
||||
child: DataTable(
|
||||
columns: csvData[0]
|
||||
.map(
|
||||
(item) => DataColumn(
|
||||
label: Text(
|
||||
item.toString(),
|
||||
),
|
||||
),
|
||||
)
|
||||
.toList(),
|
||||
rows: csvData
|
||||
.skip(1)
|
||||
.map(
|
||||
(csvrow) => DataRow(
|
||||
cells: csvrow
|
||||
.map(
|
||||
(csvItem) => DataCell(
|
||||
Text(
|
||||
csvItem.toString(),
|
||||
),
|
||||
),
|
||||
)
|
||||
.toList(),
|
||||
),
|
||||
)
|
||||
.toList(),
|
||||
),
|
||||
),
|
||||
);
|
||||
} catch (e) {
|
||||
return const ErrorMessage(message: kCsvError);
|
||||
}
|
||||
}
|
||||
}
|
@ -46,32 +46,34 @@ class _FormDataFieldState extends State<FormDataField> {
|
||||
color: colorScheme.onSurface,
|
||||
),
|
||||
decoration: InputDecoration(
|
||||
hintStyle: kCodeStyle.copyWith(
|
||||
color: colorScheme.outline.withOpacity(
|
||||
hintStyle: kCodeStyle.copyWith(
|
||||
color: colorScheme.outline.withOpacity(
|
||||
kHintOpacity,
|
||||
),
|
||||
),
|
||||
hintText: widget.hintText,
|
||||
contentPadding: const EdgeInsets.only(bottom: 16),
|
||||
focusedBorder: UnderlineInputBorder(
|
||||
borderSide: BorderSide(
|
||||
color: colorScheme.primary.withOpacity(
|
||||
kHintOpacity,
|
||||
),
|
||||
),
|
||||
hintText: widget.hintText,
|
||||
focusedBorder: UnderlineInputBorder(
|
||||
borderSide: BorderSide(
|
||||
color: colorScheme.primary.withOpacity(
|
||||
kHintOpacity,
|
||||
),
|
||||
),
|
||||
),
|
||||
enabledBorder: UnderlineInputBorder(
|
||||
borderSide: BorderSide(
|
||||
color: colorScheme.surfaceVariant,
|
||||
),
|
||||
enabledBorder: UnderlineInputBorder(
|
||||
borderSide: BorderSide(
|
||||
color: colorScheme.surfaceVariant,
|
||||
),
|
||||
),
|
||||
suffixIcon: DropdownButtonFormData(
|
||||
formDataType: widget.formDataType,
|
||||
onChanged: (p0) {
|
||||
if (widget.onFormDataTypeChanged != null) {
|
||||
widget.onFormDataTypeChanged!(p0);
|
||||
}
|
||||
},
|
||||
)),
|
||||
),
|
||||
suffixIcon: DropdownButtonFormData(
|
||||
formDataType: widget.formDataType,
|
||||
onChanged: (p0) {
|
||||
if (widget.onFormDataTypeChanged != null) {
|
||||
widget.onFormDataTypeChanged!(p0);
|
||||
}
|
||||
},
|
||||
),
|
||||
),
|
||||
onChanged: widget.onChanged,
|
||||
),
|
||||
),
|
||||
|
@ -1,6 +1,6 @@
|
||||
import 'package:apidash/consts.dart';
|
||||
import 'package:apidash/utils/header_utils.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:apidash/consts.dart';
|
||||
import 'package:flutter_typeahead/flutter_typeahead.dart';
|
||||
|
||||
class HeaderField extends StatefulWidget {
|
||||
@ -41,6 +41,7 @@ class _HeaderFieldState extends State<HeaderField> {
|
||||
|
||||
@override
|
||||
void didUpdateWidget(HeaderField oldWidget) {
|
||||
super.didUpdateWidget(oldWidget);
|
||||
if (oldWidget.initialValue != widget.initialValue) {
|
||||
controller.text = widget.initialValue ?? "";
|
||||
controller.selection =
|
||||
@ -54,8 +55,8 @@ class _HeaderFieldState extends State<HeaderField> {
|
||||
return TypeAheadField(
|
||||
key: Key(widget.keyId),
|
||||
hideOnEmpty: true,
|
||||
minCharsForSuggestions: 1,
|
||||
onSuggestionSelected: (value) {
|
||||
controller: controller,
|
||||
onSelected: (value) {
|
||||
setState(() {
|
||||
controller.text = value;
|
||||
});
|
||||
@ -68,19 +69,17 @@ class _HeaderFieldState extends State<HeaderField> {
|
||||
);
|
||||
},
|
||||
suggestionsCallback: headerSuggestionCallback,
|
||||
suggestionsBoxDecoration: suggestionBoxDecorations(context),
|
||||
textFieldConfiguration: TextFieldConfiguration(
|
||||
decorationBuilder: (context, child) =>
|
||||
suggestionBoxDecorations(context, child, colorScheme),
|
||||
constraints: const BoxConstraints(maxHeight: 400),
|
||||
builder: (context, controller, focusNode) => TextField(
|
||||
onChanged: widget.onChanged,
|
||||
controller: controller,
|
||||
style: kCodeStyle.copyWith(
|
||||
color: colorScheme.onSurface,
|
||||
),
|
||||
focusNode: focusNode,
|
||||
style: kCodeStyle.copyWith(color: colorScheme.onSurface),
|
||||
decoration: InputDecoration(
|
||||
hintStyle: kCodeStyle.copyWith(
|
||||
color: colorScheme.outline.withOpacity(
|
||||
kHintOpacity,
|
||||
),
|
||||
),
|
||||
color: colorScheme.outline.withOpacity(kHintOpacity)),
|
||||
hintText: widget.hintText,
|
||||
focusedBorder: UnderlineInputBorder(
|
||||
borderSide: BorderSide(
|
||||
@ -99,22 +98,26 @@ class _HeaderFieldState extends State<HeaderField> {
|
||||
);
|
||||
}
|
||||
|
||||
SuggestionsBoxDecoration suggestionBoxDecorations(BuildContext context) {
|
||||
return SuggestionsBoxDecoration(
|
||||
elevation: 4,
|
||||
constraints: const BoxConstraints(maxHeight: 400),
|
||||
shape: RoundedRectangleBorder(
|
||||
side: BorderSide(
|
||||
color: Theme.of(context).dividerColor,
|
||||
width: 1.2,
|
||||
Theme suggestionBoxDecorations(
|
||||
BuildContext context, Widget child, ColorScheme colorScheme) {
|
||||
return Theme(
|
||||
data: ThemeData(colorScheme: colorScheme),
|
||||
child: Material(
|
||||
elevation: 4,
|
||||
shape: RoundedRectangleBorder(
|
||||
side: BorderSide(color: Theme.of(context).dividerColor, width: 1.2),
|
||||
borderRadius: const BorderRadius.vertical(bottom: Radius.circular(8)),
|
||||
),
|
||||
borderRadius: const BorderRadius.vertical(bottom: Radius.circular(8)),
|
||||
clipBehavior: Clip.hardEdge,
|
||||
child: child,
|
||||
),
|
||||
clipBehavior: Clip.hardEdge,
|
||||
);
|
||||
}
|
||||
|
||||
Future<List<String>> headerSuggestionCallback(String pattern) async {
|
||||
Future<List<String>?> headerSuggestionCallback(String pattern) async {
|
||||
if (pattern.isEmpty) {
|
||||
return null;
|
||||
}
|
||||
return getHeaderSuggestions(pattern);
|
||||
}
|
||||
}
|
||||
|
@ -154,6 +154,7 @@ class _JsonPreviewerState extends State<JsonPreviewer> {
|
||||
|
||||
@override
|
||||
void didUpdateWidget(JsonPreviewer oldWidget) {
|
||||
super.didUpdateWidget(oldWidget);
|
||||
if (oldWidget.code != widget.code) {
|
||||
store.buildNodes(widget.code, areAllCollapsed: true);
|
||||
store.expandAll();
|
||||
|
@ -9,9 +9,12 @@ class CustomMarkdown extends StatelessWidget {
|
||||
super.key,
|
||||
required this.data,
|
||||
this.padding = const EdgeInsets.all(16.0),
|
||||
this.onTapLink,
|
||||
});
|
||||
|
||||
final String data;
|
||||
final EdgeInsets padding;
|
||||
final void Function(String text, String? href, String title)? onTapLink;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
@ -25,9 +28,10 @@ class CustomMarkdown extends StatelessWidget {
|
||||
data: data,
|
||||
selectable: true,
|
||||
extensionSet: md.ExtensionSet.gitHubFlavored,
|
||||
onTapLink: (text, href, title) {
|
||||
launchUrl(Uri.parse(href ?? ""));
|
||||
},
|
||||
onTapLink: onTapLink ??
|
||||
(text, href, title) {
|
||||
launchUrl(Uri.parse(href ?? ""));
|
||||
},
|
||||
builders: {
|
||||
"inlineButton": InlineButton(),
|
||||
},
|
||||
|
@ -7,6 +7,7 @@ import 'package:vector_graphics_compiler/vector_graphics_compiler.dart';
|
||||
import 'error_message.dart';
|
||||
import 'uint8_audio_player.dart';
|
||||
import 'json_previewer.dart';
|
||||
import 'csv_previewer.dart';
|
||||
import '../consts.dart';
|
||||
|
||||
class Previewer extends StatefulWidget {
|
||||
@ -81,6 +82,9 @@ class _PreviewerState extends State<Previewer> {
|
||||
},
|
||||
);
|
||||
}
|
||||
if (widget.type == kTypeText && widget.subtype == kSubTypeCsv) {
|
||||
return CsvPreviewer(body: widget.body);
|
||||
}
|
||||
if (widget.type == kTypeVideo) {
|
||||
// TODO: Video Player
|
||||
}
|
||||
|
@ -7,11 +7,13 @@ class URLField extends StatelessWidget {
|
||||
required this.selectedId,
|
||||
this.initialValue,
|
||||
this.onChanged,
|
||||
this.onFieldSubmitted,
|
||||
});
|
||||
|
||||
final String selectedId;
|
||||
final String? initialValue;
|
||||
final void Function(String)? onChanged;
|
||||
final void Function(String)? onFieldSubmitted;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
@ -29,6 +31,7 @@ class URLField extends StatelessWidget {
|
||||
border: InputBorder.none,
|
||||
),
|
||||
onChanged: onChanged,
|
||||
onFieldSubmitted: onFieldSubmitted,
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -95,7 +98,6 @@ class JsonSearchField extends StatelessWidget {
|
||||
controller: controller,
|
||||
onChanged: onChanged,
|
||||
style: kCodeStyle,
|
||||
cursorHeight: 18,
|
||||
decoration: const InputDecoration(
|
||||
isDense: true,
|
||||
border: InputBorder.none,
|
||||
|
Reference in New Issue
Block a user