Compare commits
No commits in common. "c1b7824cbd13509bac89fa09e531f391e8f60ee4" and "fba880e1be64ca205371bd2cc388b17b4a606cc9" have entirely different histories.
c1b7824cbd
...
fba880e1be
|
@ -135,5 +135,3 @@ AppDir/bundle/
|
||||||
appimage-build/
|
appimage-build/
|
||||||
/*.AppImage
|
/*.AppImage
|
||||||
.vscode/settings.json
|
.vscode/settings.json
|
||||||
|
|
||||||
*.patch
|
|
||||||
|
|
|
@ -6,7 +6,7 @@ Additionally read relevant files depends on task.
|
||||||
|
|
||||||
* If want to modify use cases (files at `test/features/*.feature`)
|
* If want to modify use cases (files at `test/features/*.feature`)
|
||||||
* read [`FRs.md`](docs/FRs.md)
|
* read [`FRs.md`](docs/FRs.md)
|
||||||
* If want to modify code (implement or test) of `ViewModel`, `View` of MVVM (UI widget) (files at `lib/ui/features/*/widgets/*`)
|
* If want to modify code (implement or test) of `View` of MVVM (UI widget) (files at `lib/ui/features/*/widgets/*`)
|
||||||
* read [`wireframe.md`](docs/wireframe.md), [`NFRs.md`](docs/NFRs.md), `test/features/*.feature`
|
* read [`wireframe.md`](docs/wireframe.md), [`NFRs.md`](docs/NFRs.md), `test/features/*.feature`
|
||||||
* If want to modify code (implement or test) of non-View e.g. `Model`, services...
|
* If want to modify code (implement or test) of non-View e.g. `Model`, `View Model`, services...
|
||||||
* read `test/features/*.feature`, [`NFRs.md`](docs/NFRs.md)
|
* read `test/features/*.feature`, [`NFRs.md`](docs/NFRs.md)
|
||||||
|
|
14
docs/FRs.md
14
docs/FRs.md
|
@ -2,27 +2,25 @@
|
||||||
|
|
||||||
## user stories
|
## user stories
|
||||||
|
|
||||||
The following user stories may not use formal terminology as [meta-arch.md](./meta-arch.md) and use cases(`test/*.feature`), but use oral descriptions.
|
|
||||||
|
|
||||||
* name: [PDF browser](../test/features/pdf_browser.feature)
|
* name: [PDF browser](../test/features/pdf_browser.feature)
|
||||||
* role: user
|
* role: user
|
||||||
* functionality: view and navigate PDF documents
|
* functionality: view and navigate PDF documents
|
||||||
* benefit: select page to add signature
|
* benefit: select page to add signature
|
||||||
* name: [load signature](../test/features/load_signature.feature)
|
* name: [load signature picture](../test/features/load_signature_picture.feature)
|
||||||
* role: user
|
* role: user
|
||||||
* functionality: load a signature asset file and create a signature card
|
* functionality: load a signature picture file
|
||||||
* benefit: easily add signature to PDF
|
* benefit: easily add signature to PDF
|
||||||
* name: [geometrically adjust signature picture](../test/features/geometrically_adjust_signature_picture.feature)
|
* name: [geometrically adjust signature picture](../test/features/geometrically_adjust_signature_picture.feature)
|
||||||
* role: user
|
* role: user
|
||||||
* functionality: adjust the scale, rotation and position of the signature placement on the PDF page
|
* functionality: adjust the size and position of the signature picture
|
||||||
* benefit: ensure the signature fits well on the PDF page
|
* benefit: ensure the signature fits well on the PDF page
|
||||||
* name: [graphically adjust signature picture](../test/features/graphically_adjust_signature_picture.feature)
|
* name: [graphically adjust signature picture](../test/features/graphically_adjust_signature_picture.feature)
|
||||||
* role: user
|
* role: user
|
||||||
* functionality: background removal, contrast adjustment... to enhance the appearance of the signature asset within the signature card
|
* functionality: background removal, contrast adjustment...
|
||||||
* benefit: easily improve the appearance of the signature on the PDF without additional software.
|
* benefit: easily improve the appearance of the signature on the PDF without additional software.
|
||||||
* name: [draw signature](../test/features/draw_signature.feature)
|
* name: [draw signature](../test/features/draw_signature.feature)
|
||||||
* role: user
|
* role: user
|
||||||
* functionality: draw a signature asset using mouse or touch input
|
* functionality: draw a signature using mouse or touch input
|
||||||
* benefit: create a custom signature directly on the PDF if no pre-made signature is available.
|
* benefit: create a custom signature directly on the PDF if no pre-made signature is available.
|
||||||
* name: [save signed PDF](../test/features/save_signed_pdf.feature)
|
* name: [save signed PDF](../test/features/save_signed_pdf.feature)
|
||||||
* role: user
|
* role: user
|
||||||
|
@ -30,7 +28,7 @@ The following user stories may not use formal terminology as [meta-arch.md](./me
|
||||||
* benefit: easily keep a copy of the signed document for records.
|
* benefit: easily keep a copy of the signed document for records.
|
||||||
* name: [preferences for app](../test/features/app_preferences.feature)
|
* name: [preferences for app](../test/features/app_preferences.feature)
|
||||||
* role: user
|
* role: user
|
||||||
* functionality: configure app preferences such as `language`, `theme`, `theme-color`.
|
* functionality: configure app preferences such as `theme`, `language`.
|
||||||
* benefit: customize the app experience to better fit user needs
|
* benefit: customize the app experience to better fit user needs
|
||||||
* name: [remember preferences](../test/features/remember_preferences.feature)
|
* name: [remember preferences](../test/features/remember_preferences.feature)
|
||||||
* role: user
|
* role: user
|
||||||
|
|
|
@ -1,9 +1,6 @@
|
||||||
# meta archietecture
|
# meta archietecture
|
||||||
|
|
||||||
* [MVVM](https://docs.flutter.dev/app-architecture/guide)
|
* [MVVM](https://docs.flutter.dev/app-architecture/guide)
|
||||||
* [Data layer](https://docs.flutter.dev/app-architecture/case-study/data-layer)
|
|
||||||
* View ⇆ ViewModel ⇆ Repository ⇆ Service
|
|
||||||
* Model is used across.
|
|
||||||
|
|
||||||
## Package structure
|
## Package structure
|
||||||
|
|
||||||
|
@ -14,24 +11,6 @@ The repo structure follows official [Package structure](https://docs.flutter.dev
|
||||||
* `test/widget/` contains UI widget(component) tests which focus on `View` from MVVM of each component.
|
* `test/widget/` contains UI widget(component) tests which focus on `View` from MVVM of each component.
|
||||||
* `integration_test/` for integration tests. They should be volatile to follow UI layout changes.
|
* `integration_test/` for integration tests. They should be volatile to follow UI layout changes.
|
||||||
|
|
||||||
Some rule of thumb:
|
|
||||||
* `<object>Provider` only placed at `/lib/data/repositories/` or `/lib/data/services/` to provide data source.
|
|
||||||
|
|
||||||
## Abstraction
|
|
||||||
|
|
||||||
### terminology
|
|
||||||
|
|
||||||
* signature asset
|
|
||||||
* image file of a signature, stored in the device or cloud storage
|
|
||||||
* can drawing from canvas
|
|
||||||
* signature card
|
|
||||||
* template of signature placement
|
|
||||||
* It will include modifications such as brightness, contrast, background removal, rotation of the signature asset.
|
|
||||||
* signature placement
|
|
||||||
* placed modified signature asset from signature card on a specific position on a specific page of a specific PDF document
|
|
||||||
* document
|
|
||||||
* PDF document to be signed
|
|
||||||
|
|
||||||
## key dependencies
|
## key dependencies
|
||||||
|
|
||||||
* [pdfrx](https://pub.dev/packages/pdfrx)
|
* [pdfrx](https://pub.dev/packages/pdfrx)
|
||||||
|
|
|
@ -7,9 +7,9 @@ import 'package:image/image.dart' as img;
|
||||||
|
|
||||||
import 'package:pdf_signature/data/services/export_service.dart';
|
import 'package:pdf_signature/data/services/export_service.dart';
|
||||||
import 'package:pdf_signature/data/services/export_providers.dart';
|
import 'package:pdf_signature/data/services/export_providers.dart';
|
||||||
import 'package:pdf_signature/data/repositories/signature_library_repository.dart';
|
import 'package:pdf_signature/ui/features/signature/view_model/signature_library.dart';
|
||||||
import 'package:pdf_signature/data/repositories/signature_repository.dart';
|
import 'package:pdf_signature/ui/features/signature/view_model/signature_controller.dart';
|
||||||
import 'package:pdf_signature/data/repositories/pdf_repository.dart';
|
import 'package:pdf_signature/ui/features/pdf/view_model/pdf_controller.dart';
|
||||||
import 'package:pdf_signature/ui/features/pdf/widgets/pdf_screen.dart';
|
import 'package:pdf_signature/ui/features/pdf/widgets/pdf_screen.dart';
|
||||||
import 'package:pdf_signature/l10n/app_localizations.dart';
|
import 'package:pdf_signature/l10n/app_localizations.dart';
|
||||||
|
|
||||||
|
@ -122,11 +122,11 @@ void main() {
|
||||||
final sigState = container.read(signatureProvider);
|
final sigState = container.read(signatureProvider);
|
||||||
final r = sigState.rect!;
|
final r = sigState.rect!;
|
||||||
final lib = container.read(signatureLibraryProvider);
|
final lib = container.read(signatureLibraryProvider);
|
||||||
final asset = lib.isNotEmpty ? lib.first : null;
|
final imageId = lib.isNotEmpty ? lib.first.id : 'default.png';
|
||||||
final pdf = container.read(pdfProvider);
|
final pdf = container.read(pdfProvider);
|
||||||
container
|
container
|
||||||
.read(pdfProvider.notifier)
|
.read(pdfProvider.notifier)
|
||||||
.addPlacement(page: pdf.currentPage, rect: r, asset: asset);
|
.addPlacement(page: pdf.currentPage, rect: r, imageId: imageId);
|
||||||
container.read(signatureProvider.notifier).clearActiveOverlay();
|
container.read(signatureProvider.notifier).clearActiveOverlay();
|
||||||
await tester.pumpAndSettle();
|
await tester.pumpAndSettle();
|
||||||
|
|
||||||
|
|
|
@ -3,7 +3,7 @@ import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
import 'package:flutter_localized_locales/flutter_localized_locales.dart';
|
import 'package:flutter_localized_locales/flutter_localized_locales.dart';
|
||||||
import 'package:pdf_signature/l10n/app_localizations.dart';
|
import 'package:pdf_signature/l10n/app_localizations.dart';
|
||||||
import 'package:pdf_signature/ui/features/pdf/widgets/pdf_screen.dart';
|
import 'package:pdf_signature/ui/features/pdf/widgets/pdf_screen.dart';
|
||||||
import 'package:pdf_signature/data/repositories/pdf_repository.dart';
|
import 'package:pdf_signature/ui/features/pdf/view_model/pdf_controller.dart';
|
||||||
import 'package:pdf_signature/ui/features/welcome/widgets/welcome_screen.dart';
|
import 'package:pdf_signature/ui/features/welcome/widgets/welcome_screen.dart';
|
||||||
import 'data/services/preferences_providers.dart';
|
import 'data/services/preferences_providers.dart';
|
||||||
import 'package:pdf_signature/ui/features/preferences/widgets/settings_screen.dart';
|
import 'package:pdf_signature/ui/features/preferences/widgets/settings_screen.dart';
|
||||||
|
|
|
@ -1,91 +1,32 @@
|
||||||
import 'dart:typed_data';
|
import 'dart:typed_data';
|
||||||
import 'package:flutter/widgets.dart';
|
import 'package:flutter/widgets.dart';
|
||||||
|
|
||||||
/// A simple library of signature images available to the user in the sidebar.
|
|
||||||
class SignatureAsset {
|
|
||||||
final String id; // unique id
|
|
||||||
final Uint8List bytes;
|
|
||||||
// List<List<Offset>>? strokes;
|
|
||||||
final String? name; // optional display name (e.g., filename)
|
|
||||||
const SignatureAsset({required this.id, required this.bytes, this.name});
|
|
||||||
}
|
|
||||||
|
|
||||||
class GraphicAdjust {
|
|
||||||
final double contrast;
|
|
||||||
final double brightness;
|
|
||||||
final bool bgRemoval;
|
|
||||||
|
|
||||||
const GraphicAdjust({
|
|
||||||
this.contrast = 1.0,
|
|
||||||
this.brightness = 0.0,
|
|
||||||
this.bgRemoval = false,
|
|
||||||
});
|
|
||||||
|
|
||||||
GraphicAdjust copyWith({
|
|
||||||
double? contrast,
|
|
||||||
double? brightness,
|
|
||||||
bool? bgRemoval,
|
|
||||||
}) => GraphicAdjust(
|
|
||||||
contrast: contrast ?? this.contrast,
|
|
||||||
brightness: brightness ?? this.brightness,
|
|
||||||
bgRemoval: bgRemoval ?? this.bgRemoval,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* signature card is template of signature placement
|
|
||||||
*/
|
|
||||||
class SignatureCard {
|
|
||||||
final double rotationDeg;
|
|
||||||
final SignatureAsset asset;
|
|
||||||
final GraphicAdjust graphicAdjust;
|
|
||||||
|
|
||||||
const SignatureCard({
|
|
||||||
required this.rotationDeg,
|
|
||||||
required this.asset,
|
|
||||||
this.graphicAdjust = const GraphicAdjust(),
|
|
||||||
});
|
|
||||||
|
|
||||||
SignatureCard copyWith({
|
|
||||||
double? rotationDeg,
|
|
||||||
SignatureAsset? asset,
|
|
||||||
GraphicAdjust? graphicAdjust,
|
|
||||||
}) => SignatureCard(
|
|
||||||
rotationDeg: rotationDeg ?? this.rotationDeg,
|
|
||||||
asset: asset ?? this.asset,
|
|
||||||
graphicAdjust: graphicAdjust ?? this.graphicAdjust,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Represents a single signature placement on a page combining both the
|
/// Represents a single signature placement on a page combining both the
|
||||||
/// geometric rectangle (UI coordinate space) and the signature asset
|
/// geometric rectangle (UI coordinate space) and the identifier of the
|
||||||
/// assigned to that placement.
|
/// image/signature asset assigned to that placement.
|
||||||
class SignaturePlacement {
|
class SignaturePlacement {
|
||||||
// The bounding box of this placement in UI coordinate space, implies scaling and position.
|
|
||||||
final Rect rect;
|
final Rect rect;
|
||||||
|
|
||||||
/// Rotation in degrees to apply when rendering/exporting this placement.
|
/// Rotation in degrees to apply when rendering/exporting this placement.
|
||||||
final double rotationDeg;
|
final double rotationDeg;
|
||||||
final GraphicAdjust graphicAdjust;
|
|
||||||
final SignatureAsset asset;
|
|
||||||
|
|
||||||
|
/// Identifier of the image (e.g., filename / asset id) assigned to this placement.
|
||||||
|
/// Nullable to allow a placement reserved before an image is chosen.
|
||||||
|
final String? imageId;
|
||||||
const SignaturePlacement({
|
const SignaturePlacement({
|
||||||
required this.rect,
|
required this.rect,
|
||||||
required this.asset,
|
this.imageId,
|
||||||
this.rotationDeg = 0.0,
|
this.rotationDeg = 0.0,
|
||||||
this.graphicAdjust = const GraphicAdjust(),
|
|
||||||
});
|
});
|
||||||
|
|
||||||
SignaturePlacement copyWith({
|
SignaturePlacement copyWith({
|
||||||
Rect? rect,
|
Rect? rect,
|
||||||
SignatureAsset? asset,
|
String? imageId,
|
||||||
double? rotationDeg,
|
double? rotationDeg,
|
||||||
GraphicAdjust? graphicAdjust,
|
|
||||||
}) => SignaturePlacement(
|
}) => SignaturePlacement(
|
||||||
rect: rect ?? this.rect,
|
rect: rect ?? this.rect,
|
||||||
asset: asset ?? this.asset,
|
imageId: imageId ?? this.imageId,
|
||||||
rotationDeg: rotationDeg ?? this.rotationDeg,
|
rotationDeg: rotationDeg ?? this.rotationDeg,
|
||||||
graphicAdjust: graphicAdjust ?? this.graphicAdjust,
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -96,7 +37,7 @@ class PdfState {
|
||||||
final String? pickedPdfPath;
|
final String? pickedPdfPath;
|
||||||
final Uint8List? pickedPdfBytes;
|
final Uint8List? pickedPdfBytes;
|
||||||
final int? signedPage;
|
final int? signedPage;
|
||||||
// Multiple signature placements per page, each combines geometry and asset.
|
// Multiple signature placements per page, each combines geometry and optional image id.
|
||||||
final Map<int, List<SignaturePlacement>> placementsByPage;
|
final Map<int, List<SignaturePlacement>> placementsByPage;
|
||||||
// UI state: selected placement index on the current page (if any)
|
// UI state: selected placement index on the current page (if any)
|
||||||
final int? selectedPlacementIndex;
|
final int? selectedPlacementIndex;
|
||||||
|
@ -151,8 +92,8 @@ class SignatureState {
|
||||||
final double rotation;
|
final double rotation;
|
||||||
final List<List<Offset>> strokes;
|
final List<List<Offset>> strokes;
|
||||||
final Uint8List? imageBytes;
|
final Uint8List? imageBytes;
|
||||||
// The signature asset the current overlay is based on (from library)
|
// The ID of the signature asset the current overlay is based on (from library)
|
||||||
final SignatureAsset? asset;
|
final String? assetId;
|
||||||
// When true, the active signature overlay is movable/resizable and should not be exported.
|
// When true, the active signature overlay is movable/resizable and should not be exported.
|
||||||
// When false, the overlay is confirmed (unmovable) and eligible for export.
|
// When false, the overlay is confirmed (unmovable) and eligible for export.
|
||||||
final bool editingEnabled;
|
final bool editingEnabled;
|
||||||
|
@ -165,7 +106,7 @@ class SignatureState {
|
||||||
this.rotation = 0.0,
|
this.rotation = 0.0,
|
||||||
required this.strokes,
|
required this.strokes,
|
||||||
this.imageBytes,
|
this.imageBytes,
|
||||||
this.asset,
|
this.assetId,
|
||||||
this.editingEnabled = false,
|
this.editingEnabled = false,
|
||||||
});
|
});
|
||||||
factory SignatureState.initial() => const SignatureState(
|
factory SignatureState.initial() => const SignatureState(
|
||||||
|
@ -177,7 +118,7 @@ class SignatureState {
|
||||||
rotation: 0.0,
|
rotation: 0.0,
|
||||||
strokes: [],
|
strokes: [],
|
||||||
imageBytes: null,
|
imageBytes: null,
|
||||||
asset: null,
|
assetId: null,
|
||||||
editingEnabled: false,
|
editingEnabled: false,
|
||||||
);
|
);
|
||||||
SignatureState copyWith({
|
SignatureState copyWith({
|
||||||
|
@ -189,7 +130,7 @@ class SignatureState {
|
||||||
double? rotation,
|
double? rotation,
|
||||||
List<List<Offset>>? strokes,
|
List<List<Offset>>? strokes,
|
||||||
Uint8List? imageBytes,
|
Uint8List? imageBytes,
|
||||||
SignatureAsset? asset,
|
String? assetId,
|
||||||
bool? editingEnabled,
|
bool? editingEnabled,
|
||||||
}) => SignatureState(
|
}) => SignatureState(
|
||||||
rect: rect ?? this.rect,
|
rect: rect ?? this.rect,
|
||||||
|
@ -200,7 +141,7 @@ class SignatureState {
|
||||||
rotation: rotation ?? this.rotation,
|
rotation: rotation ?? this.rotation,
|
||||||
strokes: strokes ?? this.strokes,
|
strokes: strokes ?? this.strokes,
|
||||||
imageBytes: imageBytes ?? this.imageBytes,
|
imageBytes: imageBytes ?? this.imageBytes,
|
||||||
asset: asset ?? this.asset,
|
assetId: assetId ?? this.assetId,
|
||||||
editingEnabled: editingEnabled ?? this.editingEnabled,
|
editingEnabled: editingEnabled ?? this.editingEnabled,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -148,8 +148,8 @@ class ExportService {
|
||||||
final w = r.width / uiPageSize.width * widthPts;
|
final w = r.width / uiPageSize.width * widthPts;
|
||||||
final h = r.height / uiPageSize.height * heightPts;
|
final h = r.height / uiPageSize.height * heightPts;
|
||||||
Uint8List? bytes;
|
Uint8List? bytes;
|
||||||
final id = placement.asset.id;
|
final id = placement.imageId;
|
||||||
if (id.isNotEmpty) {
|
if (id != null) {
|
||||||
bytes = libraryBytes?[id];
|
bytes = libraryBytes?[id];
|
||||||
}
|
}
|
||||||
bytes ??= signatureImageBytes; // fallback
|
bytes ??= signatureImageBytes; // fallback
|
||||||
|
@ -275,8 +275,8 @@ class ExportService {
|
||||||
final w = r.width / uiPageSize.width * widthPts;
|
final w = r.width / uiPageSize.width * widthPts;
|
||||||
final h = r.height / uiPageSize.height * heightPts;
|
final h = r.height / uiPageSize.height * heightPts;
|
||||||
Uint8List? bytes;
|
Uint8List? bytes;
|
||||||
final id = placement.asset.id;
|
final id = placement.imageId;
|
||||||
if (id.isNotEmpty) {
|
if (id != null) {
|
||||||
bytes = libraryBytes?[id];
|
bytes = libraryBytes?[id];
|
||||||
}
|
}
|
||||||
bytes ??= signatureImageBytes; // fallback
|
bytes ??= signatureImageBytes; // fallback
|
||||||
|
|
|
@ -65,7 +65,7 @@ class PdfController extends StateNotifier<PdfState> {
|
||||||
void addPlacement({
|
void addPlacement({
|
||||||
required int page,
|
required int page,
|
||||||
required Rect rect,
|
required Rect rect,
|
||||||
SignatureAsset? asset,
|
String? imageId = 'default.png',
|
||||||
double rotationDeg = 0.0,
|
double rotationDeg = 0.0,
|
||||||
}) {
|
}) {
|
||||||
if (!state.loaded) return;
|
if (!state.loaded) return;
|
||||||
|
@ -75,7 +75,7 @@ class PdfController extends StateNotifier<PdfState> {
|
||||||
list.add(
|
list.add(
|
||||||
SignaturePlacement(
|
SignaturePlacement(
|
||||||
rect: rect,
|
rect: rect,
|
||||||
asset: asset ?? SignatureAsset(id: '', bytes: Uint8List(0)),
|
imageId: imageId,
|
||||||
rotationDeg: rotationDeg,
|
rotationDeg: rotationDeg,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
@ -165,11 +165,11 @@ class PdfController extends StateNotifier<PdfState> {
|
||||||
|
|
||||||
// NOTE: Programmatic reassignment of images has been removed.
|
// NOTE: Programmatic reassignment of images has been removed.
|
||||||
|
|
||||||
// Convenience to get asset for a placement
|
// Convenience to get image name for a placement
|
||||||
SignatureAsset? assetOfPlacement({required int page, required int index}) {
|
String? imageOfPlacement({required int page, required int index}) {
|
||||||
final list = state.placementsByPage[page] ?? const [];
|
final list = state.placementsByPage[page] ?? const [];
|
||||||
if (index < 0 || index >= list.length) return null;
|
if (index < 0 || index >= list.length) return null;
|
||||||
return list[index].asset;
|
return list[index].imageId;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,7 +3,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 '../../../../data/model/model.dart';
|
import '../../../../data/model/model.dart';
|
||||||
import 'package:pdf_signature/data/repositories/signature_repository.dart';
|
import '../../signature/view_model/signature_controller.dart';
|
||||||
|
|
||||||
class AdjustmentsPanel extends ConsumerWidget {
|
class AdjustmentsPanel extends ConsumerWidget {
|
||||||
const AdjustmentsPanel({super.key, required this.sig});
|
const AdjustmentsPanel({super.key, required this.sig});
|
||||||
|
|
|
@ -2,7 +2,7 @@ import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
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:pdf_signature/data/repositories/signature_repository.dart';
|
import '../../signature/view_model/signature_controller.dart';
|
||||||
import 'adjustments_panel.dart';
|
import 'adjustments_panel.dart';
|
||||||
import '../../signature/widgets/rotated_signature_image.dart';
|
import '../../signature/widgets/rotated_signature_image.dart';
|
||||||
|
|
||||||
|
|
|
@ -4,8 +4,8 @@ import 'package:pdf_signature/l10n/app_localizations.dart';
|
||||||
import 'package:pdfrx/pdfrx.dart';
|
import 'package:pdfrx/pdfrx.dart';
|
||||||
|
|
||||||
import '../../../../data/services/export_providers.dart';
|
import '../../../../data/services/export_providers.dart';
|
||||||
import 'package:pdf_signature/data/repositories/signature_repository.dart';
|
import '../../signature/view_model/signature_controller.dart';
|
||||||
import 'package:pdf_signature/data/repositories/pdf_repository.dart';
|
import '../view_model/pdf_controller.dart';
|
||||||
import '../../signature/widgets/signature_drag_data.dart';
|
import '../../signature/widgets/signature_drag_data.dart';
|
||||||
import 'pdf_mock_continuous_list.dart';
|
import 'pdf_mock_continuous_list.dart';
|
||||||
import 'pdf_page_overlays.dart';
|
import 'pdf_page_overlays.dart';
|
||||||
|
@ -340,11 +340,11 @@ class _PdfPageAreaState extends ConsumerState<PdfPageArea> {
|
||||||
final cx = (local.dx / size.width) * widget.pageSize.width;
|
final cx = (local.dx / size.width) * widget.pageSize.width;
|
||||||
final cy = (local.dy / size.height) * widget.pageSize.height;
|
final cy = (local.dy / size.height) * widget.pageSize.height;
|
||||||
final data = details.data;
|
final data = details.data;
|
||||||
if (data is SignatureDragData && data.asset != null) {
|
if (data is SignatureDragData && data.assetId != null) {
|
||||||
// Set current overlay to use this asset
|
// Set current overlay to use this asset
|
||||||
ref
|
ref
|
||||||
.read(signatureProvider.notifier)
|
.read(signatureProvider.notifier)
|
||||||
.setImageFromLibrary(asset: data.asset!);
|
.setImageFromLibrary(assetId: data.assetId!);
|
||||||
}
|
}
|
||||||
ref.read(signatureProvider.notifier).placeAtCenter(Offset(cx, cy));
|
ref.read(signatureProvider.notifier).placeAtCenter(Offset(cx, cy));
|
||||||
ref
|
ref
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
|
|
||||||
import 'package:pdf_signature/data/repositories/signature_repository.dart';
|
import '../../signature/view_model/signature_controller.dart';
|
||||||
import '../../../../data/model/model.dart';
|
import '../../../../data/model/model.dart';
|
||||||
import 'package:pdf_signature/data/repositories/pdf_repository.dart';
|
import '../view_model/pdf_controller.dart';
|
||||||
import 'signature_overlay.dart';
|
import 'signature_overlay.dart';
|
||||||
|
|
||||||
/// Builds all overlays for a given page: placed signatures and the active one.
|
/// Builds all overlays for a given page: placed signatures and the active one.
|
||||||
|
|
|
@ -3,7 +3,7 @@ import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
import 'package:pdfrx/pdfrx.dart';
|
import 'package:pdfrx/pdfrx.dart';
|
||||||
|
|
||||||
import '../../../../data/services/export_providers.dart';
|
import '../../../../data/services/export_providers.dart';
|
||||||
import 'package:pdf_signature/data/repositories/pdf_repository.dart';
|
import '../view_model/pdf_controller.dart';
|
||||||
|
|
||||||
class PdfPagesOverview extends ConsumerWidget {
|
class PdfPagesOverview extends ConsumerWidget {
|
||||||
const PdfPagesOverview({super.key});
|
const PdfPagesOverview({super.key});
|
||||||
|
|
|
@ -10,9 +10,9 @@ import 'package:multi_split_view/multi_split_view.dart';
|
||||||
|
|
||||||
import '../../../../data/services/export_providers.dart';
|
import '../../../../data/services/export_providers.dart';
|
||||||
import 'package:image/image.dart' as img;
|
import 'package:image/image.dart' as img;
|
||||||
import 'package:pdf_signature/data/repositories/signature_repository.dart';
|
import '../../signature/view_model/signature_controller.dart';
|
||||||
import 'package:pdf_signature/data/repositories/pdf_repository.dart';
|
import '../view_model/pdf_controller.dart';
|
||||||
import 'package:pdf_signature/data/repositories/signature_library_repository.dart';
|
import '../../signature/view_model/signature_library.dart';
|
||||||
import 'draw_canvas.dart';
|
import 'draw_canvas.dart';
|
||||||
import 'pdf_toolbar.dart';
|
import 'pdf_toolbar.dart';
|
||||||
import 'pdf_page_area.dart';
|
import 'pdf_page_area.dart';
|
||||||
|
|
|
@ -3,7 +3,7 @@ import 'package:flutter/services.dart';
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
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:pdf_signature/data/repositories/pdf_repository.dart';
|
import '../view_model/pdf_controller.dart';
|
||||||
|
|
||||||
class PdfToolbar extends ConsumerStatefulWidget {
|
class PdfToolbar extends ConsumerStatefulWidget {
|
||||||
const PdfToolbar({
|
const PdfToolbar({
|
||||||
|
|
|
@ -2,11 +2,10 @@ import 'dart:typed_data';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
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:pdf_signature/data/model/model.dart' as model;
|
|
||||||
|
|
||||||
import '../../../../data/services/export_providers.dart';
|
import '../../../../data/services/export_providers.dart';
|
||||||
import 'package:pdf_signature/data/repositories/signature_repository.dart';
|
import '../../signature/view_model/signature_controller.dart';
|
||||||
import 'package:pdf_signature/data/repositories/signature_library_repository.dart';
|
import '../../signature/view_model/signature_library.dart';
|
||||||
import 'image_editor_dialog.dart';
|
import 'image_editor_dialog.dart';
|
||||||
import '../../signature/widgets/signature_card.dart';
|
import '../../signature/widgets/signature_card.dart';
|
||||||
|
|
||||||
|
@ -53,14 +52,14 @@ class _SignatureDrawerState extends ConsumerState<SignatureDrawer> {
|
||||||
child: SignatureCard(
|
child: SignatureCard(
|
||||||
key: ValueKey('sig_card_${a.id}'),
|
key: ValueKey('sig_card_${a.id}'),
|
||||||
asset:
|
asset:
|
||||||
(sig.asset?.id == a.id)
|
(sig.assetId == a.id)
|
||||||
? model.SignatureAsset(
|
? SignatureAsset(
|
||||||
id: a.id,
|
id: a.id,
|
||||||
bytes: (processed ?? a.bytes),
|
bytes: (processed ?? a.bytes),
|
||||||
name: a.name,
|
name: a.name,
|
||||||
)
|
)
|
||||||
: a,
|
: a,
|
||||||
rotationDeg: (sig.asset?.id == a.id) ? sig.rotation : 0.0,
|
rotationDeg: (sig.assetId == a.id) ? sig.rotation : 0.0,
|
||||||
disabled: disabled,
|
disabled: disabled,
|
||||||
onDelete:
|
onDelete:
|
||||||
() => ref
|
() => ref
|
||||||
|
@ -69,7 +68,7 @@ class _SignatureDrawerState extends ConsumerState<SignatureDrawer> {
|
||||||
onAdjust: () async {
|
onAdjust: () async {
|
||||||
ref
|
ref
|
||||||
.read(signatureProvider.notifier)
|
.read(signatureProvider.notifier)
|
||||||
.setImageFromLibrary(asset: a);
|
.setImageFromLibrary(assetId: a.id);
|
||||||
if (!mounted) return;
|
if (!mounted) return;
|
||||||
await showDialog(
|
await showDialog(
|
||||||
context: context,
|
context: context,
|
||||||
|
@ -80,7 +79,7 @@ class _SignatureDrawerState extends ConsumerState<SignatureDrawer> {
|
||||||
// Never reassign placed signatures via tap; only set active overlay source
|
// Never reassign placed signatures via tap; only set active overlay source
|
||||||
ref
|
ref
|
||||||
.read(signatureProvider.notifier)
|
.read(signatureProvider.notifier)
|
||||||
.setImageFromLibrary(asset: a);
|
.setImageFromLibrary(assetId: a.id);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
@ -97,11 +96,7 @@ class _SignatureDrawerState extends ConsumerState<SignatureDrawer> {
|
||||||
bytes == null
|
bytes == null
|
||||||
? Text(l.noSignatureLoaded)
|
? Text(l.noSignatureLoaded)
|
||||||
: SignatureCard(
|
: SignatureCard(
|
||||||
asset: model.SignatureAsset(
|
asset: SignatureAsset(id: '', bytes: bytes, name: ''),
|
||||||
id: '',
|
|
||||||
bytes: bytes,
|
|
||||||
name: '',
|
|
||||||
),
|
|
||||||
rotationDeg: sig.rotation,
|
rotationDeg: sig.rotation,
|
||||||
disabled: disabled,
|
disabled: disabled,
|
||||||
useCurrentBytesForDrag: true,
|
useCurrentBytesForDrag: true,
|
||||||
|
@ -153,14 +148,9 @@ class _SignatureDrawerState extends ConsumerState<SignatureDrawer> {
|
||||||
final id = ref
|
final id = ref
|
||||||
.read(signatureLibraryProvider.notifier)
|
.read(signatureLibraryProvider.notifier)
|
||||||
.add(b, name: 'image');
|
.add(b, name: 'image');
|
||||||
final asset = ref
|
ref
|
||||||
.read(signatureLibraryProvider.notifier)
|
.read(signatureProvider.notifier)
|
||||||
.byId(id);
|
.setImageFromLibrary(assetId: id);
|
||||||
if (asset != null) {
|
|
||||||
ref
|
|
||||||
.read(signatureProvider.notifier)
|
|
||||||
.setImageFromLibrary(asset: asset);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
icon: const Icon(Icons.image_outlined),
|
icon: const Icon(Icons.image_outlined),
|
||||||
|
@ -181,14 +171,9 @@ class _SignatureDrawerState extends ConsumerState<SignatureDrawer> {
|
||||||
final id = ref
|
final id = ref
|
||||||
.read(signatureLibraryProvider.notifier)
|
.read(signatureLibraryProvider.notifier)
|
||||||
.add(b, name: 'drawing');
|
.add(b, name: 'drawing');
|
||||||
final asset = ref
|
ref
|
||||||
.read(signatureLibraryProvider.notifier)
|
.read(signatureProvider.notifier)
|
||||||
.byId(id);
|
.setImageFromLibrary(assetId: id);
|
||||||
if (asset != null) {
|
|
||||||
ref
|
|
||||||
.read(signatureProvider.notifier)
|
|
||||||
.setImageFromLibrary(asset: asset);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
icon: const Icon(Icons.gesture),
|
icon: const Icon(Icons.gesture),
|
||||||
|
|
|
@ -5,9 +5,9 @@ import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
import 'package:pdf_signature/l10n/app_localizations.dart';
|
import 'package:pdf_signature/l10n/app_localizations.dart';
|
||||||
|
|
||||||
import '../../../../data/model/model.dart';
|
import '../../../../data/model/model.dart';
|
||||||
import 'package:pdf_signature/data/repositories/signature_repository.dart';
|
import '../../signature/view_model/signature_controller.dart';
|
||||||
import 'package:pdf_signature/data/repositories/pdf_repository.dart';
|
import '../view_model/pdf_controller.dart';
|
||||||
import 'package:pdf_signature/data/repositories/signature_library_repository.dart';
|
import '../../signature/view_model/signature_library.dart';
|
||||||
import 'image_editor_dialog.dart';
|
import 'image_editor_dialog.dart';
|
||||||
import '../../signature/widgets/rotated_signature_image.dart';
|
import '../../signature/widgets/rotated_signature_image.dart';
|
||||||
|
|
||||||
|
@ -245,8 +245,8 @@ class _SignatureImage extends ConsumerWidget {
|
||||||
(placementList != null && placedIndex! < placementList.length)
|
(placementList != null && placedIndex! < placementList.length)
|
||||||
? placementList[placedIndex!]
|
? placementList[placedIndex!]
|
||||||
: null;
|
: null;
|
||||||
final imgId = (placement?.asset)?.id;
|
final imgId = placement?.imageId;
|
||||||
if (imgId != null && imgId.isNotEmpty) {
|
if (imgId != null) {
|
||||||
final lib = ref.watch(signatureLibraryProvider);
|
final lib = ref.watch(signatureLibraryProvider);
|
||||||
for (final a in lib) {
|
for (final a in lib) {
|
||||||
if (a.id == imgId) {
|
if (a.id == imgId) {
|
||||||
|
|
|
@ -7,7 +7,8 @@ import 'package:image/image.dart' as img;
|
||||||
import 'package:pdf_signature/l10n/app_localizations.dart';
|
import 'package:pdf_signature/l10n/app_localizations.dart';
|
||||||
|
|
||||||
import '../../../../data/model/model.dart';
|
import '../../../../data/model/model.dart';
|
||||||
import 'pdf_repository.dart';
|
import '../../pdf/view_model/pdf_controller.dart';
|
||||||
|
import 'signature_library.dart';
|
||||||
|
|
||||||
class SignatureController extends StateNotifier<SignatureState> {
|
class SignatureController extends StateNotifier<SignatureState> {
|
||||||
SignatureController() : super(SignatureState.initial());
|
SignatureController() : super(SignatureState.initial());
|
||||||
|
@ -138,7 +139,7 @@ class SignatureController extends StateNotifier<SignatureState> {
|
||||||
}
|
}
|
||||||
|
|
||||||
void setImageBytes(Uint8List bytes) {
|
void setImageBytes(Uint8List bytes) {
|
||||||
state = state.copyWith(imageBytes: bytes, asset: null);
|
state = state.copyWith(imageBytes: bytes, assetId: null);
|
||||||
if (state.rect == null) {
|
if (state.rect == null) {
|
||||||
placeDefaultRect();
|
placeDefaultRect();
|
||||||
}
|
}
|
||||||
|
@ -147,8 +148,8 @@ class SignatureController extends StateNotifier<SignatureState> {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Select image from the shared signature library
|
// Select image from the shared signature library
|
||||||
void setImageFromLibrary({required SignatureAsset asset}) {
|
void setImageFromLibrary({required String assetId}) {
|
||||||
state = state.copyWith(asset: asset);
|
state = state.copyWith(assetId: assetId);
|
||||||
if (state.rect == null) {
|
if (state.rect == null) {
|
||||||
placeDefaultRect();
|
placeDefaultRect();
|
||||||
}
|
}
|
||||||
|
@ -176,17 +177,18 @@ class SignatureController extends StateNotifier<SignatureState> {
|
||||||
if (!pdf.loaded) return null;
|
if (!pdf.loaded) return null;
|
||||||
// Bind the processed image at placement time (so placed preview matches adjustments).
|
// Bind the processed image at placement time (so placed preview matches adjustments).
|
||||||
// If processed bytes exist, always create a new asset for this placement.
|
// If processed bytes exist, always create a new asset for this placement.
|
||||||
// Prefer reusing an existing library asset when the active overlay is
|
// Prefer reusing an existing library asset id when the active overlay is
|
||||||
// based on a library item. If there is no library asset, do NOT create
|
// based on a library item. If there is no library asset, do NOT create
|
||||||
// a new library card here — keep the placement's asset empty so the
|
// a new library card here — keep the placement's image id empty so the
|
||||||
// UI and exporter will fall back to using the processed/current bytes.
|
// UI and exporter will fall back to using the processed/current bytes.
|
||||||
|
String id = state.assetId ?? '';
|
||||||
// Store as UI-space rect (consistent with export and rendering paths)
|
// Store as UI-space rect (consistent with export and rendering paths)
|
||||||
ref
|
ref
|
||||||
.read(pdfProvider.notifier)
|
.read(pdfProvider.notifier)
|
||||||
.addPlacement(
|
.addPlacement(
|
||||||
page: pdf.currentPage,
|
page: pdf.currentPage,
|
||||||
rect: r,
|
rect: r,
|
||||||
asset: state.asset,
|
imageId: id,
|
||||||
rotationDeg: state.rotation,
|
rotationDeg: state.rotation,
|
||||||
);
|
);
|
||||||
// Newly placed index is the last one on the page
|
// Newly placed index is the last one on the page
|
||||||
|
@ -210,14 +212,15 @@ class SignatureController extends StateNotifier<SignatureState> {
|
||||||
if (r == null) return null;
|
if (r == null) return null;
|
||||||
final pdf = container.read(pdfProvider);
|
final pdf = container.read(pdfProvider);
|
||||||
if (!pdf.loaded) return null;
|
if (!pdf.loaded) return null;
|
||||||
// Reuse existing library asset if present; otherwise leave empty so the
|
// Reuse existing library id if present; otherwise leave empty so the
|
||||||
// placement will reference the current bytes via fallback paths.
|
// placement will reference the current bytes via fallback paths.
|
||||||
|
String id = state.assetId ?? '';
|
||||||
container
|
container
|
||||||
.read(pdfProvider.notifier)
|
.read(pdfProvider.notifier)
|
||||||
.addPlacement(
|
.addPlacement(
|
||||||
page: pdf.currentPage,
|
page: pdf.currentPage,
|
||||||
rect: r,
|
rect: r,
|
||||||
asset: state.asset,
|
imageId: id,
|
||||||
rotationDeg: state.rotation,
|
rotationDeg: state.rotation,
|
||||||
);
|
);
|
||||||
final idx =
|
final idx =
|
||||||
|
@ -227,11 +230,9 @@ class SignatureController extends StateNotifier<SignatureState> {
|
||||||
?.length ??
|
?.length ??
|
||||||
1) -
|
1) -
|
||||||
1;
|
1;
|
||||||
// Auto-select the newly placed item so the red box appears
|
|
||||||
if (idx >= 0) {
|
if (idx >= 0) {
|
||||||
container.read(pdfProvider.notifier).selectPlacement(idx);
|
container.read(pdfProvider.notifier).selectPlacement(idx);
|
||||||
}
|
}
|
||||||
// Freeze editing: keep rect for preview but disable interaction
|
|
||||||
state = state.copyWith(editingEnabled: false);
|
state = state.copyWith(editingEnabled: false);
|
||||||
return r;
|
return r;
|
||||||
}
|
}
|
||||||
|
@ -252,9 +253,7 @@ final signatureProvider =
|
||||||
/// Returns null if no image is loaded. The output is a PNG to preserve alpha.
|
/// Returns null if no image is loaded. The output is a PNG to preserve alpha.
|
||||||
final processedSignatureImageProvider = Provider<Uint8List?>((ref) {
|
final processedSignatureImageProvider = Provider<Uint8List?>((ref) {
|
||||||
// Watch only the fields that affect pixel processing to avoid recompute on rotation.
|
// Watch only the fields that affect pixel processing to avoid recompute on rotation.
|
||||||
final SignatureAsset? asset = ref.watch(
|
final String? assetId = ref.watch(signatureProvider.select((s) => s.assetId));
|
||||||
signatureProvider.select((s) => s.asset),
|
|
||||||
);
|
|
||||||
final Uint8List? directBytes = ref.watch(
|
final Uint8List? directBytes = ref.watch(
|
||||||
signatureProvider.select((s) => s.imageBytes),
|
signatureProvider.select((s) => s.imageBytes),
|
||||||
);
|
);
|
||||||
|
@ -270,8 +269,14 @@ final processedSignatureImageProvider = Provider<Uint8List?>((ref) {
|
||||||
|
|
||||||
// If active overlay is based on a library asset, pull its bytes
|
// If active overlay is based on a library asset, pull its bytes
|
||||||
Uint8List? bytes;
|
Uint8List? bytes;
|
||||||
if (asset != null) {
|
if (assetId != null) {
|
||||||
bytes = asset.bytes;
|
final lib = ref.watch(signatureLibraryProvider);
|
||||||
|
for (final a in lib) {
|
||||||
|
if (a.id == assetId) {
|
||||||
|
bytes = a.bytes;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
bytes = directBytes;
|
bytes = directBytes;
|
||||||
}
|
}
|
|
@ -1,6 +1,13 @@
|
||||||
import 'dart:typed_data';
|
import 'dart:typed_data';
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
import 'package:pdf_signature/data/model/model.dart';
|
|
||||||
|
/// A simple library of signature images available to the user in the sidebar.
|
||||||
|
class SignatureAsset {
|
||||||
|
final String id; // unique id
|
||||||
|
final Uint8List bytes;
|
||||||
|
final String? name; // optional display name (e.g., filename)
|
||||||
|
const SignatureAsset({required this.id, required this.bytes, this.name});
|
||||||
|
}
|
||||||
|
|
||||||
class SignatureLibraryController extends StateNotifier<List<SignatureAsset>> {
|
class SignatureLibraryController extends StateNotifier<List<SignatureAsset>> {
|
||||||
SignatureLibraryController() : super(const []);
|
SignatureLibraryController() : super(const []);
|
|
@ -1,5 +1,5 @@
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:pdf_signature/data/model/model.dart';
|
import '../view_model/signature_library.dart';
|
||||||
import 'signature_drag_data.dart';
|
import 'signature_drag_data.dart';
|
||||||
import 'rotated_signature_image.dart';
|
import 'rotated_signature_image.dart';
|
||||||
import 'package:pdf_signature/l10n/app_localizations.dart';
|
import 'package:pdf_signature/l10n/app_localizations.dart';
|
||||||
|
@ -142,7 +142,7 @@ class SignatureCard extends StatelessWidget {
|
||||||
data:
|
data:
|
||||||
useCurrentBytesForDrag
|
useCurrentBytesForDrag
|
||||||
? const SignatureDragData()
|
? const SignatureDragData()
|
||||||
: SignatureDragData(asset: asset),
|
: SignatureDragData(assetId: asset.id),
|
||||||
feedback: Opacity(
|
feedback: Opacity(
|
||||||
opacity: 0.9,
|
opacity: 0.9,
|
||||||
child: ConstrainedBox(
|
child: ConstrainedBox(
|
||||||
|
|
|
@ -1,6 +1,4 @@
|
||||||
import 'package:pdf_signature/data/model/model.dart';
|
|
||||||
|
|
||||||
class SignatureDragData {
|
class SignatureDragData {
|
||||||
final SignatureAsset? asset; // null means use current processed signature
|
final String? assetId; // null means use current processed signature
|
||||||
const SignatureDragData({this.asset});
|
const SignatureDragData({this.assetId});
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,8 +7,8 @@ import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
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:pdf_signature/data/repositories/signature_repository.dart';
|
import '../../signature/view_model/signature_controller.dart';
|
||||||
import 'package:pdf_signature/data/repositories/pdf_repository.dart';
|
import '../../pdf/view_model/pdf_controller.dart';
|
||||||
// Settings dialog is provided via global AppBar in MyApp
|
// Settings dialog is provided via global AppBar in MyApp
|
||||||
|
|
||||||
// Abstraction to make drop handling testable without constructing
|
// Abstraction to make drop handling testable without constructing
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
Feature: draw signature asset
|
Feature: draw signature
|
||||||
|
|
||||||
Scenario: Draw with mouse or touch and place on page
|
Scenario: Draw with mouse or touch and place on page
|
||||||
Given an empty signature canvas
|
Given an empty signature canvas
|
||||||
When the user draws strokes and confirms
|
When the user draws strokes and confirms
|
||||||
Then a signature asset is created
|
Then a signature image is created
|
||||||
And signature placement occurs on the selected page
|
And it is placed on the selected page
|
||||||
|
|
||||||
Scenario: Clear and redraw
|
Scenario: Clear and redraw
|
||||||
Given a drawn signature exists in the canvas
|
Given a drawn signature exists in the canvas
|
||||||
|
|
|
@ -1,13 +1,12 @@
|
||||||
Feature: geometrically adjust signature asset
|
Feature: geometrically adjust signature picture
|
||||||
|
|
||||||
Scenario: Resize and move the signature within page bounds
|
Scenario: Resize and move the signature within page bounds
|
||||||
Given a signature asset is placed on the page
|
Given a signature image is placed on the page
|
||||||
When the user drags handles to resize and drags to reposition
|
When the user drags handles to resize and drags to reposition
|
||||||
Then the size and position update in real time
|
Then the size and position update in real time
|
||||||
And the signature placement remains within the page area
|
And the signature remains within the page area
|
||||||
|
|
||||||
Scenario: Rotate the signature
|
Scenario: Lock aspect ratio while resizing
|
||||||
Given a signature asset is placed on the page
|
Given a signature image is selected
|
||||||
When the user uses rotate controls
|
When the user enables aspect ratio lock and resizes
|
||||||
Then the signature placement rotates around its center in real time
|
Then the image scales proportionally
|
||||||
And resize to fit within bounding box
|
|
||||||
|
|
|
@ -1,13 +1,13 @@
|
||||||
Feature: graphically adjust signature asset
|
Feature: graphically adjust signature picture
|
||||||
|
|
||||||
Scenario: Remove background
|
Scenario: Remove background
|
||||||
Given a signature asset is selected
|
Given a signature image is selected
|
||||||
When the user enables background removal
|
When the user enables background removal
|
||||||
Then near-white background becomes transparent in the preview
|
Then near-white background becomes transparent in the preview
|
||||||
And the user can apply the change
|
And the user can apply the change
|
||||||
|
|
||||||
Scenario: Adjust contrast and brightness
|
Scenario: Adjust contrast and brightness
|
||||||
Given a signature asset is selected
|
Given a signature image is selected
|
||||||
When the user changes contrast and brightness controls
|
When the user changes contrast and brightness controls
|
||||||
Then the preview updates immediately
|
Then the preview updates immediately
|
||||||
And the user can apply or reset adjustments
|
And the user can apply or reset adjustments
|
||||||
|
|
|
@ -1,27 +0,0 @@
|
||||||
Feature: load signature asset
|
|
||||||
|
|
||||||
Scenario Outline: Handle invalid or unsupported files
|
|
||||||
Given the user selects "<file>"
|
|
||||||
When the app attempts to load the asset
|
|
||||||
Then the user is notified of the issue
|
|
||||||
And the asset is not added to the document
|
|
||||||
|
|
||||||
Examples:
|
|
||||||
| file |
|
|
||||||
| 'corrupted.png' |
|
|
||||||
| 'signature.bmp' |
|
|
||||||
| 'empty.jpg' |
|
|
||||||
|
|
||||||
Scenario: Import a signature asset
|
|
||||||
When the user chooses a image file as a signature asset
|
|
||||||
Then the asset is loaded and shown as a signature asset
|
|
||||||
|
|
||||||
Scenario: Import a signature card
|
|
||||||
When the user chooses a signature asset to created a signature card
|
|
||||||
Then the asset is loaded and shown as a signature card
|
|
||||||
|
|
||||||
Scenario: Import a signature placement
|
|
||||||
Given a created signature card
|
|
||||||
When the user drags this signature card on the page of the document to place a signature placement
|
|
||||||
Then a signature placement appears on the page based on the signature card
|
|
||||||
|
|
|
@ -0,0 +1,18 @@
|
||||||
|
Feature: load signature picture
|
||||||
|
|
||||||
|
Scenario: Import a signature image
|
||||||
|
Given a PDF page is selected for signing
|
||||||
|
When the user chooses a signature image file
|
||||||
|
Then the image is loaded and shown as a signature asset
|
||||||
|
|
||||||
|
Scenario Outline: Handle invalid or unsupported files
|
||||||
|
Given the user selects "<file>"
|
||||||
|
When the app attempts to load the image
|
||||||
|
Then the user is notified of the issue
|
||||||
|
And the image is not added to the document
|
||||||
|
|
||||||
|
Examples:
|
||||||
|
| file |
|
||||||
|
| 'corrupted.png' |
|
||||||
|
| 'signature.bmp' |
|
||||||
|
| 'empty.jpg' |
|
|
@ -1,9 +1,9 @@
|
||||||
Feature: document browser
|
Feature: PDF browser
|
||||||
|
|
||||||
Background:
|
Background:
|
||||||
Given a sample multi-page document (5 pages) is available
|
Given a sample multi-page PDF (5 pages) is available
|
||||||
|
|
||||||
Scenario: Open a document and navigate pages
|
Scenario: Open a PDF and navigate pages
|
||||||
When the user opens the document
|
When the user opens the document
|
||||||
Then the first page is displayed
|
Then the first page is displayed
|
||||||
And the user can move to the next or previous page
|
And the user can move to the next or previous page
|
||||||
|
@ -47,6 +47,6 @@ Feature: document browser
|
||||||
Then the last page is displayed (page {5})
|
Then the last page is displayed (page {5})
|
||||||
And the page label shows "Page {5} of {5}"
|
And the page label shows "Page {5} of {5}"
|
||||||
|
|
||||||
Scenario: Go to is disabled when no document is loaded
|
Scenario: Go to is disabled when no PDF is loaded
|
||||||
Given no document is open
|
Given no document is open
|
||||||
Then the Go to input cannot be used
|
Then the Go to input cannot be used
|
||||||
|
|
|
@ -1,26 +1,26 @@
|
||||||
Feature: save signed document
|
Feature: save signed PDF
|
||||||
|
|
||||||
Scenario: Export the signed document to a new file
|
Scenario: Export the signed document to a new file
|
||||||
Given a document is open and contains at least one signature placement
|
Given a PDF is open and contains at least one placed signature
|
||||||
When the user saves/exports the document
|
When the user saves/exports the document
|
||||||
Then a new document file is saved at specified full path, location and file name
|
Then a new PDF file is saved at specified full path, location and file name
|
||||||
And the signature placements appear on the corresponding page in the output
|
And the signatures appear on the corresponding page in the output
|
||||||
And keep other unchanged content(pages) intact in the output
|
And keep other unchanged content(pages) intact in the output
|
||||||
|
|
||||||
Scenario: Vector-accurate stamping into PDF page coordinates
|
Scenario: Vector-accurate stamping into PDF page coordinates
|
||||||
Given a signature placement is placed with a position and size relative to the page
|
Given a signature is placed with a position and size relative to the page
|
||||||
When the user saves/exports the document
|
When the user saves/exports the document
|
||||||
Then the signature placement is stamped at the exact PDF page coordinates and size
|
Then the signature is stamped at the exact PDF page coordinates and size
|
||||||
And the stamp remains crisp at any zoom level (not rasterized by the screen)
|
And the stamp remains crisp at any zoom level (not rasterized by the screen)
|
||||||
And other page content remains vector and unaltered
|
And other page content remains vector and unaltered
|
||||||
|
|
||||||
Scenario: Prevent saving when nothing is placed
|
Scenario: Prevent saving when nothing is placed
|
||||||
Given a document is open with no signature placements placed
|
Given a PDF is open with no signatures placed
|
||||||
When the user attempts to save
|
When the user attempts to save
|
||||||
Then the user is notified there is nothing to save
|
Then the user is notified there is nothing to save
|
||||||
|
|
||||||
Scenario: Loading sign when exporting/saving files
|
Scenario: Loading sign when exporting/saving files
|
||||||
Given a signature placement is placed with a position and size relative to the page
|
Given a signature is placed with a position and size relative to the page
|
||||||
When the user starts exporting the document
|
When the user starts exporting the document
|
||||||
And the export process is not yet finished
|
And the export process is not yet finished
|
||||||
Then the user is notified that the export is still in progress
|
Then the user is notified that the export is still in progress
|
||||||
|
|
|
@ -1,19 +0,0 @@
|
||||||
import 'dart:typed_data';
|
|
||||||
import 'package:flutter_test/flutter_test.dart';
|
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
|
||||||
import 'package:pdf_signature/data/repositories/signature_library_repository.dart';
|
|
||||||
import 'package:pdf_signature/data/model/model.dart';
|
|
||||||
import '_world.dart';
|
|
||||||
|
|
||||||
/// Usage: a created signature card
|
|
||||||
Future<void> aCreatedSignatureCard(WidgetTester tester) async {
|
|
||||||
final container = TestWorld.container ?? ProviderContainer();
|
|
||||||
TestWorld.container = container;
|
|
||||||
// Create a dummy signature asset
|
|
||||||
final asset = SignatureAsset(
|
|
||||||
id: 'test_card',
|
|
||||||
bytes: Uint8List(100),
|
|
||||||
name: 'Test Card',
|
|
||||||
);
|
|
||||||
container.read(signatureLibraryProvider.notifier).state = [asset];
|
|
||||||
}
|
|
|
@ -1,25 +0,0 @@
|
||||||
import 'dart:typed_data';
|
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:flutter_test/flutter_test.dart';
|
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
|
||||||
import 'package:pdf_signature/data/repositories/pdf_repository.dart';
|
|
||||||
import 'package:pdf_signature/data/model/model.dart';
|
|
||||||
import '_world.dart';
|
|
||||||
|
|
||||||
/// Usage: a document is open and contains at least one signature placement
|
|
||||||
Future<void> aDocumentIsOpenAndContainsAtLeastOneSignaturePlacement(
|
|
||||||
WidgetTester tester,
|
|
||||||
) async {
|
|
||||||
final container = TestWorld.container ?? ProviderContainer();
|
|
||||||
TestWorld.container = container;
|
|
||||||
container
|
|
||||||
.read(pdfProvider.notifier)
|
|
||||||
.openPicked(path: 'test.pdf', pageCount: 5);
|
|
||||||
container
|
|
||||||
.read(pdfProvider.notifier)
|
|
||||||
.addPlacement(
|
|
||||||
page: 1,
|
|
||||||
rect: Rect.fromLTWH(10, 10, 100, 50),
|
|
||||||
asset: SignatureAsset(id: 'sig.png', bytes: Uint8List(0)),
|
|
||||||
);
|
|
||||||
}
|
|
|
@ -1,40 +0,0 @@
|
||||||
import 'dart:typed_data';
|
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:flutter_test/flutter_test.dart';
|
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
|
||||||
import 'package:pdf_signature/data/repositories/pdf_repository.dart';
|
|
||||||
import 'package:pdf_signature/data/model/model.dart';
|
|
||||||
import '_world.dart';
|
|
||||||
|
|
||||||
/// Usage: a document is open and contains multiple placed signature placements across pages
|
|
||||||
Future<void>
|
|
||||||
aDocumentIsOpenAndContainsMultiplePlacedSignaturePlacementsAcrossPages(
|
|
||||||
WidgetTester tester,
|
|
||||||
) async {
|
|
||||||
final container = TestWorld.container ?? ProviderContainer();
|
|
||||||
TestWorld.container = container;
|
|
||||||
container
|
|
||||||
.read(pdfProvider.notifier)
|
|
||||||
.openPicked(path: 'multi.pdf', pageCount: 5);
|
|
||||||
container
|
|
||||||
.read(pdfProvider.notifier)
|
|
||||||
.addPlacement(
|
|
||||||
page: 1,
|
|
||||||
rect: Rect.fromLTWH(10, 10, 100, 50),
|
|
||||||
asset: SignatureAsset(id: 'sig1.png', bytes: Uint8List(0)),
|
|
||||||
);
|
|
||||||
container
|
|
||||||
.read(pdfProvider.notifier)
|
|
||||||
.addPlacement(
|
|
||||||
page: 2,
|
|
||||||
rect: Rect.fromLTWH(20, 20, 100, 50),
|
|
||||||
asset: SignatureAsset(id: 'sig2.png', bytes: Uint8List(0)),
|
|
||||||
);
|
|
||||||
container
|
|
||||||
.read(pdfProvider.notifier)
|
|
||||||
.addPlacement(
|
|
||||||
page: 3,
|
|
||||||
rect: Rect.fromLTWH(30, 30, 100, 50),
|
|
||||||
asset: SignatureAsset(id: 'sig3.png', bytes: Uint8List(0)),
|
|
||||||
);
|
|
||||||
}
|
|
|
@ -1,16 +0,0 @@
|
||||||
import 'package:flutter_test/flutter_test.dart';
|
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
|
||||||
import 'package:pdf_signature/data/repositories/pdf_repository.dart';
|
|
||||||
import '_world.dart';
|
|
||||||
|
|
||||||
/// Usage: a document is open with no signature placements placed
|
|
||||||
Future<void> aDocumentIsOpenWithNoSignaturePlacementsPlaced(
|
|
||||||
WidgetTester tester,
|
|
||||||
) async {
|
|
||||||
final container = TestWorld.container ?? ProviderContainer();
|
|
||||||
TestWorld.container = container;
|
|
||||||
container
|
|
||||||
.read(pdfProvider.notifier)
|
|
||||||
.openPicked(path: 'empty.pdf', pageCount: 5);
|
|
||||||
// No placements added
|
|
||||||
}
|
|
|
@ -1,12 +0,0 @@
|
||||||
import 'package:flutter_test/flutter_test.dart';
|
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
|
||||||
import 'package:pdf_signature/data/repositories/pdf_repository.dart';
|
|
||||||
import '_world.dart';
|
|
||||||
|
|
||||||
/// Usage: a document page is selected for signing
|
|
||||||
Future<void> aDocumentPageIsSelectedForSigning(WidgetTester tester) async {
|
|
||||||
final container = TestWorld.container ?? ProviderContainer();
|
|
||||||
TestWorld.container = container;
|
|
||||||
container.read(pdfProvider.notifier).setSignedPage(1);
|
|
||||||
container.read(pdfProvider.notifier).jumpTo(1);
|
|
||||||
}
|
|
|
@ -1,6 +1,6 @@
|
||||||
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/data/repositories/signature_repository.dart';
|
import 'package:pdf_signature/ui/features/signature/view_model/signature_controller.dart';
|
||||||
import '_world.dart';
|
import '_world.dart';
|
||||||
|
|
||||||
/// Usage: a drawn signature exists in the canvas
|
/// Usage: a drawn signature exists in the canvas
|
||||||
|
|
|
@ -1,19 +0,0 @@
|
||||||
import 'package:flutter_test/flutter_test.dart';
|
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
|
||||||
import 'package:pdf_signature/data/repositories/pdf_repository.dart';
|
|
||||||
import 'package:pdf_signature/data/repositories/signature_repository.dart';
|
|
||||||
import 'package:pdf_signature/data/repositories/signature_library_repository.dart';
|
|
||||||
import 'package:pdf_signature/data/model/model.dart';
|
|
||||||
import '_world.dart';
|
|
||||||
|
|
||||||
/// Usage: a multi-page document is open
|
|
||||||
Future<void> aMultipageDocumentIsOpen(WidgetTester tester) async {
|
|
||||||
final container = TestWorld.container ?? ProviderContainer();
|
|
||||||
TestWorld.container = container;
|
|
||||||
container.read(signatureLibraryProvider.notifier).state = [];
|
|
||||||
container.read(pdfProvider.notifier).state = PdfState.initial();
|
|
||||||
container.read(signatureProvider.notifier).state = SignatureState.initial();
|
|
||||||
container
|
|
||||||
.read(pdfProvider.notifier)
|
|
||||||
.openPicked(path: 'mock.pdf', pageCount: 5);
|
|
||||||
}
|
|
|
@ -1,15 +1,13 @@
|
||||||
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/data/repositories/pdf_repository.dart';
|
import 'package:pdf_signature/ui/features/pdf/view_model/pdf_controller.dart';
|
||||||
import '_world.dart';
|
import '_world.dart';
|
||||||
|
|
||||||
/// Usage: a sample multi-page document (5 pages) is available
|
/// Usage: a multi-page PDF is open
|
||||||
Future<void> aSampleMultipageDocument5PagesIsAvailable(
|
Future<void> aMultipagePdfIsOpen(WidgetTester tester) async {
|
||||||
WidgetTester tester,
|
|
||||||
) async {
|
|
||||||
final container = TestWorld.container ?? ProviderContainer();
|
final container = TestWorld.container ?? ProviderContainer();
|
||||||
TestWorld.container = container;
|
TestWorld.container = container;
|
||||||
container
|
container
|
||||||
.read(pdfProvider.notifier)
|
.read(pdfProvider.notifier)
|
||||||
.openPicked(path: 'sample.pdf', pageCount: 5);
|
.openPicked(path: 'sample.pdf', pageCount: 10);
|
||||||
}
|
}
|
|
@ -1,24 +0,0 @@
|
||||||
import 'package:flutter_test/flutter_test.dart';
|
|
||||||
import '_world.dart';
|
|
||||||
|
|
||||||
/// Usage: a new document file is saved at specified full path, location and file name
|
|
||||||
Future<void> aNewDocumentFileIsSavedAtSpecifiedFullPathLocationAndFileName(
|
|
||||||
WidgetTester tester,
|
|
||||||
) async {
|
|
||||||
// Verify that export bytes were generated
|
|
||||||
expect(
|
|
||||||
TestWorld.lastExportBytes,
|
|
||||||
isNotNull,
|
|
||||||
reason: 'Export bytes should be generated after save',
|
|
||||||
);
|
|
||||||
|
|
||||||
// Simulate a saved path (in a real implementation this would come from file picker)
|
|
||||||
TestWorld.lastSavedPath =
|
|
||||||
TestWorld.lastSavedPath ?? '/tmp/signed_document.pdf';
|
|
||||||
|
|
||||||
expect(
|
|
||||||
TestWorld.lastSavedPath,
|
|
||||||
isNotNull,
|
|
||||||
reason: 'A save path should be specified',
|
|
||||||
);
|
|
||||||
}
|
|
|
@ -0,0 +1,15 @@
|
||||||
|
import 'dart:io';
|
||||||
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
|
import '_world.dart';
|
||||||
|
|
||||||
|
/// Usage: a new PDF file is saved at specified full path, location and file name
|
||||||
|
Future<void> aNewPdfFileIsSavedAtSpecifiedFullPathLocationAndFileName(
|
||||||
|
WidgetTester tester,
|
||||||
|
) async {
|
||||||
|
if (TestWorld.lastSavedPath != null) {
|
||||||
|
expect(File(TestWorld.lastSavedPath!).existsSync(), isTrue);
|
||||||
|
} else {
|
||||||
|
expect(TestWorld.lastExportBytes, isNotNull);
|
||||||
|
expect(TestWorld.lastExportBytes!.isNotEmpty, isTrue);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,26 @@
|
||||||
|
import 'dart:typed_data';
|
||||||
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
|
import 'package:pdf_signature/ui/features/signature/view_model/signature_controller.dart';
|
||||||
|
import 'package:pdf_signature/ui/features/pdf/view_model/pdf_controller.dart';
|
||||||
|
import '_world.dart';
|
||||||
|
|
||||||
|
/// Usage: a PDF is open and contains at least one placed signature
|
||||||
|
Future<void> aPdfIsOpenAndContainsAtLeastOnePlacedSignature(
|
||||||
|
WidgetTester tester,
|
||||||
|
) async {
|
||||||
|
final container = TestWorld.container ?? ProviderContainer();
|
||||||
|
TestWorld.container = container;
|
||||||
|
container
|
||||||
|
.read(pdfProvider.notifier)
|
||||||
|
.openPicked(
|
||||||
|
path: 'mock.pdf',
|
||||||
|
pageCount: 2,
|
||||||
|
bytes: Uint8List.fromList([1, 2, 3]),
|
||||||
|
);
|
||||||
|
container.read(pdfProvider.notifier).setSignedPage(1);
|
||||||
|
container.read(signatureProvider.notifier).placeDefaultRect();
|
||||||
|
container
|
||||||
|
.read(signatureProvider.notifier)
|
||||||
|
.setImageBytes(Uint8List.fromList([1, 2, 3]));
|
||||||
|
}
|
|
@ -0,0 +1,32 @@
|
||||||
|
import 'dart:typed_data';
|
||||||
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:pdf_signature/ui/features/signature/view_model/signature_controller.dart';
|
||||||
|
import 'package:pdf_signature/ui/features/pdf/view_model/pdf_controller.dart';
|
||||||
|
import '_world.dart';
|
||||||
|
|
||||||
|
/// Usage: a PDF is open and contains multiple placed signatures across pages
|
||||||
|
Future<void> aPdfIsOpenAndContainsMultiplePlacedSignaturesAcrossPages(
|
||||||
|
WidgetTester tester,
|
||||||
|
) async {
|
||||||
|
final container = TestWorld.container ?? ProviderContainer();
|
||||||
|
TestWorld.container = container;
|
||||||
|
container
|
||||||
|
.read(pdfProvider.notifier)
|
||||||
|
.openPicked(path: 'mock.pdf', pageCount: 6);
|
||||||
|
// Ensure signature image exists
|
||||||
|
container
|
||||||
|
.read(signatureProvider.notifier)
|
||||||
|
.setImageBytes(Uint8List.fromList([1, 2, 3]));
|
||||||
|
// Place on two pages
|
||||||
|
container
|
||||||
|
.read(pdfProvider.notifier)
|
||||||
|
.addPlacement(page: 1, rect: const Rect.fromLTWH(10, 10, 80, 40));
|
||||||
|
container
|
||||||
|
.read(pdfProvider.notifier)
|
||||||
|
.addPlacement(page: 4, rect: const Rect.fromLTWH(120, 200, 100, 50));
|
||||||
|
// Keep backward compatibility with existing export step expectations
|
||||||
|
container.read(pdfProvider.notifier).setSignedPage(1);
|
||||||
|
container.read(signatureProvider.notifier).placeDefaultRect();
|
||||||
|
}
|
|
@ -0,0 +1,17 @@
|
||||||
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
|
import 'package:pdf_signature/ui/features/signature/view_model/signature_controller.dart';
|
||||||
|
import 'package:pdf_signature/ui/features/pdf/view_model/pdf_controller.dart';
|
||||||
|
import '_world.dart';
|
||||||
|
|
||||||
|
/// Usage: a PDF is open with no signatures placed
|
||||||
|
Future<void> aPdfIsOpenWithNoSignaturesPlaced(WidgetTester tester) async {
|
||||||
|
// Fresh world for this scenario to avoid leftover rect/image from previous tests
|
||||||
|
TestWorld.reset();
|
||||||
|
final container = ProviderContainer();
|
||||||
|
TestWorld.container = container;
|
||||||
|
container
|
||||||
|
.read(pdfProvider.notifier)
|
||||||
|
.openPicked(path: 'mock.pdf', pageCount: 1);
|
||||||
|
container.read(signatureProvider.notifier).resetForNewPage();
|
||||||
|
}
|
|
@ -0,0 +1,14 @@
|
||||||
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
|
import 'package:pdf_signature/ui/features/pdf/view_model/pdf_controller.dart';
|
||||||
|
import '_world.dart';
|
||||||
|
|
||||||
|
/// Usage: a PDF page is selected for signing
|
||||||
|
Future<void> aPdfPageIsSelectedForSigning(WidgetTester tester) async {
|
||||||
|
final container = TestWorld.container ?? ProviderContainer();
|
||||||
|
TestWorld.container = container;
|
||||||
|
container
|
||||||
|
.read(pdfProvider.notifier)
|
||||||
|
.openPicked(path: 'mock.pdf', pageCount: 1);
|
||||||
|
container.read(pdfProvider.notifier).setSignedPage(1);
|
||||||
|
}
|
|
@ -0,0 +1,14 @@
|
||||||
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
|
import 'package:pdf_signature/ui/features/pdf/view_model/pdf_controller.dart';
|
||||||
|
import '_world.dart';
|
||||||
|
|
||||||
|
/// Usage: a sample multi-page PDF (5 pages) is available
|
||||||
|
Future<void> aSampleMultipagePdf5PagesIsAvailable(WidgetTester tester) async {
|
||||||
|
final container = TestWorld.container ?? ProviderContainer();
|
||||||
|
TestWorld.container = container;
|
||||||
|
// Open a mock document with 5 pages
|
||||||
|
container
|
||||||
|
.read(pdfProvider.notifier)
|
||||||
|
.openPicked(path: 'mock.pdf', pageCount: 5);
|
||||||
|
}
|
|
@ -1,39 +0,0 @@
|
||||||
import 'dart:typed_data';
|
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:flutter_test/flutter_test.dart';
|
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
|
||||||
import 'package:pdf_signature/data/repositories/pdf_repository.dart';
|
|
||||||
import 'package:pdf_signature/data/repositories/signature_library_repository.dart';
|
|
||||||
import 'package:pdf_signature/data/model/model.dart';
|
|
||||||
import '_world.dart';
|
|
||||||
|
|
||||||
/// Usage: a signature asset is created
|
|
||||||
Future<void> aSignatureAssetIsCreated(WidgetTester tester) async {
|
|
||||||
final container = TestWorld.container ?? ProviderContainer();
|
|
||||||
TestWorld.container = container;
|
|
||||||
|
|
||||||
// Ensure PDF is open
|
|
||||||
if (!container.read(pdfProvider).loaded) {
|
|
||||||
container
|
|
||||||
.read(pdfProvider.notifier)
|
|
||||||
.openPicked(path: 'mock.pdf', pageCount: 5);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create a dummy signature asset
|
|
||||||
final asset = SignatureAsset(
|
|
||||||
id: 'test_asset',
|
|
||||||
bytes: Uint8List(100),
|
|
||||||
name: 'Test Asset',
|
|
||||||
);
|
|
||||||
container.read(signatureLibraryProvider.notifier).state = [asset];
|
|
||||||
|
|
||||||
// Place it on the current page
|
|
||||||
final pdf = container.read(pdfProvider);
|
|
||||||
container
|
|
||||||
.read(pdfProvider.notifier)
|
|
||||||
.addPlacement(
|
|
||||||
page: pdf.currentPage,
|
|
||||||
rect: Rect.fromLTWH(50, 50, 100, 50),
|
|
||||||
asset: asset,
|
|
||||||
);
|
|
||||||
}
|
|
|
@ -1,21 +0,0 @@
|
||||||
import 'dart:typed_data';
|
|
||||||
import 'package:flutter_test/flutter_test.dart';
|
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
|
||||||
import 'package:pdf_signature/data/repositories/signature_library_repository.dart';
|
|
||||||
import 'package:pdf_signature/data/repositories/pdf_repository.dart';
|
|
||||||
import 'package:pdf_signature/data/repositories/signature_repository.dart';
|
|
||||||
import 'package:pdf_signature/data/model/model.dart';
|
|
||||||
import '_world.dart';
|
|
||||||
|
|
||||||
/// Usage: a signature asset is loaded or drawn
|
|
||||||
Future<void> aSignatureAssetIsLoadedOrDrawn(WidgetTester tester) async {
|
|
||||||
final container = TestWorld.container ?? ProviderContainer();
|
|
||||||
TestWorld.container = container;
|
|
||||||
container.read(signatureLibraryProvider.notifier).state = [];
|
|
||||||
container.read(pdfProvider.notifier).state = PdfState.initial();
|
|
||||||
container.read(signatureProvider.notifier).state = SignatureState.initial();
|
|
||||||
final bytes = Uint8List.fromList([1, 2, 3, 4, 5]);
|
|
||||||
container
|
|
||||||
.read(signatureLibraryProvider.notifier)
|
|
||||||
.add(bytes, name: 'test.png');
|
|
||||||
}
|
|
|
@ -1,46 +0,0 @@
|
||||||
import 'dart:typed_data';
|
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:flutter_test/flutter_test.dart';
|
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
|
||||||
import 'package:pdf_signature/data/repositories/pdf_repository.dart';
|
|
||||||
import 'package:pdf_signature/data/repositories/signature_library_repository.dart';
|
|
||||||
import 'package:pdf_signature/data/model/model.dart';
|
|
||||||
import '_world.dart';
|
|
||||||
|
|
||||||
/// Usage: a signature asset is placed on the page
|
|
||||||
Future<void> aSignatureAssetIsPlacedOnThePage(WidgetTester tester) async {
|
|
||||||
final container = TestWorld.container ?? ProviderContainer();
|
|
||||||
TestWorld.container = container;
|
|
||||||
|
|
||||||
// Ensure PDF is open
|
|
||||||
if (!container.read(pdfProvider).loaded) {
|
|
||||||
container
|
|
||||||
.read(pdfProvider.notifier)
|
|
||||||
.openPicked(path: 'mock.pdf', pageCount: 5);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get or create an asset
|
|
||||||
var library = container.read(signatureLibraryProvider);
|
|
||||||
SignatureAsset asset;
|
|
||||||
if (library.isNotEmpty) {
|
|
||||||
asset = library.first;
|
|
||||||
} else {
|
|
||||||
final bytes = Uint8List.fromList([1, 2, 3, 4, 5]);
|
|
||||||
final id = container
|
|
||||||
.read(signatureLibraryProvider.notifier)
|
|
||||||
.add(bytes, name: 'test.png');
|
|
||||||
asset = container
|
|
||||||
.read(signatureLibraryProvider)
|
|
||||||
.firstWhere((a) => a.id == id);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Place it on the current page
|
|
||||||
final pdf = container.read(pdfProvider);
|
|
||||||
container
|
|
||||||
.read(pdfProvider.notifier)
|
|
||||||
.addPlacement(
|
|
||||||
page: pdf.currentPage,
|
|
||||||
rect: Rect.fromLTWH(50, 50, 100, 50),
|
|
||||||
asset: asset,
|
|
||||||
);
|
|
||||||
}
|
|
|
@ -1,32 +0,0 @@
|
||||||
import 'dart:typed_data';
|
|
||||||
import 'package:flutter_test/flutter_test.dart';
|
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
|
||||||
import 'package:pdf_signature/data/repositories/signature_library_repository.dart';
|
|
||||||
import 'package:pdf_signature/data/model/model.dart';
|
|
||||||
import '_world.dart';
|
|
||||||
|
|
||||||
/// Usage: a signature asset is selected
|
|
||||||
Future<void> aSignatureAssetIsSelected(WidgetTester tester) async {
|
|
||||||
final container = TestWorld.container ?? ProviderContainer();
|
|
||||||
TestWorld.container = container;
|
|
||||||
var library = container.read(signatureLibraryProvider);
|
|
||||||
|
|
||||||
// If library is empty, add a dummy asset
|
|
||||||
if (library.isEmpty) {
|
|
||||||
final asset = SignatureAsset(
|
|
||||||
id: 'selected_asset',
|
|
||||||
bytes: Uint8List(100),
|
|
||||||
name: 'Selected Asset',
|
|
||||||
);
|
|
||||||
container.read(signatureLibraryProvider.notifier).state = [asset];
|
|
||||||
// Re-read the library
|
|
||||||
library = container.read(signatureLibraryProvider);
|
|
||||||
}
|
|
||||||
|
|
||||||
expect(
|
|
||||||
library.isNotEmpty,
|
|
||||||
true,
|
|
||||||
reason: 'Library should have at least one asset',
|
|
||||||
);
|
|
||||||
// For test purposes, we consider the first asset as selected
|
|
||||||
}
|
|
|
@ -1,23 +0,0 @@
|
||||||
import 'dart:typed_data';
|
|
||||||
import 'package:flutter_test/flutter_test.dart';
|
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
|
||||||
import 'package:pdf_signature/data/repositories/signature_library_repository.dart';
|
|
||||||
import 'package:pdf_signature/data/repositories/pdf_repository.dart';
|
|
||||||
import 'package:pdf_signature/data/repositories/signature_repository.dart';
|
|
||||||
import 'package:pdf_signature/data/model/model.dart';
|
|
||||||
import '_world.dart';
|
|
||||||
|
|
||||||
/// Usage: a signature asset loaded or drawn is wrapped in a signature card
|
|
||||||
Future<void> aSignatureAssetLoadedOrDrawnIsWrappedInASignatureCard(
|
|
||||||
WidgetTester tester,
|
|
||||||
) async {
|
|
||||||
final container = TestWorld.container ?? ProviderContainer();
|
|
||||||
TestWorld.container = container;
|
|
||||||
container.read(signatureLibraryProvider.notifier).state = [];
|
|
||||||
container.read(pdfProvider.notifier).state = PdfState.initial();
|
|
||||||
container.read(signatureProvider.notifier).state = SignatureState.initial();
|
|
||||||
final bytes = Uint8List.fromList([1, 2, 3, 4, 5]);
|
|
||||||
container
|
|
||||||
.read(signatureLibraryProvider.notifier)
|
|
||||||
.add(bytes, name: 'test.png');
|
|
||||||
}
|
|
|
@ -0,0 +1,10 @@
|
||||||
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
|
import 'package:pdf_signature/ui/features/signature/view_model/signature_controller.dart';
|
||||||
|
import '_world.dart';
|
||||||
|
|
||||||
|
/// Usage: a signature image is created
|
||||||
|
Future<void> aSignatureImageIsCreated(WidgetTester tester) async {
|
||||||
|
final container = TestWorld.container ?? ProviderContainer();
|
||||||
|
expect(container.read(signatureProvider).imageBytes, isNotNull);
|
||||||
|
}
|
|
@ -0,0 +1,14 @@
|
||||||
|
import 'dart:typed_data';
|
||||||
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
|
import 'package:pdf_signature/ui/features/signature/view_model/signature_controller.dart';
|
||||||
|
import '_world.dart';
|
||||||
|
|
||||||
|
/// Usage: a signature image is loaded or drawn
|
||||||
|
Future<void> aSignatureImageIsLoadedOrDrawn(WidgetTester tester) async {
|
||||||
|
final container = TestWorld.container ?? ProviderContainer();
|
||||||
|
TestWorld.container = container;
|
||||||
|
container
|
||||||
|
.read(signatureProvider.notifier)
|
||||||
|
.setImageBytes(Uint8List.fromList([1, 2, 3]));
|
||||||
|
}
|
|
@ -0,0 +1,20 @@
|
||||||
|
import 'dart:typed_data';
|
||||||
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
|
import 'package:pdf_signature/ui/features/signature/view_model/signature_controller.dart';
|
||||||
|
import 'package:pdf_signature/ui/features/pdf/view_model/pdf_controller.dart';
|
||||||
|
import '_world.dart';
|
||||||
|
|
||||||
|
/// Usage: a signature image is placed on the page
|
||||||
|
Future<void> aSignatureImageIsPlacedOnThePage(WidgetTester tester) async {
|
||||||
|
final container = TestWorld.container ?? ProviderContainer();
|
||||||
|
TestWorld.container = container;
|
||||||
|
container
|
||||||
|
.read(pdfProvider.notifier)
|
||||||
|
.openPicked(path: 'mock.pdf', pageCount: 5);
|
||||||
|
container.read(pdfProvider.notifier).setSignedPage(1);
|
||||||
|
// Set an image to ensure rect exists
|
||||||
|
container
|
||||||
|
.read(signatureProvider.notifier)
|
||||||
|
.setImageBytes(Uint8List.fromList([1, 2, 3]));
|
||||||
|
}
|
|
@ -0,0 +1,28 @@
|
||||||
|
import 'dart:typed_data';
|
||||||
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
|
import 'package:pdf_signature/ui/features/signature/view_model/signature_controller.dart';
|
||||||
|
import 'package:pdf_signature/ui/features/pdf/view_model/pdf_controller.dart';
|
||||||
|
import '_world.dart';
|
||||||
|
|
||||||
|
/// Usage: a signature image is selected
|
||||||
|
Future<void> aSignatureImageIsSelected(WidgetTester tester) async {
|
||||||
|
final container = TestWorld.container ?? ProviderContainer();
|
||||||
|
TestWorld.container = container;
|
||||||
|
container
|
||||||
|
.read(pdfProvider.notifier)
|
||||||
|
.openPicked(path: 'mock.pdf', pageCount: 2);
|
||||||
|
container.read(pdfProvider.notifier).setSignedPage(1);
|
||||||
|
container
|
||||||
|
.read(signatureProvider.notifier)
|
||||||
|
.setImageBytes(Uint8List.fromList([1, 2, 3]));
|
||||||
|
// Allow provider scheduler to process queued updates fully
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
// Extra pump with a non-zero duration to flush zero-delay timers
|
||||||
|
await tester.pump(const Duration(milliseconds: 1));
|
||||||
|
// Teardown to avoid pending timers from Riverpod's scheduler
|
||||||
|
addTearDown(() {
|
||||||
|
TestWorld.container?.dispose();
|
||||||
|
TestWorld.container = null;
|
||||||
|
});
|
||||||
|
}
|
|
@ -0,0 +1,25 @@
|
||||||
|
import 'dart:typed_data';
|
||||||
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:pdf_signature/ui/features/signature/view_model/signature_controller.dart';
|
||||||
|
import 'package:pdf_signature/ui/features/pdf/view_model/pdf_controller.dart';
|
||||||
|
import '_world.dart';
|
||||||
|
|
||||||
|
/// Usage: a signature is placed on page {2}
|
||||||
|
Future<void> aSignatureIsPlacedOnPage(WidgetTester tester, num page) async {
|
||||||
|
final container = TestWorld.container ?? ProviderContainer();
|
||||||
|
TestWorld.container = container;
|
||||||
|
container
|
||||||
|
.read(pdfProvider.notifier)
|
||||||
|
.openPicked(path: 'mock.pdf', pageCount: 6);
|
||||||
|
// Ensure image and rect
|
||||||
|
container
|
||||||
|
.read(signatureProvider.notifier)
|
||||||
|
.setImageBytes(Uint8List.fromList([1, 2, 3]));
|
||||||
|
container.read(signatureProvider.notifier).placeDefaultRect();
|
||||||
|
final Rect r = container.read(signatureProvider).rect!;
|
||||||
|
container
|
||||||
|
.read(pdfProvider.notifier)
|
||||||
|
.addPlacement(page: page.toInt(), rect: r, imageId: 'default.png');
|
||||||
|
}
|
|
@ -0,0 +1,37 @@
|
||||||
|
import 'dart:typed_data';
|
||||||
|
import 'dart:ui';
|
||||||
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
|
import 'package:pdf_signature/ui/features/signature/view_model/signature_controller.dart';
|
||||||
|
import 'package:pdf_signature/ui/features/pdf/view_model/pdf_controller.dart';
|
||||||
|
import '_world.dart';
|
||||||
|
|
||||||
|
/// Usage: a signature is placed with a position and size relative to the page
|
||||||
|
Future<void> aSignatureIsPlacedWithAPositionAndSizeRelativeToThePage(
|
||||||
|
WidgetTester tester,
|
||||||
|
) async {
|
||||||
|
final container = TestWorld.container ?? ProviderContainer();
|
||||||
|
TestWorld.container = container;
|
||||||
|
container
|
||||||
|
.read(pdfProvider.notifier)
|
||||||
|
.openPicked(
|
||||||
|
path: 'mock.pdf',
|
||||||
|
pageCount: 2,
|
||||||
|
bytes: Uint8List.fromList([1, 2, 3]),
|
||||||
|
);
|
||||||
|
container.read(pdfProvider.notifier).setSignedPage(1);
|
||||||
|
final r = Rect.fromLTWH(50, 100, 120, 60);
|
||||||
|
final sigN = container.read(signatureProvider.notifier);
|
||||||
|
sigN.placeDefaultRect();
|
||||||
|
// overwrite to desired rect
|
||||||
|
final sig = container.read(signatureProvider);
|
||||||
|
sigN
|
||||||
|
..toggleAspect(true)
|
||||||
|
..resize(Offset(r.width - sig.rect!.width, r.height - sig.rect!.height));
|
||||||
|
// move to target top-left
|
||||||
|
final movedDelta = Offset(r.left - sig.rect!.left, r.top - sig.rect!.top);
|
||||||
|
sigN.drag(movedDelta);
|
||||||
|
container
|
||||||
|
.read(signatureProvider.notifier)
|
||||||
|
.setImageBytes(Uint8List.fromList([4, 5, 6]));
|
||||||
|
}
|
|
@ -1,17 +0,0 @@
|
||||||
import 'package:flutter_test/flutter_test.dart';
|
|
||||||
import 'package:pdf_signature/data/repositories/pdf_repository.dart';
|
|
||||||
import '_world.dart';
|
|
||||||
|
|
||||||
/// Usage: a signature placement appears on the page based on the signature card
|
|
||||||
Future<void> aSignaturePlacementAppearsOnThePageBasedOnTheSignatureCard(
|
|
||||||
WidgetTester tester,
|
|
||||||
) async {
|
|
||||||
final container = TestWorld.container!;
|
|
||||||
final pdf = container.read(pdfProvider);
|
|
||||||
final placements = pdf.placementsByPage[pdf.currentPage] ?? [];
|
|
||||||
expect(
|
|
||||||
placements.isNotEmpty,
|
|
||||||
true,
|
|
||||||
reason: 'A signature placement should appear on the page',
|
|
||||||
);
|
|
||||||
}
|
|
|
@ -1,24 +0,0 @@
|
||||||
import 'dart:typed_data';
|
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:flutter_test/flutter_test.dart';
|
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
|
||||||
import 'package:pdf_signature/data/repositories/pdf_repository.dart';
|
|
||||||
import 'package:pdf_signature/data/model/model.dart';
|
|
||||||
import '_world.dart';
|
|
||||||
|
|
||||||
/// Usage: a signature placement is placed on page {2}
|
|
||||||
Future<void> aSignaturePlacementIsPlacedOnPage(
|
|
||||||
WidgetTester tester,
|
|
||||||
num param1,
|
|
||||||
) async {
|
|
||||||
final container = TestWorld.container ?? ProviderContainer();
|
|
||||||
TestWorld.container = container;
|
|
||||||
final page = param1.toInt();
|
|
||||||
container
|
|
||||||
.read(pdfProvider.notifier)
|
|
||||||
.addPlacement(
|
|
||||||
page: page,
|
|
||||||
rect: Rect.fromLTWH(20, 20, 100, 50),
|
|
||||||
asset: SignatureAsset(id: 'test.png', bytes: Uint8List(0)),
|
|
||||||
);
|
|
||||||
}
|
|
|
@ -1,23 +0,0 @@
|
||||||
import 'dart:typed_data';
|
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:flutter_test/flutter_test.dart';
|
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
|
||||||
import 'package:pdf_signature/data/repositories/pdf_repository.dart';
|
|
||||||
import 'package:pdf_signature/data/model/model.dart';
|
|
||||||
import '_world.dart';
|
|
||||||
|
|
||||||
/// Usage: a signature placement is placed with a position and size relative to the page
|
|
||||||
Future<void> aSignaturePlacementIsPlacedWithAPositionAndSizeRelativeToThePage(
|
|
||||||
WidgetTester tester,
|
|
||||||
) async {
|
|
||||||
final container = TestWorld.container ?? ProviderContainer();
|
|
||||||
TestWorld.container = container;
|
|
||||||
final pdf = container.read(pdfProvider);
|
|
||||||
container
|
|
||||||
.read(pdfProvider.notifier)
|
|
||||||
.addPlacement(
|
|
||||||
page: pdf.currentPage,
|
|
||||||
rect: Rect.fromLTWH(50, 50, 200, 100),
|
|
||||||
asset: SignatureAsset(id: 'test.png', bytes: Uint8List(0)),
|
|
||||||
);
|
|
||||||
}
|
|
|
@ -1,7 +1,6 @@
|
||||||
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/data/repositories/pdf_repository.dart';
|
import 'package:pdf_signature/ui/features/pdf/view_model/pdf_controller.dart';
|
||||||
import 'package:pdf_signature/data/model/model.dart';
|
|
||||||
import '_world.dart';
|
import '_world.dart';
|
||||||
|
|
||||||
/// Usage: adjusting one instance does not affect the others
|
/// Usage: adjusting one instance does not affect the others
|
||||||
|
@ -15,7 +14,7 @@ Future<void> adjustingOneInstanceDoesNotAffectTheOthers(
|
||||||
container.read(pdfProvider.notifier).removePlacement(page: 2, index: 0);
|
container.read(pdfProvider.notifier).removePlacement(page: 2, index: 0);
|
||||||
container
|
container
|
||||||
.read(pdfProvider.notifier)
|
.read(pdfProvider.notifier)
|
||||||
.addPlacement(page: 2, rect: modified, asset: before[0].asset);
|
.addPlacement(page: 2, rect: modified, imageId: before[0].imageId);
|
||||||
final after = container.read(pdfProvider.notifier).placementsOn(2);
|
final after = container.read(pdfProvider.notifier).placementsOn(2);
|
||||||
expect(after.any((p) => p.rect == before[1].rect), isTrue);
|
expect(after.any((p) => p.rect == before[1].rect), isTrue);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,22 +0,0 @@
|
||||||
import 'package:flutter_test/flutter_test.dart';
|
|
||||||
import 'package:pdf_signature/data/repositories/pdf_repository.dart';
|
|
||||||
import '_world.dart';
|
|
||||||
|
|
||||||
/// Usage: adjusting one of the signature placements does not affect the others
|
|
||||||
Future<void> adjustingOneOfTheSignaturePlacementsDoesNotAffectTheOthers(
|
|
||||||
WidgetTester tester,
|
|
||||||
) async {
|
|
||||||
final container = TestWorld.container!;
|
|
||||||
final pdf = container.read(pdfProvider);
|
|
||||||
final placements =
|
|
||||||
pdf.placementsByPage.values.expand((list) => list).toList();
|
|
||||||
|
|
||||||
// All placements should have the same asset ID (reusing the same asset)
|
|
||||||
final assetIds = placements.map((p) => p.asset.id).toSet();
|
|
||||||
expect(assetIds.length, 1);
|
|
||||||
|
|
||||||
// All should have default rotation (0.0) since none were adjusted
|
|
||||||
final rotations = placements.map((p) => p.rotationDeg).toSet();
|
|
||||||
expect(rotations.length, 1);
|
|
||||||
expect(rotations.first, 0.0);
|
|
||||||
}
|
|
|
@ -1,18 +0,0 @@
|
||||||
import 'package:flutter_test/flutter_test.dart';
|
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
|
||||||
import 'package:pdf_signature/data/repositories/pdf_repository.dart';
|
|
||||||
import '_world.dart';
|
|
||||||
|
|
||||||
/// Usage: all placed signature placements appear on their corresponding pages in the output
|
|
||||||
Future<void>
|
|
||||||
allPlacedSignaturePlacementsAppearOnTheirCorrespondingPagesInTheOutput(
|
|
||||||
WidgetTester tester,
|
|
||||||
) async {
|
|
||||||
final container = TestWorld.container ?? ProviderContainer();
|
|
||||||
final pdf = container.read(pdfProvider);
|
|
||||||
final totalPlacements = pdf.placementsByPage.values.fold(
|
|
||||||
0,
|
|
||||||
(sum, list) => sum + list.length,
|
|
||||||
);
|
|
||||||
expect(totalPlacements, greaterThan(1));
|
|
||||||
}
|
|
|
@ -0,0 +1,17 @@
|
||||||
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
|
import 'package:pdf_signature/ui/features/pdf/view_model/pdf_controller.dart';
|
||||||
|
import '_world.dart';
|
||||||
|
|
||||||
|
/// Usage: all placed signatures appear on their corresponding pages in the output
|
||||||
|
Future<void> allPlacedSignaturesAppearOnTheirCorrespondingPagesInTheOutput(
|
||||||
|
WidgetTester tester,
|
||||||
|
) async {
|
||||||
|
final container = TestWorld.container ?? ProviderContainer();
|
||||||
|
expect(container.read(pdfProvider.notifier).placementsOn(1), isNotEmpty);
|
||||||
|
// One of 4 or 5 depending on scenario
|
||||||
|
final p4 = container.read(pdfProvider.notifier).placementsOn(4);
|
||||||
|
final p5 = container.read(pdfProvider.notifier).placementsOn(5);
|
||||||
|
expect(p4.isNotEmpty || p5.isNotEmpty, isTrue);
|
||||||
|
expect(TestWorld.lastExportBytes, isNotNull);
|
||||||
|
}
|
|
@ -1,6 +1,6 @@
|
||||||
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/data/repositories/signature_repository.dart';
|
import 'package:pdf_signature/ui/features/signature/view_model/signature_controller.dart';
|
||||||
import '_world.dart';
|
import '_world.dart';
|
||||||
|
|
||||||
/// Usage: an empty signature canvas
|
/// Usage: an empty signature canvas
|
||||||
|
|
|
@ -1,14 +0,0 @@
|
||||||
import 'package:flutter_test/flutter_test.dart';
|
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
|
||||||
import 'package:pdf_signature/data/repositories/pdf_repository.dart';
|
|
||||||
import '_world.dart';
|
|
||||||
|
|
||||||
/// Usage: both signature placements are shown on their respective pages
|
|
||||||
Future<void> bothSignaturePlacementsAreShownOnTheirRespectivePages(
|
|
||||||
WidgetTester tester,
|
|
||||||
) async {
|
|
||||||
final container = TestWorld.container ?? ProviderContainer();
|
|
||||||
final pdf = container.read(pdfProvider);
|
|
||||||
expect(pdf.placementsByPage[1], isNotEmpty);
|
|
||||||
expect(pdf.placementsByPage[3], isNotEmpty);
|
|
||||||
}
|
|
|
@ -0,0 +1,15 @@
|
||||||
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
|
import 'package:pdf_signature/ui/features/pdf/view_model/pdf_controller.dart';
|
||||||
|
import '_world.dart';
|
||||||
|
|
||||||
|
/// Usage: both signatures are shown on their respective pages
|
||||||
|
Future<void> bothSignaturesAreShownOnTheirRespectivePages(
|
||||||
|
WidgetTester tester,
|
||||||
|
) async {
|
||||||
|
final container = TestWorld.container ?? ProviderContainer();
|
||||||
|
final p1 = container.read(pdfProvider.notifier).placementsOn(1);
|
||||||
|
final p3 = container.read(pdfProvider.notifier).placementsOn(3);
|
||||||
|
expect(p1, isNotEmpty);
|
||||||
|
expect(p3, isNotEmpty);
|
||||||
|
}
|
|
@ -1,8 +1,7 @@
|
||||||
import 'dart:ui';
|
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/data/repositories/pdf_repository.dart';
|
import 'package:pdf_signature/ui/features/pdf/view_model/pdf_controller.dart';
|
||||||
import 'package:pdf_signature/data/model/model.dart';
|
|
||||||
import '_world.dart';
|
import '_world.dart';
|
||||||
|
|
||||||
/// Usage: dragging or resizing one does not change the other
|
/// Usage: dragging or resizing one does not change the other
|
||||||
|
@ -21,7 +20,7 @@ Future<void> draggingOrResizingOneDoesNotChangeTheOther(
|
||||||
.addPlacement(
|
.addPlacement(
|
||||||
page: 1,
|
page: 1,
|
||||||
rect: changed,
|
rect: changed,
|
||||||
asset: list[1].asset,
|
imageId: list[1].imageId,
|
||||||
rotationDeg: list[1].rotationDeg,
|
rotationDeg: list[1].rotationDeg,
|
||||||
);
|
);
|
||||||
final after = container.read(pdfProvider.notifier).placementsOn(1);
|
final after = container.read(pdfProvider.notifier).placementsOn(1);
|
||||||
|
|
|
@ -0,0 +1,19 @@
|
||||||
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
|
import 'package:pdf_signature/ui/features/pdf/view_model/pdf_controller.dart';
|
||||||
|
import '_world.dart';
|
||||||
|
|
||||||
|
/// Usage: each signature can be dragged and resized independently
|
||||||
|
Future<void> eachSignatureCanBeDraggedAndResizedIndependently(
|
||||||
|
WidgetTester tester,
|
||||||
|
) async {
|
||||||
|
final container = TestWorld.container ?? ProviderContainer();
|
||||||
|
final list = container.read(pdfProvider.notifier).placementsOn(1);
|
||||||
|
expect(list.length, greaterThanOrEqualTo(2));
|
||||||
|
// Independence is modeled by distinct rects; ensure not equal and both within page
|
||||||
|
expect(list[0].rect, isNot(equals(list[1].rect)));
|
||||||
|
for (final p in list.take(2)) {
|
||||||
|
expect(p.rect.left, greaterThanOrEqualTo(0));
|
||||||
|
expect(p.rect.top, greaterThanOrEqualTo(0));
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,14 +0,0 @@
|
||||||
import 'package:flutter_test/flutter_test.dart';
|
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
|
||||||
import 'package:pdf_signature/data/repositories/pdf_repository.dart';
|
|
||||||
import '_world.dart';
|
|
||||||
|
|
||||||
/// Usage: each signature placement can be dragged and resized independently
|
|
||||||
Future<void> eachSignaturePlacementCanBeDraggedAndResizedIndependently(
|
|
||||||
WidgetTester tester,
|
|
||||||
) async {
|
|
||||||
final container = TestWorld.container ?? ProviderContainer();
|
|
||||||
final pdf = container.read(pdfProvider);
|
|
||||||
final placements = pdf.placementsByPage[pdf.currentPage] ?? [];
|
|
||||||
expect(placements.length, greaterThan(1));
|
|
||||||
}
|
|
|
@ -1,6 +1,6 @@
|
||||||
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/data/repositories/pdf_repository.dart';
|
import 'package:pdf_signature/ui/features/pdf/view_model/pdf_controller.dart';
|
||||||
import '_world.dart';
|
import '_world.dart';
|
||||||
|
|
||||||
/// Usage: identical signature instances appear in each location
|
/// Usage: identical signature instances appear in each location
|
||||||
|
|
|
@ -1,16 +0,0 @@
|
||||||
import 'package:flutter_test/flutter_test.dart';
|
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
|
||||||
import 'package:pdf_signature/data/repositories/pdf_repository.dart';
|
|
||||||
import '_world.dart';
|
|
||||||
|
|
||||||
/// Usage: identical signature placements appear in each location
|
|
||||||
Future<void> identicalSignaturePlacementsAppearInEachLocation(
|
|
||||||
WidgetTester tester,
|
|
||||||
) async {
|
|
||||||
final container = TestWorld.container!;
|
|
||||||
final pdf = container.read(pdfProvider);
|
|
||||||
final allPlacements =
|
|
||||||
pdf.placementsByPage.values.expand((list) => list).toList();
|
|
||||||
final assetIds = allPlacements.map((p) => p.asset.id).toSet();
|
|
||||||
expect(assetIds.length, 1); // All the same
|
|
||||||
}
|
|
|
@ -0,0 +1,10 @@
|
||||||
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
|
import 'package:pdf_signature/ui/features/signature/view_model/signature_controller.dart';
|
||||||
|
import '_world.dart';
|
||||||
|
|
||||||
|
/// Usage: it is placed on the selected page
|
||||||
|
Future<void> itIsPlacedOnTheSelectedPage(WidgetTester tester) async {
|
||||||
|
final container = TestWorld.container ?? ProviderContainer();
|
||||||
|
expect(container.read(signatureProvider).imageBytes, isNotNull);
|
||||||
|
}
|
|
@ -1,6 +1,6 @@
|
||||||
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/data/repositories/signature_repository.dart';
|
import 'package:pdf_signature/ui/features/signature/view_model/signature_controller.dart';
|
||||||
import '_world.dart';
|
import '_world.dart';
|
||||||
|
|
||||||
/// Usage: multiple strokes were drawn
|
/// Usage: multiple strokes were drawn
|
||||||
|
|
|
@ -2,7 +2,7 @@ import 'dart:typed_data';
|
||||||
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:image/image.dart' as img;
|
import 'package:image/image.dart' as img;
|
||||||
import 'package:pdf_signature/data/repositories/signature_repository.dart';
|
import 'package:pdf_signature/ui/features/signature/view_model/signature_controller.dart';
|
||||||
import '_world.dart';
|
import '_world.dart';
|
||||||
|
|
||||||
/// Usage: near-white background becomes transparent in the preview
|
/// Usage: near-white background becomes transparent in the preview
|
||||||
|
|
|
@ -0,0 +1,11 @@
|
||||||
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
|
import 'package:pdf_signature/ui/features/pdf/view_model/pdf_controller.dart';
|
||||||
|
import '_world.dart';
|
||||||
|
|
||||||
|
/// Usage: only the selected signature is removed
|
||||||
|
Future<void> onlyTheSelectedSignatureIsRemoved(WidgetTester tester) async {
|
||||||
|
final container = TestWorld.container ?? ProviderContainer();
|
||||||
|
final list = container.read(pdfProvider.notifier).placementsOn(1);
|
||||||
|
expect(list.length, 2);
|
||||||
|
}
|
|
@ -1,14 +0,0 @@
|
||||||
import 'package:flutter_test/flutter_test.dart';
|
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
|
||||||
import 'package:pdf_signature/data/repositories/pdf_repository.dart';
|
|
||||||
import '_world.dart';
|
|
||||||
|
|
||||||
/// Usage: only the selected signature placement is removed
|
|
||||||
Future<void> onlyTheSelectedSignaturePlacementIsRemoved(
|
|
||||||
WidgetTester tester,
|
|
||||||
) async {
|
|
||||||
final container = TestWorld.container ?? ProviderContainer();
|
|
||||||
final pdf = container.read(pdfProvider);
|
|
||||||
final placements = pdf.placementsByPage[pdf.currentPage] ?? [];
|
|
||||||
expect(placements.length, 2); // Started with 3, removed 1, should have 2
|
|
||||||
}
|
|
|
@ -1,6 +1,6 @@
|
||||||
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/data/repositories/pdf_repository.dart';
|
import 'package:pdf_signature/ui/features/pdf/view_model/pdf_controller.dart';
|
||||||
import '_world.dart';
|
import '_world.dart';
|
||||||
|
|
||||||
/// Usage: page {5} becomes visible in the scroll area
|
/// Usage: page {5} becomes visible in the scroll area
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
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/data/repositories/pdf_repository.dart';
|
import 'package:pdf_signature/ui/features/pdf/view_model/pdf_controller.dart';
|
||||||
import '_world.dart';
|
import '_world.dart';
|
||||||
|
|
||||||
/// Usage: page {1} is displayed
|
/// Usage: page {1} is displayed
|
||||||
|
|
|
@ -1,25 +0,0 @@
|
||||||
import 'package:flutter_test/flutter_test.dart';
|
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
|
||||||
import 'package:pdf_signature/data/repositories/pdf_repository.dart';
|
|
||||||
import '_world.dart';
|
|
||||||
|
|
||||||
/// Usage: resize to fit within bounding box
|
|
||||||
Future<void> resizeToFitWithinBoundingBox(WidgetTester tester) async {
|
|
||||||
final container = TestWorld.container ?? ProviderContainer();
|
|
||||||
final pdf = container.read(pdfProvider);
|
|
||||||
|
|
||||||
if (pdf.selectedPlacementIndex != null) {
|
|
||||||
final placements = pdf.placementsByPage[pdf.currentPage] ?? [];
|
|
||||||
if (pdf.selectedPlacementIndex! < placements.length) {
|
|
||||||
final placement = placements[pdf.selectedPlacementIndex!];
|
|
||||||
// Assume page size is 800x600 for testing
|
|
||||||
const pageWidth = 800.0;
|
|
||||||
const pageHeight = 600.0;
|
|
||||||
|
|
||||||
expect(placement.rect.left, greaterThanOrEqualTo(0));
|
|
||||||
expect(placement.rect.top, greaterThanOrEqualTo(0));
|
|
||||||
expect(placement.rect.right, lessThanOrEqualTo(pageWidth));
|
|
||||||
expect(placement.rect.bottom, lessThanOrEqualTo(pageHeight));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,17 +0,0 @@
|
||||||
import 'package:flutter_test/flutter_test.dart';
|
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
|
||||||
import 'package:pdf_signature/data/repositories/pdf_repository.dart';
|
|
||||||
import '_world.dart';
|
|
||||||
|
|
||||||
/// Usage: signature placement occurs on the selected page
|
|
||||||
Future<void> signaturePlacementOccursOnTheSelectedPage(
|
|
||||||
WidgetTester tester,
|
|
||||||
) async {
|
|
||||||
final container = TestWorld.container ?? ProviderContainer();
|
|
||||||
TestWorld.container = container;
|
|
||||||
final pdf = container.read(pdfProvider);
|
|
||||||
|
|
||||||
// Check that there's at least one placement on the current page
|
|
||||||
final placements = pdf.placementsByPage[pdf.currentPage] ?? [];
|
|
||||||
expect(placements.isNotEmpty, true);
|
|
||||||
}
|
|
|
@ -1,13 +0,0 @@
|
||||||
import 'package:flutter_test/flutter_test.dart';
|
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
|
||||||
import 'package:pdf_signature/data/repositories/signature_library_repository.dart';
|
|
||||||
import '_world.dart';
|
|
||||||
|
|
||||||
/// Usage: the app attempts to load the asset
|
|
||||||
Future<void> theAppAttemptsToLoadTheAsset(WidgetTester tester) async {
|
|
||||||
final container = TestWorld.container ?? ProviderContainer();
|
|
||||||
TestWorld.container = container;
|
|
||||||
// Simulate attempting to load an asset - for now just ensure library is accessible
|
|
||||||
final library = container.read(signatureLibraryProvider);
|
|
||||||
expect(library, isNotNull);
|
|
||||||
}
|
|
|
@ -0,0 +1,6 @@
|
||||||
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
|
|
||||||
|
/// Usage: the app attempts to load the image
|
||||||
|
Future<void> theAppAttemptsToLoadTheImage(WidgetTester tester) async {
|
||||||
|
// No-op for logic-level test; selection step already applied state.
|
||||||
|
}
|
|
@ -1,16 +0,0 @@
|
||||||
import 'package:flutter_test/flutter_test.dart';
|
|
||||||
import 'package:pdf_signature/data/repositories/signature_library_repository.dart';
|
|
||||||
import '_world.dart';
|
|
||||||
|
|
||||||
/// Usage: the asset is loaded and shown as a signature asset
|
|
||||||
Future<void> theAssetIsLoadedAndShownAsASignatureAsset(
|
|
||||||
WidgetTester tester,
|
|
||||||
) async {
|
|
||||||
final container = TestWorld.container!;
|
|
||||||
final library = container.read(signatureLibraryProvider);
|
|
||||||
expect(
|
|
||||||
library.isNotEmpty,
|
|
||||||
true,
|
|
||||||
reason: 'Asset should be loaded and shown in library',
|
|
||||||
);
|
|
||||||
}
|
|
|
@ -1,16 +0,0 @@
|
||||||
import 'package:flutter_test/flutter_test.dart';
|
|
||||||
import 'package:pdf_signature/data/repositories/signature_library_repository.dart';
|
|
||||||
import '_world.dart';
|
|
||||||
|
|
||||||
/// Usage: the asset is loaded and shown as a signature card
|
|
||||||
Future<void> theAssetIsLoadedAndShownAsASignatureCard(
|
|
||||||
WidgetTester tester,
|
|
||||||
) async {
|
|
||||||
final container = TestWorld.container!;
|
|
||||||
final library = container.read(signatureLibraryProvider);
|
|
||||||
expect(
|
|
||||||
library.isNotEmpty,
|
|
||||||
true,
|
|
||||||
reason: 'Asset should be loaded and shown as a card',
|
|
||||||
);
|
|
||||||
}
|
|
|
@ -1,14 +0,0 @@
|
||||||
import 'package:flutter_test/flutter_test.dart';
|
|
||||||
import 'package:pdf_signature/data/repositories/signature_library_repository.dart';
|
|
||||||
import '_world.dart';
|
|
||||||
|
|
||||||
/// Usage: the asset is not added to the document
|
|
||||||
Future<void> theAssetIsNotAddedToTheDocument(WidgetTester tester) async {
|
|
||||||
final container = TestWorld.container!;
|
|
||||||
final library = container.read(signatureLibraryProvider);
|
|
||||||
expect(
|
|
||||||
library.isEmpty,
|
|
||||||
true,
|
|
||||||
reason: 'Invalid asset should not be added to library',
|
|
||||||
);
|
|
||||||
}
|
|
|
@ -1,6 +1,6 @@
|
||||||
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/data/repositories/signature_repository.dart';
|
import 'package:pdf_signature/ui/features/signature/view_model/signature_controller.dart';
|
||||||
import '_world.dart';
|
import '_world.dart';
|
||||||
|
|
||||||
/// Usage: the canvas becomes blank
|
/// Usage: the canvas becomes blank
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
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/data/repositories/pdf_repository.dart';
|
import 'package:pdf_signature/ui/features/pdf/view_model/pdf_controller.dart';
|
||||||
import '_world.dart';
|
import '_world.dart';
|
||||||
|
|
||||||
/// Usage: the document is open
|
/// Usage: the document is open
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
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/data/repositories/pdf_repository.dart';
|
import 'package:pdf_signature/ui/features/pdf/view_model/pdf_controller.dart';
|
||||||
import '_world.dart';
|
import '_world.dart';
|
||||||
|
|
||||||
/// Usage: the first page is displayed
|
/// Usage: the first page is displayed
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
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/data/repositories/pdf_repository.dart';
|
import 'package:pdf_signature/ui/features/pdf/view_model/pdf_controller.dart';
|
||||||
import '_world.dart';
|
import '_world.dart';
|
||||||
|
|
||||||
/// Usage: the Go to input cannot be used
|
/// Usage: the Go to input cannot be used
|
||||||
|
|
|
@ -0,0 +1,14 @@
|
||||||
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
|
import 'package:pdf_signature/ui/features/signature/view_model/signature_controller.dart';
|
||||||
|
import '_world.dart';
|
||||||
|
|
||||||
|
/// Usage: the image is loaded and shown as a signature asset
|
||||||
|
Future<void> theImageIsLoadedAndShownAsASignatureAsset(
|
||||||
|
WidgetTester tester,
|
||||||
|
) async {
|
||||||
|
final container = TestWorld.container ?? ProviderContainer();
|
||||||
|
final sig = container.read(signatureProvider);
|
||||||
|
expect(sig.imageBytes, isNotNull);
|
||||||
|
expect(sig.rect, isNotNull);
|
||||||
|
}
|
|
@ -0,0 +1,11 @@
|
||||||
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
|
import 'package:pdf_signature/ui/features/signature/view_model/signature_controller.dart';
|
||||||
|
import '_world.dart';
|
||||||
|
|
||||||
|
/// Usage: the image is not added to the document
|
||||||
|
Future<void> theImageIsNotAddedToTheDocument(WidgetTester tester) async {
|
||||||
|
final container = TestWorld.container ?? ProviderContainer();
|
||||||
|
final sig = container.read(signatureProvider);
|
||||||
|
expect(sig.rect, isNull);
|
||||||
|
}
|
|
@ -0,0 +1,12 @@
|
||||||
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
|
import 'package:pdf_signature/ui/features/signature/view_model/signature_controller.dart';
|
||||||
|
import '_world.dart';
|
||||||
|
|
||||||
|
/// Usage: the image scales proportionally
|
||||||
|
Future<void> theImageScalesProportionally(WidgetTester tester) async {
|
||||||
|
final container = TestWorld.container ?? ProviderContainer();
|
||||||
|
final sig = container.read(signatureProvider);
|
||||||
|
final aspect = sig.rect!.width / sig.rect!.height;
|
||||||
|
expect((aspect - (TestWorld.prevAspect ?? aspect)).abs() < 0.05, isTrue);
|
||||||
|
}
|
|
@ -1,6 +1,6 @@
|
||||||
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/data/repositories/pdf_repository.dart';
|
import 'package:pdf_signature/ui/features/pdf/view_model/pdf_controller.dart';
|
||||||
import '_world.dart';
|
import '_world.dart';
|
||||||
|
|
||||||
/// Usage: the last page is displayed (page {5})
|
/// Usage: the last page is displayed (page {5})
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
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/data/repositories/signature_repository.dart';
|
import 'package:pdf_signature/ui/features/signature/view_model/signature_controller.dart';
|
||||||
import '_world.dart';
|
import '_world.dart';
|
||||||
|
|
||||||
/// Usage: the last stroke is removed
|
/// Usage: the last stroke is removed
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
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/data/repositories/pdf_repository.dart';
|
import 'package:pdf_signature/ui/features/pdf/view_model/pdf_controller.dart';
|
||||||
import '_world.dart';
|
import '_world.dart';
|
||||||
|
|
||||||
/// Usage: the left pages overview highlights page {5}
|
/// Usage: the left pages overview highlights page {5}
|
||||||
|
|
|
@ -1,13 +0,0 @@
|
||||||
import 'package:flutter_test/flutter_test.dart';
|
|
||||||
import 'package:pdf_signature/data/repositories/pdf_repository.dart';
|
|
||||||
import '_world.dart';
|
|
||||||
|
|
||||||
/// Usage: the other signature placements remain unchanged
|
|
||||||
Future<void> theOtherSignaturePlacementsRemainUnchanged(
|
|
||||||
WidgetTester tester,
|
|
||||||
) async {
|
|
||||||
final container = TestWorld.container!;
|
|
||||||
final pdf = container.read(pdfProvider);
|
|
||||||
final placements = pdf.placementsByPage[pdf.currentPage] ?? [];
|
|
||||||
expect(placements.length, 2); // Should have 2 remaining after deleting 1
|
|
||||||
}
|
|
|
@ -0,0 +1,12 @@
|
||||||
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
|
import 'package:pdf_signature/ui/features/pdf/view_model/pdf_controller.dart';
|
||||||
|
import '_world.dart';
|
||||||
|
|
||||||
|
/// Usage: the other signatures remain unchanged
|
||||||
|
Future<void> theOtherSignaturesRemainUnchanged(WidgetTester tester) async {
|
||||||
|
final container = TestWorld.container ?? ProviderContainer();
|
||||||
|
final list = container.read(pdfProvider.notifier).placementsOn(1);
|
||||||
|
// After deleting index 1, two should remain
|
||||||
|
expect(list.length, 2);
|
||||||
|
}
|
|
@ -1,6 +1,6 @@
|
||||||
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/data/repositories/pdf_repository.dart';
|
import 'package:pdf_signature/ui/features/pdf/view_model/pdf_controller.dart';
|
||||||
import '_world.dart';
|
import '_world.dart';
|
||||||
|
|
||||||
/// Usage: the page label shows "Page {5} of {5}"
|
/// Usage: the page label shows "Page {5} of {5}"
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
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/data/repositories/signature_repository.dart';
|
import 'package:pdf_signature/ui/features/signature/view_model/signature_controller.dart';
|
||||||
import '_world.dart';
|
import '_world.dart';
|
||||||
|
|
||||||
/// Usage: the preview updates immediately
|
/// Usage: the preview updates immediately
|
||||||
|
|
|
@ -0,0 +1,15 @@
|
||||||
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
|
import 'package:pdf_signature/ui/features/signature/view_model/signature_controller.dart';
|
||||||
|
import '_world.dart';
|
||||||
|
|
||||||
|
/// Usage: the signature is stamped at the exact PDF page coordinates and size
|
||||||
|
Future<void> theSignatureIsStampedAtTheExactPdfPageCoordinatesAndSize(
|
||||||
|
WidgetTester tester,
|
||||||
|
) async {
|
||||||
|
final container = TestWorld.container ?? ProviderContainer();
|
||||||
|
final sig = container.read(signatureProvider);
|
||||||
|
expect(sig.rect, isNotNull);
|
||||||
|
expect(sig.rect!.width, greaterThan(0));
|
||||||
|
expect(sig.rect!.height, greaterThan(0));
|
||||||
|
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue