TF-2764 Support press Escape shortcut key on keyboard to close viewer
This commit is contained in:
@@ -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';
|
||||||
|
|||||||
Reference in New Issue
Block a user