feat: new implement of `/lib/data/repositories/`

This commit is contained in:
insleker 2025-09-10 15:57:54 +08:00
parent 948999fe8e
commit e9cf4c30c1
109 changed files with 257 additions and 819 deletions

View File

@ -8,5 +8,5 @@ Additionally read relevant files depends on task.
* 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 `ViewModel`, `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`, repositories, services...
* read `test/features/*.feature`, [`NFRs.md`](docs/NFRs.md) * read `test/features/*.feature`, [`NFRs.md`](docs/NFRs.md)

View File

@ -60,7 +60,9 @@ But with slight modifications.
* `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: Some rule of thumb:
* `<object>Provider` only placed at `/lib/data/repositories/` or `/lib/data/services/` to provide data source. * global provider
* `<object>RepositoryProvider` only placed in `/lib/data/repositories/`, provide data to `/lib/ui`.
* `lib/data/services/*` should be stateless, and should only accessible by `Repository`.
## Abstraction ## Abstraction

View File

@ -8,8 +8,8 @@ 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_asset_repository.dart'; import 'package:pdf_signature/data/repositories/signature_asset_repository.dart';
import 'package:pdf_signature/data/repositories/signature_repository.dart'; import 'package:pdf_signature/data/repositories/signature_card_repository.dart';
import 'package:pdf_signature/data/repositories/pdf_repository.dart'; import 'package:pdf_signature/data/repositories/document_repository.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';
@ -36,7 +36,7 @@ void main() {
(ref) => DocumentStateNotifier()..openPicked(path: 'test.pdf'), (ref) => DocumentStateNotifier()..openPicked(path: 'test.pdf'),
), ),
signatureProvider.overrideWith( signatureProvider.overrideWith(
(ref) => SignatureController()..placeDefaultRect(), (ref) => SignatureCardStateNotifier()..placeDefaultRect(),
), ),
useMockViewerProvider.overrideWith((ref) => true), useMockViewerProvider.overrideWith((ref) => true),
exportServiceProvider.overrideWith((_) => fake), exportServiceProvider.overrideWith((_) => fake),

View File

@ -3,9 +3,9 @@ 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/data/repositories/document_repository.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/repositories/preferences_repository.dart';
import 'package:pdf_signature/ui/features/preferences/widgets/settings_screen.dart'; import 'package:pdf_signature/ui/features/preferences/widgets/settings_screen.dart';
class MyApp extends StatelessWidget { class MyApp extends StatelessWidget {

View File

@ -1,12 +1,15 @@
import 'dart:typed_data'; 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/data/services/export_service.dart';
import '../../domain/models/model.dart'; import '../../domain/models/model.dart';
class DocumentStateNotifier extends StateNotifier<Document> { class DocumentStateNotifier extends StateNotifier<Document> {
DocumentStateNotifier() : super(Document.initial()); DocumentStateNotifier() : super(Document.initial());
final ExportService _service = ExportService();
@visibleForTesting @visibleForTesting
void openSample() { void openSample() {
state = state.copyWith( state = state.copyWith(
@ -57,7 +60,7 @@ class DocumentStateNotifier extends StateNotifier<Document> {
list.add( list.add(
SignaturePlacement( SignaturePlacement(
rect: rect, rect: rect,
asset: asset ?? SignatureAsset(id: '', bytes: Uint8List(0)), asset: asset ?? SignatureAsset(bytes: Uint8List(0)),
rotationDeg: rotationDeg, rotationDeg: rotationDeg,
), ),
); );
@ -121,14 +124,29 @@ class DocumentStateNotifier extends StateNotifier<Document> {
); );
} }
// NOTE: Programmatic reassignment of images has been removed.
// Convenience to get asset for a placement // Convenience to get asset for a placement
SignatureAsset? assetOfPlacement({required int page, required int index}) { SignatureAsset? assetOfPlacement({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].asset;
} }
Future<void> exportDocument({
required String outputPath,
required Size uiPageSize,
required Uint8List? signatureImageBytes,
}) async {
if (!state.loaded || state.pickedPdfBytes == null) return;
final bytes = await _service.exportSignedPdfFromBytes(
srcBytes: state.pickedPdfBytes!,
uiPageSize: uiPageSize,
signatureImageBytes: signatureImageBytes,
placementsByPage: state.placementsByPage,
);
if (bytes == null) return;
_service.saveBytesToFile(bytes: bytes, outputPath: outputPath);
// await
}
} }
final documentRepositoryProvider = final documentRepositoryProvider =

View File

@ -3,6 +3,7 @@ import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:shared_preferences/shared_preferences.dart'; import 'package:shared_preferences/shared_preferences.dart';
import 'package:pdf_signature/l10n/app_localizations.dart'; import 'package:pdf_signature/l10n/app_localizations.dart';
import 'package:flutter_localized_locales/flutter_localized_locales.dart'; import 'package:flutter_localized_locales/flutter_localized_locales.dart';
import 'package:pdf_signature/domain/models/preferences.dart';
// Helpers to work with BCP-47 language tags // Helpers to work with BCP-47 language tags
String toLanguageTag(Locale loc) { String toLanguageTag(Locale loc) {
@ -27,6 +28,7 @@ Set<String> _supportedTags() {
// Keys // Keys
const _kTheme = 'theme'; // 'light'|'dark'|'system' const _kTheme = 'theme'; // 'light'|'dark'|'system'
const _kThemeColor = 'theme_color'; // 'blue'|'green'|'red'|'purple'
const _kLanguage = 'language'; // BCP-47 tag like 'en', 'zh-TW', 'es' const _kLanguage = 'language'; // BCP-47 tag like 'en', 'zh-TW', 'es'
const _kPageView = 'page_view'; // now only 'continuous' const _kPageView = 'page_view'; // now only 'continuous'
const _kExportDpi = 'export_dpi'; // double, allowed: 96,144,200,300 const _kExportDpi = 'export_dpi'; // double, allowed: 96,144,200,300
@ -63,34 +65,9 @@ String _normalizeLanguageTag(String tag) {
return tags.contains('en') ? 'en' : tags.first; return tags.contains('en') ? 'en' : tags.first;
} }
class PreferencesState { class PreferencesStateNotifier extends StateNotifier<PreferencesState> {
final String theme; // 'light' | 'dark' | 'system'
final String language; // 'en' | 'zh-TW' | 'es'
final String pageView; // only 'continuous'
final double exportDpi; // 96.0 | 144.0 | 200.0 | 300.0
const PreferencesState({
required this.theme,
required this.language,
required this.pageView,
required this.exportDpi,
});
PreferencesState copyWith({
String? theme,
String? language,
String? pageView,
double? exportDpi,
}) => PreferencesState(
theme: theme ?? this.theme,
language: language ?? this.language,
pageView: pageView ?? this.pageView,
exportDpi: exportDpi ?? this.exportDpi,
);
}
class PreferencesNotifier extends StateNotifier<PreferencesState> {
final SharedPreferences prefs; final SharedPreferences prefs;
PreferencesNotifier(this.prefs) PreferencesStateNotifier(this.prefs)
: super( : super(
PreferencesState( PreferencesState(
theme: prefs.getString(_kTheme) ?? 'system', theme: prefs.getString(_kTheme) ?? 'system',
@ -99,8 +76,8 @@ class PreferencesNotifier extends StateNotifier<PreferencesState> {
WidgetsBinding.instance.platformDispatcher.locale WidgetsBinding.instance.platformDispatcher.locale
.toLanguageTag(), .toLanguageTag(),
), ),
pageView: prefs.getString(_kPageView) ?? 'continuous',
exportDpi: _readDpi(prefs), exportDpi: _readDpi(prefs),
theme_color: prefs.getString(_kThemeColor) ?? 'blue',
), ),
) { ) {
// normalize language to supported/fallback // normalize language to supported/fallback
@ -125,11 +102,6 @@ class PreferencesNotifier extends StateNotifier<PreferencesState> {
state = state.copyWith(language: normalized); state = state.copyWith(language: normalized);
prefs.setString(_kLanguage, normalized); prefs.setString(_kLanguage, normalized);
} }
final pageViewValid = {'continuous'};
if (!pageViewValid.contains(state.pageView)) {
state = state.copyWith(pageView: 'continuous');
prefs.setString(_kPageView, 'continuous');
}
// Ensure DPI is one of allowed values // Ensure DPI is one of allowed values
const allowed = [96.0, 144.0, 200.0, 300.0]; const allowed = [96.0, 144.0, 200.0, 300.0];
if (!allowed.contains(state.exportDpi)) { if (!allowed.contains(state.exportDpi)) {
@ -158,8 +130,8 @@ class PreferencesNotifier extends StateNotifier<PreferencesState> {
state = PreferencesState( state = PreferencesState(
theme: 'system', theme: 'system',
language: normalized, language: normalized,
pageView: 'continuous',
exportDpi: 144.0, exportDpi: 144.0,
theme_color: '',
); );
await prefs.setString(_kTheme, 'system'); await prefs.setString(_kTheme, 'system');
await prefs.setString(_kLanguage, normalized); await prefs.setString(_kLanguage, normalized);
@ -167,13 +139,6 @@ class PreferencesNotifier extends StateNotifier<PreferencesState> {
await prefs.setDouble(_kExportDpi, 144.0); await prefs.setDouble(_kExportDpi, 144.0);
} }
Future<void> setPageView(String pageView) async {
final valid = {'continuous'};
if (!valid.contains(pageView)) return;
state = state.copyWith(pageView: pageView);
await prefs.setString(_kPageView, pageView);
}
Future<void> setExportDpi(double dpi) async { Future<void> setExportDpi(double dpi) async {
const allowed = [96.0, 144.0, 200.0, 300.0]; const allowed = [96.0, 144.0, 200.0, 300.0];
if (!allowed.contains(dpi)) return; if (!allowed.contains(dpi)) return;
@ -189,8 +154,8 @@ final sharedPreferencesProvider = FutureProvider<SharedPreferences>((
return p; return p;
}); });
final preferencesProvider = final preferencesRepositoryProvider =
StateNotifierProvider<PreferencesNotifier, PreferencesState>((ref) { StateNotifierProvider<PreferencesStateNotifier, PreferencesState>((ref) {
// In tests, you can override sharedPreferencesProvider // In tests, you can override sharedPreferencesProvider
final prefs = ref final prefs = ref
.watch(sharedPreferencesProvider) .watch(sharedPreferencesProvider)
@ -198,14 +163,14 @@ final preferencesProvider =
data: (p) => p, data: (p) => p,
orElse: () => throw StateError('SharedPreferences not ready'), orElse: () => throw StateError('SharedPreferences not ready'),
); );
return PreferencesNotifier(prefs); return PreferencesStateNotifier(prefs);
}); });
// pageViewModeProvider removed; the app always runs in continuous mode. // pageViewModeProvider removed; the app always runs in continuous mode.
/// Derive the active ThemeMode based on preference and platform brightness /// Derive the active ThemeMode based on preference and platform brightness
final themeModeProvider = Provider<ThemeMode>((ref) { final themeModeProvider = Provider<ThemeMode>((ref) {
final prefs = ref.watch(preferencesProvider); final prefs = ref.watch(preferencesRepositoryProvider);
switch (prefs.theme) { switch (prefs.theme) {
case 'light': case 'light':
return ThemeMode.light; return ThemeMode.light;
@ -218,7 +183,7 @@ final themeModeProvider = Provider<ThemeMode>((ref) {
}); });
final localeProvider = Provider<Locale?>((ref) { final localeProvider = Provider<Locale?>((ref) {
final prefs = ref.watch(preferencesProvider); final prefs = ref.watch(preferencesRepositoryProvider);
final supported = _supportedTags(); final supported = _supportedTags();
// Return explicit Locale for supported ones; if not supported, null to follow device // Return explicit Locale for supported ones; if not supported, null to follow device
if (supported.contains(prefs.language)) { if (supported.contains(prefs.language)) {

View File

@ -6,25 +6,15 @@ import 'package:pdf_signature/domain/models/model.dart';
class SignatureAssetRepository extends StateNotifier<List<SignatureAsset>> { class SignatureAssetRepository extends StateNotifier<List<SignatureAsset>> {
SignatureAssetRepository() : super(const []); SignatureAssetRepository() : super(const []);
String add(Uint8List bytes, {String? name}) { void add(Uint8List bytes, {String? name}) {
// Always add a new asset (allow duplicates). This lets users create multiple cards // Always add a new asset (allow duplicates). This lets users create multiple cards
// even when loading the same image repeatedly for different adjustments/usages. // even when loading the same image repeatedly for different adjustments/usages.
if (bytes.isEmpty) return ''; if (bytes.isEmpty) return;
final id = DateTime.now().microsecondsSinceEpoch.toString(); state = List.of(state)..add(SignatureAsset(bytes: bytes, name: name));
state = List.of(state)
..add(SignatureAsset(id: id, bytes: bytes, name: name));
return id;
} }
void remove(String id) { void remove(SignatureAsset asset) {
state = state.where((a) => a.id != id).toList(growable: false); state = state.where((a) => a != asset).toList(growable: false);
}
SignatureAsset? byId(String id) {
for (final a in state) {
if (a.id == id) return a;
}
return null;
} }
} }

View File

@ -0,0 +1,45 @@
import 'dart:typed_data';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:image/image.dart' as img;
import '../../domain/models/model.dart';
class SignatureCardStateNotifier extends StateNotifier<List<SignatureCard>> {
SignatureCardStateNotifier() : super(const []);
add({required SignatureAsset asset, double rotationDeg = 0.0}) {
state = List.of(state)
..add(SignatureCard(asset: asset, rotationDeg: rotationDeg));
}
void update({
required SignatureCard card,
double? rotationDeg,
GraphicAdjust? graphicAdjust,
}) {
final list = List<SignatureCard>.of(state);
for (var i = 0; i < list.length; i++) {
final c = list[i];
if (c == card) {
list[i] = c.copyWith(
rotationDeg: rotationDeg ?? c.rotationDeg,
graphicAdjust: graphicAdjust ?? c.graphicAdjust,
);
state = list;
return;
}
}
}
void remove(SignatureCard card) {
state = state.where((c) => c != card).toList(growable: false);
}
void clearAll() {
state = const [];
}
}
final signatureCardProvider =
StateNotifierProvider<SignatureCardStateNotifier, List<SignatureCard>>(
(ref) => SignatureCardStateNotifier(),
);

View File

@ -1,374 +0,0 @@
import 'dart:math' as math;
import 'dart:math';
import 'dart:typed_data';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:image/image.dart' as img;
import 'package:pdf_signature/l10n/app_localizations.dart';
import '../../domain/models/model.dart';
import 'pdf_repository.dart';
class SignatureController extends StateNotifier<SignatureCard> {
final Ref ref;
SignatureController(this.ref) : super(SignatureCard.initial());
static const Size pageSize = Size(400, 560);
void resetForNewPage() {
state = SignatureCard.initial();
ref.read(currentRectProvider.notifier).setRect(null);
ref.read(editingEnabledProvider.notifier).set(false);
}
@visibleForTesting
void placeDefaultRect() {
final w = 120.0, h = 60.0;
final rand = Random();
// Generate a center within 10%..90% of each axis to reduce off-screen risk
final cx = pageSize.width * (0.1 + rand.nextDouble() * 0.8);
final cy = pageSize.height * (0.1 + rand.nextDouble() * 0.8);
Rect r = Rect.fromCenter(center: Offset(cx, cy), width: w, height: h);
r = _clampRectToPage(r);
ref.read(currentRectProvider.notifier).setRect(r);
ref.read(editingEnabledProvider.notifier).set(true);
}
void loadSample() {
final w = 120.0, h = 60.0;
ref
.read(currentRectProvider.notifier)
.setRect(
Rect.fromCenter(
center: Offset(pageSize.width / 2, pageSize.height * 0.75),
width: w,
height: h,
),
);
ref.read(editingEnabledProvider.notifier).set(true);
}
void setInvalidSelected(BuildContext context) {
// Fallback message without localization to keep core logic testable
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(
Localizations.of<AppLocalizations>(
context,
AppLocalizations,
)!.invalidOrUnsupportedFile,
),
),
);
}
void drag(Offset delta) {
final currentRect = ref.read(currentRectProvider);
if (currentRect == null || !ref.read(editingEnabledProvider)) return;
final moved = currentRect.shift(delta);
ref.read(currentRectProvider.notifier).setRect(_clampRectToPage(moved));
}
void resize(Offset delta) {
final currentRect = ref.read(currentRectProvider);
if (currentRect == null || !ref.read(editingEnabledProvider)) return;
final r = currentRect;
double newW = r.width + delta.dx;
double newH = r.height + delta.dy;
if (ref.read(aspectLockedProvider)) {
final aspect = r.width / r.height;
// Keep ratio based on the dominant proportional delta
final dxRel = (delta.dx / r.width).abs();
final dyRel = (delta.dy / r.height).abs();
if (dxRel >= dyRel) {
newW = newW.clamp(20.0, double.infinity);
newH = newW / aspect;
} else {
newH = newH.clamp(20.0, double.infinity);
newW = newH * aspect;
}
// Scale down to fit within page bounds while preserving ratio
final scaleW = pageSize.width / newW;
final scaleH = pageSize.height / newH;
final scale = math.min(1.0, math.min(scaleW, scaleH));
newW *= scale;
newH *= scale;
// Ensure minimum size of 20x20, scaling up proportionally if needed
final minScale = math.max(1.0, math.max(20.0 / newW, 20.0 / newH));
newW *= minScale;
newH *= minScale;
Rect resized = Rect.fromLTWH(r.left, r.top, newW, newH);
resized = _clampRectPositionToPage(resized);
ref.read(currentRectProvider.notifier).setRect(resized);
return;
}
// Unlocked aspect: clamp each dimension independently
newW = newW.clamp(20.0, pageSize.width);
newH = newH.clamp(20.0, pageSize.height);
Rect resized = Rect.fromLTWH(r.left, r.top, newW, newH);
resized = _clampRectToPage(resized);
ref.read(currentRectProvider.notifier).setRect(resized);
}
Rect _clampRectToPage(Rect r) {
// Ensure size never exceeds page bounds first, to avoid invalid clamp ranges
final double w = r.width.clamp(20.0, pageSize.width);
final double h = r.height.clamp(20.0, pageSize.height);
final double left = r.left.clamp(0.0, pageSize.width - w);
final double top = r.top.clamp(0.0, pageSize.height - h);
return Rect.fromLTWH(left, top, w, h);
}
Rect _clampRectPositionToPage(Rect r) {
final double left = r.left.clamp(0.0, pageSize.width - r.width);
final double top = r.top.clamp(0.0, pageSize.height - r.height);
return Rect.fromLTWH(left, top, r.width, r.height);
}
void toggleAspect(bool v) => ref.read(aspectLockedProvider.notifier).set(v);
void setBgRemoval(bool v) =>
state = state.copyWith(
graphicAdjust: state.graphicAdjust.copyWith(bgRemoval: v),
);
void setContrast(double v) =>
state = state.copyWith(
graphicAdjust: state.graphicAdjust.copyWith(contrast: v),
);
void setBrightness(double v) =>
state = state.copyWith(
graphicAdjust: state.graphicAdjust.copyWith(brightness: v),
);
void setRotation(double deg) => state = state.copyWith(rotationDeg: deg);
void ensureRectForStrokes() {
if (ref.read(currentRectProvider) == null) {
ref
.read(currentRectProvider.notifier)
.setRect(
Rect.fromCenter(
center: Offset(pageSize.width / 2, pageSize.height * 0.75),
width: 140,
height: 70,
),
);
ref.read(editingEnabledProvider.notifier).set(true);
}
}
void setImageBytes(Uint8List bytes) {
final newAsset = SignatureAsset(id: 'drawn', bytes: bytes);
state = state.copyWith(asset: newAsset);
if (ref.read(currentRectProvider) == null) {
placeDefaultRect();
}
ref.read(editingEnabledProvider.notifier).set(true);
}
// Select image from the shared signature library
void setImageFromLibrary({required SignatureAsset asset}) {
state = state.copyWith(asset: asset);
if (ref.read(currentRectProvider) == null) {
placeDefaultRect();
}
ref.read(editingEnabledProvider.notifier).set(true);
}
void clearImage() {
state = SignatureCard.initial();
ref.read(currentRectProvider.notifier).setRect(null);
ref.read(editingEnabledProvider.notifier).set(false);
}
void placeAtCenter(Offset center, {double width = 120, double height = 60}) {
Rect r = Rect.fromCenter(center: center, width: width, height: height);
r = _clampRectToPage(r);
ref.read(currentRectProvider.notifier).setRect(r);
ref.read(editingEnabledProvider.notifier).set(true);
}
// Confirm current signature: freeze editing and place it on the PDF as an immutable overlay.
// Stores the placement rect in UI-space (SignatureController.pageSize units).
// Returns the Rect placed, or null if no rect to confirm.
Rect? confirmCurrentSignature(WidgetRef ref) {
final r = ref.read(currentRectProvider);
if (r == null) return null;
// Place onto the current page
final pdf = ref.read(documentRepositoryProvider);
if (!pdf.loaded) return null;
ref
.read(documentRepositoryProvider.notifier)
.addPlacement(
page: pdf.currentPage,
rect: r,
asset: state.asset,
rotationDeg: state.rotationDeg,
);
// Newly placed index is the last one on the page
final idx =
(ref
.read(documentRepositoryProvider)
.placementsByPage[pdf.currentPage]
?.length ??
1) -
1;
// Auto-select the newly placed item so the red box appears
if (idx >= 0) {
ref.read(documentRepositoryProvider.notifier).selectPlacement(idx);
}
// Freeze editing: keep rect for preview but disable interaction
ref.read(editingEnabledProvider.notifier).set(false);
return r;
}
// Test/helper variant: confirm using a ProviderContainer instead of WidgetRef.
// Useful in widget tests where obtaining a WidgetRef is not straightforward.
@visibleForTesting
Rect? confirmCurrentSignatureWithContainer(ProviderContainer container) {
final r = container.read(currentRectProvider);
if (r == null) return null;
final pdf = container.read(documentRepositoryProvider);
if (!pdf.loaded) return null;
container
.read(documentRepositoryProvider.notifier)
.addPlacement(
page: pdf.currentPage,
rect: r,
asset: state.asset,
rotationDeg: state.rotationDeg,
);
final idx =
(container
.read(documentRepositoryProvider)
.placementsByPage[pdf.currentPage]
?.length ??
1) -
1;
// Auto-select the newly placed item so the red box appears
if (idx >= 0) {
container.read(documentRepositoryProvider.notifier).selectPlacement(idx);
}
// Freeze editing: keep rect for preview but disable interaction
container.read(editingEnabledProvider.notifier).set(false);
return r;
}
// Remove the active overlay (draft or confirmed preview) but keep image settings intact
void clearActiveOverlay() {
ref.read(currentRectProvider.notifier).setRect(null);
ref.read(editingEnabledProvider.notifier).set(false);
}
}
final signatureCardProvider =
StateNotifierProvider<SignatureController, SignatureCard>(
(ref) => SignatureController(ref),
);
final currentRectProvider = StateNotifierProvider<RectNotifier, Rect?>(
(ref) => RectNotifier(),
);
class RectNotifier extends StateNotifier<Rect?> {
RectNotifier() : super(null);
void setRect(Rect? r) => state = r;
}
final editingEnabledProvider = StateNotifierProvider<BoolNotifier, bool>(
(ref) => BoolNotifier(false),
);
class BoolNotifier extends StateNotifier<bool> {
BoolNotifier(bool initial) : super(initial);
void set(bool v) => state = v;
}
final aspectLockedProvider = StateNotifierProvider<BoolNotifier, bool>(
(ref) => BoolNotifier(false),
);
/// Derived provider that returns processed signature image bytes according to
/// current adjustment settings (contrast/brightness) and background removal.
/// Returns null if no image is loaded. The output is a PNG to preserve alpha.
final processedSignatureImageProvider = Provider<Uint8List?>((ref) {
final SignatureAsset asset = ref.watch(
signatureCardProvider.select((s) => s.asset),
);
final double contrast = ref.watch(
signatureCardProvider.select((s) => s.graphicAdjust.contrast),
);
final double brightness = ref.watch(
signatureCardProvider.select((s) => s.graphicAdjust.brightness),
);
final bool bgRemoval = ref.watch(
signatureCardProvider.select((s) => s.graphicAdjust.bgRemoval),
);
Uint8List? bytes = asset.bytes;
if (bytes.isEmpty) return null;
// Decode (supports PNG/JPEG, etc.)
final decoded = img.decodeImage(bytes);
if (decoded == null) return bytes;
// Work on a copy and ensure an alpha channel is present (RGBA)
var out = decoded.clone();
if (out.hasPalette || !out.hasAlpha) {
// Force truecolor RGBA image so per-pixel alpha writes take effect
out = out.convert(numChannels: 4);
}
// Parameters
// Rotation is not applied here (UI uses Transform; export applies once).
const int thrLow = 220; // begin soft transparency from this avg luminance
const int thrHigh = 245; // fully transparent from this avg luminance
// Helper to clamp int
int clamp255(num v) => v.clamp(0, 255).toInt();
// Iterate pixels
for (int y = 0; y < out.height; y++) {
for (int x = 0; x < out.width; x++) {
final p = out.getPixel(x, y);
int a = clamp255(p.aNormalized * 255.0);
int r = clamp255(p.rNormalized * 255.0);
int g = clamp255(p.gNormalized * 255.0);
int b = clamp255(p.bNormalized * 255.0);
// Apply contrast/brightness in sRGB space
// new = (old-128)*contrast + 128 + brightness*255
final double brOffset = brightness * 255.0;
r = clamp255((r - 128) * contrast + 128 + brOffset);
g = clamp255((g - 128) * contrast + 128 + brOffset);
b = clamp255((b - 128) * contrast + 128 + brOffset);
// Near-white background removal (compute average luminance)
final int avg = ((r + g + b) / 3).round();
int remAlpha = 255; // 255 = fully opaque, 0 = transparent
if (bgRemoval) {
if (avg >= thrHigh) {
remAlpha = 0;
} else if (avg >= thrLow) {
// Soft fade between thrLow..thrHigh
final double t = (avg - thrLow) / (thrHigh - thrLow);
remAlpha = clamp255(255 * (1.0 - t));
} else {
remAlpha = 255;
}
}
// Combine with existing alpha (preserve existing transparency)
final newA = math.min(a, remAlpha);
out.setPixelRgba(x, y, r, g, b, newA);
}
}
// NOTE: Do not rotate here to keep UI responsive while dragging the slider.
// Rotation is applied in the UI using Transform.rotate for preview and
// performed once on confirm/export to avoid per-frame recomputation.
// Encode as PNG to preserve transparency
final png = img.encodePng(out, level: 6);
return Uint8List.fromList(png);
});

View File

@ -1,60 +0,0 @@
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:path_provider/path_provider.dart' as pp;
import 'package:file_selector/file_selector.dart' as fs;
import 'package:pdf_signature/data/services/export_service.dart';
import 'package:pdf_signature/data/services/preferences_providers.dart';
// Feature-scoped DI and configuration providers
// Toggle mock viewer (used by tests to show a gray placeholder instead of real PDF pages)
final useMockViewerProvider = Provider<bool>((_) => false);
// Export service injection for testability
final exportServiceProvider = Provider<ExportService>((_) => ExportService());
// Export DPI setting (points per inch mapping). Reads from SharedPreferences when available,
// otherwise falls back to 144.0 to keep tests deterministic without bootstrapping prefs.
final exportDpiProvider = Provider<double>((ref) {
final sp = ref.watch(sharedPreferencesProvider);
return sp.maybeWhen(
data: (prefs) {
const allowed = [96.0, 144.0, 200.0, 300.0];
final v = prefs.getDouble('export_dpi');
return (v != null && allowed.contains(v)) ? v : 144.0;
},
orElse: () => 144.0,
);
});
// Controls whether signature overlay is visible (used to hide on non-stamped pages during export)
final signatureVisibilityProvider = StateProvider<bool>((_) => true);
// Global exporting state to show loading UI and block interactions while saving/exporting
final exportingProvider = StateProvider<bool>((_) => false);
// Save path picker (injected for tests)
final savePathPickerProvider = Provider<Future<String?> Function()>((ref) {
return () async {
String? initialDir;
try {
final d = await pp.getDownloadsDirectory();
initialDir = d?.path;
} catch (_) {}
if (initialDir == null) {
try {
final d = await pp.getApplicationDocumentsDirectory();
initialDir = d.path;
} catch (_) {}
}
final location = await fs.getSaveLocation(
suggestedName: 'signed.pdf',
acceptedTypeGroups: [
const fs.XTypeGroup(label: 'PDF', extensions: ['pdf']),
],
initialDirectory: initialDir,
);
if (location == null) return null;
final path = location.path;
return path.toLowerCase().endsWith('.pdf') ? path : '$path.pdf';
};
});

View File

@ -21,25 +21,18 @@ class ExportService {
/// Inputs: /// Inputs:
/// - [inputPath]: Path to the original PDF to read /// - [inputPath]: Path to the original PDF to read
/// - [outputPath]: Path to write the composed PDF /// - [outputPath]: Path to write the composed PDF
/// - [signedPage]: 1-based page index to place the signature on (null = no overlay) /// - [uiPageSize]: The logical page size used by the UI layout (SignatureCardStateNotifier.pageSize)
/// - [signatureRectUi]: Rect in the UI's logical page space (e.g. 400x560)
/// - [uiPageSize]: The logical page size used by the UI layout (SignatureController.pageSize)
/// - [signatureImageBytes]: PNG/JPEG bytes of the signature image to overlay /// - [signatureImageBytes]: PNG/JPEG bytes of the signature image to overlay
/// - [targetDpi]: Rasterization DPI for background pages /// - [targetDpi]: Rasterization DPI for background pages
Future<bool> exportSignedPdfFromFile({ Future<bool> exportSignedPdfFromFile({
required String inputPath, required String inputPath,
required String outputPath, required String outputPath,
required int? signedPage,
required Rect? signatureRectUi,
required Size uiPageSize, required Size uiPageSize,
required Uint8List? signatureImageBytes, required Uint8List? signatureImageBytes,
Map<int, List<SignaturePlacement>>? placementsByPage, Map<int, List<SignaturePlacement>>? placementsByPage,
Map<String, Uint8List>? libraryBytes, Map<String, Uint8List>? libraryBytes,
double targetDpi = 144.0, double targetDpi = 144.0,
}) async { }) async {
// print(
// 'exportSignedPdfFromFile: enter signedPage=$signedPage outputPath=$outputPath',
// );
// Read source bytes and delegate to bytes-based exporter // Read source bytes and delegate to bytes-based exporter
Uint8List? srcBytes; Uint8List? srcBytes;
try { try {
@ -50,8 +43,6 @@ class ExportService {
if (srcBytes == null) return false; if (srcBytes == null) return false;
final bytes = await exportSignedPdfFromBytes( final bytes = await exportSignedPdfFromBytes(
srcBytes: srcBytes, srcBytes: srcBytes,
signedPage: signedPage,
signatureRectUi: signatureRectUi,
uiPageSize: uiPageSize, uiPageSize: uiPageSize,
signatureImageBytes: signatureImageBytes, signatureImageBytes: signatureImageBytes,
placementsByPage: placementsByPage, placementsByPage: placementsByPage,
@ -71,13 +62,11 @@ class ExportService {
/// Compose a new PDF from source PDF bytes; returns the resulting PDF bytes. /// Compose a new PDF from source PDF bytes; returns the resulting PDF bytes.
Future<Uint8List?> exportSignedPdfFromBytes({ Future<Uint8List?> exportSignedPdfFromBytes({
required Uint8List srcBytes, required Uint8List srcBytes,
required int? signedPage,
required Rect? signatureRectUi,
required Size uiPageSize, required Size uiPageSize,
required Uint8List? signatureImageBytes, required Uint8List? signatureImageBytes,
Map<int, List<SignaturePlacement>>? placementsByPage, Map<int, List<SignaturePlacement>>? placementsByPage,
Map<String, Uint8List>? libraryBytes, Map<String, Uint8List>? libraryBytes,
double targetDpi = 144.0, double targetDpi = 144.0
}) async { }) async {
final out = pw.Document(version: pdf.PdfVersion.pdf_1_4, compress: false); final out = pw.Document(version: pdf.PdfVersion.pdf_1_4, compress: false);
int pageIndex = 0; int pageIndex = 0;
@ -97,27 +86,13 @@ class ExportService {
final bgPng = await raster.toPng(); final bgPng = await raster.toPng();
final bgImg = pw.MemoryImage(bgPng); final bgImg = pw.MemoryImage(bgPng);
pw.MemoryImage? sigImgObj;
final hasMulti = final hasMulti =
(placementsByPage != null && placementsByPage.isNotEmpty); (placementsByPage != null && placementsByPage.isNotEmpty);
final pagePlacements = final pagePlacements =
hasMulti hasMulti
? (placementsByPage[pageIndex] ?? const <SignaturePlacement>[]) ? (placementsByPage[pageIndex] ?? const <SignaturePlacement>[])
: const <SignaturePlacement>[]; : const <SignaturePlacement>[];
final shouldStampSingle =
!hasMulti &&
signedPage != null &&
pageIndex == signedPage &&
signatureRectUi != null &&
signatureImageBytes != null &&
signatureImageBytes.isNotEmpty;
if (shouldStampSingle) {
try {
sigImgObj = pw.MemoryImage(signatureImageBytes);
} catch (_) {
sigImgObj = null;
}
}
out.addPage( out.addPage(
pw.Page( pw.Page(
@ -148,10 +123,7 @@ 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;
if (id.isNotEmpty) {
bytes = libraryBytes?[id];
}
bytes ??= signatureImageBytes; // fallback bytes ??= signatureImageBytes; // fallback
if (bytes != null && bytes.isNotEmpty) { if (bytes != null && bytes.isNotEmpty) {
pw.MemoryImage? imgObj; pw.MemoryImage? imgObj;
@ -184,26 +156,6 @@ class ExportService {
} }
} }
} }
} else if (shouldStampSingle && sigImgObj != null) {
final r = signatureRectUi;
final left = r.left / uiPageSize.width * widthPts;
final top = r.top / uiPageSize.height * heightPts;
final w = r.width / uiPageSize.width * widthPts;
final h = r.height / uiPageSize.height * heightPts;
children.add(
pw.Positioned(
left: left,
top: top,
child: pw.SizedBox(
width: w,
height: h,
child: pw.FittedBox(
fit: pw.BoxFit.contain,
child: pw.Image(sigImgObj),
),
),
),
);
} }
return pw.Stack(children: children); return pw.Stack(children: children);
}, },
@ -218,39 +170,14 @@ class ExportService {
// Fallback as A4 blank page with optional signature // Fallback as A4 blank page with optional signature
final widthPts = pdf.PdfPageFormat.a4.width; final widthPts = pdf.PdfPageFormat.a4.width;
final heightPts = pdf.PdfPageFormat.a4.height; final heightPts = pdf.PdfPageFormat.a4.height;
pw.MemoryImage? sigImgObj;
final hasMulti = final hasMulti =
(placementsByPage != null && placementsByPage.isNotEmpty); (placementsByPage != null && placementsByPage.isNotEmpty);
final pagePlacements = final pagePlacements =
hasMulti hasMulti
? (placementsByPage[1] ?? const <SignaturePlacement>[]) ? (placementsByPage[1] ?? const <SignaturePlacement>[])
: const <SignaturePlacement>[]; : const <SignaturePlacement>[];
final shouldStampSingle =
!hasMulti &&
signedPage != null &&
signedPage == 1 &&
signatureRectUi != null &&
signatureImageBytes != null &&
signatureImageBytes.isNotEmpty;
if (shouldStampSingle) {
try {
// If it's already PNG, keep as-is to preserve alpha; otherwise decode/encode PNG
final asStr = String.fromCharCodes(signatureImageBytes.take(8));
final isPng =
signatureImageBytes.length > 8 &&
signatureImageBytes[0] == 0x89 &&
asStr.startsWith('\u0089PNG');
if (isPng) {
sigImgObj = pw.MemoryImage(signatureImageBytes);
} else {
final decoded = img.decodeImage(signatureImageBytes);
if (decoded != null) {
final png = img.encodePng(decoded, level: 6);
sigImgObj = pw.MemoryImage(Uint8List.fromList(png));
}
}
} catch (_) {}
}
out.addPage( out.addPage(
pw.Page( pw.Page(
pageTheme: pw.PageTheme( pageTheme: pw.PageTheme(
@ -275,10 +202,7 @@ 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;
if (id.isNotEmpty) {
bytes = libraryBytes?[id];
}
bytes ??= signatureImageBytes; // fallback bytes ??= signatureImageBytes; // fallback
if (bytes != null && bytes.isNotEmpty) { if (bytes != null && bytes.isNotEmpty) {
pw.MemoryImage? imgObj; pw.MemoryImage? imgObj;
@ -323,26 +247,6 @@ class ExportService {
} }
} }
} }
} else if (shouldStampSingle && sigImgObj != null) {
final r = signatureRectUi;
final left = r.left / uiPageSize.width * widthPts;
final top = r.top / uiPageSize.height * heightPts;
final w = r.width / uiPageSize.width * widthPts;
final h = r.height / uiPageSize.height * heightPts;
children.add(
pw.Positioned(
left: left,
top: top,
child: pw.SizedBox(
width: w,
height: h,
child: pw.FittedBox(
fit: pw.BoxFit.contain,
child: pw.Image(sigImgObj),
),
),
),
);
} }
return pw.Stack(children: children); return pw.Stack(children: children);
}, },

View File

@ -1,3 +1,4 @@
/// TODO: remove this file and export models directly from their files.
export 'signature_asset.dart'; export 'signature_asset.dart';
export 'graphic_adjust.dart'; export 'graphic_adjust.dart';
export 'signature_card.dart'; export 'signature_card.dart';

View File

@ -0,0 +1,25 @@
/// TODO: add `freeze` and `json_serializable` to generate immutable data class with copyWith, toString, equality, and JSON support.
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({
String? theme,
String? theme_color,
String? language,
double? exportDpi,
}) => PreferencesState(
theme: theme ?? this.theme,
theme_color: theme_color ?? this.theme_color,
language: language ?? this.language,
exportDpi: exportDpi ?? this.exportDpi,
);
}

View File

@ -2,9 +2,8 @@ import 'dart:typed_data';
/// 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 { class SignatureAsset {
final String id; // unique id
final Uint8List bytes; final Uint8List bytes;
// List<List<Offset>>? strokes; // List<List<Offset>>? strokes;
final String? name; // optional display name (e.g., filename) final String? name; // optional display name (e.g., filename)
const SignatureAsset({required this.id, required this.bytes, this.name}); const SignatureAsset({required this.bytes, this.name});
} }

View File

@ -12,8 +12,8 @@ class SignatureCard {
final GraphicAdjust graphicAdjust; final GraphicAdjust graphicAdjust;
const SignatureCard({ const SignatureCard({
required this.rotationDeg,
required this.asset, required this.asset,
required this.rotationDeg,
this.graphicAdjust = const GraphicAdjust(), this.graphicAdjust = const GraphicAdjust(),
}); });
@ -28,8 +28,8 @@ class SignatureCard {
); );
factory SignatureCard.initial() => SignatureCard( factory SignatureCard.initial() => SignatureCard(
asset: SignatureAsset(bytes: Uint8List(0)),
rotationDeg: 0.0, rotationDeg: 0.0,
asset: SignatureAsset(id: '', bytes: Uint8List(0)),
graphicAdjust: const GraphicAdjust(), graphicAdjust: const GraphicAdjust(),
); );
} }

View File

@ -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 '../../../../domain/models/model.dart'; import '../../../../domain/models/model.dart';
import 'package:pdf_signature/data/repositories/signature_repository.dart'; import 'package:pdf_signature/data/repositories/signature_card_repository.dart';
class AdjustmentsPanel extends ConsumerWidget { class AdjustmentsPanel extends ConsumerWidget {
const AdjustmentsPanel({super.key, required this.sig}); const AdjustmentsPanel({super.key, required this.sig});

View File

@ -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 'package:pdf_signature/data/repositories/signature_card_repository.dart';
import 'adjustments_panel.dart'; import 'adjustments_panel.dart';
import '../../signature/widgets/rotated_signature_image.dart'; import '../../signature/widgets/rotated_signature_image.dart';

View File

@ -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 'package:pdf_signature/data/repositories/signature_card_repository.dart';
import 'package:pdf_signature/data/repositories/pdf_repository.dart'; import 'package:pdf_signature/data/repositories/document_repository.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';

View File

@ -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 'package:pdf_signature/data/repositories/signature_card_repository.dart';
import '../../../../domain/models/model.dart'; import '../../../../domain/models/model.dart';
import 'package:pdf_signature/data/repositories/pdf_repository.dart'; import 'package:pdf_signature/data/repositories/document_repository.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.
@ -36,7 +36,7 @@ class PdfPageOverlays extends ConsumerWidget {
final widgets = <Widget>[]; final widgets = <Widget>[];
for (int i = 0; i < placed.length; i++) { for (int i = 0; i < placed.length; i++) {
// Stored as UI-space rects (SignatureController.pageSize). // Stored as UI-space rects (SignatureCardStateNotifier.pageSize).
final uiRect = placed[i].rect; final uiRect = placed[i].rect;
widgets.add( widgets.add(
SignatureOverlay( SignatureOverlay(

View File

@ -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 'package:pdf_signature/data/repositories/document_repository.dart';
class PdfPagesOverview extends ConsumerWidget { class PdfPagesOverview extends ConsumerWidget {
const PdfPagesOverview({super.key}); const PdfPagesOverview({super.key});

View File

@ -3,6 +3,8 @@ import 'package:file_selector/file_selector.dart' as fs;
import 'package:flutter/foundation.dart' show kIsWeb; import 'package:flutter/foundation.dart' show kIsWeb;
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/preferences_repository.dart';
import 'package:pdf_signature/domain/models/preferences.dart';
import 'package:pdf_signature/l10n/app_localizations.dart'; import 'package:pdf_signature/l10n/app_localizations.dart';
import 'package:printing/printing.dart' as printing; import 'package:printing/printing.dart' as printing;
import 'package:pdfrx/pdfrx.dart'; import 'package:pdfrx/pdfrx.dart';
@ -10,8 +12,8 @@ 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 'package:pdf_signature/data/repositories/signature_card_repository.dart';
import 'package:pdf_signature/data/repositories/pdf_repository.dart'; import 'package:pdf_signature/data/repositories/document_repository.dart';
import 'package:pdf_signature/data/repositories/signature_asset_repository.dart'; import 'package:pdf_signature/data/repositories/signature_asset_repository.dart';
import 'draw_canvas.dart'; import 'draw_canvas.dart';
import 'pdf_toolbar.dart'; import 'pdf_toolbar.dart';
@ -28,7 +30,7 @@ class PdfSignatureHomePage extends ConsumerStatefulWidget {
} }
class _PdfSignatureHomePageState extends ConsumerState<PdfSignatureHomePage> { class _PdfSignatureHomePageState extends ConsumerState<PdfSignatureHomePage> {
static const Size _pageSize = SignatureController.pageSize; static const Size _pageSize = SignatureCardStateNotifier.pageSize;
final PdfViewerController _viewerController = PdfViewerController(); final PdfViewerController _viewerController = PdfViewerController();
bool _showPagesSidebar = true; bool _showPagesSidebar = true;
bool _showSignaturesSidebar = true; bool _showSignaturesSidebar = true;
@ -142,7 +144,11 @@ class _PdfSignatureHomePageState extends ConsumerState<PdfSignatureHomePage> {
return; return;
} }
final exporter = ref.read(exportServiceProvider); final exporter = ref.read(exportServiceProvider);
final targetDpi = ref.read(exportDpiProvider);
// get DPI from preferences
final targetDpi = ref.read(preferencesRepositoryProvider).select(
(p) => p.exportDpi,
);
final useMock = ref.read(useMockViewerProvider); final useMock = ref.read(useMockViewerProvider);
bool ok = false; bool ok = false;
String? savedPath; String? savedPath;
@ -177,7 +183,7 @@ class _PdfSignatureHomePageState extends ConsumerState<PdfSignatureHomePage> {
srcBytes: src, srcBytes: src,
signedPage: pdf.signedPage, signedPage: pdf.signedPage,
signatureRectUi: sig.rect, signatureRectUi: sig.rect,
uiPageSize: SignatureController.pageSize, uiPageSize: SignatureCardStateNotifier.pageSize,
signatureImageBytes: rotated, signatureImageBytes: rotated,
placementsByPage: pdf.placementsByPage, placementsByPage: pdf.placementsByPage,
libraryBytes: { libraryBytes: {
@ -214,7 +220,7 @@ class _PdfSignatureHomePageState extends ConsumerState<PdfSignatureHomePage> {
srcBytes: pdf.pickedPdfBytes!, srcBytes: pdf.pickedPdfBytes!,
signedPage: pdf.signedPage, signedPage: pdf.signedPage,
signatureRectUi: sig.rect, signatureRectUi: sig.rect,
uiPageSize: SignatureController.pageSize, uiPageSize: SignatureCardStateNotifier.pageSize,
signatureImageBytes: rotated, signatureImageBytes: rotated,
placementsByPage: pdf.placementsByPage, placementsByPage: pdf.placementsByPage,
libraryBytes: { libraryBytes: {
@ -245,7 +251,7 @@ class _PdfSignatureHomePageState extends ConsumerState<PdfSignatureHomePage> {
outputPath: fullPath, outputPath: fullPath,
signedPage: pdf.signedPage, signedPage: pdf.signedPage,
signatureRectUi: sig.rect, signatureRectUi: sig.rect,
uiPageSize: SignatureController.pageSize, uiPageSize: SignatureCardStateNotifier.pageSize,
signatureImageBytes: rotated, signatureImageBytes: rotated,
placementsByPage: pdf.placementsByPage, placementsByPage: pdf.placementsByPage,
libraryBytes: { libraryBytes: {
@ -467,3 +473,7 @@ class _PdfSignatureHomePageState extends ConsumerState<PdfSignatureHomePage> {
); );
} }
} }
extension on PreferencesState {
select(Function(dynamic p) param0) {}
}

View File

@ -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 'package:pdf_signature/data/repositories/document_repository.dart';
class PdfToolbar extends ConsumerStatefulWidget { class PdfToolbar extends ConsumerStatefulWidget {
const PdfToolbar({ const PdfToolbar({

View File

@ -5,7 +5,7 @@ import 'package:pdf_signature/l10n/app_localizations.dart';
import 'package:pdf_signature/domain/models/model.dart' as model; import 'package:pdf_signature/domain/models/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 'package:pdf_signature/data/repositories/signature_card_repository.dart';
import 'package:pdf_signature/data/repositories/signature_asset_repository.dart'; import 'package:pdf_signature/data/repositories/signature_asset_repository.dart';
import 'image_editor_dialog.dart'; import 'image_editor_dialog.dart';
import '../../signature/widgets/signature_card.dart'; import '../../signature/widgets/signature_card.dart';

View File

@ -5,8 +5,8 @@ import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:pdf_signature/l10n/app_localizations.dart'; import 'package:pdf_signature/l10n/app_localizations.dart';
import '../../../../domain/models/model.dart'; import '../../../../domain/models/model.dart';
import 'package:pdf_signature/data/repositories/signature_repository.dart'; import 'package:pdf_signature/data/repositories/signature_card_repository.dart';
import 'package:pdf_signature/data/repositories/pdf_repository.dart'; import 'package:pdf_signature/data/repositories/document_repository.dart';
import 'package:pdf_signature/data/repositories/signature_asset_repository.dart'; import 'package:pdf_signature/data/repositories/signature_asset_repository.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';

View File

@ -1,7 +1,7 @@
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 '../../../../data/services/preferences_providers.dart'; import '../../../../data/repositories/preferences_repository.dart';
class SettingsDialog extends ConsumerStatefulWidget { class SettingsDialog extends ConsumerStatefulWidget {
const SettingsDialog({super.key}); const SettingsDialog({super.key});
@ -19,7 +19,7 @@ class _SettingsDialogState extends ConsumerState<SettingsDialog> {
@override @override
void initState() { void initState() {
super.initState(); super.initState();
final prefs = ref.read(preferencesProvider); final prefs = ref.read(preferencesRepositoryProvider);
_theme = prefs.theme; _theme = prefs.theme;
_language = prefs.language; _language = prefs.language;
_exportDpi = prefs.exportDpi; _exportDpi = prefs.exportDpi;
@ -186,7 +186,9 @@ class _SettingsDialogState extends ConsumerState<SettingsDialog> {
const SizedBox(width: 8), const SizedBox(width: 8),
FilledButton( FilledButton(
onPressed: () async { onPressed: () async {
final n = ref.read(preferencesProvider.notifier); final n = ref.read(
preferencesRepositoryProvider.notifier,
);
if (_theme != null) await n.setTheme(_theme!); if (_theme != null) await n.setTheme(_theme!);
if (_language != null) await n.setLanguage(_language!); if (_language != null) await n.setLanguage(_language!);
if (_exportDpi != null) await n.setExportDpi(_exportDpi!); if (_exportDpi != null) await n.setExportDpi(_exportDpi!);

View File

@ -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 'package:pdf_signature/data/repositories/signature_card_repository.dart';
import 'package:pdf_signature/data/repositories/pdf_repository.dart'; import 'package:pdf_signature/data/repositories/document_repository.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

View File

@ -1,87 +0,0 @@
import 'dart:io';
import 'dart:typed_data';
import 'dart:ui' show Rect, Size;
import 'package:flutter_test/flutter_test.dart';
import 'package:image/image.dart' as img;
import 'package:pdf/pdf.dart' as pdf;
import 'package:pdf/widgets.dart' as pw;
import 'package:pdf_signature/data/services/export_service.dart';
void main() {
test(
'exportSignedPdfFromFile overlays signature image (structure/size check)',
() async {
// 1) Create a simple 1-page white PDF as the source
final srcDoc = pw.Document();
srcDoc.addPage(
pw.Page(
pageFormat: pdf.PdfPageFormat.a4,
build: (_) => pw.Container(color: pdf.PdfColors.white),
),
);
final srcBytes = await srcDoc.save();
final srcPath =
'${Directory.systemTemp.path}/export_src_${DateTime.now().millisecondsSinceEpoch}.pdf';
await File(srcPath).writeAsBytes(srcBytes, flush: true);
// 2) Create a small opaque black PNG as the signature image
final sigW = 60, sigH = 30;
final sigBitmap = img.Image(width: sigW, height: sigH);
img.fill(sigBitmap, color: img.ColorRgb8(0, 0, 0));
final sigPng = Uint8List.fromList(img.encodePng(sigBitmap));
// 3) Define signature rect in UI logical space (400x560), centered
const uiSize = Size(400, 560);
final r = Rect.fromLTWH(
uiSize.width / 2 - sigW / 2,
uiSize.height / 2 - sigH / 2,
sigW.toDouble(),
sigH.toDouble(),
);
// 4) Baseline export without signature (no overlay)
final baselinePath =
'${Directory.systemTemp.path}/export_baseline_${DateTime.now().millisecondsSinceEpoch}.pdf';
final svc = ExportService();
final okBase = await svc.exportSignedPdfFromFile(
inputPath: srcPath,
outputPath: baselinePath,
signedPage: null,
signatureRectUi: null,
uiPageSize: uiSize,
signatureImageBytes: null,
targetDpi: 144.0,
);
expect(okBase, isTrue, reason: 'baseline export should succeed');
final baseBytes = await File(baselinePath).readAsBytes();
expect(baseBytes.isNotEmpty, isTrue);
// 5) Export with overlay
final outPath =
'${Directory.systemTemp.path}/export_out_${DateTime.now().millisecondsSinceEpoch}.pdf';
final ok = await svc.exportSignedPdfFromFile(
inputPath: srcPath,
outputPath: outPath,
signedPage: 1,
signatureRectUi: r,
uiPageSize: uiSize,
signatureImageBytes: sigPng,
targetDpi: 144.0,
);
expect(ok, isTrue, reason: 'export should succeed');
final outBytes = await File(outPath).readAsBytes();
expect(outBytes.isNotEmpty, isTrue);
// 6) Heuristic validations without rasterization:
// - The output with overlay should be larger than the baseline.
// - The output should contain at least one image object marker.
expect(outBytes.length, greaterThan(baseBytes.length));
// Decode as latin1 to preserve byte-to-char mapping, then look for the image marker
final outText = String.fromCharCodes(outBytes);
final hasImageMarker = RegExp(r"/Subtype\s*/Image").hasMatch(outText);
expect(hasImageMarker, isTrue);
},
);
}

View File

@ -2,7 +2,7 @@ import 'dart:typed_data';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
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/data/repositories/document_repository.dart';
import 'package:pdf_signature/domain/models/model.dart'; import 'package:pdf_signature/domain/models/model.dart';
import '_world.dart'; import '_world.dart';

View File

@ -2,7 +2,7 @@ import 'dart:typed_data';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
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/data/repositories/document_repository.dart';
import 'package:pdf_signature/domain/models/model.dart'; import 'package:pdf_signature/domain/models/model.dart';
import '_world.dart'; import '_world.dart';

View File

@ -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/data/repositories/document_repository.dart';
import '_world.dart'; import '_world.dart';
/// Usage: a document is open with no signature placements placed /// Usage: a document is open with no signature placements placed

View File

@ -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/data/repositories/document_repository.dart';
import '_world.dart'; import '_world.dart';
/// Usage: a document page is selected for signing /// Usage: a document page is selected for signing

View File

@ -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/data/repositories/signature_card_repository.dart';
import '_world.dart'; import '_world.dart';
/// Usage: a drawn signature exists in the canvas /// Usage: a drawn signature exists in the canvas

View File

@ -1,7 +1,7 @@
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/data/repositories/document_repository.dart';
import 'package:pdf_signature/data/repositories/signature_repository.dart'; import 'package:pdf_signature/data/repositories/signature_card_repository.dart';
import 'package:pdf_signature/data/repositories/signature_asset_repository.dart'; import 'package:pdf_signature/data/repositories/signature_asset_repository.dart';
import 'package:pdf_signature/domain/models/model.dart'; import 'package:pdf_signature/domain/models/model.dart';
import '_world.dart'; import '_world.dart';

View File

@ -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/data/repositories/document_repository.dart';
import '_world.dart'; import '_world.dart';
/// Usage: a sample multi-page document (5 pages) is available /// Usage: a sample multi-page document (5 pages) is available

View File

@ -2,7 +2,7 @@ import 'dart:typed_data';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
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/data/repositories/document_repository.dart';
import 'package:pdf_signature/data/repositories/signature_asset_repository.dart'; import 'package:pdf_signature/data/repositories/signature_asset_repository.dart';
import 'package:pdf_signature/domain/models/model.dart'; import 'package:pdf_signature/domain/models/model.dart';
import '_world.dart'; import '_world.dart';

View File

@ -2,8 +2,8 @@ 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:pdf_signature/data/repositories/signature_asset_repository.dart'; import 'package:pdf_signature/data/repositories/signature_asset_repository.dart';
import 'package:pdf_signature/data/repositories/pdf_repository.dart'; import 'package:pdf_signature/data/repositories/document_repository.dart';
import 'package:pdf_signature/data/repositories/signature_repository.dart'; import 'package:pdf_signature/data/repositories/signature_card_repository.dart';
import 'package:pdf_signature/domain/models/model.dart'; import 'package:pdf_signature/domain/models/model.dart';
import '_world.dart'; import '_world.dart';

View File

@ -2,7 +2,7 @@ import 'dart:typed_data';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
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/data/repositories/document_repository.dart';
import 'package:pdf_signature/data/repositories/signature_asset_repository.dart'; import 'package:pdf_signature/data/repositories/signature_asset_repository.dart';
import 'package:pdf_signature/domain/models/model.dart'; import 'package:pdf_signature/domain/models/model.dart';
import '_world.dart'; import '_world.dart';

View File

@ -2,8 +2,8 @@ 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:pdf_signature/data/repositories/signature_asset_repository.dart'; import 'package:pdf_signature/data/repositories/signature_asset_repository.dart';
import 'package:pdf_signature/data/repositories/pdf_repository.dart'; import 'package:pdf_signature/data/repositories/document_repository.dart';
import 'package:pdf_signature/data/repositories/signature_repository.dart'; import 'package:pdf_signature/data/repositories/signature_card_repository.dart';
import 'package:pdf_signature/domain/models/model.dart'; import 'package:pdf_signature/domain/models/model.dart';
import '_world.dart'; import '_world.dart';

View File

@ -1,5 +1,5 @@
import 'package:flutter_test/flutter_test.dart'; import 'package:flutter_test/flutter_test.dart';
import 'package:pdf_signature/data/repositories/pdf_repository.dart'; import 'package:pdf_signature/data/repositories/document_repository.dart';
import '_world.dart'; import '_world.dart';
/// Usage: a signature placement appears on the page based on the signature card /// Usage: a signature placement appears on the page based on the signature card

View File

@ -2,7 +2,7 @@ import 'dart:typed_data';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
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/data/repositories/document_repository.dart';
import 'package:pdf_signature/domain/models/model.dart'; import 'package:pdf_signature/domain/models/model.dart';
import '_world.dart'; import '_world.dart';

View File

@ -2,7 +2,7 @@ import 'dart:typed_data';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
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/data/repositories/document_repository.dart';
import 'package:pdf_signature/domain/models/model.dart'; import 'package:pdf_signature/domain/models/model.dart';
import '_world.dart'; import '_world.dart';

View File

@ -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/data/repositories/document_repository.dart';
import 'package:pdf_signature/domain/models/model.dart'; import 'package:pdf_signature/domain/models/model.dart';
import '_world.dart'; import '_world.dart';

View File

@ -1,5 +1,5 @@
import 'package:flutter_test/flutter_test.dart'; import 'package:flutter_test/flutter_test.dart';
import 'package:pdf_signature/data/repositories/pdf_repository.dart'; import 'package:pdf_signature/data/repositories/document_repository.dart';
import '_world.dart'; import '_world.dart';
/// Usage: adjusting one of the signature placements does not affect the others /// Usage: adjusting one of the signature placements does not affect the others

View File

@ -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/data/repositories/document_repository.dart';
import '_world.dart'; import '_world.dart';
/// Usage: all placed signature placements appear on their corresponding pages in the output /// Usage: all placed signature placements appear on their corresponding pages in the output

View File

@ -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/data/repositories/signature_card_repository.dart';
import '_world.dart'; import '_world.dart';
/// Usage: an empty signature canvas /// Usage: an empty signature canvas

View File

@ -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/data/repositories/document_repository.dart';
import '_world.dart'; import '_world.dart';
/// Usage: both signature placements are shown on their respective pages /// Usage: both signature placements are shown on their respective pages

View File

@ -1,7 +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/data/repositories/document_repository.dart';
import 'package:pdf_signature/domain/models/model.dart'; import 'package:pdf_signature/domain/models/model.dart';
import '_world.dart'; import '_world.dart';

View File

@ -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/data/repositories/document_repository.dart';
import '_world.dart'; import '_world.dart';
/// Usage: each signature placement can be dragged and resized independently /// Usage: each signature placement can be dragged and resized independently

View File

@ -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/data/repositories/document_repository.dart';
import '_world.dart'; import '_world.dart';
/// Usage: identical signature instances appear in each location /// Usage: identical signature instances appear in each location

View File

@ -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/data/repositories/document_repository.dart';
import '_world.dart'; import '_world.dart';
/// Usage: identical signature placements appear in each location /// Usage: identical signature placements appear in each location

View File

@ -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/data/repositories/signature_card_repository.dart';
import '_world.dart'; import '_world.dart';
/// Usage: multiple strokes were drawn /// Usage: multiple strokes were drawn

View File

@ -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/data/repositories/signature_card_repository.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

View File

@ -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/data/repositories/document_repository.dart';
import '_world.dart'; import '_world.dart';
/// Usage: only the selected signature placement is removed /// Usage: only the selected signature placement is removed

View File

@ -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/data/repositories/document_repository.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

View File

@ -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/data/repositories/document_repository.dart';
import '_world.dart'; import '_world.dart';
/// Usage: page {1} is displayed /// Usage: page {1} is displayed

View File

@ -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/data/repositories/document_repository.dart';
import '_world.dart'; import '_world.dart';
/// Usage: resize to fit within bounding box /// Usage: resize to fit within bounding box

View File

@ -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/data/repositories/document_repository.dart';
import '_world.dart'; import '_world.dart';
/// Usage: signature placement occurs on the selected page /// Usage: signature placement occurs on the selected page

View File

@ -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/data/repositories/signature_card_repository.dart';
import '_world.dart'; import '_world.dart';
/// Usage: the canvas becomes blank /// Usage: the canvas becomes blank

View File

@ -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/data/repositories/document_repository.dart';
import '_world.dart'; import '_world.dart';
/// Usage: the document is open /// Usage: the document is open

View File

@ -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/data/repositories/document_repository.dart';
import '_world.dart'; import '_world.dart';
/// Usage: the first page is displayed /// Usage: the first page is displayed

View File

@ -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/data/repositories/document_repository.dart';
import '_world.dart'; import '_world.dart';
/// Usage: the Go to input cannot be used /// Usage: the Go to input cannot be used

View File

@ -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/data/repositories/document_repository.dart';
import '_world.dart'; import '_world.dart';
/// Usage: the last page is displayed (page {5}) /// Usage: the last page is displayed (page {5})

View File

@ -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/data/repositories/signature_card_repository.dart';
import '_world.dart'; import '_world.dart';
/// Usage: the last stroke is removed /// Usage: the last stroke is removed

View File

