TF-2764 Support press Escape shortcut key on keyboard to close viewer

This commit is contained in:
dab246
2024-05-22 16:32:40 +07:00
committed by Dat H. Pham
parent 9351ebfd33
commit 652f15e356
2 changed files with 160 additions and 144 deletions
@@ -60,6 +60,7 @@ class _PDFViewerState extends State<PDFViewer> {
final ValueNotifier<Object?> _pdfViewStateNotifier = ValueNotifier<Object?>(null); final ValueNotifier<Object?> _pdfViewStateNotifier = ValueNotifier<Object?>(null);
final DeviceInfoPlugin _deviceInfoPlugin = Get.find<DeviceInfoPlugin>(); final DeviceInfoPlugin _deviceInfoPlugin = Get.find<DeviceInfoPlugin>();
final FocusNode _keyboardFocusNode = FocusNode();
@override @override
void initState() { void initState() {
@@ -116,161 +117,167 @@ class _PDFViewerState extends State<PDFViewer> {
_downloadInteractorStreamSubscription?.cancel(); _downloadInteractorStreamSubscription?.cancel();
_downloadInteractorStreamSubscription = null; _downloadInteractorStreamSubscription = null;
_downloadAttachmentCancelToken = null; _downloadAttachmentCancelToken = null;
_keyboardFocusNode.dispose();
super.dispose(); super.dispose();
} }
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Center( return RawKeyboardListener(
child: Stack( focusNode: _keyboardFocusNode,
children: [ autofocus: true,
Center( onKey: _handleKeyboardEventListener,
child: SizedBox( child: Center(
width: double.infinity, child: Stack(
child: ValueListenableBuilder( children: [
valueListenable: _pdfViewStateNotifier, Center(
builder: (context, viewState, widget) { child: SizedBox(
if (viewState is DownloadAttachmentForWebSuccess) { width: double.infinity,
return PdfViewer.openData( child: ValueListenableBuilder(
viewState.bytes, valueListenable: _pdfViewStateNotifier,
viewerController: _pdfViewerController, builder: (context, viewState, widget) {
onError: (error) { if (viewState is DownloadAttachmentForWebSuccess) {
logError('_PDFViewerState::build:openData:onError:: $error'); return PdfViewer.openData(
_pdfViewStateNotifier.value = DownloadAttachmentForWebFailure(exception: error); viewState.bytes,
}, viewerController: _pdfViewerController,
params: PdfViewerParams( onError: (error) {
panAxis: PanAxis.vertical, logError('_PDFViewerState::build:openData:onError:: $error');
scrollByMouseWheel: 0.5, _pdfViewStateNotifier.value = DownloadAttachmentForWebFailure(exception: error);
layoutPages: (viewSize, pages) {
List<Rect> rect = [];
final viewWidth = viewSize.width;
final viewHeight = viewSize.height;
final maxHeight = pages.fold<double>(0.0, (maxHeight, page) => max(maxHeight, page.height));
final maxWidth = pages.fold<double>(0.0, (maxWidth, page) => max(maxWidth, page.width));
final ratio = viewHeight / max(maxHeight, maxWidth);
log('_PDFViewerState::build: viewWidth = $viewWidth | viewHeight = $viewHeight | maxHeight = $maxHeight | ratio = $ratio');
var top = 0.0;
double padding = 16.0;
for (var page in pages) {
final width = page.width * ratio;
final height = page.height * ratio;
final left = viewWidth > viewHeight ? (viewWidth / 2) - (width / 2) : 0.0;
rect.add(Rect.fromLTWH(left, top, width, height));
top += height + padding;
}
return rect;
}, },
onClickOutSidePageViewer: _closeView params: PdfViewerParams(
), panAxis: PanAxis.vertical,
); scrollByMouseWheel: 0.5,
} else if (viewState is DownloadingAttachmentForWeb) { layoutPages: (viewSize, pages) {
return CircularPercentIndicator( List<Rect> rect = [];
percent: viewState.progress / 100, final viewWidth = viewSize.width;
progressColor: AppColor.primaryColor, final viewHeight = viewSize.height;
lineWidth: 4.0, final maxHeight = pages.fold<double>(0.0, (maxHeight, page) => max(maxHeight, page.height));
backgroundColor: Colors.white, final maxWidth = pages.fold<double>(0.0, (maxWidth, page) => max(maxWidth, page.width));
radius: 40, final ratio = viewHeight / max(maxHeight, maxWidth);
center: Text( log('_PDFViewerState::build: viewWidth = $viewWidth | viewHeight = $viewHeight | maxHeight = $maxHeight | ratio = $ratio');
'${viewState.progress.round()}%', var top = 0.0;
style: Theme.of(context).textTheme.labelMedium?.copyWith( double padding = 16.0;
color: Colors.white, for (var page in pages) {
fontSize: 17, final width = page.width * ratio;
fontWeight: FontWeight.w500 final height = page.height * ratio;
final left = viewWidth > viewHeight ? (viewWidth / 2) - (width / 2) : 0.0;
rect.add(Rect.fromLTWH(left, top, width, height));
top += height + padding;
}
return rect;
},
onClickOutSidePageViewer: _closeView
), ),
),
);
} else if (viewState is DownloadAttachmentForWebFailure) {
return Center(
child: Text(
AppLocalizations.of(context).noPreviewAvailable,
style: Theme.of(context).textTheme.labelMedium?.copyWith(
color: Colors.white,
fontSize: 17,
fontWeight: FontWeight.w500
),
),
);
} else {
return const Center(
child: CircleLoadingWidget(
size: 80,
strokeWidth: 4.0,
)
);
}
},
)
),
),
Align(
alignment: AlignmentDirectional.topCenter,
child: ValueListenableBuilder(
valueListenable: _pdfViewStateNotifier,
builder: (context, viewState, child) {
if (viewState is DownloadAttachmentForWebSuccess) {
return FutureBuilder<BaseDeviceInfo>(
future: _deviceInfoPlugin.deviceInfo,
builder: (context, deviceInfo) {
BrowserName? browserName;
if (deviceInfo.hasData && deviceInfo.data is WebBrowserInfo) {
browserName = (deviceInfo.data as WebBrowserInfo).browserName;
}
return TopBarPDFViewer(
attachment: widget.attachment,
downloadAction: () => widget.downloadAction?.call(
viewState.bytes,
widget.attachment.generateFileName()
),
printAction: isBrowserSupportedPrinting(browserName) ?
() => widget.printAction?.call(viewState.bytes, widget.attachment.generateFileName())
: null,
); );
}); } else if (viewState is DownloadingAttachmentForWeb) {
} return CircularPercentIndicator(
return child ?? const SizedBox.shrink(); percent: viewState.progress / 100,
}, progressColor: AppColor.primaryColor,
child: TopBarPDFViewer( lineWidth: 4.0,
attachment: widget.attachment, backgroundColor: Colors.white,
closeAction: () { radius: 40,
_downloadAttachmentCancelToken?.cancel(); center: Text(
Navigator.maybeOf(context)?.pop(); '${viewState.progress.round()}%',
}, style: Theme.of(context).textTheme.labelMedium?.copyWith(
color: Colors.white,
fontSize: 17,
fontWeight: FontWeight.w500
),
),
);
} else if (viewState is DownloadAttachmentForWebFailure) {
return Center(
child: Text(
AppLocalizations.of(context).noPreviewAvailable,
style: Theme.of(context).textTheme.labelMedium?.copyWith(
color: Colors.white,
fontSize: 17,
fontWeight: FontWeight.w500
),
),
);
} else {
return const Center(
child: CircleLoadingWidget(
size: 80,
strokeWidth: 4.0,
)
);
}
},
)
), ),
), ),
), Align(
ValueListenableBuilder( alignment: AlignmentDirectional.topCenter,
valueListenable: _pdfViewerController, child: ValueListenableBuilder(
builder: (_, __, ___) { valueListenable: _pdfViewStateNotifier,
if (_pdfViewerController.isReady) { builder: (context, viewState, child) {
return Align( if (viewState is DownloadAttachmentForWebSuccess) {
alignment: AlignmentDirectional.bottomCenter, return FutureBuilder<BaseDeviceInfo>(
child: PaginationPDFViewer( future: _deviceInfoPlugin.deviceInfo,
pdfViewerController: _pdfViewerController, builder: (context, deviceInfo) {
), BrowserName? browserName;
); if (deviceInfo.hasData && deviceInfo.data is WebBrowserInfo) {
} else { browserName = (deviceInfo.data as WebBrowserInfo).browserName;
return const SizedBox.shrink(); }
return TopBarPDFViewer(
attachment: widget.attachment,
downloadAction: () => widget.downloadAction?.call(
viewState.bytes,
widget.attachment.generateFileName()
),
printAction: isBrowserSupportedPrinting(browserName) ?
() => widget.printAction?.call(viewState.bytes, widget.attachment.generateFileName())
: null,
);
});
}
return child ?? const SizedBox.shrink();
},
child: TopBarPDFViewer(
attachment: widget.attachment,
closeAction: () {
_downloadAttachmentCancelToken?.cancel();
Navigator.maybeOf(context)?.pop();
},
),
),
),
ValueListenableBuilder(
valueListenable: _pdfViewerController,
builder: (_, __, ___) {
if (_pdfViewerController.isReady) {
return Align(
alignment: AlignmentDirectional.bottomCenter,
child: PaginationPDFViewer(
pdfViewerController: _pdfViewerController,
),
);
} else {
return const SizedBox.shrink();
}
} }
} ),
), ValueListenableBuilder(
ValueListenableBuilder( valueListenable: _pdfViewerController,
valueListenable: _pdfViewerController, builder: (context, m, child) {
builder: (context, m, child) { if (!_pdfViewerController.isReady) return Container();
if (!_pdfViewerController.isReady) return Container(); final v = _pdfViewerController.viewRect;
final v = _pdfViewerController.viewRect; final all = _pdfViewerController.fullSize;
final all = _pdfViewerController.fullSize; final top = v.top / all.height * v.height;
final top = v.top / all.height * v.height; final height = v.height / all.height * v.height;
final height = v.height / all.height * v.height; return Positioned(
return Positioned( right: 0,
right: 0, top: top,
top: top, height: height,
height: height, width: 8,
width: 8, child: Container(color: AppColor.colorTextBody),
child: Container(color: AppColor.colorTextBody), );
); },
}, ),
), ],
], ),
), ),
); );
} }
@@ -279,4 +286,10 @@ class _PDFViewerState extends State<PDFViewer> {
_downloadAttachmentCancelToken?.cancel(); _downloadAttachmentCancelToken?.cancel();
Navigator.maybeOf(context)?.pop(); Navigator.maybeOf(context)?.pop();
} }
void _handleKeyboardEventListener(RawKeyEvent event) {
if (event is RawKeyDownEvent && event.logicalKey == LogicalKeyboardKey.escape) {
_closeView();
}
}
} }
@@ -8,6 +8,9 @@ import 'package:jmap_dart_client/jmap/account_id.dart';
import 'package:jmap_dart_client/jmap/core/capability/calendar_event_capability.dart'; import 'package:jmap_dart_client/jmap/core/capability/calendar_event_capability.dart';
import 'package:jmap_dart_client/jmap/core/capability/capability_identifier.dart'; import 'package:jmap_dart_client/jmap/core/capability/capability_identifier.dart';
import 'package:jmap_dart_client/jmap/core/id.dart'; import 'package:jmap_dart_client/jmap/core/id.dart';
import 'package:jmap_dart_client/jmap/core/session/session.dart';
import 'package:jmap_dart_client/jmap/core/state.dart';
import 'package:jmap_dart_client/jmap/core/user_name.dart';
import 'package:jmap_dart_client/jmap/mail/calendar/calendar_event.dart'; import 'package:jmap_dart_client/jmap/mail/calendar/calendar_event.dart';
import 'package:jmap_dart_client/jmap/mail/calendar/reply/calendar_event_accept_response.dart'; import 'package:jmap_dart_client/jmap/mail/calendar/reply/calendar_event_accept_response.dart';
import 'package:jmap_dart_client/jmap/mail/email/email.dart'; import 'package:jmap_dart_client/jmap/mail/email/email.dart';