feat: add multi split view to display sidebars
This commit is contained in:
parent
1acd95fc94
commit
0969ec2931
|
@ -6,6 +6,7 @@ import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
import 'package:pdf_signature/l10n/app_localizations.dart';
|
import 'package:pdf_signature/l10n/app_localizations.dart';
|
||||||
import 'package:printing/printing.dart' as printing;
|
import 'package:printing/printing.dart' as printing;
|
||||||
import 'package:pdfrx/pdfrx.dart';
|
import 'package:pdfrx/pdfrx.dart';
|
||||||
|
import 'package:multi_split_view/multi_split_view.dart';
|
||||||
|
|
||||||
import '../../../../data/services/export_providers.dart';
|
import '../../../../data/services/export_providers.dart';
|
||||||
import '../view_model/view_model.dart';
|
import '../view_model/view_model.dart';
|
||||||
|
@ -14,7 +15,6 @@ import 'pdf_toolbar.dart';
|
||||||
import 'pdf_page_area.dart';
|
import 'pdf_page_area.dart';
|
||||||
import 'pages_sidebar.dart';
|
import 'pages_sidebar.dart';
|
||||||
import 'signatures_sidebar.dart';
|
import 'signatures_sidebar.dart';
|
||||||
// adjustments are available via ImageEditorDialog
|
|
||||||
|
|
||||||
class PdfSignatureHomePage extends ConsumerStatefulWidget {
|
class PdfSignatureHomePage extends ConsumerStatefulWidget {
|
||||||
const PdfSignatureHomePage({super.key});
|
const PdfSignatureHomePage({super.key});
|
||||||
|
@ -30,7 +30,17 @@ class _PdfSignatureHomePageState extends ConsumerState<PdfSignatureHomePage> {
|
||||||
bool _showPagesSidebar = true;
|
bool _showPagesSidebar = true;
|
||||||
bool _showSignaturesSidebar = true;
|
bool _showSignaturesSidebar = true;
|
||||||
int _zoomLevel = 100; // percentage for display only
|
int _zoomLevel = 100; // percentage for display only
|
||||||
// No split view controller; using a simple Row layout.
|
|
||||||
|
// Split view controller to manage resizable sidebars without remounting the center area.
|
||||||
|
late final MultiSplitViewController _splitController;
|
||||||
|
late final List<Area> _areas;
|
||||||
|
double _lastPagesWidth = 160;
|
||||||
|
double _lastSignaturesWidth = 220;
|
||||||
|
// Configurable sidebar constraints
|
||||||
|
final double _pagesMin = 100;
|
||||||
|
final double _pagesMax = 250;
|
||||||
|
final double _signaturesMin = 140;
|
||||||
|
final double _signaturesMax = 250;
|
||||||
|
|
||||||
// Exposed for tests to trigger the invalid-file SnackBar without UI.
|
// Exposed for tests to trigger the invalid-file SnackBar without UI.
|
||||||
@visibleForTesting
|
@visibleForTesting
|
||||||
|
@ -57,8 +67,6 @@ class _PdfSignatureHomePageState extends ConsumerState<PdfSignatureHomePage> {
|
||||||
ref.read(pdfProvider.notifier).jumpTo(page);
|
ref.read(pdfProvider.notifier).jumpTo(page);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Zoom is managed by pdfrx viewer (Ctrl +/- etc.). No custom zoom here.
|
|
||||||
|
|
||||||
Future<Uint8List?> _loadSignatureFromFile() async {
|
Future<Uint8List?> _loadSignatureFromFile() async {
|
||||||
final typeGroup = const fs.XTypeGroup(
|
final typeGroup = const fs.XTypeGroup(
|
||||||
label: 'Image',
|
label: 'Image',
|
||||||
|
@ -76,8 +84,6 @@ class _PdfSignatureHomePageState extends ConsumerState<PdfSignatureHomePage> {
|
||||||
return bytes;
|
return bytes;
|
||||||
}
|
}
|
||||||
|
|
||||||
// _createNewSignature was removed as the toolbar no longer exposes this action.
|
|
||||||
|
|
||||||
void _confirmSignature() {
|
void _confirmSignature() {
|
||||||
ref.read(signatureProvider.notifier).confirmCurrentSignature(ref);
|
ref.read(signatureProvider.notifier).confirmCurrentSignature(ref);
|
||||||
}
|
}
|
||||||
|
@ -252,7 +258,93 @@ class _PdfSignatureHomePageState extends ConsumerState<PdfSignatureHomePage> {
|
||||||
return name;
|
return name;
|
||||||
}
|
}
|
||||||
|
|
||||||
// No initState/dispose needed for a controller.
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
// Build areas once with builders; keep these instances stable.
|
||||||
|
_areas = [
|
||||||
|
Area(
|
||||||
|
size: _lastPagesWidth,
|
||||||
|
min: _pagesMin,
|
||||||
|
max: _pagesMax,
|
||||||
|
builder:
|
||||||
|
(context, area) => Offstage(
|
||||||
|
offstage: !_showPagesSidebar,
|
||||||
|
child: const PagesSidebar(),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Area(
|
||||||
|
flex: 1,
|
||||||
|
builder:
|
||||||
|
(context, area) => RepaintBoundary(
|
||||||
|
child: PdfPageArea(
|
||||||
|
key: const ValueKey('pdf_page_area'),
|
||||||
|
pageSize: _pageSize,
|
||||||
|
viewerController: _viewerController,
|
||||||
|
onDragSignature: _onDragSignature,
|
||||||
|
onResizeSignature: _onResizeSignature,
|
||||||
|
onConfirmSignature: _confirmSignature,
|
||||||
|
onClearActiveOverlay:
|
||||||
|
() =>
|
||||||
|
ref
|
||||||
|
.read(signatureProvider.notifier)
|
||||||
|
.clearActiveOverlay(),
|
||||||
|
onSelectPlaced: _onSelectPlaced,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Area(
|
||||||
|
size: _lastSignaturesWidth,
|
||||||
|
min: _signaturesMin,
|
||||||
|
max: _signaturesMax,
|
||||||
|
builder:
|
||||||
|
(context, area) => Offstage(
|
||||||
|
offstage: !_showSignaturesSidebar,
|
||||||
|
child: SignaturesSidebar(
|
||||||
|
onLoadSignatureFromFile: _loadSignatureFromFile,
|
||||||
|
onOpenDrawCanvas: _openDrawCanvas,
|
||||||
|
onSave: _saveSignedPdf,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
];
|
||||||
|
_splitController = MultiSplitViewController(areas: _areas);
|
||||||
|
// Apply initial collapse if needed
|
||||||
|
_applySidebarVisibility();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
_splitController.dispose();
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
void _applySidebarVisibility() {
|
||||||
|
// Left pages sidebar
|
||||||
|
final left = _splitController.areas[0];
|
||||||
|
if (_showPagesSidebar) {
|
||||||
|
left.max = _pagesMax;
|
||||||
|
left.min = _pagesMin;
|
||||||
|
left.size = _lastPagesWidth.clamp(_pagesMin, _pagesMax);
|
||||||
|
} else {
|
||||||
|
_lastPagesWidth = left.size ?? _lastPagesWidth;
|
||||||
|
left.min = 0;
|
||||||
|
left.max = 1;
|
||||||
|
left.size = 1; // effectively hidden
|
||||||
|
}
|
||||||
|
// Right signatures sidebar
|
||||||
|
final right = _splitController.areas[2];
|
||||||
|
if (_showSignaturesSidebar) {
|
||||||
|
right.max = _signaturesMax;
|
||||||
|
right.min = _signaturesMin;
|
||||||
|
right.size = _lastSignaturesWidth.clamp(_signaturesMin, _signaturesMax);
|
||||||
|
} else {
|
||||||
|
_lastSignaturesWidth = right.size ?? _lastSignaturesWidth;
|
||||||
|
right.min = 0;
|
||||||
|
right.max = 1;
|
||||||
|
right.size = 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
@ -293,49 +385,19 @@ class _PdfSignatureHomePageState extends ConsumerState<PdfSignatureHomePage> {
|
||||||
onTogglePagesSidebar:
|
onTogglePagesSidebar:
|
||||||
() => setState(() {
|
() => setState(() {
|
||||||
_showPagesSidebar = !_showPagesSidebar;
|
_showPagesSidebar = !_showPagesSidebar;
|
||||||
|
_applySidebarVisibility();
|
||||||
}),
|
}),
|
||||||
onToggleSignaturesSidebar:
|
onToggleSignaturesSidebar:
|
||||||
() => setState(() {
|
() => setState(() {
|
||||||
_showSignaturesSidebar = !_showSignaturesSidebar;
|
_showSignaturesSidebar = !_showSignaturesSidebar;
|
||||||
|
_applySidebarVisibility();
|
||||||
}),
|
}),
|
||||||
),
|
),
|
||||||
const SizedBox(height: 8),
|
const SizedBox(height: 8),
|
||||||
Expanded(
|
Expanded(
|
||||||
child: Row(
|
child: MultiSplitView(
|
||||||
children: [
|
controller: _splitController,
|
||||||
if (_showPagesSidebar)
|
axis: Axis.horizontal,
|
||||||
const SizedBox(width: 160, child: PagesSidebar()),
|
|
||||||
Expanded(
|
|
||||||
child: AbsorbPointer(
|
|
||||||
absorbing: isExporting,
|
|
||||||
child: RepaintBoundary(
|
|
||||||
child: PdfPageArea(
|
|
||||||
key: const ValueKey('pdf_page_area'),
|
|
||||||
pageSize: _pageSize,
|
|
||||||
viewerController: _viewerController,
|
|
||||||
onDragSignature: _onDragSignature,
|
|
||||||
onResizeSignature: _onResizeSignature,
|
|
||||||
onConfirmSignature: _confirmSignature,
|
|
||||||
onClearActiveOverlay:
|
|
||||||
() =>
|
|
||||||
ref
|
|
||||||
.read(signatureProvider.notifier)
|
|
||||||
.clearActiveOverlay(),
|
|
||||||
onSelectPlaced: _onSelectPlaced,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
if (_showSignaturesSidebar)
|
|
||||||
SizedBox(
|
|
||||||
width: 220,
|
|
||||||
child: SignaturesSidebar(
|
|
||||||
onLoadSignatureFromFile: _loadSignatureFromFile,
|
|
||||||
onOpenDrawCanvas: _openDrawCanvas,
|
|
||||||
onSave: _saveSignedPdf,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
import 'dart:ui';
|
|
||||||
import 'package:flutter_test/flutter_test.dart';
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
import 'package:pdf_signature/ui/features/pdf/view_model/view_model.dart';
|
import 'package:pdf_signature/ui/features/pdf/view_model/view_model.dart';
|
||||||
|
|
Loading…
Reference in New Issue