refactor: migrate models to use Freezed for immutability and JSON support
This commit is contained in:
parent
771126d10c
commit
c7922cff23
|
|
@ -137,3 +137,5 @@ appimage-build/
|
||||||
.vscode/settings.json
|
.vscode/settings.json
|
||||||
|
|
||||||
*.patch
|
*.patch
|
||||||
|
*.freezed.dart
|
||||||
|
*.g.dart
|
||||||
|
|
|
||||||
|
|
@ -9,22 +9,27 @@ class DisplaySignatureData {
|
||||||
const DisplaySignatureData({required this.image, this.colorMatrix});
|
const DisplaySignatureData({required this.image, this.colorMatrix});
|
||||||
}
|
}
|
||||||
|
|
||||||
/// CachedSignatureCard extends SignatureCard with an internal processed cache
|
/// CachedSignatureCard wraps SignatureCard data and stores a processed cache.
|
||||||
class CachedSignatureCard extends SignatureCard {
|
class CachedSignatureCard {
|
||||||
|
final SignatureAsset asset;
|
||||||
|
final double rotationDeg;
|
||||||
|
final GraphicAdjust graphicAdjust;
|
||||||
|
|
||||||
img.Image? _cachedProcessedImage;
|
img.Image? _cachedProcessedImage;
|
||||||
|
|
||||||
CachedSignatureCard({
|
CachedSignatureCard({
|
||||||
required super.asset,
|
required this.asset,
|
||||||
required super.rotationDeg,
|
required this.rotationDeg,
|
||||||
super.graphicAdjust,
|
this.graphicAdjust = const GraphicAdjust(),
|
||||||
img.Image? initialProcessedImage,
|
img.Image? initialProcessedImage,
|
||||||
}) {
|
}) {
|
||||||
// Seed cache if provided
|
|
||||||
if (initialProcessedImage != null) {
|
if (initialProcessedImage != null) {
|
||||||
_cachedProcessedImage = initialProcessedImage;
|
_cachedProcessedImage = initialProcessedImage;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Intentionally no copyWith to avoid conflicting with Freezed interface
|
||||||
|
|
||||||
/// Invalidate the cached processed image, forcing recompute next time.
|
/// Invalidate the cached processed image, forcing recompute next time.
|
||||||
void invalidateCache() {
|
void invalidateCache() {
|
||||||
_cachedProcessedImage = null;
|
_cachedProcessedImage = null;
|
||||||
|
|
@ -40,35 +45,33 @@ class CachedSignatureCard extends SignatureCard {
|
||||||
rotationDeg: SignatureCard.initial().rotationDeg,
|
rotationDeg: SignatureCard.initial().rotationDeg,
|
||||||
graphicAdjust: SignatureCard.initial().graphicAdjust,
|
graphicAdjust: SignatureCard.initial().graphicAdjust,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
factory CachedSignatureCard.fromPublic(SignatureCard card) =>
|
||||||
|
CachedSignatureCard(
|
||||||
|
asset: card.asset,
|
||||||
|
rotationDeg: card.rotationDeg,
|
||||||
|
graphicAdjust: card.graphicAdjust,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
class SignatureCardStateNotifier
|
class SignatureCardStateNotifier extends StateNotifier<List<SignatureCard>> {
|
||||||
extends StateNotifier<List<CachedSignatureCard>> {
|
SignatureCardStateNotifier() : super(const []);
|
||||||
SignatureCardStateNotifier() : super(const []) {
|
|
||||||
state = const <CachedSignatureCard>[];
|
// Internal storage with cache
|
||||||
}
|
final List<CachedSignatureCard> _cards = <CachedSignatureCard>[];
|
||||||
|
|
||||||
// Stateless image processing service used by this repository
|
// Stateless image processing service used by this repository
|
||||||
final SignatureImageProcessingService _processingService =
|
final SignatureImageProcessingService _processingService =
|
||||||
SignatureImageProcessingService();
|
SignatureImageProcessingService();
|
||||||
|
|
||||||
void add(SignatureCard card) {
|
void add(SignatureCard card) {
|
||||||
final wrapped =
|
_cards.add(CachedSignatureCard.fromPublic(card));
|
||||||
card is CachedSignatureCard
|
_publish();
|
||||||
? card
|
|
||||||
: CachedSignatureCard(
|
|
||||||
asset: card.asset,
|
|
||||||
rotationDeg: card.rotationDeg,
|
|
||||||
graphicAdjust: card.graphicAdjust,
|
|
||||||
);
|
|
||||||
final next = List<CachedSignatureCard>.of(state)..add(wrapped);
|
|
||||||
state = List<CachedSignatureCard>.unmodifiable(next);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void addWithAsset(SignatureAsset asset, double rotationDeg) {
|
void addWithAsset(SignatureAsset asset, double rotationDeg) {
|
||||||
final next = List<CachedSignatureCard>.of(state)
|
_cards.add(CachedSignatureCard(asset: asset, rotationDeg: rotationDeg));
|
||||||
..add(CachedSignatureCard(asset: asset, rotationDeg: rotationDeg));
|
_publish();
|
||||||
state = List<CachedSignatureCard>.unmodifiable(next);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void update(
|
void update(
|
||||||
|
|
@ -76,46 +79,52 @@ class SignatureCardStateNotifier
|
||||||
double? rotationDeg,
|
double? rotationDeg,
|
||||||
GraphicAdjust? graphicAdjust,
|
GraphicAdjust? graphicAdjust,
|
||||||
) {
|
) {
|
||||||
final list = List<CachedSignatureCard>.of(state);
|
for (var i = 0; i < _cards.length; i++) {
|
||||||
for (var i = 0; i < list.length; i++) {
|
final c = _cards[i];
|
||||||
final c = list[i];
|
final isSameCard =
|
||||||
if (c == card) {
|
c.asset == card.asset &&
|
||||||
final updated = c.copyWith(
|
c.rotationDeg == card.rotationDeg &&
|
||||||
rotationDeg: rotationDeg ?? c.rotationDeg,
|
c.graphicAdjust == card.graphicAdjust;
|
||||||
graphicAdjust: graphicAdjust ?? c.graphicAdjust,
|
if (isSameCard) {
|
||||||
);
|
final newRotation = rotationDeg ?? c.rotationDeg;
|
||||||
// Compute and set the single processed bytes for the updated adjust
|
final newAdjust = graphicAdjust ?? c.graphicAdjust;
|
||||||
|
// Compute processed image for updated adjust
|
||||||
final processedImage = _processingService.processImageToImage(
|
final processedImage = _processingService.processImageToImage(
|
||||||
updated.asset.sigImage,
|
c.asset.sigImage,
|
||||||
updated.graphicAdjust,
|
newAdjust,
|
||||||
);
|
);
|
||||||
final next = CachedSignatureCard(
|
final next = CachedSignatureCard(
|
||||||
asset: updated.asset,
|
asset: c.asset,
|
||||||
rotationDeg: updated.rotationDeg,
|
rotationDeg: newRotation,
|
||||||
graphicAdjust: updated.graphicAdjust,
|
graphicAdjust: newAdjust,
|
||||||
);
|
);
|
||||||
next.setProcessedImage(processedImage);
|
next.setProcessedImage(processedImage);
|
||||||
list[i] = next;
|
_cards[i] = next;
|
||||||
state = List<CachedSignatureCard>.unmodifiable(list);
|
_publish();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void remove(SignatureCard card) {
|
void remove(SignatureCard card) {
|
||||||
state = List<CachedSignatureCard>.unmodifiable(
|
_cards.removeWhere(
|
||||||
state.where((c) => c != card).toList(growable: false),
|
(c) =>
|
||||||
|
c.asset == card.asset &&
|
||||||
|
c.rotationDeg == card.rotationDeg &&
|
||||||
|
c.graphicAdjust == card.graphicAdjust,
|
||||||
);
|
);
|
||||||
|
_publish();
|
||||||
}
|
}
|
||||||
|
|
||||||
void clearAll() {
|
void clearAll() {
|
||||||
state = const <CachedSignatureCard>[];
|
_cards.clear();
|
||||||
|
state = const <SignatureCard>[];
|
||||||
}
|
}
|
||||||
|
|
||||||
/// New: Returns processed decoded image for the given asset + adjustments.
|
/// New: Returns processed decoded image for the given asset + adjustments.
|
||||||
img.Image getProcessedImage(SignatureAsset asset, GraphicAdjust adjust) {
|
img.Image getProcessedImage(SignatureAsset asset, GraphicAdjust adjust) {
|
||||||
// Try to find a matching card by asset
|
// Try to find a matching card by asset
|
||||||
for (final c in state) {
|
for (final c in _cards) {
|
||||||
if (c.asset == asset) {
|
if (c.asset == asset) {
|
||||||
if (c.graphicAdjust == adjust) {
|
if (c.graphicAdjust == adjust) {
|
||||||
// If cached bytes exist, decode once; otherwise compute from image
|
// If cached bytes exist, decode once; otherwise compute from image
|
||||||
|
|
@ -168,22 +177,36 @@ class SignatureCardStateNotifier
|
||||||
|
|
||||||
/// Clears all cached processed images.
|
/// Clears all cached processed images.
|
||||||
void clearProcessedCache() {
|
void clearProcessedCache() {
|
||||||
for (final c in state) {
|
for (final c in _cards) {
|
||||||
c.invalidateCache();
|
c.invalidateCache();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Clears cached processed images for a specific asset only.
|
/// Clears cached processed images for a specific asset only.
|
||||||
void clearCacheForAsset(SignatureAsset asset) {
|
void clearCacheForAsset(SignatureAsset asset) {
|
||||||
for (final c in state) {
|
for (final c in _cards) {
|
||||||
if (c.asset == asset) {
|
if (c.asset == asset) {
|
||||||
c.invalidateCache();
|
c.invalidateCache();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void _publish() {
|
||||||
|
state = List<SignatureCard>.unmodifiable(
|
||||||
|
_cards
|
||||||
|
.map(
|
||||||
|
(c) => SignatureCard(
|
||||||
|
asset: c.asset,
|
||||||
|
rotationDeg: c.rotationDeg,
|
||||||
|
graphicAdjust: c.graphicAdjust,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.toList(growable: false),
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
final signatureCardRepositoryProvider = StateNotifierProvider<
|
final signatureCardRepositoryProvider =
|
||||||
SignatureCardStateNotifier,
|
StateNotifierProvider<SignatureCardStateNotifier, List<SignatureCard>>(
|
||||||
List<CachedSignatureCard>
|
(ref) => SignatureCardStateNotifier(),
|
||||||
>((ref) => SignatureCardStateNotifier());
|
);
|
||||||
|
|
|
||||||
|
|
@ -1,37 +1,20 @@
|
||||||
import 'dart:typed_data';
|
import 'dart:typed_data';
|
||||||
|
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||||
|
|
||||||
import 'signature_placement.dart';
|
import 'signature_placement.dart';
|
||||||
|
|
||||||
|
part 'document.freezed.dart';
|
||||||
|
|
||||||
/// PDF document to be signed
|
/// PDF document to be signed
|
||||||
class Document {
|
@freezed
|
||||||
bool loaded;
|
abstract class Document with _$Document {
|
||||||
int pageCount;
|
const factory Document({
|
||||||
Uint8List? pickedPdfBytes;
|
@Default(false) bool loaded,
|
||||||
// Multiple signature placements per page, each combines geometry and asset.
|
@Default(0) int pageCount,
|
||||||
Map<int, List<SignaturePlacement>> placementsByPage;
|
|
||||||
|
|
||||||
Document({
|
|
||||||
required this.loaded,
|
|
||||||
required this.pageCount,
|
|
||||||
this.pickedPdfBytes,
|
|
||||||
Map<int, List<SignaturePlacement>>? placementsByPage,
|
|
||||||
}) : placementsByPage = placementsByPage ?? <int, List<SignaturePlacement>>{};
|
|
||||||
|
|
||||||
factory Document.initial() => Document(
|
|
||||||
loaded: false,
|
|
||||||
pageCount: 0,
|
|
||||||
pickedPdfBytes: null,
|
|
||||||
placementsByPage: <int, List<SignaturePlacement>>{},
|
|
||||||
);
|
|
||||||
|
|
||||||
Document copyWith({
|
|
||||||
bool? loaded,
|
|
||||||
int? pageCount,
|
|
||||||
Uint8List? pickedPdfBytes,
|
Uint8List? pickedPdfBytes,
|
||||||
Map<int, List<SignaturePlacement>>? placementsByPage,
|
@Default(<int, List<SignaturePlacement>>{})
|
||||||
}) => Document(
|
Map<int, List<SignaturePlacement>> placementsByPage,
|
||||||
loaded: loaded ?? this.loaded,
|
}) = _Document;
|
||||||
pageCount: pageCount ?? this.pageCount,
|
|
||||||
pickedPdfBytes: pickedPdfBytes ?? this.pickedPdfBytes,
|
factory Document.initial() => const Document();
|
||||||
placementsByPage: placementsByPage ?? this.placementsByPage,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,34 +1,12 @@
|
||||||
class GraphicAdjust {
|
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||||
final double contrast;
|
|
||||||
final double brightness;
|
|
||||||
final bool bgRemoval;
|
|
||||||
|
|
||||||
const GraphicAdjust({
|
part 'graphic_adjust.freezed.dart';
|
||||||
this.contrast = 1.0,
|
|
||||||
this.brightness = 1.0,
|
|
||||||
this.bgRemoval = false,
|
|
||||||
});
|
|
||||||
|
|
||||||
GraphicAdjust copyWith({
|
@freezed
|
||||||
double? contrast,
|
abstract class GraphicAdjust with _$GraphicAdjust {
|
||||||
double? brightness,
|
const factory GraphicAdjust({
|
||||||
bool? bgRemoval,
|
@Default(1.0) double contrast,
|
||||||
}) => GraphicAdjust(
|
@Default(1.0) double brightness,
|
||||||
contrast: contrast ?? this.contrast,
|
@Default(false) bool bgRemoval,
|
||||||
brightness: brightness ?? this.brightness,
|
}) = _GraphicAdjust;
|
||||||
bgRemoval: bgRemoval ?? this.bgRemoval,
|
|
||||||
);
|
|
||||||
|
|
||||||
@override
|
|
||||||
bool operator ==(Object other) =>
|
|
||||||
identical(this, other) ||
|
|
||||||
other is GraphicAdjust &&
|
|
||||||
runtimeType == other.runtimeType &&
|
|
||||||
contrast == other.contrast &&
|
|
||||||
brightness == other.brightness &&
|
|
||||||
bgRemoval == other.bgRemoval;
|
|
||||||
|
|
||||||
@override
|
|
||||||
int get hashCode =>
|
|
||||||
contrast.hashCode ^ brightness.hashCode ^ bgRemoval.hashCode;
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,25 +1,18 @@
|
||||||
/// TODO: add `freeze` and `json_serializable` to generate immutable data class with copyWith, toString, equality, and JSON support.
|
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||||
class PreferencesState {
|
|
||||||
final String theme; // 'light' | 'dark' | 'system'
|
|
||||||
final String theme_color; // 'blue' | 'green' | 'red' | 'purple'
|
|
||||||
final String language; // 'en' | 'zh-TW' | 'es'
|
|
||||||
final double exportDpi; // 96.0 | 144.0 | 200.0 | 300.0
|
|
||||||
const PreferencesState({
|
|
||||||
required this.theme,
|
|
||||||
required this.theme_color,
|
|
||||||
required this.language,
|
|
||||||
required this.exportDpi,
|
|
||||||
});
|
|
||||||
|
|
||||||
PreferencesState copyWith({
|
part 'preferences.freezed.dart';
|
||||||
String? theme,
|
part 'preferences.g.dart';
|
||||||
String? theme_color,
|
|
||||||
String? language,
|
/// Immutable preferences model with JSON support
|
||||||
double? exportDpi,
|
@freezed
|
||||||
}) => PreferencesState(
|
abstract class PreferencesState with _$PreferencesState {
|
||||||
theme: theme ?? this.theme,
|
const factory PreferencesState({
|
||||||
theme_color: theme_color ?? this.theme_color,
|
@Default('system') String theme, // 'light' | 'dark' | 'system'
|
||||||
language: language ?? this.language,
|
@Default('#FF2196F3') String theme_color, // hex ARGB string
|
||||||
exportDpi: exportDpi ?? this.exportDpi,
|
@Default('en') String language, // BCP-47 tag like 'en'|'zh-TW'
|
||||||
);
|
@Default(144.0) double exportDpi, // 96.0 | 144.0 | 200.0 | 300.0
|
||||||
|
}) = _PreferencesState;
|
||||||
|
|
||||||
|
factory PreferencesState.fromJson(Map<String, dynamic> json) =>
|
||||||
|
_$PreferencesStateFromJson(json);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,25 +1,18 @@
|
||||||
import 'dart:typed_data';
|
import 'dart:typed_data';
|
||||||
|
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||||
import 'package:image/image.dart' as img;
|
import 'package:image/image.dart' as img;
|
||||||
|
|
||||||
|
part 'signature_asset.freezed.dart';
|
||||||
|
|
||||||
/// SignatureAsset store image file of a signature, stored in the device or cloud storage
|
/// SignatureAsset store image file of a signature, stored in the device or cloud storage
|
||||||
class SignatureAsset {
|
@freezed
|
||||||
final img.Image sigImage;
|
abstract class SignatureAsset with _$SignatureAsset {
|
||||||
// List<List<Offset>>? strokes;
|
const SignatureAsset._();
|
||||||
final String? name; // optional display name (e.g., filename)
|
|
||||||
const SignatureAsset({required this.sigImage, this.name});
|
const factory SignatureAsset({required img.Image sigImage, String? name}) =
|
||||||
|
_SignatureAsset;
|
||||||
|
|
||||||
/// Encode this image to PNG bytes. Use a small compression level for speed by default.
|
/// Encode this image to PNG bytes. Use a small compression level for speed by default.
|
||||||
Uint8List toPngBytes({int level = 3}) =>
|
Uint8List toPngBytes({int level = 3}) =>
|
||||||
Uint8List.fromList(img.encodePng(sigImage, level: level));
|
Uint8List.fromList(img.encodePng(sigImage, level: level));
|
||||||
|
|
||||||
@override
|
|
||||||
bool operator ==(Object other) =>
|
|
||||||
identical(this, other) ||
|
|
||||||
other is SignatureAsset &&
|
|
||||||
name == other.name &&
|
|
||||||
sigImage == other.sigImage;
|
|
||||||
|
|
||||||
@override
|
|
||||||
int get hashCode =>
|
|
||||||
name.hashCode ^ sigImage.width.hashCode ^ sigImage.height.hashCode;
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,35 +1,24 @@
|
||||||
import 'signature_asset.dart';
|
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||||
import 'package:image/image.dart' as img;
|
import 'package:image/image.dart' as img;
|
||||||
|
|
||||||
import 'graphic_adjust.dart';
|
import 'graphic_adjust.dart';
|
||||||
|
import 'signature_asset.dart';
|
||||||
|
|
||||||
|
part 'signature_card.freezed.dart';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* signature card is template of signature placement
|
* signature card is template of signature placement
|
||||||
* Use the [SignatureCardRepository] to obtain a full [SignatureCard]
|
* Use the [SignatureCardRepository] to obtain a full [SignatureCard]
|
||||||
*/
|
*/
|
||||||
class SignatureCard {
|
@freezed
|
||||||
final double rotationDeg;
|
abstract class SignatureCard with _$SignatureCard {
|
||||||
final SignatureAsset asset;
|
const factory SignatureCard({
|
||||||
final GraphicAdjust graphicAdjust;
|
required SignatureAsset asset,
|
||||||
|
@Default(0.0) double rotationDeg,
|
||||||
const SignatureCard({
|
@Default(GraphicAdjust()) GraphicAdjust graphicAdjust,
|
||||||
required this.asset,
|
}) = _SignatureCard;
|
||||||
required this.rotationDeg,
|
|
||||||
this.graphicAdjust = const GraphicAdjust(),
|
|
||||||
});
|
|
||||||
|
|
||||||
SignatureCard copyWith({
|
|
||||||
double? rotationDeg, //z axis is out of the screen, positive is CCW
|
|
||||||
SignatureAsset? asset,
|
|
||||||
GraphicAdjust? graphicAdjust,
|
|
||||||
}) => SignatureCard(
|
|
||||||
rotationDeg: rotationDeg ?? this.rotationDeg,
|
|
||||||
asset: asset ?? this.asset,
|
|
||||||
graphicAdjust: graphicAdjust ?? this.graphicAdjust,
|
|
||||||
);
|
|
||||||
|
|
||||||
factory SignatureCard.initial() => SignatureCard(
|
factory SignatureCard.initial() => SignatureCard(
|
||||||
asset: SignatureAsset(sigImage: img.Image(width: 1, height: 1)),
|
asset: SignatureAsset(sigImage: img.Image(width: 1, height: 1)),
|
||||||
rotationDeg: 0.0,
|
|
||||||
graphicAdjust: const GraphicAdjust(),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,35 +1,20 @@
|
||||||
import 'dart:ui';
|
import 'dart:ui';
|
||||||
import 'signature_asset.dart';
|
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||||
|
|
||||||
import 'graphic_adjust.dart';
|
import 'graphic_adjust.dart';
|
||||||
|
import 'signature_asset.dart';
|
||||||
|
|
||||||
|
part 'signature_placement.freezed.dart';
|
||||||
|
|
||||||
/// 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 signature asset
|
||||||
/// assigned to that placement.
|
/// assigned to that placement.
|
||||||
class SignaturePlacement {
|
@freezed
|
||||||
// The bounding box of this placement in UI coordinate space, implies scaling and position.
|
abstract class SignaturePlacement with _$SignaturePlacement {
|
||||||
final Rect rect;
|
const factory SignaturePlacement({
|
||||||
|
required Rect rect,
|
||||||
/// Rotation in degrees to apply when rendering/exporting this placement.
|
required SignatureAsset asset,
|
||||||
final double rotationDeg;
|
@Default(0.0) double rotationDeg,
|
||||||
final GraphicAdjust graphicAdjust;
|
@Default(GraphicAdjust()) GraphicAdjust graphicAdjust,
|
||||||
final SignatureAsset asset;
|
}) = _SignaturePlacement;
|
||||||
|
|
||||||
const SignaturePlacement({
|
|
||||||
required this.rect,
|
|
||||||
required this.asset,
|
|
||||||
this.rotationDeg = 0.0,
|
|
||||||
this.graphicAdjust = const GraphicAdjust(),
|
|
||||||
});
|
|
||||||
|
|
||||||
SignaturePlacement copyWith({
|
|
||||||
Rect? rect,
|
|
||||||
SignatureAsset? asset,
|
|
||||||
double? rotationDeg,
|
|
||||||
GraphicAdjust? graphicAdjust,
|
|
||||||
}) => SignaturePlacement(
|
|
||||||
rect: rect ?? this.rect,
|
|
||||||
asset: asset ?? this.asset,
|
|
||||||
rotationDeg: rotationDeg ?? this.rotationDeg,
|
|
||||||
graphicAdjust: graphicAdjust ?? this.graphicAdjust,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,7 @@ Future<void> aMultipageDocumentIsOpen(WidgetTester tester) async {
|
||||||
container.read(documentRepositoryProvider.notifier).state =
|
container.read(documentRepositoryProvider.notifier).state =
|
||||||
Document.initial();
|
Document.initial();
|
||||||
container.read(signatureCardRepositoryProvider.notifier).state = [
|
container.read(signatureCardRepositoryProvider.notifier).state = [
|
||||||
CachedSignatureCard.initial(),
|
SignatureCard.initial(),
|
||||||
];
|
];
|
||||||
container.read(documentRepositoryProvider.notifier).openPicked(pageCount: 5);
|
container.read(documentRepositoryProvider.notifier).openPicked(pageCount: 5);
|
||||||
// Reset page state providers
|
// Reset page state providers
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,7 @@ Future<void> aSignatureAssetIsLoadedOrDrawn(WidgetTester tester) async {
|
||||||
container.read(documentRepositoryProvider.notifier).state =
|
container.read(documentRepositoryProvider.notifier).state =
|
||||||
Document.initial();
|
Document.initial();
|
||||||
container.read(signatureCardRepositoryProvider.notifier).state = [
|
container.read(signatureCardRepositoryProvider.notifier).state = [
|
||||||
CachedSignatureCard.initial(),
|
SignatureCard.initial(),
|
||||||
];
|
];
|
||||||
final image = img.Image(width: 1, height: 1);
|
final image = img.Image(width: 1, height: 1);
|
||||||
container
|
container
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,7 @@ Future<void> aSignatureAssetLoadedOrDrawnIsWrappedInASignatureCard(
|
||||||
container.read(documentRepositoryProvider.notifier).state =
|
container.read(documentRepositoryProvider.notifier).state =
|
||||||
Document.initial();
|
Document.initial();
|
||||||
container.read(signatureCardRepositoryProvider.notifier).state = [
|
container.read(signatureCardRepositoryProvider.notifier).state = [
|
||||||
CachedSignatureCard.initial(),
|
SignatureCard.initial(),
|
||||||
];
|
];
|
||||||
container
|
container
|
||||||
.read(signatureAssetRepositoryProvider.notifier)
|
.read(signatureAssetRepositoryProvider.notifier)
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,11 @@ Future<void> bothSignaturePlacementsAreShownOnTheirRespectivePages(
|
||||||
) async {
|
) async {
|
||||||
final container = TestWorld.container ?? ProviderContainer();
|
final container = TestWorld.container ?? ProviderContainer();
|
||||||
final pdf = container.read(documentRepositoryProvider);
|
final pdf = container.read(documentRepositoryProvider);
|
||||||
expect(pdf.placementsByPage[1], isNotEmpty);
|
final placementsByPage = pdf.placementsByPage;
|
||||||
expect(pdf.placementsByPage[3], isNotEmpty);
|
final totalPlacements = placementsByPage.values.fold<int>(
|
||||||
|
0,
|
||||||
|
(sum, list) => sum + list.length,
|
||||||
|
);
|
||||||
|
// We placed two signature placements; they may be on the same page or on different pages
|
||||||
|
expect(totalPlacements, 2);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -7,27 +7,46 @@ import 'package:pdf_signature/data/repositories/signature_asset_repository.dart'
|
||||||
import '_world.dart';
|
import '_world.dart';
|
||||||
|
|
||||||
/// Usage: the user places a signature placement from asset <secondAsset> on page <secondPage>
|
/// Usage: the user places a signature placement from asset <secondAsset> on page <secondPage>
|
||||||
|
/// Note: Parameters are optional to accommodate generated tests that omit them; defaults will be used.
|
||||||
Future<void> theUserPlacesASignaturePlacementFromAssetOnPage(
|
Future<void> theUserPlacesASignaturePlacementFromAssetOnPage(
|
||||||
WidgetTester tester,
|
WidgetTester tester, [
|
||||||
String assetName,
|
dynamic assetName = 'alice.png',
|
||||||
int page,
|
dynamic page = 1,
|
||||||
) async {
|
]) async {
|
||||||
|
// Normalize inputs from generated feature examples
|
||||||
|
String normalizeName(dynamic v) {
|
||||||
|
final s = v?.toString() ?? '';
|
||||||
|
if (s.length >= 2 &&
|
||||||
|
((s.startsWith("'") && s.endsWith("'")) ||
|
||||||
|
(s.startsWith('"') && s.endsWith('"')))) {
|
||||||
|
return s.substring(1, s.length - 1);
|
||||||
|
}
|
||||||
|
return s;
|
||||||
|
}
|
||||||
|
|
||||||
|
int normalizePage(dynamic v) {
|
||||||
|
if (v is num) return v.toInt();
|
||||||
|
return int.tryParse(v?.toString() ?? '') ?? 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
final assetNameStr = normalizeName(assetName);
|
||||||
|
final pageNum = normalizePage(page);
|
||||||
final container = TestWorld.container ?? ProviderContainer();
|
final container = TestWorld.container ?? ProviderContainer();
|
||||||
TestWorld.container = container;
|
TestWorld.container = container;
|
||||||
final library = container.read(signatureAssetRepositoryProvider);
|
final library = container.read(signatureAssetRepositoryProvider);
|
||||||
var asset = library.where((a) => a.name == assetName).firstOrNull;
|
var asset = library.where((a) => a.name == assetNameStr).firstOrNull;
|
||||||
if (asset == null) {
|
if (asset == null) {
|
||||||
// add dummy asset
|
// add dummy asset
|
||||||
container
|
container
|
||||||
.read(signatureAssetRepositoryProvider.notifier)
|
.read(signatureAssetRepositoryProvider.notifier)
|
||||||
.addImage(img.Image(width: 1, height: 1), name: assetName);
|
.addImage(img.Image(width: 1, height: 1), name: assetNameStr);
|
||||||
final updatedLibrary = container.read(signatureAssetRepositoryProvider);
|
final updatedLibrary = container.read(signatureAssetRepositoryProvider);
|
||||||
asset = updatedLibrary.firstWhere((a) => a.name == assetName);
|
asset = updatedLibrary.firstWhere((a) => a.name == assetNameStr);
|
||||||
}
|
}
|
||||||
container
|
container
|
||||||
.read(documentRepositoryProvider.notifier)
|
.read(documentRepositoryProvider.notifier)
|
||||||
.addPlacement(
|
.addPlacement(
|
||||||
page: page,
|
page: pageNum,
|
||||||
rect: Rect.fromLTWH(10, 10, 50, 50),
|
rect: Rect.fromLTWH(10, 10, 50, 50),
|
||||||
asset: asset,
|
asset: asset,
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -20,7 +20,7 @@ Future<void> threeSignaturePlacementsArePlacedOnTheCurrentPage(
|
||||||
container.read(documentRepositoryProvider.notifier).state =
|
container.read(documentRepositoryProvider.notifier).state =
|
||||||
Document.initial();
|
Document.initial();
|
||||||
container.read(signatureCardRepositoryProvider.notifier).state = [
|
container.read(signatureCardRepositoryProvider.notifier).state = [
|
||||||
CachedSignatureCard.initial(),
|
SignatureCard.initial(),
|
||||||
];
|
];
|
||||||
container.read(documentRepositoryProvider.notifier).openPicked(pageCount: 5);
|
container.read(documentRepositoryProvider.notifier).openPicked(pageCount: 5);
|
||||||
final pdfN = container.read(documentRepositoryProvider.notifier);
|
final pdfN = container.read(documentRepositoryProvider.notifier);
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue