feat: new implement of `/lib/data/repositories/`
This commit is contained in:
parent
948999fe8e
commit
e9cf4c30c1
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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),
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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 =
|
|
@ -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)) {
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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(),
|
||||
);
|
|
@ -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);
|
||||
});
|
|
@ -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';
|
||||
};
|
||||
});
|
|
@ -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);
|
||||
},
|
||||
|
|
|
@ -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';
|
||||
|
|
|
@ -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,
|
||||
);
|
||||
}
|
|
@ -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});
|
||||
}
|
||||
|
|
|
@ -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(),
|
||||
);
|
||||
}
|
||||
|
|
|
@ -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});
|
||||
|
|
|
@ -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';
|
||||
|
||||
|
|
|
@ -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';
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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});
|
||||
|
|
|
@ -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) {}
|
||||
}
|
||||
|
|
|
@ -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({
|
||||
|
|
|
@ -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';
|
||||
|
|
|
@ -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';
|
||||
|
|
|
@ -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!);
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
},
|
||||
);
|
||||
}
|
|
@ -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';
|
||||
|
||||
|
|
|
@ -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';
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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';
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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';
|
||||
|
|
|
@ -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';
|
||||
|
||||
|
|
|
@ -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';
|
||||
|
|
|
@ -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';
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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';
|
||||
|
||||
|
|
|
@ -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';
|
||||
|
||||
|
|
|
@ -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';
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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';
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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})
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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}"
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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';
|
||||
|
|
|
@ -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';
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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}
|
||||
|
|
|
@ -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';
|
||||
|
||||
|
|
|
@ -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';
|
||||
|
|
|
@ -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';
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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';
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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>"
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
Loading…
Reference in New Issue