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)
* 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`
* 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)

View File

@ -60,7 +60,9 @@ But with slight modifications.
* `integration_test/` for integration tests. They should be volatile to follow UI layout changes.
Some rule of thumb:
* `<object>Provider` only placed at `/lib/data/repositories/` or `/lib/data/services/` to provide data source.
* 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

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_providers.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/pdf_repository.dart';
import 'package:pdf_signature/data/repositories/signature_card_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/l10n/app_localizations.dart';
@ -36,7 +36,7 @@ void main() {
(ref) => DocumentStateNotifier()..openPicked(path: 'test.pdf'),
),
signatureProvider.overrideWith(
(ref) => SignatureController()..placeDefaultRect(),
(ref) => SignatureCardStateNotifier()..placeDefaultRect(),
),
useMockViewerProvider.overrideWith((ref) => true),
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:pdf_signature/l10n/app_localizations.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 'data/services/preferences_providers.dart';
import 'data/repositories/preferences_repository.dart';
import 'package:pdf_signature/ui/features/preferences/widgets/settings_screen.dart';
class MyApp extends StatelessWidget {

View File

@ -1,12 +1,15 @@
import 'dart:typed_data';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:pdf_signature/data/services/export_service.dart';
import '../../domain/models/model.dart';
class DocumentStateNotifier extends StateNotifier<Document> {
DocumentStateNotifier() : super(Document.initial());
final ExportService _service = ExportService();
@visibleForTesting
void openSample() {
state = state.copyWith(
@ -57,7 +60,7 @@ class DocumentStateNotifier extends StateNotifier<Document> {
list.add(
SignaturePlacement(
rect: rect,
asset: asset ?? SignatureAsset(id: '', bytes: Uint8List(0)),
asset: asset ?? SignatureAsset(bytes: Uint8List(0)),
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
SignatureAsset? assetOfPlacement({required int page, required int index}) {
final list = state.placementsByPage[page] ?? const [];
if (index < 0 || index >= list.length) return null;
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 =

View File

@ -3,6 +3,7 @@ import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:pdf_signature/l10n/app_localizations.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
String toLanguageTag(Locale loc) {
@ -27,6 +28,7 @@ Set<String> _supportedTags() {
// Keys
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 _kPageView = 'page_view'; // now only 'continuous'
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;
}
class 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> {
class PreferencesStateNotifier extends StateNotifier<PreferencesState> {
final SharedPreferences prefs;
PreferencesNotifier(this.prefs)
PreferencesStateNotifier(this.prefs)
: super(
PreferencesState(
theme: prefs.getString(_kTheme) ?? 'system',
@ -99,8 +76,8 @@ class PreferencesNotifier extends StateNotifier<PreferencesState> {
WidgetsBinding.instance.platformDispatcher.locale
.toLanguageTag(),
),
pageView: prefs.getString(_kPageView) ?? 'continuous',
exportDpi: _readDpi(prefs),
theme_color: prefs.getString(_kThemeColor) ?? 'blue',
),
) {
// normalize language to supported/fallback
@ -125,11 +102,6 @@ class PreferencesNotifier extends StateNotifier<PreferencesState> {
state = state.copyWith(language: 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
const allowed = [96.0, 144.0, 200.0, 300.0];
if (!allowed.contains(state.exportDpi)) {
@ -158,8 +130,8 @@ class PreferencesNotifier extends StateNotifier<PreferencesState> {
state = PreferencesState(
theme: 'system',
language: normalized,
pageView: 'continuous',
exportDpi: 144.0,
theme_color: '',
);
await prefs.setString(_kTheme, 'system');
await prefs.setString(_kLanguage, normalized);
@ -167,13 +139,6 @@ class PreferencesNotifier extends StateNotifier<PreferencesState> {
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 {
const allowed = [96.0, 144.0, 200.0, 300.0];
if (!allowed.contains(dpi)) return;
@ -189,8 +154,8 @@ final sharedPreferencesProvider = FutureProvider<SharedPreferences>((
return p;
});
final preferencesProvider =
StateNotifierProvider<PreferencesNotifier, PreferencesState>((ref) {
final preferencesRepositoryProvider =
StateNotifierProvider<PreferencesStateNotifier, PreferencesState>((ref) {
// In tests, you can override sharedPreferencesProvider
final prefs = ref
.watch(sharedPreferencesProvider)
@ -198,14 +163,14 @@ final preferencesProvider =
data: (p) => p,
orElse: () => throw StateError('SharedPreferences not ready'),
);
return PreferencesNotifier(prefs);
return PreferencesStateNotifier(prefs);
});
// pageViewModeProvider removed; the app always runs in continuous mode.
/// Derive the active ThemeMode based on preference and platform brightness
final themeModeProvider = Provider<ThemeMode>((ref) {
final prefs = ref.watch(preferencesProvider);
final prefs = ref.watch(preferencesRepositoryProvider);
switch (prefs.theme) {
case 'light':
return ThemeMode.light;
@ -218,7 +183,7 @@ final themeModeProvider = Provider<ThemeMode>((ref) {
});
final localeProvider = Provider<Locale?>((ref) {
final prefs = ref.watch(preferencesProvider);
final prefs = ref.watch(preferencesRepositoryProvider);
final supported = _supportedTags();
// Return explicit Locale for supported ones; if not supported, null to follow device
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>> {
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
// even when loading the same image repeatedly for different adjustments/usages.
if (bytes.isEmpty) return '';
final id = DateTime.now().microsecondsSinceEpoch.toString();
state = List.of(state)
..add(SignatureAsset(id: id, bytes: bytes, name: name));
return id;
if (bytes.isEmpty) return;
state = List.of(state)..add(SignatureAsset(bytes: bytes, name: name));
}
void remove(String id) {
state = state.where((a) => a.id != id).toList(growable: false);
}
SignatureAsset? byId(String id) {
for (final a in state) {
if (a.id == id) return a;
}
return null;
void remove(SignatureAsset asset) {
state = state.where((a) => a != asset).toList(growable: false);
}
}

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:
/// - [inputPath]: Path to the original PDF to read
/// - [outputPath]: Path to write the composed PDF
/// - [signedPage]: 1-based page index to place the signature on (null = no overlay)
/// - [signatureRectUi]: Rect in the UI's logical page space (e.g. 400x560)
/// - [uiPageSize]: The logical page size used by the UI layout (SignatureController.pageSize)
/// - [uiPageSize]: The logical page size used by the UI layout (SignatureCardStateNotifier.pageSize)
/// - [signatureImageBytes]: PNG/JPEG bytes of the signature image to overlay
/// - [targetDpi]: Rasterization DPI for background pages
Future<bool> exportSignedPdfFromFile({
required String inputPath,
required String outputPath,
required int? signedPage,
required Rect? signatureRectUi,
required Size uiPageSize,
required Uint8List? signatureImageBytes,
Map<int, List<SignaturePlacement>>? placementsByPage,
Map<String, Uint8List>? libraryBytes,
double targetDpi = 144.0,
}) async {
// print(
// 'exportSignedPdfFromFile: enter signedPage=$signedPage outputPath=$outputPath',
// );
// Read source bytes and delegate to bytes-based exporter
Uint8List? srcBytes;
try {
@ -50,8 +43,6 @@ class ExportService {
if (srcBytes == null) return false;
final bytes = await exportSignedPdfFromBytes(
srcBytes: srcBytes,
signedPage: signedPage,
signatureRectUi: signatureRectUi,
uiPageSize: uiPageSize,
signatureImageBytes: signatureImageBytes,
placementsByPage: placementsByPage,
@ -71,13 +62,11 @@ class ExportService {
/// Compose a new PDF from source PDF bytes; returns the resulting PDF bytes.
Future<Uint8List?> exportSignedPdfFromBytes({
required Uint8List srcBytes,
required int? signedPage,
required Rect? signatureRectUi,
required Size uiPageSize,
required Uint8List? signatureImageBytes,
Map<int, List<SignaturePlacement>>? placementsByPage,
Map<String, Uint8List>? libraryBytes,
double targetDpi = 144.0,
double targetDpi = 144.0
}) async {
final out = pw.Document(version: pdf.PdfVersion.pdf_1_4, compress: false);
int pageIndex = 0;
@ -97,27 +86,13 @@ class ExportService {
final bgPng = await raster.toPng();
final bgImg = pw.MemoryImage(bgPng);
pw.MemoryImage? sigImgObj;
final hasMulti =
(placementsByPage != null && placementsByPage.isNotEmpty);
final pagePlacements =
hasMulti
? (placementsByPage[pageIndex] ?? 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(
pw.Page(
@ -148,10 +123,7 @@ class ExportService {
final w = r.width / uiPageSize.width * widthPts;
final h = r.height / uiPageSize.height * heightPts;
Uint8List? bytes;
final id = placement.asset.id;
if (id.isNotEmpty) {
bytes = libraryBytes?[id];
}
bytes ??= signatureImageBytes; // fallback
if (bytes != null && bytes.isNotEmpty) {
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);
},
@ -218,39 +170,14 @@ class ExportService {
// Fallback as A4 blank page with optional signature
final widthPts = pdf.PdfPageFormat.a4.width;
final heightPts = pdf.PdfPageFormat.a4.height;
pw.MemoryImage? sigImgObj;
final hasMulti =
(placementsByPage != null && placementsByPage.isNotEmpty);
final pagePlacements =
hasMulti
? (placementsByPage[1] ?? 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(
pw.Page(
pageTheme: pw.PageTheme(
@ -275,10 +202,7 @@ class ExportService {
final w = r.width / uiPageSize.width * widthPts;
final h = r.height / uiPageSize.height * heightPts;
Uint8List? bytes;
final id = placement.asset.id;
if (id.isNotEmpty) {
bytes = libraryBytes?[id];
}
bytes ??= signatureImageBytes; // fallback
if (bytes != null && bytes.isNotEmpty) {
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);
},

View File

@ -1,3 +1,4 @@
/// TODO: remove this file and export models directly from their files.
export 'signature_asset.dart';
export 'graphic_adjust.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
class SignatureAsset {
final String id; // unique id
final Uint8List bytes;
// List<List<Offset>>? strokes;
final String? name; // optional display name (e.g., filename)
const SignatureAsset({required this.id, required this.bytes, this.name});
const SignatureAsset({required this.bytes, this.name});
}

View File

@ -12,8 +12,8 @@ class SignatureCard {
final GraphicAdjust graphicAdjust;
const SignatureCard({
required this.rotationDeg,
required this.asset,
required this.rotationDeg,
this.graphicAdjust = const GraphicAdjust(),
});
@ -28,8 +28,8 @@ class SignatureCard {
);
factory SignatureCard.initial() => SignatureCard(
asset: SignatureAsset(bytes: Uint8List(0)),
rotationDeg: 0.0,
asset: SignatureAsset(id: '', bytes: Uint8List(0)),
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 '../../../../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 {
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: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 '../../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 '../../../../data/services/export_providers.dart';
import 'package:pdf_signature/data/repositories/signature_repository.dart';
import 'package:pdf_signature/data/repositories/pdf_repository.dart';
import 'package:pdf_signature/data/repositories/signature_card_repository.dart';
import 'package:pdf_signature/data/repositories/document_repository.dart';
import '../../signature/widgets/signature_drag_data.dart';
import 'pdf_mock_continuous_list.dart';
import 'pdf_page_overlays.dart';

View File

@ -1,9 +1,9 @@
import 'package:flutter/material.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 'package:pdf_signature/data/repositories/pdf_repository.dart';
import 'package:pdf_signature/data/repositories/document_repository.dart';
import 'signature_overlay.dart';
/// Builds all overlays for a given page: placed signatures and the active one.
@ -36,7 +36,7 @@ class PdfPageOverlays extends ConsumerWidget {
final widgets = <Widget>[];
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;
widgets.add(
SignatureOverlay(

View File

@ -3,7 +3,7 @@ import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:pdfrx/pdfrx.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 {
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/material.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:printing/printing.dart' as printing;
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 'package:image/image.dart' as img;
import 'package:pdf_signature/data/repositories/signature_repository.dart';
import 'package:pdf_signature/data/repositories/pdf_repository.dart';
import 'package:pdf_signature/data/repositories/signature_card_repository.dart';
import 'package:pdf_signature/data/repositories/document_repository.dart';
import 'package:pdf_signature/data/repositories/signature_asset_repository.dart';
import 'draw_canvas.dart';
import 'pdf_toolbar.dart';
@ -28,7 +30,7 @@ class PdfSignatureHomePage extends ConsumerStatefulWidget {
}
class _PdfSignatureHomePageState extends ConsumerState<PdfSignatureHomePage> {
static const Size _pageSize = SignatureController.pageSize;
static const Size _pageSize = SignatureCardStateNotifier.pageSize;
final PdfViewerController _viewerController = PdfViewerController();
bool _showPagesSidebar = true;
bool _showSignaturesSidebar = true;
@ -142,7 +144,11 @@ class _PdfSignatureHomePageState extends ConsumerState<PdfSignatureHomePage> {
return;
}
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);
bool ok = false;
String? savedPath;
@ -177,7 +183,7 @@ class _PdfSignatureHomePageState extends ConsumerState<PdfSignatureHomePage> {
srcBytes: src,
signedPage: pdf.signedPage,
signatureRectUi: sig.rect,
uiPageSize: SignatureController.pageSize,
uiPageSize: SignatureCardStateNotifier.pageSize,
signatureImageBytes: rotated,
placementsByPage: pdf.placementsByPage,
libraryBytes: {
@ -214,7 +220,7 @@ class _PdfSignatureHomePageState extends ConsumerState<PdfSignatureHomePage> {
srcBytes: pdf.pickedPdfBytes!,
signedPage: pdf.signedPage,
signatureRectUi: sig.rect,
uiPageSize: SignatureController.pageSize,
uiPageSize: SignatureCardStateNotifier.pageSize,
signatureImageBytes: rotated,
placementsByPage: pdf.placementsByPage,
libraryBytes: {
@ -245,7 +251,7 @@ class _PdfSignatureHomePageState extends ConsumerState<PdfSignatureHomePage> {
outputPath: fullPath,
signedPage: pdf.signedPage,
signatureRectUi: sig.rect,
uiPageSize: SignatureController.pageSize,
uiPageSize: SignatureCardStateNotifier.pageSize,
signatureImageBytes: rotated,
placementsByPage: pdf.placementsByPage,
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: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 {
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 '../../../../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 'image_editor_dialog.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 '../../../../domain/models/model.dart';
import 'package:pdf_signature/data/repositories/signature_repository.dart';
import 'package:pdf_signature/data/repositories/pdf_repository.dart';
import 'package:pdf_signature/data/repositories/signature_card_repository.dart';
import 'package:pdf_signature/data/repositories/document_repository.dart';
import 'package:pdf_signature/data/repositories/signature_asset_repository.dart';
import 'image_editor_dialog.dart';
import '../../signature/widgets/rotated_signature_image.dart';

View File

@ -1,7 +1,7 @@
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.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 {
const SettingsDialog({super.key});
@ -19,7 +19,7 @@ class _SettingsDialogState extends ConsumerState<SettingsDialog> {
@override
void initState() {
super.initState();
final prefs = ref.read(preferencesProvider);
final prefs = ref.read(preferencesRepositoryProvider);
_theme = prefs.theme;
_language = prefs.language;
_exportDpi = prefs.exportDpi;
@ -186,7 +186,9 @@ class _SettingsDialogState extends ConsumerState<SettingsDialog> {
const SizedBox(width: 8),
FilledButton(
onPressed: () async {
final n = ref.read(preferencesProvider.notifier);
final n = ref.read(
preferencesRepositoryProvider.notifier,
);
if (_theme != null) await n.setTheme(_theme!);
if (_language != null) await n.setLanguage(_language!);
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:pdf_signature/l10n/app_localizations.dart';
import 'package:pdf_signature/data/repositories/signature_repository.dart';
import 'package:pdf_signature/data/repositories/pdf_repository.dart';
import 'package:pdf_signature/data/repositories/signature_card_repository.dart';
import 'package:pdf_signature/data/repositories/document_repository.dart';
// Settings dialog is provided via global AppBar in MyApp
// 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_test/flutter_test.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:pdf_signature/data/repositories/pdf_repository.dart';
import 'package:pdf_signature/data/repositories/document_repository.dart';
import 'package:pdf_signature/domain/models/model.dart';
import '_world.dart';

View File

@ -2,7 +2,7 @@ import 'dart:typed_data';
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:pdf_signature/data/repositories/pdf_repository.dart';
import 'package:pdf_signature/data/repositories/document_repository.dart';
import 'package:pdf_signature/domain/models/model.dart';
import '_world.dart';

View File

@ -1,6 +1,6 @@
import 'package:flutter_test/flutter_test.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:pdf_signature/data/repositories/pdf_repository.dart';
import 'package:pdf_signature/data/repositories/document_repository.dart';
import '_world.dart';
/// 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_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';
/// Usage: a document page is selected for signing

View File

@ -1,6 +1,6 @@
import 'package:flutter_test/flutter_test.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';
/// Usage: a drawn signature exists in the canvas

View File

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

View File

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

View File

@ -2,8 +2,8 @@ import 'dart:typed_data';
import 'package:flutter_test/flutter_test.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:pdf_signature/data/repositories/signature_asset_repository.dart';
import 'package:pdf_signature/data/repositories/pdf_repository.dart';
import 'package:pdf_signature/data/repositories/signature_repository.dart';
import 'package:pdf_signature/data/repositories/document_repository.dart';
import 'package:pdf_signature/data/repositories/signature_card_repository.dart';
import 'package:pdf_signature/domain/models/model.dart';
import '_world.dart';

View File

@ -2,7 +2,7 @@ import 'dart:typed_data';
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:pdf_signature/data/repositories/pdf_repository.dart';
import 'package:pdf_signature/data/repositories/document_repository.dart';
import 'package:pdf_signature/data/repositories/signature_asset_repository.dart';
import 'package:pdf_signature/domain/models/model.dart';
import '_world.dart';

View File

@ -2,8 +2,8 @@ import 'dart:typed_data';
import 'package:flutter_test/flutter_test.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:pdf_signature/data/repositories/signature_asset_repository.dart';
import 'package:pdf_signature/data/repositories/pdf_repository.dart';
import 'package:pdf_signature/data/repositories/signature_repository.dart';
import 'package:pdf_signature/data/repositories/document_repository.dart';
import 'package:pdf_signature/data/repositories/signature_card_repository.dart';
import 'package:pdf_signature/domain/models/model.dart';
import '_world.dart';

View File

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

View File

@ -2,7 +2,7 @@ import 'dart:typed_data';
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:pdf_signature/data/repositories/pdf_repository.dart';
import 'package:pdf_signature/data/repositories/document_repository.dart';
import 'package:pdf_signature/domain/models/model.dart';
import '_world.dart';

View File

@ -1,6 +1,6 @@
import 'package:flutter_test/flutter_test.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:pdf_signature/data/repositories/pdf_repository.dart';
import 'package:pdf_signature/data/repositories/document_repository.dart';
import 'package:pdf_signature/domain/models/model.dart';
import '_world.dart';

View File

@ -1,5 +1,5 @@
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';
/// 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_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';
/// 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_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';
/// Usage: an empty signature canvas

View File

@ -1,6 +1,6 @@
import 'package:flutter_test/flutter_test.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:pdf_signature/data/repositories/pdf_repository.dart';
import 'package:pdf_signature/data/repositories/document_repository.dart';
import '_world.dart';
/// Usage: both signature placements are shown on their respective pages

View File

@ -1,7 +1,7 @@
import 'dart:ui';
import 'package:flutter_test/flutter_test.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:pdf_signature/data/repositories/pdf_repository.dart';
import 'package:pdf_signature/data/repositories/document_repository.dart';
import 'package:pdf_signature/domain/models/model.dart';
import '_world.dart';

View File

@ -1,6 +1,6 @@
import 'package:flutter_test/flutter_test.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:pdf_signature/data/repositories/pdf_repository.dart';
import 'package:pdf_signature/data/repositories/document_repository.dart';
import '_world.dart';
/// 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_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';
/// Usage: identical signature instances appear in each location

View File

@ -1,6 +1,6 @@
import 'package:flutter_test/flutter_test.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:pdf_signature/data/repositories/pdf_repository.dart';
import 'package:pdf_signature/data/repositories/document_repository.dart';
import '_world.dart';
/// Usage: identical signature placements appear in each location

View File

@ -1,6 +1,6 @@
import 'package:flutter_test/flutter_test.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';
/// 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_riverpod/flutter_riverpod.dart';
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';
/// 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_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';
/// Usage: only the selected signature placement is removed

View File

@ -1,6 +1,6 @@
import 'package:flutter_test/flutter_test.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:pdf_signature/data/repositories/pdf_repository.dart';
import 'package:pdf_signature/data/repositories/document_repository.dart';
import '_world.dart';
/// 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_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';
/// Usage: page {1} is displayed

View File

@ -1,6 +1,6 @@
import 'package:flutter_test/flutter_test.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:pdf_signature/data/repositories/pdf_repository.dart';
import 'package:pdf_signature/data/repositories/document_repository.dart';
import '_world.dart';
/// Usage: resize to fit within bounding box

View File

@ -1,6 +1,6 @@
import 'package:flutter_test/flutter_test.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:pdf_signature/data/repositories/pdf_repository.dart';
import 'package:pdf_signature/data/repositories/document_repository.dart';
import '_world.dart';
/// Usage: signature placement occurs on the selected page

View File

@ -1,6 +1,6 @@
import 'package:flutter_test/flutter_test.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';
/// Usage: the canvas becomes blank

View File

@ -1,6 +1,6 @@
import 'package:flutter_test/flutter_test.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:pdf_signature/data/repositories/pdf_repository.dart';
import 'package:pdf_signature/data/repositories/document_repository.dart';
import '_world.dart';
/// Usage: the document is open

View File

@ -1,6 +1,6 @@
import 'package:flutter_test/flutter_test.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:pdf_signature/data/repositories/pdf_repository.dart';
import 'package:pdf_signature/data/repositories/document_repository.dart';
import '_world.dart';
/// Usage: the first page is displayed

View File

@ -1,6 +1,6 @@
import 'package:flutter_test/flutter_test.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:pdf_signature/data/repositories/pdf_repository.dart';
import 'package:pdf_signature/data/repositories/document_repository.dart';
import '_world.dart';
/// Usage: the Go to input cannot be used

View File

@ -1,6 +1,6 @@
import 'package:flutter_test/flutter_test.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:pdf_signature/data/repositories/pdf_repository.dart';
import 'package:pdf_signature/data/repositories/document_repository.dart';
import '_world.dart';
/// Usage: the last page is displayed (page {5})

View File

@ -1,6 +1,6 @@
import 'package:flutter_test/flutter_test.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';
/// Usage: the last stroke is removed

View File

@ -1,6 +1,6 @@
import 'package:flutter_test/flutter_test.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:pdf_signature/data/repositories/pdf_repository.dart';
import 'package:pdf_signature/data/repositories/document_repository.dart';
import '_world.dart';
/// Usage: the left pages overview highlights page {5}

View File

@ -1,5 +1,5 @@
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';
/// Usage: the other signature placements remain unchanged

View File

@ -1,6 +1,6 @@
import 'package:flutter_test/flutter_test.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:pdf_signature/data/repositories/pdf_repository.dart';
import 'package:pdf_signature/data/repositories/document_repository.dart';
import '_world.dart';
/// 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_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';
/// Usage: the preview updates immediately

View File

@ -1,6 +1,6 @@
import 'package:flutter_test/flutter_test.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:pdf_signature/data/repositories/pdf_repository.dart';
import 'package:pdf_signature/data/repositories/document_repository.dart';
import '_world.dart';
/// 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_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';
/// 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_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';
/// Usage: the signature placement on page {2} remains

View File

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

View File

@ -1,6 +1,6 @@
import 'package:flutter_test/flutter_test.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';
/// Usage: the user can apply or reset adjustments

View File

@ -1,6 +1,6 @@
import 'package:flutter_test/flutter_test.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:pdf_signature/data/repositories/pdf_repository.dart';
import 'package:pdf_signature/data/repositories/document_repository.dart';
import '_world.dart';
/// 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_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';
/// Usage: the user changes contrast and brightness controls

View File

@ -1,6 +1,6 @@
import 'package:flutter_test/flutter_test.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';
/// Usage: the user chooses undo

View File

@ -1,6 +1,6 @@
import 'package:flutter_test/flutter_test.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';
/// Usage: the user clears the canvas

View File

@ -1,6 +1,6 @@
import 'package:flutter_test/flutter_test.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:pdf_signature/data/repositories/pdf_repository.dart';
import 'package:pdf_signature/data/repositories/document_repository.dart';
import '_world.dart';
/// 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_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';
/// 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_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';
/// Usage: the user deletes one selected signature placement

View File

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

View File

@ -2,7 +2,7 @@ import 'dart:typed_data';
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:pdf_signature/data/repositories/pdf_repository.dart';
import 'package:pdf_signature/data/repositories/document_repository.dart';
import 'package:pdf_signature/data/repositories/signature_asset_repository.dart';
import 'package:pdf_signature/domain/models/model.dart';
import '_world.dart';

View File

@ -1,7 +1,7 @@
import 'dart:typed_data';
import 'package:flutter_test/flutter_test.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:pdf_signature/data/repositories/signature_repository.dart';
import 'package:pdf_signature/data/repositories/signature_card_repository.dart';
import '_world.dart';
/// Usage: the user draws strokes and confirms

View File

@ -1,6 +1,6 @@
import 'package:flutter_test/flutter_test.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';
/// Usage: the user enables background removal

View File

@ -1,6 +1,6 @@
import 'package:flutter_test/flutter_test.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:pdf_signature/data/repositories/pdf_repository.dart';
import 'package:pdf_signature/data/repositories/document_repository.dart';
import '_world.dart';
/// 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_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';
/// Usage: the user is notified of the issue

View File

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

View File

@ -2,7 +2,7 @@ import 'dart:typed_data';
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:pdf_signature/data/repositories/pdf_repository.dart';
import 'package:pdf_signature/data/repositories/document_repository.dart';
import 'package:pdf_signature/data/repositories/signature_asset_repository.dart';
import 'package:pdf_signature/domain/models/model.dart';
import '_world.dart';

View File

@ -2,7 +2,7 @@ import 'dart:typed_data';
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:pdf_signature/data/repositories/pdf_repository.dart';
import 'package:pdf_signature/data/repositories/document_repository.dart';
import 'package:pdf_signature/domain/models/model.dart';
import '_world.dart';

View File

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

View File

@ -1,8 +1,8 @@
import 'dart:typed_data';
import 'package:flutter_test/flutter_test.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:pdf_signature/data/repositories/signature_repository.dart';
import 'package:pdf_signature/data/repositories/pdf_repository.dart';
import 'package:pdf_signature/data/repositories/signature_card_repository.dart';
import 'package:pdf_signature/data/repositories/document_repository.dart';
import '_world.dart';
/// Usage: the user saves/exports the document

View File

@ -1,6 +1,6 @@
import 'package:flutter_test/flutter_test.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:pdf_signature/data/repositories/pdf_repository.dart';
import 'package:pdf_signature/data/repositories/document_repository.dart';
import '_world.dart';
/// Usage: the user selects "<file>"

View File

@ -1,6 +1,6 @@
import 'package:flutter_test/flutter_test.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:pdf_signature/data/repositories/pdf_repository.dart';
import 'package:pdf_signature/data/repositories/document_repository.dart';
import '_world.dart';
/// 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_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';
/// Usage: the user uses rotate controls

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