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)
|
* read [`FRs.md`](docs/FRs.md)
|
||||||
* If want to modify code (implement or test) of `ViewModel`, `View` of MVVM (UI widget) (files at `lib/ui/features/*/widgets/*`)
|
* If want to modify code (implement or test) of `ViewModel`, `View` of MVVM (UI widget) (files at `lib/ui/features/*/widgets/*`)
|
||||||
* read [`wireframe.md`](docs/wireframe.md), [`NFRs.md`](docs/NFRs.md), `test/features/*.feature`
|
* read [`wireframe.md`](docs/wireframe.md), [`NFRs.md`](docs/NFRs.md), `test/features/*.feature`
|
||||||
* If want to modify code (implement or test) of non-View e.g. `Model`, services...
|
* If want to modify code (implement or test) of non-View e.g. `Model`, repositories, services...
|
||||||
* read `test/features/*.feature`, [`NFRs.md`](docs/NFRs.md)
|
* read `test/features/*.feature`, [`NFRs.md`](docs/NFRs.md)
|
||||||
|
|
|
@ -60,7 +60,9 @@ But with slight modifications.
|
||||||
* `integration_test/` for integration tests. They should be volatile to follow UI layout changes.
|
* `integration_test/` for integration tests. They should be volatile to follow UI layout changes.
|
||||||
|
|
||||||
Some rule of thumb:
|
Some rule of thumb:
|
||||||
* `<object>Provider` only placed at `/lib/data/repositories/` or `/lib/data/services/` to provide data source.
|
* global provider
|
||||||
|
* `<object>RepositoryProvider` only placed in `/lib/data/repositories/`, provide data to `/lib/ui`.
|
||||||
|
* `lib/data/services/*` should be stateless, and should only accessible by `Repository`.
|
||||||
|
|
||||||
## Abstraction
|
## Abstraction
|
||||||
|
|
||||||
|
|
|
@ -8,8 +8,8 @@ import 'package:image/image.dart' as img;
|
||||||
import 'package:pdf_signature/data/services/export_service.dart';
|
import 'package:pdf_signature/data/services/export_service.dart';
|
||||||
import 'package:pdf_signature/data/services/export_providers.dart';
|
import 'package:pdf_signature/data/services/export_providers.dart';
|
||||||
import 'package:pdf_signature/data/repositories/signature_asset_repository.dart';
|
import 'package:pdf_signature/data/repositories/signature_asset_repository.dart';
|
||||||
import 'package:pdf_signature/data/repositories/signature_repository.dart';
|
import 'package:pdf_signature/data/repositories/signature_card_repository.dart';
|
||||||
import 'package:pdf_signature/data/repositories/pdf_repository.dart';
|
import 'package:pdf_signature/data/repositories/document_repository.dart';
|
||||||
import 'package:pdf_signature/ui/features/pdf/widgets/pdf_screen.dart';
|
import 'package:pdf_signature/ui/features/pdf/widgets/pdf_screen.dart';
|
||||||
import 'package:pdf_signature/l10n/app_localizations.dart';
|
import 'package:pdf_signature/l10n/app_localizations.dart';
|
||||||
|
|
||||||
|
@ -36,7 +36,7 @@ void main() {
|
||||||
(ref) => DocumentStateNotifier()..openPicked(path: 'test.pdf'),
|
(ref) => DocumentStateNotifier()..openPicked(path: 'test.pdf'),
|
||||||
),
|
),
|
||||||
signatureProvider.overrideWith(
|
signatureProvider.overrideWith(
|
||||||
(ref) => SignatureController()..placeDefaultRect(),
|
(ref) => SignatureCardStateNotifier()..placeDefaultRect(),
|
||||||
),
|
),
|
||||||
useMockViewerProvider.overrideWith((ref) => true),
|
useMockViewerProvider.overrideWith((ref) => true),
|
||||||
exportServiceProvider.overrideWith((_) => fake),
|
exportServiceProvider.overrideWith((_) => fake),
|
||||||
|
|
|
@ -3,9 +3,9 @@ import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
import 'package:flutter_localized_locales/flutter_localized_locales.dart';
|
import 'package:flutter_localized_locales/flutter_localized_locales.dart';
|
||||||
import 'package:pdf_signature/l10n/app_localizations.dart';
|
import 'package:pdf_signature/l10n/app_localizations.dart';
|
||||||
import 'package:pdf_signature/ui/features/pdf/widgets/pdf_screen.dart';
|
import 'package:pdf_signature/ui/features/pdf/widgets/pdf_screen.dart';
|
||||||
import 'package:pdf_signature/data/repositories/pdf_repository.dart';
|
import 'package:pdf_signature/data/repositories/document_repository.dart';
|
||||||
import 'package:pdf_signature/ui/features/welcome/widgets/welcome_screen.dart';
|
import 'package:pdf_signature/ui/features/welcome/widgets/welcome_screen.dart';
|
||||||
import 'data/services/preferences_providers.dart';
|
import 'data/repositories/preferences_repository.dart';
|
||||||
import 'package:pdf_signature/ui/features/preferences/widgets/settings_screen.dart';
|
import 'package:pdf_signature/ui/features/preferences/widgets/settings_screen.dart';
|
||||||
|
|
||||||
class MyApp extends StatelessWidget {
|
class MyApp extends StatelessWidget {
|
||||||
|
|
|
@ -1,12 +1,15 @@
|
||||||
import 'dart:typed_data';
|
import 'dart:typed_data';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
|
import 'package:pdf_signature/data/services/export_service.dart';
|
||||||
|
|
||||||
import '../../domain/models/model.dart';
|
import '../../domain/models/model.dart';
|
||||||
|
|
||||||
class DocumentStateNotifier extends StateNotifier<Document> {
|
class DocumentStateNotifier extends StateNotifier<Document> {
|
||||||
DocumentStateNotifier() : super(Document.initial());
|
DocumentStateNotifier() : super(Document.initial());
|
||||||
|
|
||||||
|
final ExportService _service = ExportService();
|
||||||
|
|
||||||
@visibleForTesting
|
@visibleForTesting
|
||||||
void openSample() {
|
void openSample() {
|
||||||
state = state.copyWith(
|
state = state.copyWith(
|
||||||
|
@ -57,7 +60,7 @@ class DocumentStateNotifier extends StateNotifier<Document> {
|
||||||
list.add(
|
list.add(
|
||||||
SignaturePlacement(
|
SignaturePlacement(
|
||||||
rect: rect,
|
rect: rect,
|
||||||
asset: asset ?? SignatureAsset(id: '', bytes: Uint8List(0)),
|
asset: asset ?? SignatureAsset(bytes: Uint8List(0)),
|
||||||
rotationDeg: rotationDeg,
|
rotationDeg: rotationDeg,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
@ -121,14 +124,29 @@ class DocumentStateNotifier extends StateNotifier<Document> {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// NOTE: Programmatic reassignment of images has been removed.
|
|
||||||
|
|
||||||
// Convenience to get asset for a placement
|
// Convenience to get asset for a placement
|
||||||
SignatureAsset? assetOfPlacement({required int page, required int index}) {
|
SignatureAsset? assetOfPlacement({required int page, required int index}) {
|
||||||
final list = state.placementsByPage[page] ?? const [];
|
final list = state.placementsByPage[page] ?? const [];
|
||||||
if (index < 0 || index >= list.length) return null;
|
if (index < 0 || index >= list.length) return null;
|
||||||
return list[index].asset;
|
return list[index].asset;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<void> exportDocument({
|
||||||
|
required String outputPath,
|
||||||
|
required Size uiPageSize,
|
||||||
|
required Uint8List? signatureImageBytes,
|
||||||
|
}) async {
|
||||||
|
if (!state.loaded || state.pickedPdfBytes == null) return;
|
||||||
|
final bytes = await _service.exportSignedPdfFromBytes(
|
||||||
|
srcBytes: state.pickedPdfBytes!,
|
||||||
|
uiPageSize: uiPageSize,
|
||||||
|
signatureImageBytes: signatureImageBytes,
|
||||||
|
placementsByPage: state.placementsByPage,
|
||||||
|
);
|
||||||
|
if (bytes == null) return;
|
||||||
|
_service.saveBytesToFile(bytes: bytes, outputPath: outputPath);
|
||||||
|
// await
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
final documentRepositoryProvider =
|
final documentRepositoryProvider =
|
|
@ -3,6 +3,7 @@ import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
import 'package:shared_preferences/shared_preferences.dart';
|
import 'package:shared_preferences/shared_preferences.dart';
|
||||||
import 'package:pdf_signature/l10n/app_localizations.dart';
|
import 'package:pdf_signature/l10n/app_localizations.dart';
|
||||||
import 'package:flutter_localized_locales/flutter_localized_locales.dart';
|
import 'package:flutter_localized_locales/flutter_localized_locales.dart';
|
||||||
|
import 'package:pdf_signature/domain/models/preferences.dart';
|
||||||
|
|
||||||
// Helpers to work with BCP-47 language tags
|
// Helpers to work with BCP-47 language tags
|
||||||
String toLanguageTag(Locale loc) {
|
String toLanguageTag(Locale loc) {
|
||||||
|
@ -27,6 +28,7 @@ Set<String> _supportedTags() {
|
||||||
|
|
||||||
// Keys
|
// Keys
|
||||||
const _kTheme = 'theme'; // 'light'|'dark'|'system'
|
const _kTheme = 'theme'; // 'light'|'dark'|'system'
|
||||||
|
const _kThemeColor = 'theme_color'; // 'blue'|'green'|'red'|'purple'
|
||||||
const _kLanguage = 'language'; // BCP-47 tag like 'en', 'zh-TW', 'es'
|
const _kLanguage = 'language'; // BCP-47 tag like 'en', 'zh-TW', 'es'
|
||||||
const _kPageView = 'page_view'; // now only 'continuous'
|
const _kPageView = 'page_view'; // now only 'continuous'
|
||||||
const _kExportDpi = 'export_dpi'; // double, allowed: 96,144,200,300
|
const _kExportDpi = 'export_dpi'; // double, allowed: 96,144,200,300
|
||||||
|
@ -63,34 +65,9 @@ String _normalizeLanguageTag(String tag) {
|
||||||
return tags.contains('en') ? 'en' : tags.first;
|
return tags.contains('en') ? 'en' : tags.first;
|
||||||
}
|
}
|
||||||
|
|
||||||
class PreferencesState {
|
class PreferencesStateNotifier extends StateNotifier<PreferencesState> {
|
||||||
final String theme; // 'light' | 'dark' | 'system'
|
|
||||||
final String language; // 'en' | 'zh-TW' | 'es'
|
|
||||||
final String pageView; // only 'continuous'
|
|
||||||
final double exportDpi; // 96.0 | 144.0 | 200.0 | 300.0
|
|
||||||
const PreferencesState({
|
|
||||||
required this.theme,
|
|
||||||
required this.language,
|
|
||||||
required this.pageView,
|
|
||||||
required this.exportDpi,
|
|
||||||
});
|
|
||||||
|
|
||||||
PreferencesState copyWith({
|
|
||||||
String? theme,
|
|
||||||
String? language,
|
|
||||||
String? pageView,
|
|
||||||
double? exportDpi,
|
|
||||||
}) => PreferencesState(
|
|
||||||
theme: theme ?? this.theme,
|
|
||||||
language: language ?? this.language,
|
|
||||||
pageView: pageView ?? this.pageView,
|
|
||||||
exportDpi: exportDpi ?? this.exportDpi,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
class PreferencesNotifier extends StateNotifier<PreferencesState> {
|
|
||||||
final SharedPreferences prefs;
|
final SharedPreferences prefs;
|
||||||
PreferencesNotifier(this.prefs)
|
PreferencesStateNotifier(this.prefs)
|
||||||
: super(
|
: super(
|
||||||
PreferencesState(
|
PreferencesState(
|
||||||
theme: prefs.getString(_kTheme) ?? 'system',
|
theme: prefs.getString(_kTheme) ?? 'system',
|
||||||
|
@ -99,8 +76,8 @@ class PreferencesNotifier extends StateNotifier<PreferencesState> {
|
||||||
WidgetsBinding.instance.platformDispatcher.locale
|
WidgetsBinding.instance.platformDispatcher.locale
|
||||||
.toLanguageTag(),
|
.toLanguageTag(),
|
||||||
),
|
),
|
||||||
pageView: prefs.getString(_kPageView) ?? 'continuous',
|
|
||||||
exportDpi: _readDpi(prefs),
|
exportDpi: _readDpi(prefs),
|
||||||
|
theme_color: prefs.getString(_kThemeColor) ?? 'blue',
|
||||||
),
|
),
|
||||||
) {
|
) {
|
||||||
// normalize language to supported/fallback
|
// normalize language to supported/fallback
|
||||||
|
@ -125,11 +102,6 @@ class PreferencesNotifier extends StateNotifier<PreferencesState> {
|
||||||
state = state.copyWith(language: normalized);
|
state = state.copyWith(language: normalized);
|
||||||
prefs.setString(_kLanguage, normalized);
|
prefs.setString(_kLanguage, normalized);
|
||||||
}
|
}
|
||||||
final pageViewValid = {'continuous'};
|
|
||||||
if (!pageViewValid.contains(state.pageView)) {
|
|
||||||
state = state.copyWith(pageView: 'continuous');
|
|
||||||
prefs.setString(_kPageView, 'continuous');
|
|
||||||
}
|
|
||||||
// Ensure DPI is one of allowed values
|
// Ensure DPI is one of allowed values
|
||||||
const allowed = [96.0, 144.0, 200.0, 300.0];
|
const allowed = [96.0, 144.0, 200.0, 300.0];
|
||||||
if (!allowed.contains(state.exportDpi)) {
|
if (!allowed.contains(state.exportDpi)) {
|
||||||
|
@ -158,8 +130,8 @@ class PreferencesNotifier extends StateNotifier<PreferencesState> {
|
||||||
state = PreferencesState(
|
state = PreferencesState(
|
||||||
theme: 'system',
|
theme: 'system',
|
||||||
language: normalized,
|
language: normalized,
|
||||||
pageView: 'continuous',
|
|
||||||
exportDpi: 144.0,
|
exportDpi: 144.0,
|
||||||
|
theme_color: '',
|
||||||
);
|
);
|
||||||
await prefs.setString(_kTheme, 'system');
|
await prefs.setString(_kTheme, 'system');
|
||||||
await prefs.setString(_kLanguage, normalized);
|
await prefs.setString(_kLanguage, normalized);
|
||||||
|
@ -167,13 +139,6 @@ class PreferencesNotifier extends StateNotifier<PreferencesState> {
|
||||||
await prefs.setDouble(_kExportDpi, 144.0);
|
await prefs.setDouble(_kExportDpi, 144.0);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> setPageView(String pageView) async {
|
|
||||||
final valid = {'continuous'};
|
|
||||||
if (!valid.contains(pageView)) return;
|
|
||||||
state = state.copyWith(pageView: pageView);
|
|
||||||
await prefs.setString(_kPageView, pageView);
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> setExportDpi(double dpi) async {
|
Future<void> setExportDpi(double dpi) async {
|
||||||
const allowed = [96.0, 144.0, 200.0, 300.0];
|
const allowed = [96.0, 144.0, 200.0, 300.0];
|
||||||
if (!allowed.contains(dpi)) return;
|
if (!allowed.contains(dpi)) return;
|
||||||
|
@ -189,8 +154,8 @@ final sharedPreferencesProvider = FutureProvider<SharedPreferences>((
|
||||||
return p;
|
return p;
|
||||||
});
|
});
|
||||||
|
|
||||||
final preferencesProvider =
|
final preferencesRepositoryProvider =
|
||||||
StateNotifierProvider<PreferencesNotifier, PreferencesState>((ref) {
|
StateNotifierProvider<PreferencesStateNotifier, PreferencesState>((ref) {
|
||||||
// In tests, you can override sharedPreferencesProvider
|
// In tests, you can override sharedPreferencesProvider
|
||||||
final prefs = ref
|
final prefs = ref
|
||||||
.watch(sharedPreferencesProvider)
|
.watch(sharedPreferencesProvider)
|
||||||
|
@ -198,14 +163,14 @@ final preferencesProvider =
|
||||||
data: (p) => p,
|
data: (p) => p,
|
||||||
orElse: () => throw StateError('SharedPreferences not ready'),
|
orElse: () => throw StateError('SharedPreferences not ready'),
|
||||||
);
|
);
|
||||||
return PreferencesNotifier(prefs);
|
return PreferencesStateNotifier(prefs);
|
||||||
});
|
});
|
||||||
|
|
||||||
// pageViewModeProvider removed; the app always runs in continuous mode.
|
// pageViewModeProvider removed; the app always runs in continuous mode.
|
||||||
|
|
||||||
/// Derive the active ThemeMode based on preference and platform brightness
|
/// Derive the active ThemeMode based on preference and platform brightness
|
||||||
final themeModeProvider = Provider<ThemeMode>((ref) {
|
final themeModeProvider = Provider<ThemeMode>((ref) {
|
||||||
final prefs = ref.watch(preferencesProvider);
|
final prefs = ref.watch(preferencesRepositoryProvider);
|
||||||
switch (prefs.theme) {
|
switch (prefs.theme) {
|
||||||
case 'light':
|
case 'light':
|
||||||
return ThemeMode.light;
|
return ThemeMode.light;
|
||||||
|
@ -218,7 +183,7 @@ final themeModeProvider = Provider<ThemeMode>((ref) {
|
||||||
});
|
});
|
||||||
|
|
||||||
final localeProvider = Provider<Locale?>((ref) {
|
final localeProvider = Provider<Locale?>((ref) {
|
||||||
final prefs = ref.watch(preferencesProvider);
|
final prefs = ref.watch(preferencesRepositoryProvider);
|
||||||
final supported = _supportedTags();
|
final supported = _supportedTags();
|
||||||
// Return explicit Locale for supported ones; if not supported, null to follow device
|
// Return explicit Locale for supported ones; if not supported, null to follow device
|
||||||
if (supported.contains(prefs.language)) {
|
if (supported.contains(prefs.language)) {
|
|
@ -6,25 +6,15 @@ import 'package:pdf_signature/domain/models/model.dart';
|
||||||
class SignatureAssetRepository extends StateNotifier<List<SignatureAsset>> {
|
class SignatureAssetRepository extends StateNotifier<List<SignatureAsset>> {
|
||||||
SignatureAssetRepository() : super(const []);
|
SignatureAssetRepository() : super(const []);
|
||||||
|
|
||||||
String add(Uint8List bytes, {String? name}) {
|
void add(Uint8List bytes, {String? name}) {
|
||||||
// Always add a new asset (allow duplicates). This lets users create multiple cards
|
// Always add a new asset (allow duplicates). This lets users create multiple cards
|
||||||
// even when loading the same image repeatedly for different adjustments/usages.
|
// even when loading the same image repeatedly for different adjustments/usages.
|
||||||
if (bytes.isEmpty) return '';
|
if (bytes.isEmpty) return;
|
||||||
final id = DateTime.now().microsecondsSinceEpoch.toString();
|
state = List.of(state)..add(SignatureAsset(bytes: bytes, name: name));
|
||||||
state = List.of(state)
|
|
||||||
..add(SignatureAsset(id: id, bytes: bytes, name: name));
|
|
||||||
return id;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void remove(String id) {
|
void remove(SignatureAsset asset) {
|
||||||
state = state.where((a) => a.id != id).toList(growable: false);
|
state = state.where((a) => a != asset).toList(growable: false);
|
||||||
}
|
|
||||||
|
|
||||||
SignatureAsset? byId(String id) {
|
|
||||||
for (final a in state) {
|
|
||||||
if (a.id == id) return a;
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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:
|
/// Inputs:
|
||||||
/// - [inputPath]: Path to the original PDF to read
|
/// - [inputPath]: Path to the original PDF to read
|
||||||
/// - [outputPath]: Path to write the composed PDF
|
/// - [outputPath]: Path to write the composed PDF
|
||||||
/// - [signedPage]: 1-based page index to place the signature on (null = no overlay)
|
/// - [uiPageSize]: The logical page size used by the UI layout (SignatureCardStateNotifier.pageSize)
|
||||||
/// - [signatureRectUi]: Rect in the UI's logical page space (e.g. 400x560)
|
|
||||||
/// - [uiPageSize]: The logical page size used by the UI layout (SignatureController.pageSize)
|
|
||||||
/// - [signatureImageBytes]: PNG/JPEG bytes of the signature image to overlay
|
/// - [signatureImageBytes]: PNG/JPEG bytes of the signature image to overlay
|
||||||
/// - [targetDpi]: Rasterization DPI for background pages
|
/// - [targetDpi]: Rasterization DPI for background pages
|
||||||
Future<bool> exportSignedPdfFromFile({
|
Future<bool> exportSignedPdfFromFile({
|
||||||
required String inputPath,
|
required String inputPath,
|
||||||
required String outputPath,
|
required String outputPath,
|
||||||
required int? signedPage,
|
|
||||||
required Rect? signatureRectUi,
|
|
||||||
required Size uiPageSize,
|
required Size uiPageSize,
|
||||||
required Uint8List? signatureImageBytes,
|
required Uint8List? signatureImageBytes,
|
||||||
Map<int, List<SignaturePlacement>>? placementsByPage,
|
Map<int, List<SignaturePlacement>>? placementsByPage,
|
||||||
Map<String, Uint8List>? libraryBytes,
|
Map<String, Uint8List>? libraryBytes,
|
||||||
double targetDpi = 144.0,
|
double targetDpi = 144.0,
|
||||||
}) async {
|
}) async {
|
||||||
// print(
|
|
||||||
// 'exportSignedPdfFromFile: enter signedPage=$signedPage outputPath=$outputPath',
|
|
||||||
// );
|
|
||||||
// Read source bytes and delegate to bytes-based exporter
|
// Read source bytes and delegate to bytes-based exporter
|
||||||
Uint8List? srcBytes;
|
Uint8List? srcBytes;
|
||||||
try {
|
try {
|
||||||
|
@ -50,8 +43,6 @@ class ExportService {
|
||||||
if (srcBytes == null) return false;
|
if (srcBytes == null) return false;
|
||||||
final bytes = await exportSignedPdfFromBytes(
|
final bytes = await exportSignedPdfFromBytes(
|
||||||
srcBytes: srcBytes,
|
srcBytes: srcBytes,
|
||||||
signedPage: signedPage,
|
|
||||||
signatureRectUi: signatureRectUi,
|
|
||||||
uiPageSize: uiPageSize,
|
uiPageSize: uiPageSize,
|
||||||
signatureImageBytes: signatureImageBytes,
|
signatureImageBytes: signatureImageBytes,
|
||||||
placementsByPage: placementsByPage,
|
placementsByPage: placementsByPage,
|
||||||
|
@ -71,13 +62,11 @@ class ExportService {
|
||||||
/// Compose a new PDF from source PDF bytes; returns the resulting PDF bytes.
|
/// Compose a new PDF from source PDF bytes; returns the resulting PDF bytes.
|
||||||
Future<Uint8List?> exportSignedPdfFromBytes({
|
Future<Uint8List?> exportSignedPdfFromBytes({
|
||||||
required Uint8List srcBytes,
|
required Uint8List srcBytes,
|
||||||
required int? signedPage,
|
|
||||||
required Rect? signatureRectUi,
|
|
||||||
required Size uiPageSize,
|
required Size uiPageSize,
|
||||||
required Uint8List? signatureImageBytes,
|
required Uint8List? signatureImageBytes,
|
||||||
Map<int, List<SignaturePlacement>>? placementsByPage,
|
Map<int, List<SignaturePlacement>>? placementsByPage,
|
||||||
Map<String, Uint8List>? libraryBytes,
|
Map<String, Uint8List>? libraryBytes,
|
||||||
double targetDpi = 144.0,
|
double targetDpi = 144.0
|
||||||
}) async {
|
}) async {
|
||||||
final out = pw.Document(version: pdf.PdfVersion.pdf_1_4, compress: false);
|
final out = pw.Document(version: pdf.PdfVersion.pdf_1_4, compress: false);
|
||||||
int pageIndex = 0;
|
int pageIndex = 0;
|
||||||
|
@ -97,27 +86,13 @@ class ExportService {
|
||||||
final bgPng = await raster.toPng();
|
final bgPng = await raster.toPng();
|
||||||
final bgImg = pw.MemoryImage(bgPng);
|
final bgImg = pw.MemoryImage(bgPng);
|
||||||
|
|
||||||
pw.MemoryImage? sigImgObj;
|
|
||||||
final hasMulti =
|
final hasMulti =
|
||||||
(placementsByPage != null && placementsByPage.isNotEmpty);
|
(placementsByPage != null && placementsByPage.isNotEmpty);
|
||||||
final pagePlacements =
|
final pagePlacements =
|
||||||
hasMulti
|
hasMulti
|
||||||
? (placementsByPage[pageIndex] ?? const <SignaturePlacement>[])
|
? (placementsByPage[pageIndex] ?? const <SignaturePlacement>[])
|
||||||
: const <SignaturePlacement>[];
|
: const <SignaturePlacement>[];
|
||||||
final shouldStampSingle =
|
|
||||||
!hasMulti &&
|
|
||||||
signedPage != null &&
|
|
||||||
pageIndex == signedPage &&
|
|
||||||
signatureRectUi != null &&
|
|
||||||
signatureImageBytes != null &&
|
|
||||||
signatureImageBytes.isNotEmpty;
|
|
||||||
if (shouldStampSingle) {
|
|
||||||
try {
|
|
||||||
sigImgObj = pw.MemoryImage(signatureImageBytes);
|
|
||||||
} catch (_) {
|
|
||||||
sigImgObj = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
out.addPage(
|
out.addPage(
|
||||||
pw.Page(
|
pw.Page(
|
||||||
|
@ -148,10 +123,7 @@ class ExportService {
|
||||||
final w = r.width / uiPageSize.width * widthPts;
|
final w = r.width / uiPageSize.width * widthPts;
|
||||||
final h = r.height / uiPageSize.height * heightPts;
|
final h = r.height / uiPageSize.height * heightPts;
|
||||||
Uint8List? bytes;
|
Uint8List? bytes;
|
||||||
final id = placement.asset.id;
|
|
||||||
if (id.isNotEmpty) {
|
|
||||||
bytes = libraryBytes?[id];
|
|
||||||
}
|
|
||||||
bytes ??= signatureImageBytes; // fallback
|
bytes ??= signatureImageBytes; // fallback
|
||||||
if (bytes != null && bytes.isNotEmpty) {
|
if (bytes != null && bytes.isNotEmpty) {
|
||||||
pw.MemoryImage? imgObj;
|
pw.MemoryImage? imgObj;
|
||||||
|
@ -184,26 +156,6 @@ class ExportService {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if (shouldStampSingle && sigImgObj != null) {
|
|
||||||
final r = signatureRectUi;
|
|
||||||
final left = r.left / uiPageSize.width * widthPts;
|
|
||||||
final top = r.top / uiPageSize.height * heightPts;
|
|
||||||
final w = r.width / uiPageSize.width * widthPts;
|
|
||||||
final h = r.height / uiPageSize.height * heightPts;
|
|
||||||
children.add(
|
|
||||||
pw.Positioned(
|
|
||||||
left: left,
|
|
||||||
top: top,
|
|
||||||
child: pw.SizedBox(
|
|
||||||
width: w,
|
|
||||||
height: h,
|
|
||||||
child: pw.FittedBox(
|
|
||||||
fit: pw.BoxFit.contain,
|
|
||||||
child: pw.Image(sigImgObj),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
return pw.Stack(children: children);
|
return pw.Stack(children: children);
|
||||||
},
|
},
|
||||||
|
@ -218,39 +170,14 @@ class ExportService {
|
||||||
// Fallback as A4 blank page with optional signature
|
// Fallback as A4 blank page with optional signature
|
||||||
final widthPts = pdf.PdfPageFormat.a4.width;
|
final widthPts = pdf.PdfPageFormat.a4.width;
|
||||||
final heightPts = pdf.PdfPageFormat.a4.height;
|
final heightPts = pdf.PdfPageFormat.a4.height;
|
||||||
pw.MemoryImage? sigImgObj;
|
|
||||||
final hasMulti =
|
final hasMulti =
|
||||||
(placementsByPage != null && placementsByPage.isNotEmpty);
|
(placementsByPage != null && placementsByPage.isNotEmpty);
|
||||||
final pagePlacements =
|
final pagePlacements =
|
||||||
hasMulti
|
hasMulti
|
||||||
? (placementsByPage[1] ?? const <SignaturePlacement>[])
|
? (placementsByPage[1] ?? const <SignaturePlacement>[])
|
||||||
: const <SignaturePlacement>[];
|
: const <SignaturePlacement>[];
|
||||||
final shouldStampSingle =
|
|
||||||
!hasMulti &&
|
|
||||||
signedPage != null &&
|
|
||||||
signedPage == 1 &&
|
|
||||||
signatureRectUi != null &&
|
|
||||||
signatureImageBytes != null &&
|
|
||||||
signatureImageBytes.isNotEmpty;
|
|
||||||
if (shouldStampSingle) {
|
|
||||||
try {
|
|
||||||
// If it's already PNG, keep as-is to preserve alpha; otherwise decode/encode PNG
|
|
||||||
final asStr = String.fromCharCodes(signatureImageBytes.take(8));
|
|
||||||
final isPng =
|
|
||||||
signatureImageBytes.length > 8 &&
|
|
||||||
signatureImageBytes[0] == 0x89 &&
|
|
||||||
asStr.startsWith('\u0089PNG');
|
|
||||||
if (isPng) {
|
|
||||||
sigImgObj = pw.MemoryImage(signatureImageBytes);
|
|
||||||
} else {
|
|
||||||
final decoded = img.decodeImage(signatureImageBytes);
|
|
||||||
if (decoded != null) {
|
|
||||||
final png = img.encodePng(decoded, level: 6);
|
|
||||||
sigImgObj = pw.MemoryImage(Uint8List.fromList(png));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (_) {}
|
|
||||||
}
|
|
||||||
out.addPage(
|
out.addPage(
|
||||||
pw.Page(
|
pw.Page(
|
||||||
pageTheme: pw.PageTheme(
|
pageTheme: pw.PageTheme(
|
||||||
|
@ -275,10 +202,7 @@ class ExportService {
|
||||||
final w = r.width / uiPageSize.width * widthPts;
|
final w = r.width / uiPageSize.width * widthPts;
|
||||||
final h = r.height / uiPageSize.height * heightPts;
|
final h = r.height / uiPageSize.height * heightPts;
|
||||||
Uint8List? bytes;
|
Uint8List? bytes;
|
||||||
final id = placement.asset.id;
|
|
||||||
if (id.isNotEmpty) {
|
|
||||||
bytes = libraryBytes?[id];
|
|
||||||
}
|
|
||||||
bytes ??= signatureImageBytes; // fallback
|
bytes ??= signatureImageBytes; // fallback
|
||||||
if (bytes != null && bytes.isNotEmpty) {
|
if (bytes != null && bytes.isNotEmpty) {
|
||||||
pw.MemoryImage? imgObj;
|
pw.MemoryImage? imgObj;
|
||||||
|
@ -323,26 +247,6 @@ class ExportService {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if (shouldStampSingle && sigImgObj != null) {
|
|
||||||
final r = signatureRectUi;
|
|
||||||
final left = r.left / uiPageSize.width * widthPts;
|
|
||||||
final top = r.top / uiPageSize.height * heightPts;
|
|
||||||
final w = r.width / uiPageSize.width * widthPts;
|
|
||||||
final h = r.height / uiPageSize.height * heightPts;
|
|
||||||
children.add(
|
|
||||||
pw.Positioned(
|
|
||||||
left: left,
|
|
||||||
top: top,
|
|
||||||
child: pw.SizedBox(
|
|
||||||
width: w,
|
|
||||||
height: h,
|
|
||||||
child: pw.FittedBox(
|
|
||||||
fit: pw.BoxFit.contain,
|
|
||||||
child: pw.Image(sigImgObj),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
return pw.Stack(children: children);
|
return pw.Stack(children: children);
|
||||||
},
|
},
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
/// TODO: remove this file and export models directly from their files.
|
||||||
export 'signature_asset.dart';
|
export 'signature_asset.dart';
|
||||||
export 'graphic_adjust.dart';
|
export 'graphic_adjust.dart';
|
||||||
export 'signature_card.dart';
|
export 'signature_card.dart';
|
||||||
|
|
|
@ -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
|
/// SignatureAsset store image file of a signature, stored in the device or cloud storage
|
||||||
class SignatureAsset {
|
class SignatureAsset {
|
||||||
final String id; // unique id
|
|
||||||
final Uint8List bytes;
|
final Uint8List bytes;
|
||||||
// List<List<Offset>>? strokes;
|
// List<List<Offset>>? strokes;
|
||||||
final String? name; // optional display name (e.g., filename)
|
final String? name; // optional display name (e.g., filename)
|
||||||
const SignatureAsset({required this.id, required this.bytes, this.name});
|
const SignatureAsset({required this.bytes, this.name});
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,8 +12,8 @@ class SignatureCard {
|
||||||
final GraphicAdjust graphicAdjust;
|
final GraphicAdjust graphicAdjust;
|
||||||
|
|
||||||
const SignatureCard({
|
const SignatureCard({
|
||||||
required this.rotationDeg,
|
|
||||||
required this.asset,
|
required this.asset,
|
||||||
|
required this.rotationDeg,
|
||||||
this.graphicAdjust = const GraphicAdjust(),
|
this.graphicAdjust = const GraphicAdjust(),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -28,8 +28,8 @@ class SignatureCard {
|
||||||
);
|
);
|
||||||
|
|
||||||
factory SignatureCard.initial() => SignatureCard(
|
factory SignatureCard.initial() => SignatureCard(
|
||||||
|
asset: SignatureAsset(bytes: Uint8List(0)),
|
||||||
rotationDeg: 0.0,
|
rotationDeg: 0.0,
|
||||||
asset: SignatureAsset(id: '', bytes: Uint8List(0)),
|
|
||||||
graphicAdjust: const GraphicAdjust(),
|
graphicAdjust: const GraphicAdjust(),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,7 +3,7 @@ import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
import 'package:pdf_signature/l10n/app_localizations.dart';
|
import 'package:pdf_signature/l10n/app_localizations.dart';
|
||||||
|
|
||||||
import '../../../../domain/models/model.dart';
|
import '../../../../domain/models/model.dart';
|
||||||
import 'package:pdf_signature/data/repositories/signature_repository.dart';
|
import 'package:pdf_signature/data/repositories/signature_card_repository.dart';
|
||||||
|
|
||||||
class AdjustmentsPanel extends ConsumerWidget {
|
class AdjustmentsPanel extends ConsumerWidget {
|
||||||
const AdjustmentsPanel({super.key, required this.sig});
|
const AdjustmentsPanel({super.key, required this.sig});
|
||||||
|
|
|
@ -2,7 +2,7 @@ import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
import 'package:pdf_signature/l10n/app_localizations.dart';
|
import 'package:pdf_signature/l10n/app_localizations.dart';
|
||||||
|
|
||||||
import 'package:pdf_signature/data/repositories/signature_repository.dart';
|
import 'package:pdf_signature/data/repositories/signature_card_repository.dart';
|
||||||
import 'adjustments_panel.dart';
|
import 'adjustments_panel.dart';
|
||||||
import '../../signature/widgets/rotated_signature_image.dart';
|
import '../../signature/widgets/rotated_signature_image.dart';
|
||||||
|
|
||||||
|
|
|
@ -4,8 +4,8 @@ import 'package:pdf_signature/l10n/app_localizations.dart';
|
||||||
import 'package:pdfrx/pdfrx.dart';
|
import 'package:pdfrx/pdfrx.dart';
|
||||||
|
|
||||||
import '../../../../data/services/export_providers.dart';
|
import '../../../../data/services/export_providers.dart';
|
||||||
import 'package:pdf_signature/data/repositories/signature_repository.dart';
|
import 'package:pdf_signature/data/repositories/signature_card_repository.dart';
|
||||||
import 'package:pdf_signature/data/repositories/pdf_repository.dart';
|
import 'package:pdf_signature/data/repositories/document_repository.dart';
|
||||||
import '../../signature/widgets/signature_drag_data.dart';
|
import '../../signature/widgets/signature_drag_data.dart';
|
||||||
import 'pdf_mock_continuous_list.dart';
|
import 'pdf_mock_continuous_list.dart';
|
||||||
import 'pdf_page_overlays.dart';
|
import 'pdf_page_overlays.dart';
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
|
|
||||||
import 'package:pdf_signature/data/repositories/signature_repository.dart';
|
import 'package:pdf_signature/data/repositories/signature_card_repository.dart';
|
||||||
import '../../../../domain/models/model.dart';
|
import '../../../../domain/models/model.dart';
|
||||||
import 'package:pdf_signature/data/repositories/pdf_repository.dart';
|
import 'package:pdf_signature/data/repositories/document_repository.dart';
|
||||||
import 'signature_overlay.dart';
|
import 'signature_overlay.dart';
|
||||||
|
|
||||||
/// Builds all overlays for a given page: placed signatures and the active one.
|
/// Builds all overlays for a given page: placed signatures and the active one.
|
||||||
|
@ -36,7 +36,7 @@ class PdfPageOverlays extends ConsumerWidget {
|
||||||
final widgets = <Widget>[];
|
final widgets = <Widget>[];
|
||||||
|
|
||||||
for (int i = 0; i < placed.length; i++) {
|
for (int i = 0; i < placed.length; i++) {
|
||||||
// Stored as UI-space rects (SignatureController.pageSize).
|
// Stored as UI-space rects (SignatureCardStateNotifier.pageSize).
|
||||||
final uiRect = placed[i].rect;
|
final uiRect = placed[i].rect;
|
||||||
widgets.add(
|
widgets.add(
|
||||||
SignatureOverlay(
|
SignatureOverlay(
|
||||||
|
|
|
@ -3,7 +3,7 @@ import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
import 'package:pdfrx/pdfrx.dart';
|
import 'package:pdfrx/pdfrx.dart';
|
||||||
|
|
||||||
import '../../../../data/services/export_providers.dart';
|
import '../../../../data/services/export_providers.dart';
|
||||||
import 'package:pdf_signature/data/repositories/pdf_repository.dart';
|
import 'package:pdf_signature/data/repositories/document_repository.dart';
|
||||||
|
|
||||||
class PdfPagesOverview extends ConsumerWidget {
|
class PdfPagesOverview extends ConsumerWidget {
|
||||||
const PdfPagesOverview({super.key});
|
const PdfPagesOverview({super.key});
|
||||||
|
|
|
@ -3,6 +3,8 @@ import 'package:file_selector/file_selector.dart' as fs;
|
||||||
import 'package:flutter/foundation.dart' show kIsWeb;
|
import 'package:flutter/foundation.dart' show kIsWeb;
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
|
import 'package:pdf_signature/data/repositories/preferences_repository.dart';
|
||||||
|
import 'package:pdf_signature/domain/models/preferences.dart';
|
||||||
import 'package:pdf_signature/l10n/app_localizations.dart';
|
import 'package:pdf_signature/l10n/app_localizations.dart';
|
||||||
import 'package:printing/printing.dart' as printing;
|
import 'package:printing/printing.dart' as printing;
|
||||||
import 'package:pdfrx/pdfrx.dart';
|
import 'package:pdfrx/pdfrx.dart';
|
||||||
|
@ -10,8 +12,8 @@ import 'package:multi_split_view/multi_split_view.dart';
|
||||||
|
|
||||||
import '../../../../data/services/export_providers.dart';
|
import '../../../../data/services/export_providers.dart';
|
||||||
import 'package:image/image.dart' as img;
|
import 'package:image/image.dart' as img;
|
||||||
import 'package:pdf_signature/data/repositories/signature_repository.dart';
|
import 'package:pdf_signature/data/repositories/signature_card_repository.dart';
|
||||||
import 'package:pdf_signature/data/repositories/pdf_repository.dart';
|
import 'package:pdf_signature/data/repositories/document_repository.dart';
|
||||||
import 'package:pdf_signature/data/repositories/signature_asset_repository.dart';
|
import 'package:pdf_signature/data/repositories/signature_asset_repository.dart';
|
||||||
import 'draw_canvas.dart';
|
import 'draw_canvas.dart';
|
||||||
import 'pdf_toolbar.dart';
|
import 'pdf_toolbar.dart';
|
||||||
|
@ -28,7 +30,7 @@ class PdfSignatureHomePage extends ConsumerStatefulWidget {
|
||||||
}
|
}
|
||||||
|
|
||||||
class _PdfSignatureHomePageState extends ConsumerState<PdfSignatureHomePage> {
|
class _PdfSignatureHomePageState extends ConsumerState<PdfSignatureHomePage> {
|
||||||
static const Size _pageSize = SignatureController.pageSize;
|
static const Size _pageSize = SignatureCardStateNotifier.pageSize;
|
||||||
final PdfViewerController _viewerController = PdfViewerController();
|
final PdfViewerController _viewerController = PdfViewerController();
|
||||||
bool _showPagesSidebar = true;
|
bool _showPagesSidebar = true;
|
||||||
bool _showSignaturesSidebar = true;
|
bool _showSignaturesSidebar = true;
|
||||||
|
@ -142,7 +144,11 @@ class _PdfSignatureHomePageState extends ConsumerState<PdfSignatureHomePage> {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
final exporter = ref.read(exportServiceProvider);
|
final exporter = ref.read(exportServiceProvider);
|
||||||
final targetDpi = ref.read(exportDpiProvider);
|
|
||||||
|
// get DPI from preferences
|
||||||
|
final targetDpi = ref.read(preferencesRepositoryProvider).select(
|
||||||
|
(p) => p.exportDpi,
|
||||||
|
);
|
||||||
final useMock = ref.read(useMockViewerProvider);
|
final useMock = ref.read(useMockViewerProvider);
|
||||||
bool ok = false;
|
bool ok = false;
|
||||||
String? savedPath;
|
String? savedPath;
|
||||||
|
@ -177,7 +183,7 @@ class _PdfSignatureHomePageState extends ConsumerState<PdfSignatureHomePage> {
|
||||||
srcBytes: src,
|
srcBytes: src,
|
||||||
signedPage: pdf.signedPage,
|
signedPage: pdf.signedPage,
|
||||||
signatureRectUi: sig.rect,
|
signatureRectUi: sig.rect,
|
||||||
uiPageSize: SignatureController.pageSize,
|
uiPageSize: SignatureCardStateNotifier.pageSize,
|
||||||
signatureImageBytes: rotated,
|
signatureImageBytes: rotated,
|
||||||
placementsByPage: pdf.placementsByPage,
|
placementsByPage: pdf.placementsByPage,
|
||||||
libraryBytes: {
|
libraryBytes: {
|
||||||
|
@ -214,7 +220,7 @@ class _PdfSignatureHomePageState extends ConsumerState<PdfSignatureHomePage> {
|
||||||
srcBytes: pdf.pickedPdfBytes!,
|
srcBytes: pdf.pickedPdfBytes!,
|
||||||
signedPage: pdf.signedPage,
|
signedPage: pdf.signedPage,
|
||||||
signatureRectUi: sig.rect,
|
signatureRectUi: sig.rect,
|
||||||
uiPageSize: SignatureController.pageSize,
|
uiPageSize: SignatureCardStateNotifier.pageSize,
|
||||||
signatureImageBytes: rotated,
|
signatureImageBytes: rotated,
|
||||||
placementsByPage: pdf.placementsByPage,
|
placementsByPage: pdf.placementsByPage,
|
||||||
libraryBytes: {
|
libraryBytes: {
|
||||||
|
@ -245,7 +251,7 @@ class _PdfSignatureHomePageState extends ConsumerState<PdfSignatureHomePage> {
|
||||||
outputPath: fullPath,
|
outputPath: fullPath,
|
||||||
signedPage: pdf.signedPage,
|
signedPage: pdf.signedPage,
|
||||||
signatureRectUi: sig.rect,
|
signatureRectUi: sig.rect,
|
||||||
uiPageSize: SignatureController.pageSize,
|
uiPageSize: SignatureCardStateNotifier.pageSize,
|
||||||
signatureImageBytes: rotated,
|
signatureImageBytes: rotated,
|
||||||
placementsByPage: pdf.placementsByPage,
|
placementsByPage: pdf.placementsByPage,
|
||||||
libraryBytes: {
|
libraryBytes: {
|
||||||
|
@ -467,3 +473,7 @@ class _PdfSignatureHomePageState extends ConsumerState<PdfSignatureHomePage> {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
extension on PreferencesState {
|
||||||
|
select(Function(dynamic p) param0) {}
|
||||||
|
}
|
||||||
|
|
|
@ -3,7 +3,7 @@ import 'package:flutter/services.dart';
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
import 'package:pdf_signature/l10n/app_localizations.dart';
|
import 'package:pdf_signature/l10n/app_localizations.dart';
|
||||||
|
|
||||||
import 'package:pdf_signature/data/repositories/pdf_repository.dart';
|
import 'package:pdf_signature/data/repositories/document_repository.dart';
|
||||||
|
|
||||||
class PdfToolbar extends ConsumerStatefulWidget {
|
class PdfToolbar extends ConsumerStatefulWidget {
|
||||||
const PdfToolbar({
|
const PdfToolbar({
|
||||||
|
|
|
@ -5,7 +5,7 @@ import 'package:pdf_signature/l10n/app_localizations.dart';
|
||||||
import 'package:pdf_signature/domain/models/model.dart' as model;
|
import 'package:pdf_signature/domain/models/model.dart' as model;
|
||||||
|
|
||||||
import '../../../../data/services/export_providers.dart';
|
import '../../../../data/services/export_providers.dart';
|
||||||
import 'package:pdf_signature/data/repositories/signature_repository.dart';
|
import 'package:pdf_signature/data/repositories/signature_card_repository.dart';
|
||||||
import 'package:pdf_signature/data/repositories/signature_asset_repository.dart';
|
import 'package:pdf_signature/data/repositories/signature_asset_repository.dart';
|
||||||
import 'image_editor_dialog.dart';
|
import 'image_editor_dialog.dart';
|
||||||
import '../../signature/widgets/signature_card.dart';
|
import '../../signature/widgets/signature_card.dart';
|
||||||
|
|
|
@ -5,8 +5,8 @@ import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
import 'package:pdf_signature/l10n/app_localizations.dart';
|
import 'package:pdf_signature/l10n/app_localizations.dart';
|
||||||
|
|
||||||
import '../../../../domain/models/model.dart';
|
import '../../../../domain/models/model.dart';
|
||||||
import 'package:pdf_signature/data/repositories/signature_repository.dart';
|
import 'package:pdf_signature/data/repositories/signature_card_repository.dart';
|
||||||
import 'package:pdf_signature/data/repositories/pdf_repository.dart';
|
import 'package:pdf_signature/data/repositories/document_repository.dart';
|
||||||
import 'package:pdf_signature/data/repositories/signature_asset_repository.dart';
|
import 'package:pdf_signature/data/repositories/signature_asset_repository.dart';
|
||||||
import 'image_editor_dialog.dart';
|
import 'image_editor_dialog.dart';
|
||||||
import '../../signature/widgets/rotated_signature_image.dart';
|
import '../../signature/widgets/rotated_signature_image.dart';
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
import 'package:pdf_signature/l10n/app_localizations.dart';
|
import 'package:pdf_signature/l10n/app_localizations.dart';
|
||||||
import '../../../../data/services/preferences_providers.dart';
|
import '../../../../data/repositories/preferences_repository.dart';
|
||||||
|
|
||||||
class SettingsDialog extends ConsumerStatefulWidget {
|
class SettingsDialog extends ConsumerStatefulWidget {
|
||||||
const SettingsDialog({super.key});
|
const SettingsDialog({super.key});
|
||||||
|
@ -19,7 +19,7 @@ class _SettingsDialogState extends ConsumerState<SettingsDialog> {
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
final prefs = ref.read(preferencesProvider);
|
final prefs = ref.read(preferencesRepositoryProvider);
|
||||||
_theme = prefs.theme;
|
_theme = prefs.theme;
|
||||||
_language = prefs.language;
|
_language = prefs.language;
|
||||||
_exportDpi = prefs.exportDpi;
|
_exportDpi = prefs.exportDpi;
|
||||||
|
@ -186,7 +186,9 @@ class _SettingsDialogState extends ConsumerState<SettingsDialog> {
|
||||||
const SizedBox(width: 8),
|
const SizedBox(width: 8),
|
||||||
FilledButton(
|
FilledButton(
|
||||||
onPressed: () async {
|
onPressed: () async {
|
||||||
final n = ref.read(preferencesProvider.notifier);
|
final n = ref.read(
|
||||||
|
preferencesRepositoryProvider.notifier,
|
||||||
|
);
|
||||||
if (_theme != null) await n.setTheme(_theme!);
|
if (_theme != null) await n.setTheme(_theme!);
|
||||||
if (_language != null) await n.setLanguage(_language!);
|
if (_language != null) await n.setLanguage(_language!);
|
||||||
if (_exportDpi != null) await n.setExportDpi(_exportDpi!);
|
if (_exportDpi != null) await n.setExportDpi(_exportDpi!);
|
||||||
|
|
|
@ -7,8 +7,8 @@ import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
import 'package:pdf_signature/l10n/app_localizations.dart';
|
import 'package:pdf_signature/l10n/app_localizations.dart';
|
||||||
|
|
||||||
import 'package:pdf_signature/data/repositories/signature_repository.dart';
|
import 'package:pdf_signature/data/repositories/signature_card_repository.dart';
|
||||||
import 'package:pdf_signature/data/repositories/pdf_repository.dart';
|
import 'package:pdf_signature/data/repositories/document_repository.dart';
|
||||||
// Settings dialog is provided via global AppBar in MyApp
|
// Settings dialog is provided via global AppBar in MyApp
|
||||||
|
|
||||||
// Abstraction to make drop handling testable without constructing
|
// Abstraction to make drop handling testable without constructing
|
||||||
|
|
|
@ -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/material.dart';
|
||||||
import 'package:flutter_test/flutter_test.dart';
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
import 'package:pdf_signature/data/repositories/pdf_repository.dart';
|
import 'package:pdf_signature/data/repositories/document_repository.dart';
|
||||||
import 'package:pdf_signature/domain/models/model.dart';
|
import 'package:pdf_signature/domain/models/model.dart';
|
||||||
import '_world.dart';
|
import '_world.dart';
|
||||||
|
|
||||||
|
|
|
@ -2,7 +2,7 @@ import 'dart:typed_data';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_test/flutter_test.dart';
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
import 'package:pdf_signature/data/repositories/pdf_repository.dart';
|
import 'package:pdf_signature/data/repositories/document_repository.dart';
|
||||||
import 'package:pdf_signature/domain/models/model.dart';
|
import 'package:pdf_signature/domain/models/model.dart';
|
||||||
import '_world.dart';
|
import '_world.dart';
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import 'package:flutter_test/flutter_test.dart';
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
import 'package:pdf_signature/data/repositories/pdf_repository.dart';
|
import 'package:pdf_signature/data/repositories/document_repository.dart';
|
||||||
import '_world.dart';
|
import '_world.dart';
|
||||||
|
|
||||||
/// Usage: a document is open with no signature placements placed
|
/// Usage: a document is open with no signature placements placed
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import 'package:flutter_test/flutter_test.dart';
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
import 'package:pdf_signature/data/repositories/pdf_repository.dart';
|
import 'package:pdf_signature/data/repositories/document_repository.dart';
|
||||||
import '_world.dart';
|
import '_world.dart';
|
||||||
|
|
||||||
/// Usage: a document page is selected for signing
|
/// Usage: a document page is selected for signing
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import 'package:flutter_test/flutter_test.dart';
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
import 'package:pdf_signature/data/repositories/signature_repository.dart';
|
import 'package:pdf_signature/data/repositories/signature_card_repository.dart';
|
||||||
import '_world.dart';
|
import '_world.dart';
|
||||||
|
|
||||||
/// Usage: a drawn signature exists in the canvas
|
/// Usage: a drawn signature exists in the canvas
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import 'package:flutter_test/flutter_test.dart';
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
import 'package:pdf_signature/data/repositories/pdf_repository.dart';
|
import 'package:pdf_signature/data/repositories/document_repository.dart';
|
||||||
import 'package:pdf_signature/data/repositories/signature_repository.dart';
|
import 'package:pdf_signature/data/repositories/signature_card_repository.dart';
|
||||||
import 'package:pdf_signature/data/repositories/signature_asset_repository.dart';
|
import 'package:pdf_signature/data/repositories/signature_asset_repository.dart';
|
||||||
import 'package:pdf_signature/domain/models/model.dart';
|
import 'package:pdf_signature/domain/models/model.dart';
|
||||||
import '_world.dart';
|
import '_world.dart';
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import 'package:flutter_test/flutter_test.dart';
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
import 'package:pdf_signature/data/repositories/pdf_repository.dart';
|
import 'package:pdf_signature/data/repositories/document_repository.dart';
|
||||||
import '_world.dart';
|
import '_world.dart';
|
||||||
|
|
||||||
/// Usage: a sample multi-page document (5 pages) is available
|
/// Usage: a sample multi-page document (5 pages) is available
|
||||||
|
|
|
@ -2,7 +2,7 @@ import 'dart:typed_data';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_test/flutter_test.dart';
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
import 'package:pdf_signature/data/repositories/pdf_repository.dart';
|
import 'package:pdf_signature/data/repositories/document_repository.dart';
|
||||||
import 'package:pdf_signature/data/repositories/signature_asset_repository.dart';
|
import 'package:pdf_signature/data/repositories/signature_asset_repository.dart';
|
||||||
import 'package:pdf_signature/domain/models/model.dart';
|
import 'package:pdf_signature/domain/models/model.dart';
|
||||||
import '_world.dart';
|
import '_world.dart';
|
||||||
|
|
|
@ -2,8 +2,8 @@ import 'dart:typed_data';
|
||||||
import 'package:flutter_test/flutter_test.dart';
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
import 'package:pdf_signature/data/repositories/signature_asset_repository.dart';
|
import 'package:pdf_signature/data/repositories/signature_asset_repository.dart';
|
||||||
import 'package:pdf_signature/data/repositories/pdf_repository.dart';
|
import 'package:pdf_signature/data/repositories/document_repository.dart';
|
||||||
import 'package:pdf_signature/data/repositories/signature_repository.dart';
|
import 'package:pdf_signature/data/repositories/signature_card_repository.dart';
|
||||||
import 'package:pdf_signature/domain/models/model.dart';
|
import 'package:pdf_signature/domain/models/model.dart';
|
||||||
import '_world.dart';
|
import '_world.dart';
|
||||||
|
|
||||||
|
|
|
@ -2,7 +2,7 @@ import 'dart:typed_data';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_test/flutter_test.dart';
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
import 'package:pdf_signature/data/repositories/pdf_repository.dart';
|
import 'package:pdf_signature/data/repositories/document_repository.dart';
|
||||||
import 'package:pdf_signature/data/repositories/signature_asset_repository.dart';
|
import 'package:pdf_signature/data/repositories/signature_asset_repository.dart';
|
||||||
import 'package:pdf_signature/domain/models/model.dart';
|
import 'package:pdf_signature/domain/models/model.dart';
|
||||||
import '_world.dart';
|
import '_world.dart';
|
||||||
|
|
|
@ -2,8 +2,8 @@ import 'dart:typed_data';
|
||||||
import 'package:flutter_test/flutter_test.dart';
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
import 'package:pdf_signature/data/repositories/signature_asset_repository.dart';
|
import 'package:pdf_signature/data/repositories/signature_asset_repository.dart';
|
||||||
import 'package:pdf_signature/data/repositories/pdf_repository.dart';
|
import 'package:pdf_signature/data/repositories/document_repository.dart';
|
||||||
import 'package:pdf_signature/data/repositories/signature_repository.dart';
|
import 'package:pdf_signature/data/repositories/signature_card_repository.dart';
|
||||||
import 'package:pdf_signature/domain/models/model.dart';
|
import 'package:pdf_signature/domain/models/model.dart';
|
||||||
import '_world.dart';
|
import '_world.dart';
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import 'package:flutter_test/flutter_test.dart';
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
import 'package:pdf_signature/data/repositories/pdf_repository.dart';
|
import 'package:pdf_signature/data/repositories/document_repository.dart';
|
||||||
import '_world.dart';
|
import '_world.dart';
|
||||||
|
|
||||||
/// Usage: a signature placement appears on the page based on the signature card
|
/// Usage: a signature placement appears on the page based on the signature card
|
||||||
|
|
|
@ -2,7 +2,7 @@ import 'dart:typed_data';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_test/flutter_test.dart';
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
import 'package:pdf_signature/data/repositories/pdf_repository.dart';
|
import 'package:pdf_signature/data/repositories/document_repository.dart';
|
||||||
import 'package:pdf_signature/domain/models/model.dart';
|
import 'package:pdf_signature/domain/models/model.dart';
|
||||||
import '_world.dart';
|
import '_world.dart';
|
||||||
|
|
||||||
|
|
|
@ -2,7 +2,7 @@ import 'dart:typed_data';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_test/flutter_test.dart';
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
import 'package:pdf_signature/data/repositories/pdf_repository.dart';
|
import 'package:pdf_signature/data/repositories/document_repository.dart';
|
||||||
import 'package:pdf_signature/domain/models/model.dart';
|
import 'package:pdf_signature/domain/models/model.dart';
|
||||||
import '_world.dart';
|
import '_world.dart';
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import 'package:flutter_test/flutter_test.dart';
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
import 'package:pdf_signature/data/repositories/pdf_repository.dart';
|
import 'package:pdf_signature/data/repositories/document_repository.dart';
|
||||||
import 'package:pdf_signature/domain/models/model.dart';
|
import 'package:pdf_signature/domain/models/model.dart';
|
||||||
import '_world.dart';
|
import '_world.dart';
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import 'package:flutter_test/flutter_test.dart';
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
import 'package:pdf_signature/data/repositories/pdf_repository.dart';
|
import 'package:pdf_signature/data/repositories/document_repository.dart';
|
||||||
import '_world.dart';
|
import '_world.dart';
|
||||||
|
|
||||||
/// Usage: adjusting one of the signature placements does not affect the others
|
/// Usage: adjusting one of the signature placements does not affect the others
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import 'package:flutter_test/flutter_test.dart';
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
import 'package:pdf_signature/data/repositories/pdf_repository.dart';
|
import 'package:pdf_signature/data/repositories/document_repository.dart';
|
||||||
import '_world.dart';
|
import '_world.dart';
|
||||||
|
|
||||||
/// Usage: all placed signature placements appear on their corresponding pages in the output
|
/// Usage: all placed signature placements appear on their corresponding pages in the output
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import 'package:flutter_test/flutter_test.dart';
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
import 'package:pdf_signature/data/repositories/signature_repository.dart';
|
import 'package:pdf_signature/data/repositories/signature_card_repository.dart';
|
||||||
import '_world.dart';
|
import '_world.dart';
|
||||||
|
|
||||||
/// Usage: an empty signature canvas
|
/// Usage: an empty signature canvas
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import 'package:flutter_test/flutter_test.dart';
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
import 'package:pdf_signature/data/repositories/pdf_repository.dart';
|
import 'package:pdf_signature/data/repositories/document_repository.dart';
|
||||||
import '_world.dart';
|
import '_world.dart';
|
||||||
|
|
||||||
/// Usage: both signature placements are shown on their respective pages
|
/// Usage: both signature placements are shown on their respective pages
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import 'dart:ui';
|
import 'dart:ui';
|
||||||
import 'package:flutter_test/flutter_test.dart';
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
import 'package:pdf_signature/data/repositories/pdf_repository.dart';
|
import 'package:pdf_signature/data/repositories/document_repository.dart';
|
||||||
import 'package:pdf_signature/domain/models/model.dart';
|
import 'package:pdf_signature/domain/models/model.dart';
|
||||||
import '_world.dart';
|
import '_world.dart';
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import 'package:flutter_test/flutter_test.dart';
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
import 'package:pdf_signature/data/repositories/pdf_repository.dart';
|
import 'package:pdf_signature/data/repositories/document_repository.dart';
|
||||||
import '_world.dart';
|
import '_world.dart';
|
||||||
|
|
||||||
/// Usage: each signature placement can be dragged and resized independently
|
/// Usage: each signature placement can be dragged and resized independently
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import 'package:flutter_test/flutter_test.dart';
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
import 'package:pdf_signature/data/repositories/pdf_repository.dart';
|
import 'package:pdf_signature/data/repositories/document_repository.dart';
|
||||||
import '_world.dart';
|
import '_world.dart';
|
||||||
|
|
||||||
/// Usage: identical signature instances appear in each location
|
/// Usage: identical signature instances appear in each location
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import 'package:flutter_test/flutter_test.dart';
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
import 'package:pdf_signature/data/repositories/pdf_repository.dart';
|
import 'package:pdf_signature/data/repositories/document_repository.dart';
|
||||||
import '_world.dart';
|
import '_world.dart';
|
||||||
|
|
||||||
/// Usage: identical signature placements appear in each location
|
/// Usage: identical signature placements appear in each location
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import 'package:flutter_test/flutter_test.dart';
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
import 'package:pdf_signature/data/repositories/signature_repository.dart';
|
import 'package:pdf_signature/data/repositories/signature_card_repository.dart';
|
||||||
import '_world.dart';
|
import '_world.dart';
|
||||||
|
|
||||||
/// Usage: multiple strokes were drawn
|
/// Usage: multiple strokes were drawn
|
||||||
|
|
|
@ -2,7 +2,7 @@ import 'dart:typed_data';
|
||||||
import 'package:flutter_test/flutter_test.dart';
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
import 'package:image/image.dart' as img;
|
import 'package:image/image.dart' as img;
|
||||||
import 'package:pdf_signature/data/repositories/signature_repository.dart';
|
import 'package:pdf_signature/data/repositories/signature_card_repository.dart';
|
||||||
import '_world.dart';
|
import '_world.dart';
|
||||||
|
|
||||||
/// Usage: near-white background becomes transparent in the preview
|
/// Usage: near-white background becomes transparent in the preview
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import 'package:flutter_test/flutter_test.dart';
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
import 'package:pdf_signature/data/repositories/pdf_repository.dart';
|
import 'package:pdf_signature/data/repositories/document_repository.dart';
|
||||||
import '_world.dart';
|
import '_world.dart';
|
||||||
|
|
||||||
/// Usage: only the selected signature placement is removed
|
/// Usage: only the selected signature placement is removed
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import 'package:flutter_test/flutter_test.dart';
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
import 'package:pdf_signature/data/repositories/pdf_repository.dart';
|
import 'package:pdf_signature/data/repositories/document_repository.dart';
|
||||||
import '_world.dart';
|
import '_world.dart';
|
||||||
|
|
||||||
/// Usage: page {5} becomes visible in the scroll area
|
/// Usage: page {5} becomes visible in the scroll area
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import 'package:flutter_test/flutter_test.dart';
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
import 'package:pdf_signature/data/repositories/pdf_repository.dart';
|
import 'package:pdf_signature/data/repositories/document_repository.dart';
|
||||||
import '_world.dart';
|
import '_world.dart';
|
||||||
|
|
||||||
/// Usage: page {1} is displayed
|
/// Usage: page {1} is displayed
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import 'package:flutter_test/flutter_test.dart';
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
import 'package:pdf_signature/data/repositories/pdf_repository.dart';
|
import 'package:pdf_signature/data/repositories/document_repository.dart';
|
||||||
import '_world.dart';
|
import '_world.dart';
|
||||||
|
|
||||||
/// Usage: resize to fit within bounding box
|
/// Usage: resize to fit within bounding box
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import 'package:flutter_test/flutter_test.dart';
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
import 'package:pdf_signature/data/repositories/pdf_repository.dart';
|
import 'package:pdf_signature/data/repositories/document_repository.dart';
|
||||||
import '_world.dart';
|
import '_world.dart';
|
||||||
|
|
||||||
/// Usage: signature placement occurs on the selected page
|
/// Usage: signature placement occurs on the selected page
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import 'package:flutter_test/flutter_test.dart';
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
import 'package:pdf_signature/data/repositories/signature_repository.dart';
|
import 'package:pdf_signature/data/repositories/signature_card_repository.dart';
|
||||||
import '_world.dart';
|
import '_world.dart';
|
||||||
|
|
||||||
/// Usage: the canvas becomes blank
|
/// Usage: the canvas becomes blank
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import 'package:flutter_test/flutter_test.dart';
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
import 'package:pdf_signature/data/repositories/pdf_repository.dart';
|
import 'package:pdf_signature/data/repositories/document_repository.dart';
|
||||||
import '_world.dart';
|
import '_world.dart';
|
||||||
|
|
||||||
/// Usage: the document is open
|
/// Usage: the document is open
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import 'package:flutter_test/flutter_test.dart';
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
import 'package:pdf_signature/data/repositories/pdf_repository.dart';
|
import 'package:pdf_signature/data/repositories/document_repository.dart';
|
||||||
import '_world.dart';
|
import '_world.dart';
|
||||||
|
|
||||||
/// Usage: the first page is displayed
|
/// Usage: the first page is displayed
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import 'package:flutter_test/flutter_test.dart';
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
import 'package:pdf_signature/data/repositories/pdf_repository.dart';
|
import 'package:pdf_signature/data/repositories/document_repository.dart';
|
||||||
import '_world.dart';
|
import '_world.dart';
|
||||||
|
|
||||||
/// Usage: the Go to input cannot be used
|
/// Usage: the Go to input cannot be used
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import 'package:flutter_test/flutter_test.dart';
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
import 'package:pdf_signature/data/repositories/pdf_repository.dart';
|
import 'package:pdf_signature/data/repositories/document_repository.dart';
|
||||||
import '_world.dart';
|
import '_world.dart';
|
||||||
|
|
||||||
/// Usage: the last page is displayed (page {5})
|
/// Usage: the last page is displayed (page {5})
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import 'package:flutter_test/flutter_test.dart';
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
import 'package:pdf_signature/data/repositories/signature_repository.dart';
|
import 'package:pdf_signature/data/repositories/signature_card_repository.dart';
|
||||||
import '_world.dart';
|
import '_world.dart';
|
||||||
|
|
||||||
/// Usage: the last stroke is removed
|
/// Usage: the last stroke is removed
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import 'package:flutter_test/flutter_test.dart';
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
import 'package:pdf_signature/data/repositories/pdf_repository.dart';
|
import 'package:pdf_signature/data/repositories/document_repository.dart';
|
||||||
import '_world.dart';
|
import '_world.dart';
|
||||||
|
|
||||||
/// Usage: the left pages overview highlights page {5}
|
/// Usage: the left pages overview highlights page {5}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import 'package:flutter_test/flutter_test.dart';
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
import 'package:pdf_signature/data/repositories/pdf_repository.dart';
|
import 'package:pdf_signature/data/repositories/document_repository.dart';
|
||||||
import '_world.dart';
|
import '_world.dart';
|
||||||
|
|
||||||
/// Usage: the other signature placements remain unchanged
|
/// Usage: the other signature placements remain unchanged
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import 'package:flutter_test/flutter_test.dart';
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
import 'package:pdf_signature/data/repositories/pdf_repository.dart';
|
import 'package:pdf_signature/data/repositories/document_repository.dart';
|
||||||
import '_world.dart';
|
import '_world.dart';
|
||||||
|
|
||||||
/// Usage: the page label shows "Page {5} of {5}"
|
/// Usage: the page label shows "Page {5} of {5}"
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import 'package:flutter_test/flutter_test.dart';
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
import 'package:pdf_signature/data/repositories/signature_repository.dart';
|
import 'package:pdf_signature/data/repositories/signature_card_repository.dart';
|
||||||
import '_world.dart';
|
import '_world.dart';
|
||||||
|
|
||||||
/// Usage: the preview updates immediately
|
/// Usage: the preview updates immediately
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import 'package:flutter_test/flutter_test.dart';
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
import 'package:pdf_signature/data/repositories/pdf_repository.dart';
|
import 'package:pdf_signature/data/repositories/document_repository.dart';
|
||||||
import '_world.dart';
|
import '_world.dart';
|
||||||
|
|
||||||
/// Usage: the signature placement is stamped at the exact PDF page coordinates and size
|
/// Usage: the signature placement is stamped at the exact PDF page coordinates and size
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import 'package:flutter_test/flutter_test.dart';
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
import 'package:pdf_signature/data/repositories/pdf_repository.dart';
|
import 'package:pdf_signature/data/repositories/document_repository.dart';
|
||||||
import '_world.dart';
|
import '_world.dart';
|
||||||
|
|
||||||
/// Usage: the signature placement on page {5} is shown on page {5}
|
/// Usage: the signature placement on page {5} is shown on page {5}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import 'package:flutter_test/flutter_test.dart';
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
import 'package:pdf_signature/data/repositories/pdf_repository.dart';
|
import 'package:pdf_signature/data/repositories/document_repository.dart';
|
||||||
import '_world.dart';
|
import '_world.dart';
|
||||||
|
|
||||||
/// Usage: the signature placement on page {2} remains
|
/// Usage: the signature placement on page {2} remains
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import 'package:flutter_test/flutter_test.dart';
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
import 'package:pdf_signature/data/repositories/pdf_repository.dart';
|
import 'package:pdf_signature/data/repositories/document_repository.dart';
|
||||||
import '_world.dart';
|
import '_world.dart';
|
||||||
|
|
||||||
/// Usage: the signature placement remains within the page area
|
/// Usage: the signature placement remains within the page area
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import 'package:flutter_test/flutter_test.dart';
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
import 'package:pdf_signature/data/repositories/pdf_repository.dart';
|
import 'package:pdf_signature/data/repositories/document_repository.dart';
|
||||||
import '_world.dart';
|
import '_world.dart';
|
||||||
|
|
||||||
/// Usage: the signature placement rotates around its center in real time
|
/// Usage: the signature placement rotates around its center in real time
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import 'package:flutter_test/flutter_test.dart';
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
import 'package:pdf_signature/data/repositories/pdf_repository.dart';
|
import 'package:pdf_signature/data/repositories/document_repository.dart';
|
||||||
import '_world.dart';
|
import '_world.dart';
|
||||||
|
|
||||||
/// Usage: the signature placements appear on the corresponding page in the output
|
/// Usage: the signature placements appear on the corresponding page in the output
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import 'package:flutter_test/flutter_test.dart';
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
import 'package:pdf_signature/data/repositories/pdf_repository.dart';
|
import 'package:pdf_signature/data/repositories/document_repository.dart';
|
||||||
import '_world.dart';
|
import '_world.dart';
|
||||||
|
|
||||||
/// Usage: the size and position update in real time
|
/// Usage: the size and position update in real time
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import 'package:flutter_test/flutter_test.dart';
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
import 'package:pdf_signature/data/repositories/signature_repository.dart';
|
import 'package:pdf_signature/data/repositories/signature_card_repository.dart';
|
||||||
import 'package:pdf_signature/data/repositories/pdf_repository.dart';
|
import 'package:pdf_signature/data/repositories/document_repository.dart';
|
||||||
import '_world.dart';
|
import '_world.dart';
|
||||||
|
|
||||||
/// Usage: the user attempts to save
|
/// Usage: the user attempts to save
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import 'package:flutter_test/flutter_test.dart';
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
import 'package:pdf_signature/data/repositories/signature_repository.dart';
|
import 'package:pdf_signature/data/repositories/signature_card_repository.dart';
|
||||||
import '_world.dart';
|
import '_world.dart';
|
||||||
|
|
||||||
/// Usage: the user can apply or reset adjustments
|
/// Usage: the user can apply or reset adjustments
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import 'package:flutter_test/flutter_test.dart';
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
import 'package:pdf_signature/data/repositories/pdf_repository.dart';
|
import 'package:pdf_signature/data/repositories/document_repository.dart';
|
||||||
import '_world.dart';
|
import '_world.dart';
|
||||||
|
|
||||||
/// Usage: the user can move to the next or previous page
|
/// Usage: the user can move to the next or previous page
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import 'package:flutter_test/flutter_test.dart';
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
import 'package:pdf_signature/data/repositories/signature_repository.dart';
|
import 'package:pdf_signature/data/repositories/signature_card_repository.dart';
|
||||||
import '_world.dart';
|
import '_world.dart';
|
||||||
|
|
||||||
/// Usage: the user changes contrast and brightness controls
|
/// Usage: the user changes contrast and brightness controls
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import 'package:flutter_test/flutter_test.dart';
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
import 'package:pdf_signature/data/repositories/signature_repository.dart';
|
import 'package:pdf_signature/data/repositories/signature_card_repository.dart';
|
||||||
import '_world.dart';
|
import '_world.dart';
|
||||||
|
|
||||||
/// Usage: the user chooses undo
|
/// Usage: the user chooses undo
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import 'package:flutter_test/flutter_test.dart';
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
import 'package:pdf_signature/data/repositories/signature_repository.dart';
|
import 'package:pdf_signature/data/repositories/signature_card_repository.dart';
|
||||||
import '_world.dart';
|
import '_world.dart';
|
||||||
|
|
||||||
/// Usage: the user clears the canvas
|
/// Usage: the user clears the canvas
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import 'package:flutter_test/flutter_test.dart';
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
import 'package:pdf_signature/data/repositories/pdf_repository.dart';
|
import 'package:pdf_signature/data/repositories/document_repository.dart';
|
||||||
import '_world.dart';
|
import '_world.dart';
|
||||||
|
|
||||||
/// Usage: the user clicks the Go to apply button
|
/// Usage: the user clicks the Go to apply button
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import 'package:flutter_test/flutter_test.dart';
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
import 'package:pdf_signature/data/repositories/pdf_repository.dart';
|
import 'package:pdf_signature/data/repositories/document_repository.dart';
|
||||||
import '_world.dart';
|
import '_world.dart';
|
||||||
|
|
||||||
/// Usage: the user clicks the thumbnail for page {2}
|
/// Usage: the user clicks the thumbnail for page {2}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import 'package:flutter_test/flutter_test.dart';
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
import 'package:pdf_signature/data/repositories/pdf_repository.dart';
|
import 'package:pdf_signature/data/repositories/document_repository.dart';
|
||||||
import '_world.dart';
|
import '_world.dart';
|
||||||
|
|
||||||
/// Usage: the user deletes one selected signature placement
|
/// Usage: the user deletes one selected signature placement
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_test/flutter_test.dart';
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
import 'package:pdf_signature/data/repositories/pdf_repository.dart';
|
import 'package:pdf_signature/data/repositories/document_repository.dart';
|
||||||
import '_world.dart';
|
import '_world.dart';
|
||||||
|
|
||||||
/// Usage: the user drags handles to resize and drags to reposition
|
/// Usage: the user drags handles to resize and drags to reposition
|
||||||
|
|
|
@ -2,7 +2,7 @@ import 'dart:typed_data';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_test/flutter_test.dart';
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
import 'package:pdf_signature/data/repositories/pdf_repository.dart';
|
import 'package:pdf_signature/data/repositories/document_repository.dart';
|
||||||
import 'package:pdf_signature/data/repositories/signature_asset_repository.dart';
|
import 'package:pdf_signature/data/repositories/signature_asset_repository.dart';
|
||||||
import 'package:pdf_signature/domain/models/model.dart';
|
import 'package:pdf_signature/domain/models/model.dart';
|
||||||
import '_world.dart';
|
import '_world.dart';
|
||||||
|
|
|
@ -2,7 +2,7 @@ import 'dart:typed_data';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_test/flutter_test.dart';
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
import 'package:pdf_signature/data/repositories/pdf_repository.dart';
|
import 'package:pdf_signature/data/repositories/document_repository.dart';
|
||||||
import 'package:pdf_signature/data/repositories/signature_asset_repository.dart';
|
import 'package:pdf_signature/data/repositories/signature_asset_repository.dart';
|
||||||
import 'package:pdf_signature/domain/models/model.dart';
|
import 'package:pdf_signature/domain/models/model.dart';
|
||||||
import '_world.dart';
|
import '_world.dart';
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import 'dart:typed_data';
|
import 'dart:typed_data';
|
||||||
import 'package:flutter_test/flutter_test.dart';
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
import 'package:pdf_signature/data/repositories/signature_repository.dart';
|
import 'package:pdf_signature/data/repositories/signature_card_repository.dart';
|
||||||
import '_world.dart';
|
import '_world.dart';
|
||||||
|
|
||||||
/// Usage: the user draws strokes and confirms
|
/// Usage: the user draws strokes and confirms
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import 'package:flutter_test/flutter_test.dart';
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
import 'package:pdf_signature/data/repositories/signature_repository.dart';
|
import 'package:pdf_signature/data/repositories/signature_card_repository.dart';
|
||||||
import '_world.dart';
|
import '_world.dart';
|
||||||
|
|
||||||
/// Usage: the user enables background removal
|
/// Usage: the user enables background removal
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import 'package:flutter_test/flutter_test.dart';
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
import 'package:pdf_signature/data/repositories/pdf_repository.dart';
|
import 'package:pdf_signature/data/repositories/document_repository.dart';
|
||||||
import '_world.dart';
|
import '_world.dart';
|
||||||
|
|
||||||
/// Usage: the user enters {99} into the Go to input and applies it
|
/// Usage: the user enters {99} into the Go to input and applies it
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import 'package:flutter_test/flutter_test.dart';
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
import 'package:pdf_signature/data/repositories/signature_repository.dart';
|
import 'package:pdf_signature/data/repositories/signature_card_repository.dart';
|
||||||
import '_world.dart';
|
import '_world.dart';
|
||||||
|
|
||||||
/// Usage: the user is notified of the issue
|
/// Usage: the user is notified of the issue
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import 'package:flutter_test/flutter_test.dart';
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
import 'package:pdf_signature/data/repositories/pdf_repository.dart';
|
import 'package:pdf_signature/data/repositories/document_repository.dart';
|
||||||
import '_world.dart';
|
import '_world.dart';
|
||||||
|
|
||||||
/// Usage: the user jumps to page {2}
|
/// Usage: the user jumps to page {2}
|
||||||
|
|
|
@ -2,7 +2,7 @@ import 'dart:typed_data';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_test/flutter_test.dart';
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
import 'package:pdf_signature/data/repositories/pdf_repository.dart';
|
import 'package:pdf_signature/data/repositories/document_repository.dart';
|
||||||
import 'package:pdf_signature/domain/models/model.dart';
|
import 'package:pdf_signature/domain/models/model.dart';
|
||||||
import '_world.dart';
|
import '_world.dart';
|
||||||
|
|
||||||
|
|
|
@ -2,7 +2,7 @@ import 'dart:typed_data';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_test/flutter_test.dart';
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
import 'package:pdf_signature/data/repositories/pdf_repository.dart';
|
import 'package:pdf_signature/data/repositories/document_repository.dart';
|
||||||
import 'package:pdf_signature/data/repositories/signature_asset_repository.dart';
|
import 'package:pdf_signature/data/repositories/signature_asset_repository.dart';
|
||||||
import 'package:pdf_signature/domain/models/model.dart';
|
import 'package:pdf_signature/domain/models/model.dart';
|
||||||
import '_world.dart';
|
import '_world.dart';
|
||||||
|
|
|
@ -2,7 +2,7 @@ import 'dart:typed_data';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_test/flutter_test.dart';
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
import 'package:pdf_signature/data/repositories/pdf_repository.dart';
|
import 'package:pdf_signature/data/repositories/document_repository.dart';
|
||||||
import 'package:pdf_signature/domain/models/model.dart';
|
import 'package:pdf_signature/domain/models/model.dart';
|
||||||
import '_world.dart';
|
import '_world.dart';
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import 'package:flutter_test/flutter_test.dart';
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:pdf_signature/data/repositories/pdf_repository.dart';
|
import 'package:pdf_signature/data/repositories/document_repository.dart';
|
||||||
import '_world.dart';
|
import '_world.dart';
|
||||||
|
|
||||||
/// Usage: the user places it in multiple locations in the document
|
/// Usage: the user places it in multiple locations in the document
|
||||||
|
|
|
@ -2,7 +2,7 @@ import 'dart:typed_data';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_test/flutter_test.dart';
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
import 'package:pdf_signature/data/repositories/pdf_repository.dart';
|
import 'package:pdf_signature/data/repositories/document_repository.dart';
|
||||||
import 'package:pdf_signature/domain/models/model.dart';
|
import 'package:pdf_signature/domain/models/model.dart';
|
||||||
import '_world.dart';
|
import '_world.dart';
|
||||||
|
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
import 'dart:typed_data';
|
import 'dart:typed_data';
|
||||||
import 'package:flutter_test/flutter_test.dart';
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
import 'package:pdf_signature/data/repositories/signature_repository.dart';
|
import 'package:pdf_signature/data/repositories/signature_card_repository.dart';
|
||||||
import 'package:pdf_signature/data/repositories/pdf_repository.dart';
|
import 'package:pdf_signature/data/repositories/document_repository.dart';
|
||||||
import '_world.dart';
|
import '_world.dart';
|
||||||
|
|
||||||
/// Usage: the user saves/exports the document
|
/// Usage: the user saves/exports the document
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import 'package:flutter_test/flutter_test.dart';
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
import 'package:pdf_signature/data/repositories/pdf_repository.dart';
|
import 'package:pdf_signature/data/repositories/document_repository.dart';
|
||||||
import '_world.dart';
|
import '_world.dart';
|
||||||
|
|
||||||
/// Usage: the user selects "<file>"
|
/// Usage: the user selects "<file>"
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import 'package:flutter_test/flutter_test.dart';
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
import 'package:pdf_signature/data/repositories/pdf_repository.dart';
|
import 'package:pdf_signature/data/repositories/document_repository.dart';
|
||||||
import '_world.dart';
|
import '_world.dart';
|
||||||
|
|
||||||
/// Usage: the user types {3} into the Go to input and presses Enter
|
/// Usage: the user types {3} into the Go to input and presses Enter
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import 'package:flutter_test/flutter_test.dart';
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
import 'package:pdf_signature/data/repositories/pdf_repository.dart';
|
import 'package:pdf_signature/data/repositories/document_repository.dart';
|
||||||
import '_world.dart';
|
import '_world.dart';
|
||||||
|
|
||||||
/// Usage: the user uses rotate controls
|
/// Usage: the user uses rotate controls
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue