feat: remove button `btn_mark_signing`
This commit is contained in:
parent
cded635f02
commit
a53e881d7b
|
@ -5,7 +5,6 @@ class PdfState {
|
||||||
final bool loaded;
|
final bool loaded;
|
||||||
final int pageCount;
|
final int pageCount;
|
||||||
final int currentPage;
|
final int currentPage;
|
||||||
final bool markedForSigning;
|
|
||||||
final String? pickedPdfPath;
|
final String? pickedPdfPath;
|
||||||
final Uint8List? pickedPdfBytes;
|
final Uint8List? pickedPdfBytes;
|
||||||
final int? signedPage;
|
final int? signedPage;
|
||||||
|
@ -13,7 +12,6 @@ class PdfState {
|
||||||
required this.loaded,
|
required this.loaded,
|
||||||
required this.pageCount,
|
required this.pageCount,
|
||||||
required this.currentPage,
|
required this.currentPage,
|
||||||
required this.markedForSigning,
|
|
||||||
this.pickedPdfPath,
|
this.pickedPdfPath,
|
||||||
this.pickedPdfBytes,
|
this.pickedPdfBytes,
|
||||||
this.signedPage,
|
this.signedPage,
|
||||||
|
@ -22,7 +20,6 @@ class PdfState {
|
||||||
loaded: false,
|
loaded: false,
|
||||||
pageCount: 0,
|
pageCount: 0,
|
||||||
currentPage: 1,
|
currentPage: 1,
|
||||||
markedForSigning: false,
|
|
||||||
pickedPdfBytes: null,
|
pickedPdfBytes: null,
|
||||||
signedPage: null,
|
signedPage: null,
|
||||||
);
|
);
|
||||||
|
@ -30,7 +27,6 @@ class PdfState {
|
||||||
bool? loaded,
|
bool? loaded,
|
||||||
int? pageCount,
|
int? pageCount,
|
||||||
int? currentPage,
|
int? currentPage,
|
||||||
bool? markedForSigning,
|
|
||||||
String? pickedPdfPath,
|
String? pickedPdfPath,
|
||||||
Uint8List? pickedPdfBytes,
|
Uint8List? pickedPdfBytes,
|
||||||
int? signedPage,
|
int? signedPage,
|
||||||
|
@ -38,7 +34,6 @@ class PdfState {
|
||||||
loaded: loaded ?? this.loaded,
|
loaded: loaded ?? this.loaded,
|
||||||
pageCount: pageCount ?? this.pageCount,
|
pageCount: pageCount ?? this.pageCount,
|
||||||
currentPage: currentPage ?? this.currentPage,
|
currentPage: currentPage ?? this.currentPage,
|
||||||
markedForSigning: markedForSigning ?? this.markedForSigning,
|
|
||||||
pickedPdfPath: pickedPdfPath ?? this.pickedPdfPath,
|
pickedPdfPath: pickedPdfPath ?? this.pickedPdfPath,
|
||||||
pickedPdfBytes: pickedPdfBytes ?? this.pickedPdfBytes,
|
pickedPdfBytes: pickedPdfBytes ?? this.pickedPdfBytes,
|
||||||
signedPage: signedPage ?? this.signedPage,
|
signedPage: signedPage ?? this.signedPage,
|
||||||
|
|
|
@ -31,8 +31,6 @@
|
||||||
},
|
},
|
||||||
"goTo": "Go to:",
|
"goTo": "Go to:",
|
||||||
"dpi": "DPI:",
|
"dpi": "DPI:",
|
||||||
"markForSigning": "Mark for Signing",
|
|
||||||
"unmarkSigning": "Unmark Signing",
|
|
||||||
"saveSignedPdf": "Save Signed PDF",
|
"saveSignedPdf": "Save Signed PDF",
|
||||||
"loadSignatureFromFile": "Load Signature from file",
|
"loadSignatureFromFile": "Load Signature from file",
|
||||||
"drawSignature": "Draw Signature",
|
"drawSignature": "Draw Signature",
|
||||||
|
|
|
@ -20,8 +20,6 @@
|
||||||
"pageInfo": "Página {current}/{total}",
|
"pageInfo": "Página {current}/{total}",
|
||||||
"goTo": "Ir a:",
|
"goTo": "Ir a:",
|
||||||
"dpi": "DPI:",
|
"dpi": "DPI:",
|
||||||
"markForSigning": "Marcar para firmar",
|
|
||||||
"unmarkSigning": "Quitar marca",
|
|
||||||
"saveSignedPdf": "Guardar PDF firmado",
|
"saveSignedPdf": "Guardar PDF firmado",
|
||||||
"loadSignatureFromFile": "Cargar firma desde archivo",
|
"loadSignatureFromFile": "Cargar firma desde archivo",
|
||||||
"drawSignature": "Dibujar firma",
|
"drawSignature": "Dibujar firma",
|
||||||
|
|
|
@ -209,18 +209,6 @@ abstract class AppLocalizations {
|
||||||
/// **'DPI:'**
|
/// **'DPI:'**
|
||||||
String get dpi;
|
String get dpi;
|
||||||
|
|
||||||
/// No description provided for @markForSigning.
|
|
||||||
///
|
|
||||||
/// In en, this message translates to:
|
|
||||||
/// **'Mark for Signing'**
|
|
||||||
String get markForSigning;
|
|
||||||
|
|
||||||
/// No description provided for @unmarkSigning.
|
|
||||||
///
|
|
||||||
/// In en, this message translates to:
|
|
||||||
/// **'Unmark Signing'**
|
|
||||||
String get unmarkSigning;
|
|
||||||
|
|
||||||
/// No description provided for @saveSignedPdf.
|
/// No description provided for @saveSignedPdf.
|
||||||
///
|
///
|
||||||
/// In en, this message translates to:
|
/// In en, this message translates to:
|
||||||
|
|
|
@ -66,12 +66,6 @@ class AppLocalizationsEn extends AppLocalizations {
|
||||||
@override
|
@override
|
||||||
String get dpi => 'DPI:';
|
String get dpi => 'DPI:';
|
||||||
|
|
||||||
@override
|
|
||||||
String get markForSigning => 'Mark for Signing';
|
|
||||||
|
|
||||||
@override
|
|
||||||
String get unmarkSigning => 'Unmark Signing';
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get saveSignedPdf => 'Save Signed PDF';
|
String get saveSignedPdf => 'Save Signed PDF';
|
||||||
|
|
||||||
|
|
|
@ -66,12 +66,6 @@ class AppLocalizationsEs extends AppLocalizations {
|
||||||
@override
|
@override
|
||||||
String get dpi => 'DPI:';
|
String get dpi => 'DPI:';
|
||||||
|
|
||||||
@override
|
|
||||||
String get markForSigning => 'Marcar para firmar';
|
|
||||||
|
|
||||||
@override
|
|
||||||
String get unmarkSigning => 'Quitar marca';
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get saveSignedPdf => 'Guardar PDF firmado';
|
String get saveSignedPdf => 'Guardar PDF firmado';
|
||||||
|
|
||||||
|
|
|
@ -66,12 +66,6 @@ class AppLocalizationsZh extends AppLocalizations {
|
||||||
@override
|
@override
|
||||||
String get dpi => 'DPI:';
|
String get dpi => 'DPI:';
|
||||||
|
|
||||||
@override
|
|
||||||
String get markForSigning => '標記簽署';
|
|
||||||
|
|
||||||
@override
|
|
||||||
String get unmarkSigning => '取消標記';
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get saveSignedPdf => '儲存已簽名 PDF';
|
String get saveSignedPdf => '儲存已簽名 PDF';
|
||||||
|
|
||||||
|
@ -194,12 +188,6 @@ class AppLocalizationsZhTw extends AppLocalizationsZh {
|
||||||
@override
|
@override
|
||||||
String get dpi => 'DPI:';
|
String get dpi => 'DPI:';
|
||||||
|
|
||||||
@override
|
|
||||||
String get markForSigning => '標記簽署';
|
|
||||||
|
|
||||||
@override
|
|
||||||
String get unmarkSigning => '取消標記';
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get saveSignedPdf => '儲存已簽名 PDF';
|
String get saveSignedPdf => '儲存已簽名 PDF';
|
||||||
|
|
||||||
|
|
|
@ -20,8 +20,6 @@
|
||||||
"pageInfo": "第 {current}/{total} 頁",
|
"pageInfo": "第 {current}/{total} 頁",
|
||||||
"goTo": "前往:",
|
"goTo": "前往:",
|
||||||
"dpi": "DPI:",
|
"dpi": "DPI:",
|
||||||
"markForSigning": "標記簽署",
|
|
||||||
"unmarkSigning": "取消標記",
|
|
||||||
"saveSignedPdf": "儲存已簽名 PDF",
|
"saveSignedPdf": "儲存已簽名 PDF",
|
||||||
"loadSignatureFromFile": "從檔案載入簽名",
|
"loadSignatureFromFile": "從檔案載入簽名",
|
||||||
"drawSignature": "手寫簽名",
|
"drawSignature": "手寫簽名",
|
||||||
|
|
|
@ -20,8 +20,6 @@
|
||||||
"pageInfo": "第 {current}/{total} 頁",
|
"pageInfo": "第 {current}/{total} 頁",
|
||||||
"goTo": "前往:",
|
"goTo": "前往:",
|
||||||
"dpi": "DPI:",
|
"dpi": "DPI:",
|
||||||
"markForSigning": "標記簽署",
|
|
||||||
"unmarkSigning": "取消標記",
|
|
||||||
"saveSignedPdf": "儲存已簽名 PDF",
|
"saveSignedPdf": "儲存已簽名 PDF",
|
||||||
"loadSignatureFromFile": "從檔案載入簽名",
|
"loadSignatureFromFile": "從檔案載入簽名",
|
||||||
"drawSignature": "手寫簽名",
|
"drawSignature": "手寫簽名",
|
||||||
|
|
|
@ -16,7 +16,6 @@ class PdfController extends StateNotifier<PdfState> {
|
||||||
loaded: true,
|
loaded: true,
|
||||||
pageCount: samplePageCount,
|
pageCount: samplePageCount,
|
||||||
currentPage: 1,
|
currentPage: 1,
|
||||||
markedForSigning: false,
|
|
||||||
pickedPdfPath: null,
|
pickedPdfPath: null,
|
||||||
signedPage: null,
|
signedPage: null,
|
||||||
);
|
);
|
||||||
|
@ -31,7 +30,6 @@ class PdfController extends StateNotifier<PdfState> {
|
||||||
loaded: true,
|
loaded: true,
|
||||||
pageCount: pageCount,
|
pageCount: pageCount,
|
||||||
currentPage: 1,
|
currentPage: 1,
|
||||||
markedForSigning: false,
|
|
||||||
pickedPdfPath: path,
|
pickedPdfPath: path,
|
||||||
pickedPdfBytes: bytes,
|
pickedPdfBytes: bytes,
|
||||||
signedPage: null,
|
signedPage: null,
|
||||||
|
@ -44,15 +42,14 @@ class PdfController extends StateNotifier<PdfState> {
|
||||||
state = state.copyWith(currentPage: clamped);
|
state = state.copyWith(currentPage: clamped);
|
||||||
}
|
}
|
||||||
|
|
||||||
void toggleMark() {
|
// Set or clear the page that will receive the signature overlay.
|
||||||
|
void setSignedPage(int? page) {
|
||||||
if (!state.loaded) return;
|
if (!state.loaded) return;
|
||||||
if (state.signedPage != null) {
|
if (page == null) {
|
||||||
state = state.copyWith(markedForSigning: false, signedPage: null);
|
state = state.copyWith(signedPage: null);
|
||||||
} else {
|
} else {
|
||||||
state = state.copyWith(
|
final clamped = page.clamp(1, state.pageCount);
|
||||||
markedForSigning: true,
|
state = state.copyWith(signedPage: clamped);
|
||||||
signedPage: state.currentPage,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -51,13 +51,9 @@ class _PdfSignatureHomePageState extends ConsumerState<PdfSignatureHomePage> {
|
||||||
ref.read(pdfProvider.notifier).jumpTo(page);
|
ref.read(pdfProvider.notifier).jumpTo(page);
|
||||||
}
|
}
|
||||||
|
|
||||||
void _toggleMarkForSigning() {
|
// mark-for-signing removed; no toggle needed
|
||||||
ref.read(pdfProvider.notifier).toggleMark();
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> _loadSignatureFromFile() async {
|
Future<void> _loadSignatureFromFile() async {
|
||||||
final pdf = ref.read(pdfProvider);
|
|
||||||
if (!pdf.markedForSigning) return;
|
|
||||||
final typeGroup = const fs.XTypeGroup(
|
final typeGroup = const fs.XTypeGroup(
|
||||||
label: 'Image',
|
label: 'Image',
|
||||||
extensions: ['png', 'jpg', 'jpeg', 'webp'],
|
extensions: ['png', 'jpg', 'jpeg', 'webp'],
|
||||||
|
@ -67,6 +63,11 @@ class _PdfSignatureHomePageState extends ConsumerState<PdfSignatureHomePage> {
|
||||||
final bytes = await file.readAsBytes();
|
final bytes = await file.readAsBytes();
|
||||||
final sig = ref.read(signatureProvider.notifier);
|
final sig = ref.read(signatureProvider.notifier);
|
||||||
sig.setImageBytes(bytes);
|
sig.setImageBytes(bytes);
|
||||||
|
// When a signature is added, set the current page as signed.
|
||||||
|
final p = ref.read(pdfProvider);
|
||||||
|
if (p.loaded) {
|
||||||
|
ref.read(pdfProvider.notifier).setSignedPage(p.currentPage);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void _onDragSignature(Offset delta) {
|
void _onDragSignature(Offset delta) {
|
||||||
|
@ -78,8 +79,6 @@ class _PdfSignatureHomePageState extends ConsumerState<PdfSignatureHomePage> {
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _openDrawCanvas() async {
|
Future<void> _openDrawCanvas() async {
|
||||||
final pdf = ref.read(pdfProvider);
|
|
||||||
if (!pdf.markedForSigning) return;
|
|
||||||
final result = await showModalBottomSheet<Uint8List>(
|
final result = await showModalBottomSheet<Uint8List>(
|
||||||
context: context,
|
context: context,
|
||||||
isScrollControlled: true,
|
isScrollControlled: true,
|
||||||
|
@ -89,6 +88,11 @@ class _PdfSignatureHomePageState extends ConsumerState<PdfSignatureHomePage> {
|
||||||
if (result != null && result.isNotEmpty) {
|
if (result != null && result.isNotEmpty) {
|
||||||
// Use the drawn image as signature content
|
// Use the drawn image as signature content
|
||||||
ref.read(signatureProvider.notifier).setImageBytes(result);
|
ref.read(signatureProvider.notifier).setImageBytes(result);
|
||||||
|
// Mark current page as signed when a signature is created
|
||||||
|
final p = ref.read(pdfProvider);
|
||||||
|
if (p.loaded) {
|
||||||
|
ref.read(pdfProvider.notifier).setSignedPage(p.currentPage);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -386,31 +390,24 @@ class _PdfSignatureHomePageState extends ConsumerState<PdfSignatureHomePage> {
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
ElevatedButton(
|
// Removed: Mark for signing button
|
||||||
key: const Key('btn_mark_signing'),
|
|
||||||
onPressed: disabled ? null : _toggleMarkForSigning,
|
|
||||||
child: Text(
|
|
||||||
pdf.markedForSigning ? l.unmarkSigning : l.markForSigning,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
if (pdf.loaded)
|
if (pdf.loaded)
|
||||||
ElevatedButton(
|
ElevatedButton(
|
||||||
key: const Key('btn_save_pdf'),
|
key: const Key('btn_save_pdf'),
|
||||||
onPressed: disabled ? null : _saveSignedPdf,
|
onPressed: disabled ? null : _saveSignedPdf,
|
||||||
child: Text(l.saveSignedPdf),
|
child: Text(l.saveSignedPdf),
|
||||||
),
|
),
|
||||||
if (pdf.markedForSigning) ...[
|
// Signature tools are available when a PDF is loaded
|
||||||
OutlinedButton(
|
OutlinedButton(
|
||||||
key: const Key('btn_load_signature_picker'),
|
key: const Key('btn_load_signature_picker'),
|
||||||
onPressed: disabled ? null : _loadSignatureFromFile,
|
onPressed: disabled || !pdf.loaded ? null : _loadSignatureFromFile,
|
||||||
child: Text(l.loadSignatureFromFile),
|
child: Text(l.loadSignatureFromFile),
|
||||||
),
|
),
|
||||||
ElevatedButton(
|
ElevatedButton(
|
||||||
key: const Key('btn_draw_signature'),
|
key: const Key('btn_draw_signature'),
|
||||||
onPressed: disabled ? null : _openDrawCanvas,
|
onPressed: disabled || !pdf.loaded ? null : _openDrawCanvas,
|
||||||
child: Text(l.drawSignature),
|
child: Text(l.drawSignature),
|
||||||
),
|
),
|
||||||
],
|
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
|
|
@ -6,6 +6,14 @@ class _Token {
|
||||||
String get jpeg => '$base.jpeg';
|
String get jpeg => '$base.jpeg';
|
||||||
String get webp => '$base.webp';
|
String get webp => '$base.webp';
|
||||||
String get bmp => '$base.bmp';
|
String get bmp => '$base.bmp';
|
||||||
|
// Allow combining tokens with a dash, e.g., zh - TW -> 'zh-TW'
|
||||||
|
_Token operator -(Object other) {
|
||||||
|
if (other is _Token) {
|
||||||
|
return _Token('$base-${other.base}');
|
||||||
|
}
|
||||||
|
return _Token(base);
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String toString() => base;
|
String toString() => base;
|
||||||
}
|
}
|
||||||
|
@ -14,3 +22,14 @@ class _Token {
|
||||||
const corrupted = _Token('corrupted');
|
const corrupted = _Token('corrupted');
|
||||||
const signature = _Token('signature');
|
const signature = _Token('signature');
|
||||||
const empty = _Token('empty');
|
const empty = _Token('empty');
|
||||||
|
|
||||||
|
// Preferences & i18n tokens used by generated tests
|
||||||
|
const light = _Token('light');
|
||||||
|
const dark = _Token('dark');
|
||||||
|
const system = _Token('system');
|
||||||
|
const en = _Token('en');
|
||||||
|
const es = _Token('es');
|
||||||
|
const zh = _Token('zh');
|
||||||
|
const TW = _Token('TW');
|
||||||
|
const theme = _Token('theme');
|
||||||
|
const language = _Token('language');
|
||||||
|
|
|
@ -17,7 +17,7 @@ Future<void> aPdfIsOpenAndContainsAtLeastOnePlacedSignature(
|
||||||
pageCount: 2,
|
pageCount: 2,
|
||||||
bytes: Uint8List.fromList([1, 2, 3]),
|
bytes: Uint8List.fromList([1, 2, 3]),
|
||||||
);
|
);
|
||||||
container.read(pdfProvider.notifier).toggleMark();
|
container.read(pdfProvider.notifier).setSignedPage(1);
|
||||||
container.read(signatureProvider.notifier).placeDefaultRect();
|
container.read(signatureProvider.notifier).placeDefaultRect();
|
||||||
container
|
container
|
||||||
.read(signatureProvider.notifier)
|
.read(signatureProvider.notifier)
|
||||||
|
|
|
@ -10,5 +10,5 @@ Future<void> aPdfPageIsSelectedForSigning(WidgetTester tester) async {
|
||||||
container
|
container
|
||||||
.read(pdfProvider.notifier)
|
.read(pdfProvider.notifier)
|
||||||
.openPicked(path: 'mock.pdf', pageCount: 1);
|
.openPicked(path: 'mock.pdf', pageCount: 1);
|
||||||
container.read(pdfProvider.notifier).toggleMark();
|
container.read(pdfProvider.notifier).setSignedPage(1);
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,7 +11,7 @@ Future<void> aSignatureImageIsPlacedOnThePage(WidgetTester tester) async {
|
||||||
container
|
container
|
||||||
.read(pdfProvider.notifier)
|
.read(pdfProvider.notifier)
|
||||||
.openPicked(path: 'mock.pdf', pageCount: 5);
|
.openPicked(path: 'mock.pdf', pageCount: 5);
|
||||||
container.read(pdfProvider.notifier).toggleMark();
|
container.read(pdfProvider.notifier).setSignedPage(1);
|
||||||
// Set an image to ensure rect exists
|
// Set an image to ensure rect exists
|
||||||
container
|
container
|
||||||
.read(signatureProvider.notifier)
|
.read(signatureProvider.notifier)
|
||||||
|
|
|
@ -11,7 +11,7 @@ Future<void> aSignatureImageIsSelected(WidgetTester tester) async {
|
||||||
container
|
container
|
||||||
.read(pdfProvider.notifier)
|
.read(pdfProvider.notifier)
|
||||||
.openPicked(path: 'mock.pdf', pageCount: 2);
|
.openPicked(path: 'mock.pdf', pageCount: 2);
|
||||||
container.read(pdfProvider.notifier).toggleMark();
|
container.read(pdfProvider.notifier).setSignedPage(1);
|
||||||
container
|
container
|
||||||
.read(signatureProvider.notifier)
|
.read(signatureProvider.notifier)
|
||||||
.setImageBytes(Uint8List.fromList([1, 2, 3]));
|
.setImageBytes(Uint8List.fromList([1, 2, 3]));
|
||||||
|
|
|
@ -18,7 +18,7 @@ Future<void> aSignatureIsPlacedWithAPositionAndSizeRelativeToThePage(
|
||||||
pageCount: 2,
|
pageCount: 2,
|
||||||
bytes: Uint8List.fromList([1, 2, 3]),
|
bytes: Uint8List.fromList([1, 2, 3]),
|
||||||
);
|
);
|
||||||
container.read(pdfProvider.notifier).toggleMark();
|
container.read(pdfProvider.notifier).setSignedPage(1);
|
||||||
final r = Rect.fromLTWH(50, 100, 120, 60);
|
final r = Rect.fromLTWH(50, 100, 120, 60);
|
||||||
final sigN = container.read(signatureProvider.notifier);
|
final sigN = container.read(signatureProvider.notifier);
|
||||||
sigN.placeDefaultRect();
|
sigN.placeDefaultRect();
|
||||||
|
|
|
@ -1,9 +1,8 @@
|
||||||
import 'package:flutter_test/flutter_test.dart';
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
import 'package:pdf_signature/ui/features/pdf/view_model/view_model.dart';
|
|
||||||
import '_world.dart';
|
import '_world.dart';
|
||||||
|
|
||||||
/// Usage: I toggle mark
|
/// Usage: I toggle mark
|
||||||
Future<void> iToggleMark(WidgetTester tester) async {
|
Future<void> iToggleMark(WidgetTester tester) async {
|
||||||
final c = TestWorld.container!;
|
// Feature removed; no-op for backward-compatible tests
|
||||||
c.read(pdfProvider.notifier).toggleMark();
|
TestWorld.container; // keep reference to avoid unused warnings
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,9 +1,7 @@
|
||||||
import 'package:flutter_test/flutter_test.dart';
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
import 'package:pdf_signature/ui/features/pdf/view_model/view_model.dart';
|
|
||||||
import '_world.dart';
|
|
||||||
|
|
||||||
/// Usage: pdf marked for signing is {false}
|
/// Usage: pdf marked for signing is {false}
|
||||||
Future<void> pdfMarkedForSigningIs(WidgetTester tester, bool expected) async {
|
Future<void> pdfMarkedForSigningIs(WidgetTester tester, bool expected) async {
|
||||||
final c = TestWorld.container!;
|
// Feature removed; assert expectation is false for backward compatibility
|
||||||
expect(c.read(pdfProvider).markedForSigning, expected);
|
expect(expected, false);
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,10 +4,10 @@ import '_world.dart';
|
||||||
/// Usage: the app language is {"<language>"}
|
/// Usage: the app language is {"<language>"}
|
||||||
Future<void> theAppLanguageIs(
|
Future<void> theAppLanguageIs(
|
||||||
WidgetTester tester,
|
WidgetTester tester,
|
||||||
String param1,
|
String languageWrapped,
|
||||||
dynamic language,
|
|
||||||
) async {
|
) async {
|
||||||
final lang = language.toString();
|
String unwrap(String s) =>
|
||||||
expect(param1, '{${lang}}');
|
s.startsWith('{') && s.endsWith('}') ? s.substring(1, s.length - 1) : s;
|
||||||
|
final lang = unwrap(languageWrapped);
|
||||||
expect(TestWorld.currentLanguage, lang);
|
expect(TestWorld.currentLanguage, lang);
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,13 +2,10 @@ import 'package:flutter_test/flutter_test.dart';
|
||||||
import '_world.dart';
|
import '_world.dart';
|
||||||
|
|
||||||
/// Usage: the app UI theme is {"<theme>"}
|
/// Usage: the app UI theme is {"<theme>"}
|
||||||
Future<void> theAppUiThemeIs(
|
Future<void> theAppUiThemeIs(WidgetTester tester, String themeWrapped) async {
|
||||||
WidgetTester tester,
|
String unwrap(String s) =>
|
||||||
String param1,
|
s.startsWith('{') && s.endsWith('}') ? s.substring(1, s.length - 1) : s;
|
||||||
dynamic theme,
|
final t = unwrap(themeWrapped);
|
||||||
) async {
|
|
||||||
final t = theme.toString();
|
|
||||||
expect(param1, '{${t}}');
|
|
||||||
if (t == 'system') {
|
if (t == 'system') {
|
||||||
// When checking for 'system', we validate that selectedTheme is system
|
// When checking for 'system', we validate that selectedTheme is system
|
||||||
expect(TestWorld.selectedTheme, 'system');
|
expect(TestWorld.selectedTheme, 'system');
|
||||||
|
|
|
@ -4,14 +4,12 @@ import '_world.dart';
|
||||||
/// Usage: the preference {language} is saved as {"<language>"}
|
/// Usage: the preference {language} is saved as {"<language>"}
|
||||||
Future<void> thePreferenceIsSavedAs(
|
Future<void> thePreferenceIsSavedAs(
|
||||||
WidgetTester tester,
|
WidgetTester tester,
|
||||||
dynamic param1,
|
dynamic keyToken,
|
||||||
String param2,
|
String valueWrapped,
|
||||||
dynamic _value,
|
|
||||||
) async {
|
) async {
|
||||||
final key = param1.toString();
|
String unwrap(String s) =>
|
||||||
final expectedTokenWrapped = param2; // like "{light}"
|
s.startsWith('{') && s.endsWith('}') ? s.substring(1, s.length - 1) : s;
|
||||||
final expectedValue = _value.toString();
|
final key = keyToken.toString();
|
||||||
// Check token string matches braces-syntax just for parity
|
final expected = unwrap(valueWrapped);
|
||||||
expect(expectedTokenWrapped, '{${expectedValue}}');
|
expect(TestWorld.prefs[key], expected);
|
||||||
expect(TestWorld.prefs[key], expectedValue);
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,15 +4,13 @@ import '_world.dart';
|
||||||
/// Usage: the user previously set theme {"<theme>"} and language {"<language>"}
|
/// Usage: the user previously set theme {"<theme>"} and language {"<language>"}
|
||||||
Future<void> theUserPreviouslySetThemeAndLanguage(
|
Future<void> theUserPreviouslySetThemeAndLanguage(
|
||||||
WidgetTester tester,
|
WidgetTester tester,
|
||||||
String param1,
|
String themeWrapped,
|
||||||
String param2,
|
String languageWrapped,
|
||||||
dynamic theme,
|
|
||||||
dynamic language,
|
|
||||||
) async {
|
) async {
|
||||||
final t = theme.toString();
|
String unwrap(String s) =>
|
||||||
final lang = language.toString();
|
s.startsWith('{') && s.endsWith('}') ? s.substring(1, s.length - 1) : s;
|
||||||
expect(param1, '{${t}}');
|
final t = unwrap(themeWrapped);
|
||||||
expect(param2, '{${lang}}');
|
final lang = unwrap(languageWrapped);
|
||||||
// Simulate stored values
|
// Simulate stored values
|
||||||
TestWorld.prefs['theme'] = t;
|
TestWorld.prefs['theme'] = t;
|
||||||
TestWorld.prefs['language'] = lang;
|
TestWorld.prefs['language'] = lang;
|
||||||
|
|
|
@ -13,7 +13,7 @@ Future<void> theUserSelects(WidgetTester tester, dynamic file) async {
|
||||||
container
|
container
|
||||||
.read(pdfProvider.notifier)
|
.read(pdfProvider.notifier)
|
||||||
.openPicked(path: 'mock.pdf', pageCount: 1);
|
.openPicked(path: 'mock.pdf', pageCount: 1);
|
||||||
container.read(pdfProvider.notifier).toggleMark();
|
container.read(pdfProvider.notifier).setSignedPage(1);
|
||||||
// For invalid/unsupported/empty selections we do NOT set image bytes.
|
// For invalid/unsupported/empty selections we do NOT set image bytes.
|
||||||
// This simulates a failed load and keeps rect null.
|
// This simulates a failed load and keeps rect null.
|
||||||
final token = file.toString();
|
final token = file.toString();
|
||||||
|
|
|
@ -0,0 +1,65 @@
|
||||||
|
import 'dart:typed_data';
|
||||||
|
import 'dart:ui' show PointerDeviceKind;
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
|
import 'package:hand_signature/signature.dart' as hand;
|
||||||
|
|
||||||
|
import 'package:pdf_signature/ui/features/pdf/widgets/draw_canvas.dart';
|
||||||
|
import 'package:pdf_signature/l10n/app_localizations.dart';
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
testWidgets('DrawCanvas exports non-empty bytes on confirm', (tester) async {
|
||||||
|
Uint8List? exported;
|
||||||
|
final sink = ValueNotifier<Uint8List?>(null);
|
||||||
|
final control = hand.HandSignatureControl();
|
||||||
|
await tester.pumpWidget(
|
||||||
|
MaterialApp(
|
||||||
|
localizationsDelegates: AppLocalizations.localizationsDelegates,
|
||||||
|
supportedLocales: AppLocalizations.supportedLocales,
|
||||||
|
home: Scaffold(
|
||||||
|
body: DrawCanvas(
|
||||||
|
control: control,
|
||||||
|
debugBytesSink: sink,
|
||||||
|
onConfirm: (bytes) {
|
||||||
|
exported = bytes;
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
|
||||||
|
// Draw a simple stroke inside the pad
|
||||||
|
final pad = find.byKey(const Key('hand_signature_pad'));
|
||||||
|
expect(pad, findsOneWidget);
|
||||||
|
final rect = tester.getRect(pad);
|
||||||
|
final g = await tester.startGesture(
|
||||||
|
Offset(rect.left + 20, rect.center.dy),
|
||||||
|
kind: PointerDeviceKind.touch,
|
||||||
|
);
|
||||||
|
for (int i = 0; i < 10; i++) {
|
||||||
|
await g.moveBy(
|
||||||
|
const Offset(12, 0),
|
||||||
|
timeStamp: Duration(milliseconds: 16 * (i + 1)),
|
||||||
|
);
|
||||||
|
await tester.pump(const Duration(milliseconds: 16));
|
||||||
|
}
|
||||||
|
await g.up();
|
||||||
|
await tester.pump(const Duration(milliseconds: 50));
|
||||||
|
|
||||||
|
// Confirm export
|
||||||
|
await tester.tap(find.byKey(const Key('btn_canvas_confirm')));
|
||||||
|
// Wait until notifier receives bytes
|
||||||
|
await tester.pumpAndSettle(const Duration(milliseconds: 50));
|
||||||
|
await tester.runAsync(() async {
|
||||||
|
final end = DateTime.now().add(const Duration(seconds: 2));
|
||||||
|
while (sink.value == null && DateTime.now().isBefore(end)) {
|
||||||
|
await Future<void>.delayed(const Duration(milliseconds: 20));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
exported ??= sink.value;
|
||||||
|
|
||||||
|
expect(exported, isNotNull);
|
||||||
|
expect(exported!.isNotEmpty, isTrue);
|
||||||
|
});
|
||||||
|
}
|
|
@ -0,0 +1,58 @@
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
|
|
||||||
|
import 'package:pdf_signature/data/services/export_service.dart';
|
||||||
|
import 'package:pdf_signature/data/services/providers.dart';
|
||||||
|
import 'package:pdf_signature/ui/features/pdf/view_model/view_model.dart';
|
||||||
|
import 'package:pdf_signature/ui/features/pdf/widgets/pdf_screen.dart';
|
||||||
|
import 'package:pdf_signature/l10n/app_localizations.dart';
|
||||||
|
|
||||||
|
class RecordingExporter extends ExportService {
|
||||||
|
bool called = false;
|
||||||
|
@override
|
||||||
|
Future<bool> saveBytesToFile({required bytes, required outputPath}) async {
|
||||||
|
called = true;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class BasicExporter extends ExportService {}
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
testWidgets('Save uses file selector (via provider) and injected exporter', (
|
||||||
|
tester,
|
||||||
|
) async {
|
||||||
|
final fake = RecordingExporter();
|
||||||
|
await tester.pumpWidget(
|
||||||
|
ProviderScope(
|
||||||
|
overrides: [
|
||||||
|
pdfProvider.overrideWith(
|
||||||
|
(ref) => PdfController()..openPicked(path: 'test.pdf'),
|
||||||
|
),
|
||||||
|
signatureProvider.overrideWith(
|
||||||
|
(ref) => SignatureController()..placeDefaultRect(),
|
||||||
|
),
|
||||||
|
useMockViewerProvider.overrideWith((ref) => true),
|
||||||
|
exportServiceProvider.overrideWith((_) => fake),
|
||||||
|
savePathPickerProvider.overrideWith(
|
||||||
|
(_) => () async => 'C:/tmp/output.pdf',
|
||||||
|
),
|
||||||
|
],
|
||||||
|
child: MaterialApp(
|
||||||
|
localizationsDelegates: AppLocalizations.localizationsDelegates,
|
||||||
|
supportedLocales: AppLocalizations.supportedLocales,
|
||||||
|
home: PdfSignatureHomePage(),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
await tester.pump();
|
||||||
|
|
||||||
|
// Trigger save directly (mark toggle no longer required)
|
||||||
|
await tester.tap(find.byKey(const Key('btn_save_pdf')));
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
|
||||||
|
// Expect success UI
|
||||||
|
expect(find.textContaining('Saved:'), findsOneWidget);
|
||||||
|
});
|
||||||
|
}
|
|
@ -0,0 +1,49 @@
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
|
|
||||||
|
import 'package:pdf_signature/ui/features/pdf/widgets/pdf_screen.dart';
|
||||||
|
import 'package:pdf_signature/ui/features/pdf/view_model/view_model.dart';
|
||||||
|
import 'package:pdf_signature/data/services/providers.dart';
|
||||||
|
import 'package:pdf_signature/l10n/app_localizations.dart';
|
||||||
|
|
||||||
|
Future<void> pumpWithOpenPdf(WidgetTester tester) async {
|
||||||
|
await tester.pumpWidget(
|
||||||
|
ProviderScope(
|
||||||
|
overrides: [
|
||||||
|
pdfProvider.overrideWith(
|
||||||
|
(ref) => PdfController()..openPicked(path: 'test.pdf'),
|
||||||
|
),
|
||||||
|
useMockViewerProvider.overrideWith((ref) => true),
|
||||||
|
],
|
||||||
|
child: MaterialApp(
|
||||||
|
localizationsDelegates: AppLocalizations.localizationsDelegates,
|
||||||
|
supportedLocales: AppLocalizations.supportedLocales,
|
||||||
|
home: const PdfSignatureHomePage(),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
await tester.pump();
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> pumpWithOpenPdfAndSig(WidgetTester tester) async {
|
||||||
|
await tester.pumpWidget(
|
||||||
|
ProviderScope(
|
||||||
|
overrides: [
|
||||||
|
pdfProvider.overrideWith(
|
||||||
|
(ref) => PdfController()..openPicked(path: 'test.pdf'),
|
||||||
|
),
|
||||||
|
signatureProvider.overrideWith(
|
||||||
|
(ref) => SignatureController()..placeDefaultRect(),
|
||||||
|
),
|
||||||
|
useMockViewerProvider.overrideWith((ref) => true),
|
||||||
|
],
|
||||||
|
child: MaterialApp(
|
||||||
|
localizationsDelegates: AppLocalizations.localizationsDelegates,
|
||||||
|
supportedLocales: AppLocalizations.supportedLocales,
|
||||||
|
home: const PdfSignatureHomePage(),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
await tester.pump();
|
||||||
|
}
|
|
@ -0,0 +1,32 @@
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
|
|
||||||
|
import 'helpers.dart';
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
testWidgets('Open a PDF and navigate pages', (tester) async {
|
||||||
|
await pumpWithOpenPdf(tester);
|
||||||
|
final pageInfo = find.byKey(const Key('lbl_page_info'));
|
||||||
|
expect(pageInfo, findsOneWidget);
|
||||||
|
expect((tester.widget<Text>(pageInfo)).data, 'Page 1/5');
|
||||||
|
|
||||||
|
await tester.tap(find.byKey(const Key('btn_next')));
|
||||||
|
await tester.pump();
|
||||||
|
expect((tester.widget<Text>(pageInfo)).data, 'Page 2/5');
|
||||||
|
|
||||||
|
await tester.tap(find.byKey(const Key('btn_prev')));
|
||||||
|
await tester.pump();
|
||||||
|
expect((tester.widget<Text>(pageInfo)).data, 'Page 1/5');
|
||||||
|
});
|
||||||
|
|
||||||
|
testWidgets('Jump to a specific page', (tester) async {
|
||||||
|
await pumpWithOpenPdf(tester);
|
||||||
|
|
||||||
|
final goto = find.byKey(const Key('txt_goto'));
|
||||||
|
await tester.enterText(goto, '4');
|
||||||
|
await tester.testTextInput.receiveAction(TextInputAction.done);
|
||||||
|
await tester.pump();
|
||||||
|
final pageInfo = find.byKey(const Key('lbl_page_info'));
|
||||||
|
expect((tester.widget<Text>(pageInfo)).data, 'Page 4/5');
|
||||||
|
});
|
||||||
|
}
|
|
@ -0,0 +1,72 @@
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
|
|
||||||
|
import 'helpers.dart';
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
testWidgets('Resize and move signature within page bounds', (tester) async {
|
||||||
|
await pumpWithOpenPdfAndSig(tester);
|
||||||
|
|
||||||
|
final overlay = find.byKey(const Key('signature_overlay'));
|
||||||
|
expect(overlay, findsOneWidget);
|
||||||
|
final posBefore = tester.getTopLeft(overlay);
|
||||||
|
|
||||||
|
// drag the overlay
|
||||||
|
await tester.drag(overlay, const Offset(30, -20));
|
||||||
|
await tester.pump();
|
||||||
|
final posAfter = tester.getTopLeft(overlay);
|
||||||
|
// Allow equality in case clamped at edges
|
||||||
|
expect(posAfter.dx >= posBefore.dx, isTrue);
|
||||||
|
expect(posAfter.dy <= posBefore.dy, isTrue);
|
||||||
|
|
||||||
|
// resize via handle
|
||||||
|
final handle = find.byKey(const Key('signature_handle'));
|
||||||
|
final sizeBefore = tester.getSize(overlay);
|
||||||
|
await tester.drag(handle, const Offset(40, 40));
|
||||||
|
await tester.pump();
|
||||||
|
final sizeAfter = tester.getSize(overlay);
|
||||||
|
expect(sizeAfter.width >= sizeBefore.width, isTrue);
|
||||||
|
expect(sizeAfter.height >= sizeBefore.height, isTrue);
|
||||||
|
});
|
||||||
|
|
||||||
|
testWidgets('Lock aspect ratio while resizing', (tester) async {
|
||||||
|
await pumpWithOpenPdfAndSig(tester);
|
||||||
|
|
||||||
|
final overlay = find.byKey(const Key('signature_overlay'));
|
||||||
|
final sizeBefore = tester.getSize(overlay);
|
||||||
|
final aspect = sizeBefore.width / sizeBefore.height;
|
||||||
|
await tester.tap(find.byKey(const Key('chk_aspect_lock')));
|
||||||
|
await tester.pump();
|
||||||
|
await tester.drag(
|
||||||
|
find.byKey(const Key('signature_handle')),
|
||||||
|
const Offset(60, 10),
|
||||||
|
);
|
||||||
|
await tester.pump();
|
||||||
|
final sizeAfter = tester.getSize(overlay);
|
||||||
|
final newAspect = (sizeAfter.width / sizeAfter.height);
|
||||||
|
expect((newAspect - aspect).abs() < 0.15, isTrue);
|
||||||
|
});
|
||||||
|
|
||||||
|
testWidgets('Background removal and adjustments controls change state', (
|
||||||
|
tester,
|
||||||
|
) async {
|
||||||
|
await pumpWithOpenPdfAndSig(tester);
|
||||||
|
|
||||||
|
// toggle bg removal
|
||||||
|
await tester.tap(find.byKey(const Key('swt_bg_removal')));
|
||||||
|
await tester.pump();
|
||||||
|
// move sliders
|
||||||
|
await tester.drag(
|
||||||
|
find.byKey(const Key('sld_contrast')),
|
||||||
|
const Offset(50, 0),
|
||||||
|
);
|
||||||
|
await tester.drag(
|
||||||
|
find.byKey(const Key('sld_brightness')),
|
||||||
|
const Offset(-50, 0),
|
||||||
|
);
|
||||||
|
await tester.pump();
|
||||||
|
|
||||||
|
// basic smoke: overlay still present
|
||||||
|
expect(find.byKey(const Key('signature_overlay')), findsOneWidget);
|
||||||
|
});
|
||||||
|
}
|
|
@ -0,0 +1,17 @@
|
||||||
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
|
|
||||||
|
import 'helpers.dart';
|
||||||
|
import 'package:pdf_signature/ui/features/pdf/widgets/pdf_screen.dart';
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
testWidgets('Show invalid/unsupported file SnackBar via test hook', (
|
||||||
|
tester,
|
||||||
|
) async {
|
||||||
|
await pumpWithOpenPdf(tester);
|
||||||
|
final dynamic state =
|
||||||
|
tester.state(find.byType(PdfSignatureHomePage)) as dynamic;
|
||||||
|
state.debugShowInvalidSignatureSnackBar();
|
||||||
|
await tester.pump();
|
||||||
|
expect(find.text('Invalid or unsupported file'), findsOneWidget);
|
||||||
|
});
|
||||||
|
}
|
|
@ -1,313 +1,2 @@
|
||||||
// This is a basic Flutter widget test.
|
// Split into multiple *_test.dart files. Intentionally left empty.
|
||||||
//
|
void main() {}
|
||||||
// To perform an interaction with a widget in your test, use the WidgetTester
|
|
||||||
// utility in the flutter_test package. For example, you can send tap and scroll
|
|
||||||
// gestures. You can also use WidgetTester to find child widgets in the widget
|
|
||||||
// tree, read text, and verify that the values of widget properties are correct.
|
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:flutter_test/flutter_test.dart';
|
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
|
||||||
import 'dart:typed_data';
|
|
||||||
import 'dart:ui' show PointerDeviceKind;
|
|
||||||
|
|
||||||
import 'package:pdf_signature/ui/features/pdf/view_model/view_model.dart';
|
|
||||||
import 'package:pdf_signature/data/services/providers.dart';
|
|
||||||
import 'package:pdf_signature/ui/features/pdf/widgets/pdf_screen.dart';
|
|
||||||
import 'package:pdf_signature/ui/features/pdf/widgets/draw_canvas.dart';
|
|
||||||
|
|
||||||
import 'package:pdf_signature/data/services/export_service.dart';
|
|
||||||
import 'package:hand_signature/signature.dart' as hand;
|
|
||||||
|
|
||||||
// Fakes for export service (top-level; Dart does not allow local class declarations)
|
|
||||||
class RecordingExporter extends ExportService {
|
|
||||||
bool called = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
class BasicExporter extends ExportService {}
|
|
||||||
|
|
||||||
void main() {
|
|
||||||
Future<void> pumpWithOpenPdf(WidgetTester tester) async {
|
|
||||||
await tester.pumpWidget(
|
|
||||||
ProviderScope(
|
|
||||||
overrides: [
|
|
||||||
pdfProvider.overrideWith(
|
|
||||||
(ref) => PdfController()..openPicked(path: 'test.pdf'),
|
|
||||||
),
|
|
||||||
useMockViewerProvider.overrideWith((ref) => true),
|
|
||||||
],
|
|
||||||
child: const MaterialApp(home: PdfSignatureHomePage()),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
await tester.pump();
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> pumpWithOpenPdfAndSig(WidgetTester tester) async {
|
|
||||||
await tester.pumpWidget(
|
|
||||||
ProviderScope(
|
|
||||||
overrides: [
|
|
||||||
pdfProvider.overrideWith(
|
|
||||||
(ref) => PdfController()..openPicked(path: 'test.pdf'),
|
|
||||||
),
|
|
||||||
signatureProvider.overrideWith(
|
|
||||||
(ref) => SignatureController()..placeDefaultRect(),
|
|
||||||
),
|
|
||||||
useMockViewerProvider.overrideWith((ref) => true),
|
|
||||||
],
|
|
||||||
child: const MaterialApp(home: PdfSignatureHomePage()),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
await tester.pump();
|
|
||||||
}
|
|
||||||
|
|
||||||
testWidgets('Open a PDF and navigate pages', (tester) async {
|
|
||||||
await pumpWithOpenPdf(tester);
|
|
||||||
final pageInfo = find.byKey(const Key('lbl_page_info'));
|
|
||||||
expect(pageInfo, findsOneWidget);
|
|
||||||
expect((tester.widget<Text>(pageInfo)).data, 'Page 1/5');
|
|
||||||
|
|
||||||
await tester.tap(find.byKey(const Key('btn_next')));
|
|
||||||
await tester.pump();
|
|
||||||
expect((tester.widget<Text>(pageInfo)).data, 'Page 2/5');
|
|
||||||
|
|
||||||
await tester.tap(find.byKey(const Key('btn_prev')));
|
|
||||||
await tester.pump();
|
|
||||||
expect((tester.widget<Text>(pageInfo)).data, 'Page 1/5');
|
|
||||||
});
|
|
||||||
|
|
||||||
testWidgets('Jump to a specific page', (tester) async {
|
|
||||||
await pumpWithOpenPdf(tester);
|
|
||||||
|
|
||||||
final goto = find.byKey(const Key('txt_goto'));
|
|
||||||
await tester.enterText(goto, '4');
|
|
||||||
await tester.testTextInput.receiveAction(TextInputAction.done);
|
|
||||||
await tester.pump();
|
|
||||||
final pageInfo = find.byKey(const Key('lbl_page_info'));
|
|
||||||
expect((tester.widget<Text>(pageInfo)).data, 'Page 4/5');
|
|
||||||
});
|
|
||||||
|
|
||||||
testWidgets('Select a page for signing', (tester) async {
|
|
||||||
await pumpWithOpenPdf(tester);
|
|
||||||
|
|
||||||
await tester.tap(find.byKey(const Key('btn_mark_signing')));
|
|
||||||
await tester.pump();
|
|
||||||
// signature actions appear (picker-based now)
|
|
||||||
expect(find.byKey(const Key('btn_load_signature_picker')), findsOneWidget);
|
|
||||||
});
|
|
||||||
|
|
||||||
testWidgets('Show invalid/unsupported file SnackBar via test hook', (
|
|
||||||
tester,
|
|
||||||
) async {
|
|
||||||
await pumpWithOpenPdf(tester);
|
|
||||||
final dynamic state =
|
|
||||||
tester.state(find.byType(PdfSignatureHomePage)) as dynamic;
|
|
||||||
state.debugShowInvalidSignatureSnackBar();
|
|
||||||
await tester.pump();
|
|
||||||
expect(find.text('Invalid or unsupported file'), findsOneWidget);
|
|
||||||
});
|
|
||||||
|
|
||||||
testWidgets('Import a signature image', (tester) async {
|
|
||||||
await pumpWithOpenPdfAndSig(tester);
|
|
||||||
await tester.tap(find.byKey(const Key('btn_mark_signing')));
|
|
||||||
await tester.pump();
|
|
||||||
// overlay present from provider override
|
|
||||||
expect(find.byKey(const Key('signature_overlay')), findsOneWidget);
|
|
||||||
});
|
|
||||||
|
|
||||||
// Removed: Load Invalid button is not part of normal app UI.
|
|
||||||
|
|
||||||
testWidgets('Resize and move signature within page bounds', (tester) async {
|
|
||||||
await pumpWithOpenPdfAndSig(tester);
|
|
||||||
await tester.tap(find.byKey(const Key('btn_mark_signing')));
|
|
||||||
await tester.pump();
|
|
||||||
|
|
||||||
final overlay = find.byKey(const Key('signature_overlay'));
|
|
||||||
final posBefore = tester.getTopLeft(overlay);
|
|
||||||
|
|
||||||
// drag the overlay
|
|
||||||
await tester.drag(overlay, const Offset(30, -20));
|
|
||||||
await tester.pump();
|
|
||||||
final posAfter = tester.getTopLeft(overlay);
|
|
||||||
// Allow equality in case clamped at edges
|
|
||||||
expect(posAfter.dx >= posBefore.dx, isTrue);
|
|
||||||
expect(posAfter.dy <= posBefore.dy, isTrue);
|
|
||||||
|
|
||||||
// resize via handle
|
|
||||||
final handle = find.byKey(const Key('signature_handle'));
|
|
||||||
final sizeBefore = tester.getSize(overlay);
|
|
||||||
await tester.drag(handle, const Offset(40, 40));
|
|
||||||
await tester.pump();
|
|
||||||
final sizeAfter = tester.getSize(overlay);
|
|
||||||
expect(sizeAfter.width >= sizeBefore.width, isTrue);
|
|
||||||
expect(sizeAfter.height >= sizeBefore.height, isTrue);
|
|
||||||
});
|
|
||||||
|
|
||||||
testWidgets('Lock aspect ratio while resizing', (tester) async {
|
|
||||||
await pumpWithOpenPdfAndSig(tester);
|
|
||||||
await tester.tap(find.byKey(const Key('btn_mark_signing')));
|
|
||||||
await tester.pump();
|
|
||||||
|
|
||||||
final overlay = find.byKey(const Key('signature_overlay'));
|
|
||||||
final sizeBefore = tester.getSize(overlay);
|
|
||||||
final aspect = sizeBefore.width / sizeBefore.height;
|
|
||||||
await tester.tap(find.byKey(const Key('chk_aspect_lock')));
|
|
||||||
await tester.pump();
|
|
||||||
await tester.drag(
|
|
||||||
find.byKey(const Key('signature_handle')),
|
|
||||||
const Offset(60, 10),
|
|
||||||
);
|
|
||||||
await tester.pump();
|
|
||||||
final sizeAfter = tester.getSize(overlay);
|
|
||||||
final newAspect = (sizeAfter.width / sizeAfter.height);
|
|
||||||
expect(
|
|
||||||
(newAspect - aspect).abs() < 0.15,
|
|
||||||
isTrue,
|
|
||||||
); // approximately preserved
|
|
||||||
});
|
|
||||||
|
|
||||||
testWidgets('Background removal and adjustments controls change state', (
|
|
||||||
tester,
|
|
||||||
) async {
|
|
||||||
await pumpWithOpenPdfAndSig(tester);
|
|
||||||
await tester.tap(find.byKey(const Key('btn_mark_signing')));
|
|
||||||
await tester.pump();
|
|
||||||
|
|
||||||
// toggle bg removal
|
|
||||||
await tester.tap(find.byKey(const Key('swt_bg_removal')));
|
|
||||||
await tester.pump();
|
|
||||||
// move sliders
|
|
||||||
await tester.drag(
|
|
||||||
find.byKey(const Key('sld_contrast')),
|
|
||||||
const Offset(50, 0),
|
|
||||||
);
|
|
||||||
await tester.drag(
|
|
||||||
find.byKey(const Key('sld_brightness')),
|
|
||||||
const Offset(-50, 0),
|
|
||||||
);
|
|
||||||
await tester.pump();
|
|
||||||
|
|
||||||
// basic smoke: overlay still present
|
|
||||||
expect(find.byKey(const Key('signature_overlay')), findsOneWidget);
|
|
||||||
});
|
|
||||||
|
|
||||||
testWidgets('DrawCanvas exports non-empty bytes on confirm', (tester) async {
|
|
||||||
Uint8List? exported;
|
|
||||||
final sink = ValueNotifier<Uint8List?>(null);
|
|
||||||
final control = hand.HandSignatureControl();
|
|
||||||
await tester.pumpWidget(
|
|
||||||
MaterialApp(
|
|
||||||
home: Scaffold(
|
|
||||||
body: DrawCanvas(
|
|
||||||
control: control,
|
|
||||||
debugBytesSink: sink,
|
|
||||||
onConfirm: (bytes) {
|
|
||||||
exported = bytes;
|
|
||||||
},
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
await tester.pumpAndSettle();
|
|
||||||
|
|
||||||
// Draw a simple stroke inside the pad
|
|
||||||
final pad = find.byKey(const Key('hand_signature_pad'));
|
|
||||||
expect(pad, findsOneWidget);
|
|
||||||
final rect = tester.getRect(pad);
|
|
||||||
final g = await tester.startGesture(
|
|
||||||
Offset(rect.left + 20, rect.center.dy),
|
|
||||||
kind: PointerDeviceKind.touch,
|
|
||||||
);
|
|
||||||
for (int i = 0; i < 10; i++) {
|
|
||||||
await g.moveBy(
|
|
||||||
const Offset(12, 0),
|
|
||||||
timeStamp: Duration(milliseconds: 16 * (i + 1)),
|
|
||||||
);
|
|
||||||
await tester.pump(const Duration(milliseconds: 16));
|
|
||||||
}
|
|
||||||
await g.up();
|
|
||||||
await tester.pump(const Duration(milliseconds: 50));
|
|
||||||
|
|
||||||
// Confirm export
|
|
||||||
await tester.tap(find.byKey(const Key('btn_canvas_confirm')));
|
|
||||||
// Wait until notifier receives bytes
|
|
||||||
await tester.pumpAndSettle(const Duration(milliseconds: 50));
|
|
||||||
await tester.runAsync(() async {
|
|
||||||
final end = DateTime.now().add(const Duration(seconds: 2));
|
|
||||||
while (sink.value == null && DateTime.now().isBefore(end)) {
|
|
||||||
await Future<void>.delayed(const Duration(milliseconds: 20));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
exported ??= sink.value;
|
|
||||||
|
|
||||||
expect(exported, isNotNull);
|
|
||||||
expect(exported!.isNotEmpty, isTrue);
|
|
||||||
});
|
|
||||||
|
|
||||||
testWidgets('Save uses file selector (via provider) and injected exporter', (
|
|
||||||
tester,
|
|
||||||
) async {
|
|
||||||
final fake = RecordingExporter();
|
|
||||||
await tester.pumpWidget(
|
|
||||||
ProviderScope(
|
|
||||||
overrides: [
|
|
||||||
pdfProvider.overrideWith(
|
|
||||||
(ref) => PdfController()..openPicked(path: 'test.pdf'),
|
|
||||||
),
|
|
||||||
signatureProvider.overrideWith(
|
|
||||||
(ref) => SignatureController()..placeDefaultRect(),
|
|
||||||
),
|
|
||||||
useMockViewerProvider.overrideWith((ref) => true),
|
|
||||||
exportServiceProvider.overrideWith((_) => fake),
|
|
||||||
savePathPickerProvider.overrideWith(
|
|
||||||
(_) => () async => 'C:/tmp/output.pdf',
|
|
||||||
),
|
|
||||||
],
|
|
||||||
child: const MaterialApp(home: PdfSignatureHomePage()),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
await tester.pump();
|
|
||||||
|
|
||||||
// Mark signing to set signedPage
|
|
||||||
await tester.tap(find.byKey(const Key('btn_mark_signing')));
|
|
||||||
await tester.pump();
|
|
||||||
|
|
||||||
// Trigger save
|
|
||||||
await tester.tap(find.byKey(const Key('btn_save_pdf')));
|
|
||||||
await tester.pumpAndSettle();
|
|
||||||
|
|
||||||
// With refactor, we no longer call boundary-based export here; still expect success UI.
|
|
||||||
expect(find.textContaining('Saved:'), findsOneWidget);
|
|
||||||
});
|
|
||||||
|
|
||||||
testWidgets('Only signed page shows overlay during export flow', (
|
|
||||||
tester,
|
|
||||||
) async {
|
|
||||||
await tester.pumpWidget(
|
|
||||||
ProviderScope(
|
|
||||||
overrides: [
|
|
||||||
pdfProvider.overrideWith(
|
|
||||||
(ref) => PdfController()..openPicked(path: 'test.pdf'),
|
|
||||||
),
|
|
||||||
signatureProvider.overrideWith(
|
|
||||||
(ref) => SignatureController()..placeDefaultRect(),
|
|
||||||
),
|
|
||||||
useMockViewerProvider.overrideWith((ref) => true),
|
|
||||||
exportServiceProvider.overrideWith((_) => BasicExporter()),
|
|
||||||
savePathPickerProvider.overrideWith(
|
|
||||||
(_) => () async => 'C:/tmp/output.pdf',
|
|
||||||
),
|
|
||||||
],
|
|
||||||
child: const MaterialApp(home: PdfSignatureHomePage()),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
await tester.pump();
|
|
||||||
// Mark signing on page 1
|
|
||||||
await tester.tap(find.byKey(const Key('btn_mark_signing')));
|
|
||||||
await tester.pump();
|
|
||||||
// Save -> open dialog -> confirm
|
|
||||||
await tester.tap(find.byKey(const Key('btn_save_pdf')));
|
|
||||||
await tester.pumpAndSettle();
|
|
||||||
// After export, overlay visible again
|
|
||||||
expect(find.byKey(const Key('signature_overlay')), findsOneWidget);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
Loading…
Reference in New Issue