@ -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/data/repositories/document_repository.dart';
import '_world.dart'; import '_world.dart';
/// Usage: the left pages overview highlights page {5} /// Usage: the left pages overview highlights page {5}

View File

@ -1,5 +1,5 @@
import 'package:flutter_test/flutter_test.dart'; import 'package:flutter_test/flutter_test.dart';
import 'package:pdf_signature/data/repositories/pdf_repository.dart'; import 'package:pdf_signature/data/repositories/document_repository.dart';
import '_world.dart'; import '_world.dart';
/// Usage: the other signature placements remain unchanged /// Usage: the other signature placements remain unchanged

View File

@ -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/data/repositories/document_repository.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}"

View File

@ -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/data/repositories/signature_card_repository.dart';
import '_world.dart'; import '_world.dart';
/// Usage: the preview updates immediately /// Usage: the preview updates immediately

View File

@ -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/data/repositories/document_repository.dart';
import '_world.dart'; import '_world.dart';
/// Usage: the signature placement is stamped at the exact PDF page coordinates and size /// Usage: the signature placement is stamped at the exact PDF page coordinates and size

View File

@ -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/data/repositories/document_repository.dart';
import '_world.dart'; import '_world.dart';
/// Usage: the signature placement on page {5} is shown on page {5} /// Usage: the signature placement on page {5} is shown on page {5}

View File

@ -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/data/repositories/document_repository.dart';
import '_world.dart'; import '_world.dart';
/// Usage: the signature placement on page {2} remains /// Usage: the signature placement on page {2} remains

View File

@ -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/data/repositories/document_repository.dart';
import '_world.dart'; import '_world.dart';
/// Usage: the signature placement remains within the page area /// Usage: the signature placement remains within the page area

View File

@ -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/data/repositories/document_repository.dart';
import '_world.dart'; import '_world.dart';
/// Usage: the signature placement rotates around its center in real time /// Usage: the signature placement rotates around its center in real time

View File

@ -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/data/repositories/document_repository.dart';
import '_world.dart'; import '_world.dart';
/// Usage: the signature placements appear on the corresponding page in the output /// Usage: the signature placements appear on the corresponding page in the output

View File

@ -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/data/repositories/document_repository.dart';
import '_world.dart'; import '_world.dart';
/// Usage: the size and position update in real time /// Usage: the size and position update in real time

View File

@ -1,7 +1,7 @@
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/data/repositories/signature_card_repository.dart';
import 'package:pdf_signature/data/repositories/pdf_repository.dart'; import 'package:pdf_signature/data/repositories/document_repository.dart';
import '_world.dart'; import '_world.dart';
/// Usage: the user attempts to save /// Usage: the user attempts to save

View File

@ -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/data/repositories/signature_card_repository.dart';
import '_world.dart'; import '_world.dart';
/// Usage: the user can apply or reset adjustments /// Usage: the user can apply or reset adjustments

View File

@ -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/data/repositories/document_repository.dart';
import '_world.dart'; import '_world.dart';
/// Usage: the user can move to the next or previous page /// Usage: the user can move to the next or previous page

View File

@ -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/data/repositories/signature_card_repository.dart';
import '_world.dart'; import '_world.dart';
/// Usage: the user changes contrast and brightness controls /// Usage: the user changes contrast and brightness controls

View File

@ -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/data/repositories/signature_card_repository.dart';
import '_world.dart'; import '_world.dart';
/// Usage: the user chooses undo /// Usage: the user chooses undo

View File

@ -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/data/repositories/signature_card_repository.dart';
import '_world.dart'; import '_world.dart';
/// Usage: the user clears the canvas /// Usage: the user clears the canvas

View File

@ -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/data/repositories/document_repository.dart';
import '_world.dart'; import '_world.dart';
/// Usage: the user clicks the Go to apply button /// Usage: the user clicks the Go to apply button

View File

@ -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/data/repositories/document_repository.dart';
import '_world.dart'; import '_world.dart';
/// Usage: the user clicks the thumbnail for page {2} /// Usage: the user clicks the thumbnail for page {2}

View File

@ -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/data/repositories/document_repository.dart';
import '_world.dart'; import '_world.dart';
/// Usage: the user deletes one selected signature placement /// Usage: the user deletes one selected signature placement

View File

@ -1,7 +1,7 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
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/data/repositories/document_repository.dart';
import '_world.dart'; import '_world.dart';
/// Usage: the user drags handles to resize and drags to reposition /// Usage: the user drags handles to resize and drags to reposition

View File

@ -2,7 +2,7 @@ import 'dart:typed_data';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
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/data/repositories/document_repository.dart';
import 'package:pdf_signature/data/repositories/signature_asset_repository.dart'; import 'package:pdf_signature/data/repositories/signature_asset_repository.dart';
import 'package:pdf_signature/domain/models/model.dart'; import 'package:pdf_signature/domain/models/model.dart';
import '_world.dart'; import '_world.dart';

View File

@ -2,7 +2,7 @@ import 'dart:typed_data';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
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/data/repositories/document_repository.dart';
import 'package:pdf_signature/data/repositories/signature_asset_repository.dart'; import 'package:pdf_signature/data/repositories/signature_asset_repository.dart';
import 'package:pdf_signature/domain/models/model.dart'; import 'package:pdf_signature/domain/models/model.dart';
import '_world.dart'; import '_world.dart';

View File

@ -1,7 +1,7 @@
import 'dart:typed_data'; 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:pdf_signature/data/repositories/signature_repository.dart'; import 'package:pdf_signature/data/repositories/signature_card_repository.dart';
import '_world.dart'; import '_world.dart';
/// Usage: the user draws strokes and confirms /// Usage: the user draws strokes and confirms

View File

@ -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/data/repositories/signature_card_repository.dart';
import '_world.dart'; import '_world.dart';
/// Usage: the user enables background removal /// Usage: the user enables background removal

View File

@ -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/data/repositories/document_repository.dart';
import '_world.dart'; import '_world.dart';
/// Usage: the user enters {99} into the Go to input and applies it /// Usage: the user enters {99} into the Go to input and applies it

View File

@ -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/data/repositories/signature_card_repository.dart';
import '_world.dart'; import '_world.dart';
/// Usage: the user is notified of the issue /// Usage: the user is notified of the issue

View File

@ -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/data/repositories/document_repository.dart';
import '_world.dart'; import '_world.dart';
/// Usage: the user jumps to page {2} /// Usage: the user jumps to page {2}

View File

@ -2,7 +2,7 @@ import 'dart:typed_data';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
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/data/repositories/document_repository.dart';
import 'package:pdf_signature/domain/models/model.dart'; import 'package:pdf_signature/domain/models/model.dart';
import '_world.dart'; import '_world.dart';

View File

@ -2,7 +2,7 @@ import 'dart:typed_data';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
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/data/repositories/document_repository.dart';
import 'package:pdf_signature/data/repositories/signature_asset_repository.dart'; import 'package:pdf_signature/data/repositories/signature_asset_repository.dart';
import 'package:pdf_signature/domain/models/model.dart'; import 'package:pdf_signature/domain/models/model.dart';
import '_world.dart'; import '_world.dart';

View File

@ -2,7 +2,7 @@ import 'dart:typed_data';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
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/data/repositories/document_repository.dart';
import 'package:pdf_signature/domain/models/model.dart'; import 'package:pdf_signature/domain/models/model.dart';
import '_world.dart'; import '_world.dart';

View File

@ -1,7 +1,7 @@
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:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:pdf_signature/data/repositories/pdf_repository.dart'; import 'package:pdf_signature/data/repositories/document_repository.dart';
import '_world.dart'; import '_world.dart';
/// Usage: the user places it in multiple locations in the document /// Usage: the user places it in multiple locations in the document

View File

@ -2,7 +2,7 @@ import 'dart:typed_data';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
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/data/repositories/document_repository.dart';
import 'package:pdf_signature/domain/models/model.dart'; import 'package:pdf_signature/domain/models/model.dart';
import '_world.dart'; import '_world.dart';

View File

@ -1,8 +1,8 @@
import 'dart:typed_data'; 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:pdf_signature/data/repositories/signature_repository.dart'; import 'package:pdf_signature/data/repositories/signature_card_repository.dart';
import 'package:pdf_signature/data/repositories/pdf_repository.dart'; import 'package:pdf_signature/data/repositories/document_repository.dart';
import '_world.dart'; import '_world.dart';
/// Usage: the user saves/exports the document /// Usage: the user saves/exports the document

View File

@ -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/data/repositories/document_repository.dart';
import '_world.dart'; import '_world.dart';
/// Usage: the user selects "<file>" /// Usage: the user selects "<file>"

View File

@ -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/data/repositories/document_repository.dart';
import '_world.dart'; import '_world.dart';
/// Usage: the user types {3} into the Go to input and presses Enter /// Usage: the user types {3} into the Go to input and presses Enter

View File

@ -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/data/repositories/document_repository.dart';
import '_world.dart'; import '_world.dart';
/// Usage: the user uses rotate controls /// Usage: the user uses rotate controls

Some files were not shown because too many files have changed in this diff Show More