<feat: New project structure>

<feat: New release>
This commit is contained in:
Alessandro Autiero
2023-09-02 15:34:15 +02:00
parent 64b33102f4
commit b41e22adeb
953 changed files with 1373072 additions and 0 deletions

View File

@@ -0,0 +1,256 @@
import 'package:clipboard/clipboard.dart';
import 'package:fluent_ui/fluent_ui.dart' hide showDialog;
import 'package:fluent_ui/fluent_ui.dart' as fluent show showDialog;
import 'package:reboot_launcher/src/dialog/message.dart';
import 'package:reboot_launcher/src/page/home_page.dart';
import 'dialog_button.dart';
Future<T?> showDialog<T extends Object?>({required WidgetBuilder builder}) => fluent.showDialog(
context: pageKey.currentContext!,
useRootNavigator: false,
builder: builder
);
abstract class AbstractDialog extends StatelessWidget {
const AbstractDialog({Key? key}) : super(key: key);
@override
Widget build(BuildContext context);
}
class GenericDialog extends AbstractDialog {
final Widget header;
final List<DialogButton> buttons;
final EdgeInsets? padding;
const GenericDialog({super.key, required this.header, required this.buttons, this.padding});
@override
Widget build(BuildContext context) => ContentDialog(
style: ContentDialogThemeData(
padding: padding ?? const EdgeInsets.only(left: 20, right: 20, top: 15.0, bottom: 5.0)
),
content: header,
actions: buttons
);
}
class FormDialog extends AbstractDialog {
final Widget content;
final List<DialogButton> buttons;
const FormDialog({super.key, required this.content, required this.buttons});
@override
Widget build(BuildContext context) {
return Form(
child: Builder(
builder: (context) {
var parsed = buttons.map((entry) => _createFormButton(entry, context)).toList();
return GenericDialog(
header: content,
buttons: parsed
);
}
)
);
}
DialogButton _createFormButton(DialogButton entry, BuildContext context) {
if (entry.type != ButtonType.primary) {
return entry;
}
return DialogButton(
text: entry.text,
type: entry.type,
onTap: () {
if(!Form.of(context).validate()) {
return;
}
entry.onTap?.call();
}
);
}
}
class InfoDialog extends AbstractDialog {
final String text;
final List<DialogButton>? buttons;
const InfoDialog({required this.text, this.buttons, super.key});
InfoDialog.ofOnly({required this.text, required DialogButton button, super.key})
: buttons = [button];
@override
Widget build(BuildContext context) {
return GenericDialog(
header: SizedBox(
width: double.infinity,
child: Text(text, textAlign: TextAlign.center)
),
buttons: buttons ?? [_createDefaultButton()],
padding: const EdgeInsets.only(left: 20, right: 20, top: 15.0, bottom: 15.0)
);
}
DialogButton _createDefaultButton() {
return DialogButton(
text: "Close",
type: ButtonType.only
);
}
}
class ProgressDialog extends AbstractDialog {
final String text;
final Function()? onStop;
const ProgressDialog({required this.text, this.onStop, super.key});
@override
Widget build(BuildContext context) {
return GenericDialog(
header: InfoLabel(
label: text,
child: Container(
padding: const EdgeInsets.only(bottom: 16.0),
width: double.infinity,
child: const ProgressBar()
),
),
buttons: [
DialogButton(
text: "Close",
type: ButtonType.only,
onTap: onStop
)
]
);
}
}
class FutureBuilderDialog extends AbstractDialog {
final Future future;
final String loadingMessage;
final Widget successfulBody;
final Widget unsuccessfulBody;
final Function(Object) errorMessageBuilder;
final Function()? onError;
final bool closeAutomatically;
const FutureBuilderDialog(
{super.key,
required this.future,
required this.loadingMessage,
required this.successfulBody,
required this.unsuccessfulBody,
required this.errorMessageBuilder,
this.onError,
this.closeAutomatically = false});
static Container ofMessage(String message) {
return Container(
width: double.infinity,
padding: const EdgeInsets.only(bottom: 16.0),
child: Text(
message,
textAlign: TextAlign.center
)
);
}
@override
Widget build(BuildContext context) {
return FutureBuilder(
future: future,
builder: (context, snapshot) => GenericDialog(
header: _createBody(context, snapshot),
buttons: [_createButton(context, snapshot)]
)
);
}
Widget _createBody(BuildContext context, AsyncSnapshot snapshot){
if (snapshot.hasError) {
onError?.call();
return ofMessage(errorMessageBuilder(snapshot.error!));
}
if(snapshot.connectionState == ConnectionState.done && (snapshot.data == null || (snapshot.data is bool && !snapshot.data))){
return unsuccessfulBody;
}
if (!snapshot.hasData) {
return _createLoadingBody();
}
if(closeAutomatically){
WidgetsBinding.instance
.addPostFrameCallback((_) => Navigator.of(context).pop(true));
return _createLoadingBody();
}
return successfulBody;
}
InfoLabel _createLoadingBody() {
return InfoLabel(
label: loadingMessage,
child: Container(
padding: const EdgeInsets.only(bottom: 16.0),
width: double.infinity,
child: const ProgressBar()),
);
}
DialogButton _createButton(BuildContext context, AsyncSnapshot snapshot){
return DialogButton(
text: snapshot.hasData
|| snapshot.hasError
|| (snapshot.connectionState == ConnectionState.done && snapshot.data == null) ? "Close" : "Stop",
type: ButtonType.only,
onTap: () => Navigator.of(context).pop(!snapshot.hasError && snapshot.hasData)
);
}
}
class ErrorDialog extends AbstractDialog {
final Object exception;
final StackTrace? stackTrace;
final Function(Object) errorMessageBuilder;
const ErrorDialog({super.key, required this.exception, required this.errorMessageBuilder, this.stackTrace});
static DialogButton createCopyErrorButton({required Object error, required StackTrace? stackTrace, required Function() onClick, ButtonType type = ButtonType.primary}) => DialogButton(
text: "Copy error",
type: type,
onTap: () async {
FlutterClipboard.controlC("An error occurred: $error\nStacktrace:\n $stackTrace");
showMessage("Copied error to clipboard");
onClick();
},
);
@override
Widget build(BuildContext context) {
return InfoDialog(
text: errorMessageBuilder(exception),
buttons: [
DialogButton(
type: stackTrace == null ? ButtonType.only : ButtonType.secondary
),
if(stackTrace != null)
createCopyErrorButton(
error: exception,
stackTrace: stackTrace,
onClick: () => Navigator.pop(context)
)
],
);
}
}

View File

@@ -0,0 +1,55 @@
import 'package:fluent_ui/fluent_ui.dart' hide showDialog;
class DialogButton extends StatefulWidget {
final String? text;
final Function()? onTap;
final ButtonType type;
const DialogButton(
{Key? key,
this.text,
this.onTap,
required this.type})
: assert(type != ButtonType.primary || onTap != null,
"OnTap handler cannot be null for primary buttons"),
assert(type != ButtonType.primary || text != null,
"Text cannot be null for primary buttons"),
super(key: key);
@override
State<DialogButton> createState() => _DialogButtonState();
}
class _DialogButtonState extends State<DialogButton> {
@override
Widget build(BuildContext context) => widget.type == ButtonType.only ? _onlyButton : _button;
SizedBox get _onlyButton => SizedBox(
width: double.infinity,
child: _button
);
Widget get _button => widget.type == ButtonType.primary ? _primaryButton : _secondaryButton;
Widget get _primaryButton {
return Button(
onPressed: widget.onTap!,
child: Text(widget.text!),
);
}
Widget get _secondaryButton {
return Button(
onPressed: widget.onTap ?? _onDefaultSecondaryActionTap,
child: Text(widget.text ?? "Close"),
);
}
void _onDefaultSecondaryActionTap() => Navigator.of(context).pop(null);
}
enum ButtonType {
primary,
secondary,
only
}

View File

@@ -0,0 +1,38 @@
import 'package:fluent_ui/fluent_ui.dart' hide showDialog;
import 'package:reboot_launcher/src/page/home_page.dart';
import 'package:sync/semaphore.dart';
Semaphore _semaphore = Semaphore();
OverlayEntry? _lastOverlay;
void showMessage(String text, {InfoBarSeverity severity = InfoBarSeverity.info, bool loading = false, Duration? duration = snackbarShortDuration}) {
try {
_semaphore.acquire();
if(_lastOverlay?.mounted == true) {
_lastOverlay?.remove();
}
var pageIndexValue = pageIndex.value;
_lastOverlay = showSnackbar(
pageKey.currentContext!,
InfoBar(
title: Text(text),
isLong: true,
isIconVisible: true,
content: SizedBox(
width: double.infinity,
child: loading ? const ProgressBar() : const SizedBox()
),
severity: severity
),
margin: EdgeInsets.only(
left: 330.0,
right: 16.0,
bottom: pageIndexValue == 0 || pageIndexValue == 1 || pageIndexValue == 3 || pageIndexValue == 4 ? 72 : 16
),
duration: duration
);
}finally {
_semaphore.release();
}
}