Compare commits
No commits in common. "c152531b20094dc8613b9bf4d46a80be712c77b3" and "a354efb0b1a8b93928da654ed8543c7d3db9a9dc" have entirely different histories.
c152531b20
...
a354efb0b1
|
@ -32,8 +32,7 @@ flutter run -d <device_id>
|
||||||
|
|
||||||
### build
|
### build
|
||||||
|
|
||||||
#### Windows
|
For Windows
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
flutter build windows
|
flutter build windows
|
||||||
# create windows installer
|
# create windows installer
|
||||||
|
|
|
@ -7,9 +7,7 @@ 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/ui/features/pdf/view_model/signature_library.dart';
|
import 'package:pdf_signature/ui/features/pdf/view_model/view_model.dart';
|
||||||
import 'package:pdf_signature/ui/features/pdf/view_model/signature_controller.dart';
|
|
||||||
import 'package:pdf_signature/ui/features/pdf/view_model/pdf_controller.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';
|
||||||
|
|
||||||
|
|
|
@ -3,7 +3,7 @@ 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/ui/features/pdf/view_model/pdf_controller.dart';
|
import 'package:pdf_signature/ui/features/pdf/view_model/view_model.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/services/preferences_providers.dart';
|
||||||
import 'package:pdf_signature/ui/features/preferences/widgets/settings_screen.dart';
|
import 'package:pdf_signature/ui/features/preferences/widgets/settings_screen.dart';
|
||||||
|
|
|
@ -171,14 +171,7 @@ class ExportService {
|
||||||
pw.Positioned(
|
pw.Positioned(
|
||||||
left: left,
|
left: left,
|
||||||
top: top,
|
top: top,
|
||||||
child: pw.SizedBox(
|
child: pw.Image(imgObj, width: w, height: h),
|
||||||
width: w,
|
|
||||||
height: h,
|
|
||||||
child: pw.FittedBox(
|
|
||||||
fit: pw.BoxFit.contain,
|
|
||||||
child: pw.Image(imgObj),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -194,14 +187,7 @@ class ExportService {
|
||||||
pw.Positioned(
|
pw.Positioned(
|
||||||
left: left,
|
left: left,
|
||||||
top: top,
|
top: top,
|
||||||
child: pw.SizedBox(
|
child: pw.Image(sigImgObj, width: w, height: h),
|
||||||
width: w,
|
|
||||||
height: h,
|
|
||||||
child: pw.FittedBox(
|
|
||||||
fit: pw.BoxFit.contain,
|
|
||||||
child: pw.Image(sigImgObj),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -308,14 +294,7 @@ class ExportService {
|
||||||
pw.Positioned(
|
pw.Positioned(
|
||||||
left: left,
|
left: left,
|
||||||
top: top,
|
top: top,
|
||||||
child: pw.SizedBox(
|
child: pw.Image(imgObj, width: w, height: h),
|
||||||
width: w,
|
|
||||||
height: h,
|
|
||||||
child: pw.FittedBox(
|
|
||||||
fit: pw.BoxFit.contain,
|
|
||||||
child: pw.Image(imgObj),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -331,14 +310,7 @@ class ExportService {
|
||||||
pw.Positioned(
|
pw.Positioned(
|
||||||
left: left,
|
left: left,
|
||||||
top: top,
|
top: top,
|
||||||
child: pw.SizedBox(
|
child: pw.Image(sigImgObj, width: w, height: h),
|
||||||
width: w,
|
|
||||||
height: h,
|
|
||||||
child: pw.FittedBox(
|
|
||||||
fit: pw.BoxFit.contain,
|
|
||||||
child: pw.Image(sigImgObj),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,172 +0,0 @@
|
||||||
import 'dart:typed_data';
|
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
|
||||||
|
|
||||||
import '../../../../data/model/model.dart';
|
|
||||||
|
|
||||||
class PdfController extends StateNotifier<PdfState> {
|
|
||||||
PdfController() : super(PdfState.initial());
|
|
||||||
static const int samplePageCount = 5;
|
|
||||||
|
|
||||||
@visibleForTesting
|
|
||||||
void openSample() {
|
|
||||||
state = state.copyWith(
|
|
||||||
loaded: true,
|
|
||||||
pageCount: samplePageCount,
|
|
||||||
currentPage: 1,
|
|
||||||
pickedPdfPath: null,
|
|
||||||
signedPage: null,
|
|
||||||
placementsByPage: {},
|
|
||||||
placementImageByPage: {},
|
|
||||||
selectedPlacementIndex: null,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
void openPicked({
|
|
||||||
required String path,
|
|
||||||
int pageCount = samplePageCount,
|
|
||||||
Uint8List? bytes,
|
|
||||||
}) {
|
|
||||||
state = state.copyWith(
|
|
||||||
loaded: true,
|
|
||||||
pageCount: pageCount,
|
|
||||||
currentPage: 1,
|
|
||||||
pickedPdfPath: path,
|
|
||||||
pickedPdfBytes: bytes,
|
|
||||||
signedPage: null,
|
|
||||||
placementsByPage: {},
|
|
||||||
placementImageByPage: {},
|
|
||||||
selectedPlacementIndex: null,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
void jumpTo(int page) {
|
|
||||||
if (!state.loaded) return;
|
|
||||||
final clamped = page.clamp(1, state.pageCount);
|
|
||||||
state = state.copyWith(currentPage: clamped, selectedPlacementIndex: null);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set or clear the page that will receive the signature overlay.
|
|
||||||
void setSignedPage(int? page) {
|
|
||||||
if (!state.loaded) return;
|
|
||||||
if (page == null) {
|
|
||||||
state = state.copyWith(signedPage: null, selectedPlacementIndex: null);
|
|
||||||
} else {
|
|
||||||
final clamped = page.clamp(1, state.pageCount);
|
|
||||||
state = state.copyWith(signedPage: clamped, selectedPlacementIndex: null);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void setPageCount(int count) {
|
|
||||||
if (!state.loaded) return;
|
|
||||||
state = state.copyWith(pageCount: count.clamp(1, 9999));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Multiple-signature helpers (rects are stored in normalized fractions 0..1
|
|
||||||
// relative to the page size: left/top/width/height are all 0..1)
|
|
||||||
void addPlacement({
|
|
||||||
required int page,
|
|
||||||
required Rect rect,
|
|
||||||
String image = 'default.png',
|
|
||||||
}) {
|
|
||||||
if (!state.loaded) return;
|
|
||||||
final p = page.clamp(1, state.pageCount);
|
|
||||||
final map = Map<int, List<Rect>>.from(state.placementsByPage);
|
|
||||||
final list = List<Rect>.from(map[p] ?? const []);
|
|
||||||
list.add(rect);
|
|
||||||
map[p] = list;
|
|
||||||
// Sync image mapping list
|
|
||||||
final imgMap = Map<int, List<String>>.from(state.placementImageByPage);
|
|
||||||
final imgList = List<String>.from(imgMap[p] ?? const []);
|
|
||||||
imgList.add(image);
|
|
||||||
imgMap[p] = imgList;
|
|
||||||
state = state.copyWith(
|
|
||||||
placementsByPage: map,
|
|
||||||
placementImageByPage: imgMap,
|
|
||||||
selectedPlacementIndex: null,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
void removePlacement({required int page, required int index}) {
|
|
||||||
if (!state.loaded) return;
|
|
||||||
final p = page.clamp(1, state.pageCount);
|
|
||||||
final map = Map<int, List<Rect>>.from(state.placementsByPage);
|
|
||||||
final list = List<Rect>.from(map[p] ?? const []);
|
|
||||||
if (index >= 0 && index < list.length) {
|
|
||||||
list.removeAt(index);
|
|
||||||
// Sync image mapping
|
|
||||||
final imgMap = Map<int, List<String>>.from(state.placementImageByPage);
|
|
||||||
final imgList = List<String>.from(imgMap[p] ?? const []);
|
|
||||||
if (index >= 0 && index < imgList.length) {
|
|
||||||
imgList.removeAt(index);
|
|
||||||
}
|
|
||||||
if (list.isEmpty) {
|
|
||||||
map.remove(p);
|
|
||||||
imgMap.remove(p);
|
|
||||||
} else {
|
|
||||||
map[p] = list;
|
|
||||||
imgMap[p] = imgList;
|
|
||||||
}
|
|
||||||
state = state.copyWith(
|
|
||||||
placementsByPage: map,
|
|
||||||
placementImageByPage: imgMap,
|
|
||||||
selectedPlacementIndex: null,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update the rect of an existing placement on a page.
|
|
||||||
void updatePlacementRect({
|
|
||||||
required int page,
|
|
||||||
required int index,
|
|
||||||
required Rect rect,
|
|
||||||
}) {
|
|
||||||
if (!state.loaded) return;
|
|
||||||
final p = page.clamp(1, state.pageCount);
|
|
||||||
final map = Map<int, List<Rect>>.from(state.placementsByPage);
|
|
||||||
final list = List<Rect>.from(map[p] ?? const []);
|
|
||||||
if (index >= 0 && index < list.length) {
|
|
||||||
list[index] = rect;
|
|
||||||
map[p] = list;
|
|
||||||
state = state.copyWith(placementsByPage: map);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
List<Rect> placementsOn(int page) {
|
|
||||||
return List<Rect>.from(state.placementsByPage[page] ?? const []);
|
|
||||||
}
|
|
||||||
|
|
||||||
void selectPlacement(int? index) {
|
|
||||||
if (!state.loaded) return;
|
|
||||||
// Only allow valid index on current page; otherwise clear
|
|
||||||
if (index == null) {
|
|
||||||
state = state.copyWith(selectedPlacementIndex: null);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
final list = state.placementsByPage[state.currentPage] ?? const [];
|
|
||||||
if (index >= 0 && index < list.length) {
|
|
||||||
state = state.copyWith(selectedPlacementIndex: index);
|
|
||||||
} else {
|
|
||||||
state = state.copyWith(selectedPlacementIndex: null);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void deleteSelectedPlacement() {
|
|
||||||
final idx = state.selectedPlacementIndex;
|
|
||||||
if (idx == null) return;
|
|
||||||
removePlacement(page: state.currentPage, index: idx);
|
|
||||||
}
|
|
||||||
|
|
||||||
// NOTE: Programmatic reassignment of images has been removed.
|
|
||||||
|
|
||||||
// Convenience to get image name for a placement
|
|
||||||
String? imageOfPlacement({required int page, required int index}) {
|
|
||||||
final list = state.placementImageByPage[page] ?? const [];
|
|
||||||
if (index < 0 || index >= list.length) return null;
|
|
||||||
return list[index];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
final pdfProvider = StateNotifierProvider<PdfController, PdfState>(
|
|
||||||
(ref) => PdfController(),
|
|
||||||
);
|
|
|
@ -1,40 +0,0 @@
|
||||||
import 'dart:typed_data';
|
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
|
||||||
|
|
||||||
/// A simple library of signature images available to the user in the sidebar.
|
|
||||||
class SignatureAsset {
|
|
||||||
final String id; // unique id
|
|
||||||
final Uint8List bytes;
|
|
||||||
final String? name; // optional display name (e.g., filename)
|
|
||||||
const SignatureAsset({required this.id, required this.bytes, this.name});
|
|
||||||
}
|
|
||||||
|
|
||||||
class SignatureLibraryController extends StateNotifier<List<SignatureAsset>> {
|
|
||||||
SignatureLibraryController() : super(const []);
|
|
||||||
|
|
||||||
String add(Uint8List bytes, {String? name}) {
|
|
||||||
// Always add a new asset (allow duplicates). This lets users create multiple cards
|
|
||||||
// even when loading the same image repeatedly for different adjustments/usages.
|
|
||||||
if (bytes.isEmpty) return '';
|
|
||||||
final id = DateTime.now().microsecondsSinceEpoch.toString();
|
|
||||||
state = List.of(state)
|
|
||||||
..add(SignatureAsset(id: id, bytes: bytes, name: name));
|
|
||||||
return id;
|
|
||||||
}
|
|
||||||
|
|
||||||
void remove(String id) {
|
|
||||||
state = state.where((a) => a.id != id).toList(growable: false);
|
|
||||||
}
|
|
||||||
|
|
||||||
SignatureAsset? byId(String id) {
|
|
||||||
for (final a in state) {
|
|
||||||
if (a.id == id) return a;
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
final signatureLibraryProvider =
|
|
||||||
StateNotifierProvider<SignatureLibraryController, List<SignatureAsset>>(
|
|
||||||
(ref) => SignatureLibraryController(),
|
|
||||||
);
|
|
|
@ -7,8 +7,211 @@ import 'package:image/image.dart' as img;
|
||||||
import 'package:pdf_signature/l10n/app_localizations.dart';
|
import 'package:pdf_signature/l10n/app_localizations.dart';
|
||||||
|
|
||||||
import '../../../../data/model/model.dart';
|
import '../../../../data/model/model.dart';
|
||||||
import 'pdf_controller.dart';
|
|
||||||
import 'signature_library.dart';
|
class PdfController extends StateNotifier<PdfState> {
|
||||||
|
PdfController() : super(PdfState.initial());
|
||||||
|
static const int samplePageCount = 5;
|
||||||
|
|
||||||
|
@visibleForTesting
|
||||||
|
void openSample() {
|
||||||
|
state = state.copyWith(
|
||||||
|
loaded: true,
|
||||||
|
pageCount: samplePageCount,
|
||||||
|
currentPage: 1,
|
||||||
|
pickedPdfPath: null,
|
||||||
|
signedPage: null,
|
||||||
|
placementsByPage: {},
|
||||||
|
placementImageByPage: {},
|
||||||
|
selectedPlacementIndex: null,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
void openPicked({
|
||||||
|
required String path,
|
||||||
|
int pageCount = samplePageCount,
|
||||||
|
Uint8List? bytes,
|
||||||
|
}) {
|
||||||
|
state = state.copyWith(
|
||||||
|
loaded: true,
|
||||||
|
pageCount: pageCount,
|
||||||
|
currentPage: 1,
|
||||||
|
pickedPdfPath: path,
|
||||||
|
pickedPdfBytes: bytes,
|
||||||
|
signedPage: null,
|
||||||
|
placementsByPage: {},
|
||||||
|
placementImageByPage: {},
|
||||||
|
selectedPlacementIndex: null,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
void jumpTo(int page) {
|
||||||
|
if (!state.loaded) return;
|
||||||
|
final clamped = page.clamp(1, state.pageCount);
|
||||||
|
state = state.copyWith(currentPage: clamped, selectedPlacementIndex: null);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set or clear the page that will receive the signature overlay.
|
||||||
|
void setSignedPage(int? page) {
|
||||||
|
if (!state.loaded) return;
|
||||||
|
if (page == null) {
|
||||||
|
state = state.copyWith(signedPage: null, selectedPlacementIndex: null);
|
||||||
|
} else {
|
||||||
|
final clamped = page.clamp(1, state.pageCount);
|
||||||
|
state = state.copyWith(signedPage: clamped, selectedPlacementIndex: null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void setPageCount(int count) {
|
||||||
|
if (!state.loaded) return;
|
||||||
|
state = state.copyWith(pageCount: count.clamp(1, 9999));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Multiple-signature helpers (rects are stored in normalized fractions 0..1
|
||||||
|
// relative to the page size: left/top/width/height are all 0..1)
|
||||||
|
void addPlacement({
|
||||||
|
required int page,
|
||||||
|
required Rect rect,
|
||||||
|
String image = 'default.png',
|
||||||
|
}) {
|
||||||
|
if (!state.loaded) return;
|
||||||
|
final p = page.clamp(1, state.pageCount);
|
||||||
|
final map = Map<int, List<Rect>>.from(state.placementsByPage);
|
||||||
|
final list = List<Rect>.from(map[p] ?? const []);
|
||||||
|
list.add(rect);
|
||||||
|
map[p] = list;
|
||||||
|
// Sync image mapping list
|
||||||
|
final imgMap = Map<int, List<String>>.from(state.placementImageByPage);
|
||||||
|
final imgList = List<String>.from(imgMap[p] ?? const []);
|
||||||
|
imgList.add(image);
|
||||||
|
imgMap[p] = imgList;
|
||||||
|
state = state.copyWith(
|
||||||
|
placementsByPage: map,
|
||||||
|
placementImageByPage: imgMap,
|
||||||
|
selectedPlacementIndex: null,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
void removePlacement({required int page, required int index}) {
|
||||||
|
if (!state.loaded) return;
|
||||||
|
final p = page.clamp(1, state.pageCount);
|
||||||
|
final map = Map<int, List<Rect>>.from(state.placementsByPage);
|
||||||
|
final list = List<Rect>.from(map[p] ?? const []);
|
||||||
|
if (index >= 0 && index < list.length) {
|
||||||
|
list.removeAt(index);
|
||||||
|
// Sync image mapping
|
||||||
|
final imgMap = Map<int, List<String>>.from(state.placementImageByPage);
|
||||||
|
final imgList = List<String>.from(imgMap[p] ?? const []);
|
||||||
|
if (index >= 0 && index < imgList.length) {
|
||||||
|
imgList.removeAt(index);
|
||||||
|
}
|
||||||
|
if (list.isEmpty) {
|
||||||
|
map.remove(p);
|
||||||
|
imgMap.remove(p);
|
||||||
|
} else {
|
||||||
|
map[p] = list;
|
||||||
|
imgMap[p] = imgList;
|
||||||
|
}
|
||||||
|
state = state.copyWith(
|
||||||
|
placementsByPage: map,
|
||||||
|
placementImageByPage: imgMap,
|
||||||
|
selectedPlacementIndex: null,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update the rect of an existing placement on a page.
|
||||||
|
void updatePlacementRect({
|
||||||
|
required int page,
|
||||||
|
required int index,
|
||||||
|
required Rect rect,
|
||||||
|
}) {
|
||||||
|
if (!state.loaded) return;
|
||||||
|
final p = page.clamp(1, state.pageCount);
|
||||||
|
final map = Map<int, List<Rect>>.from(state.placementsByPage);
|
||||||
|
final list = List<Rect>.from(map[p] ?? const []);
|
||||||
|
if (index >= 0 && index < list.length) {
|
||||||
|
list[index] = rect;
|
||||||
|
map[p] = list;
|
||||||
|
state = state.copyWith(placementsByPage: map);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
List<Rect> placementsOn(int page) {
|
||||||
|
return List<Rect>.from(state.placementsByPage[page] ?? const []);
|
||||||
|
}
|
||||||
|
|
||||||
|
void selectPlacement(int? index) {
|
||||||
|
if (!state.loaded) return;
|
||||||
|
// Only allow valid index on current page; otherwise clear
|
||||||
|
if (index == null) {
|
||||||
|
state = state.copyWith(selectedPlacementIndex: null);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
final list = state.placementsByPage[state.currentPage] ?? const [];
|
||||||
|
if (index >= 0 && index < list.length) {
|
||||||
|
state = state.copyWith(selectedPlacementIndex: index);
|
||||||
|
} else {
|
||||||
|
state = state.copyWith(selectedPlacementIndex: null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void deleteSelectedPlacement() {
|
||||||
|
final idx = state.selectedPlacementIndex;
|
||||||
|
if (idx == null) return;
|
||||||
|
removePlacement(page: state.currentPage, index: idx);
|
||||||
|
}
|
||||||
|
|
||||||
|
// NOTE: Programmatic reassignment of images has been removed.
|
||||||
|
|
||||||
|
// Convenience to get image name for a placement
|
||||||
|
String? imageOfPlacement({required int page, required int index}) {
|
||||||
|
final list = state.placementImageByPage[page] ?? const [];
|
||||||
|
if (index < 0 || index >= list.length) return null;
|
||||||
|
return list[index];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
final pdfProvider = StateNotifierProvider<PdfController, PdfState>(
|
||||||
|
(ref) => PdfController(),
|
||||||
|
);
|
||||||
|
|
||||||
|
/// A simple library of signature images available to the user in the sidebar.
|
||||||
|
class SignatureAsset {
|
||||||
|
final String id; // unique id
|
||||||
|
final Uint8List bytes;
|
||||||
|
final String? name; // optional display name (e.g., filename)
|
||||||
|
const SignatureAsset({required this.id, required this.bytes, this.name});
|
||||||
|
}
|
||||||
|
|
||||||
|
class SignatureLibraryController extends StateNotifier<List<SignatureAsset>> {
|
||||||
|
SignatureLibraryController() : super(const []);
|
||||||
|
|
||||||
|
String add(Uint8List bytes, {String? name}) {
|
||||||
|
// Always add a new asset (allow duplicates). This lets users create multiple cards
|
||||||
|
// even when loading the same image repeatedly for different adjustments/usages.
|
||||||
|
if (bytes.isEmpty) return '';
|
||||||
|
final id = DateTime.now().microsecondsSinceEpoch.toString();
|
||||||
|
state = List.of(state)
|
||||||
|
..add(SignatureAsset(id: id, bytes: bytes, name: name));
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
void remove(String id) {
|
||||||
|
state = state.where((a) => a.id != id).toList(growable: false);
|
||||||
|
}
|
||||||
|
|
||||||
|
SignatureAsset? byId(String id) {
|
||||||
|
for (final a in state) {
|
||||||
|
if (a.id == id) return a;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
final signatureLibraryProvider =
|
||||||
|
StateNotifierProvider<SignatureLibraryController, List<SignatureAsset>>(
|
||||||
|
(ref) => SignatureLibraryController(),
|
||||||
|
);
|
||||||
|
|
||||||
class SignatureController extends StateNotifier<SignatureState> {
|
class SignatureController extends StateNotifier<SignatureState> {
|
||||||
SignatureController() : super(SignatureState.initial());
|
SignatureController() : super(SignatureState.initial());
|
||||||
|
@ -182,31 +385,21 @@ class SignatureController extends StateNotifier<SignatureState> {
|
||||||
// Bind the processed image at placement time (so placed preview matches adjustments).
|
// Bind the processed image at placement time (so placed preview matches adjustments).
|
||||||
// If processed bytes exist, always create a new asset for this placement.
|
// If processed bytes exist, always create a new asset for this placement.
|
||||||
String id = '';
|
String id = '';
|
||||||
// Compose final bytes for placement: apply adjustments (processed) then rotation.
|
final processed = ref.read(processedSignatureImageProvider);
|
||||||
Uint8List? srcBytes = ref.read(processedSignatureImageProvider);
|
if (processed != null && processed.isNotEmpty) {
|
||||||
srcBytes ??= state.imageBytes;
|
|
||||||
// If still null, fall back to asset reference only.
|
|
||||||
if (srcBytes != null && srcBytes.isNotEmpty) {
|
|
||||||
final rot = state.rotation % 360;
|
|
||||||
Uint8List finalBytes = srcBytes;
|
|
||||||
if (rot != 0) {
|
|
||||||
try {
|
|
||||||
final decoded = img.decodeImage(srcBytes);
|
|
||||||
if (decoded != null) {
|
|
||||||
var out = img.copyRotate(
|
|
||||||
decoded,
|
|
||||||
angle: rot,
|
|
||||||
interpolation: img.Interpolation.linear,
|
|
||||||
);
|
|
||||||
finalBytes = Uint8List.fromList(img.encodePng(out, level: 6));
|
|
||||||
}
|
|
||||||
} catch (_) {}
|
|
||||||
}
|
|
||||||
id = ref
|
id = ref
|
||||||
.read(signatureLibraryProvider.notifier)
|
.read(signatureLibraryProvider.notifier)
|
||||||
.add(finalBytes, name: 'image');
|
.add(processed, name: 'image');
|
||||||
} else {
|
} else {
|
||||||
id = state.assetId ?? 'default.png';
|
// Fallback to current image source
|
||||||
|
final bytes = state.imageBytes;
|
||||||
|
if (bytes != null && bytes.isNotEmpty) {
|
||||||
|
id = ref
|
||||||
|
.read(signatureLibraryProvider.notifier)
|
||||||
|
.add(bytes, name: 'image');
|
||||||
|
} else {
|
||||||
|
id = state.assetId ?? 'default.png';
|
||||||
|
}
|
||||||
}
|
}
|
||||||
// Store as UI-space rect (consistent with export and rendering paths)
|
// Store as UI-space rect (consistent with export and rendering paths)
|
||||||
ref
|
ref
|
||||||
|
@ -233,29 +426,20 @@ class SignatureController extends StateNotifier<SignatureState> {
|
||||||
final pdf = container.read(pdfProvider);
|
final pdf = container.read(pdfProvider);
|
||||||
if (!pdf.loaded) return null;
|
if (!pdf.loaded) return null;
|
||||||
String id = '';
|
String id = '';
|
||||||
Uint8List? srcBytes = container.read(processedSignatureImageProvider);
|
final processed = container.read(processedSignatureImageProvider);
|
||||||
srcBytes ??= state.imageBytes;
|
if (processed != null && processed.isNotEmpty) {
|
||||||
if (srcBytes != null && srcBytes.isNotEmpty) {
|
|
||||||
final rot = state.rotation % 360;
|
|
||||||
Uint8List finalBytes = srcBytes;
|
|
||||||
if (rot != 0) {
|
|
||||||
try {
|
|
||||||
final decoded = img.decodeImage(srcBytes);
|
|
||||||
if (decoded != null) {
|
|
||||||
var out = img.copyRotate(
|
|
||||||
decoded,
|
|
||||||
angle: rot,
|
|
||||||
interpolation: img.Interpolation.linear,
|
|
||||||
);
|
|
||||||
finalBytes = Uint8List.fromList(img.encodePng(out, level: 6));
|
|
||||||
}
|
|
||||||
} catch (_) {}
|
|
||||||
}
|
|
||||||
id = container
|
id = container
|
||||||
.read(signatureLibraryProvider.notifier)
|
.read(signatureLibraryProvider.notifier)
|
||||||
.add(finalBytes, name: 'image');
|
.add(processed, name: 'image');
|
||||||
} else {
|
} else {
|
||||||
id = state.assetId ?? 'default.png';
|
final bytes = state.imageBytes;
|
||||||
|
if (bytes != null && bytes.isNotEmpty) {
|
||||||
|
id = container
|
||||||
|
.read(signatureLibraryProvider.notifier)
|
||||||
|
.add(bytes, name: 'image');
|
||||||
|
} else {
|
||||||
|
id = state.assetId ?? 'default.png';
|
||||||
|
}
|
||||||
}
|
}
|
||||||
container
|
container
|
||||||
.read(pdfProvider.notifier)
|
.read(pdfProvider.notifier)
|
||||||
|
@ -289,33 +473,19 @@ final signatureProvider =
|
||||||
/// current adjustment settings (contrast/brightness) and background removal.
|
/// current adjustment settings (contrast/brightness) and background removal.
|
||||||
/// Returns null if no image is loaded. The output is a PNG to preserve alpha.
|
/// Returns null if no image is loaded. The output is a PNG to preserve alpha.
|
||||||
final processedSignatureImageProvider = Provider<Uint8List?>((ref) {
|
final processedSignatureImageProvider = Provider<Uint8List?>((ref) {
|
||||||
// Watch only the fields that affect pixel processing to avoid recompute on rotation.
|
final s = ref.watch(signatureProvider);
|
||||||
final String? assetId = ref.watch(signatureProvider.select((s) => s.assetId));
|
|
||||||
final Uint8List? directBytes = ref.watch(
|
|
||||||
signatureProvider.select((s) => s.imageBytes),
|
|
||||||
);
|
|
||||||
final double contrast = ref.watch(
|
|
||||||
signatureProvider.select((s) => s.contrast),
|
|
||||||
);
|
|
||||||
final double brightness = ref.watch(
|
|
||||||
signatureProvider.select((s) => s.brightness),
|
|
||||||
);
|
|
||||||
final bool bgRemoval = ref.watch(
|
|
||||||
signatureProvider.select((s) => s.bgRemoval),
|
|
||||||
);
|
|
||||||
|
|
||||||
// If active overlay is based on a library asset, pull its bytes
|
// If active overlay is based on a library asset, pull its bytes
|
||||||
Uint8List? bytes;
|
Uint8List? bytes;
|
||||||
if (assetId != null) {
|
if (s.assetId != null) {
|
||||||
final lib = ref.watch(signatureLibraryProvider);
|
final lib = ref.watch(signatureLibraryProvider);
|
||||||
for (final a in lib) {
|
for (final a in lib) {
|
||||||
if (a.id == assetId) {
|
if (a.id == s.assetId) {
|
||||||
bytes = a.bytes;
|
bytes = a.bytes;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
bytes = directBytes;
|
bytes = s.imageBytes;
|
||||||
}
|
}
|
||||||
if (bytes == null || bytes.isEmpty) return null;
|
if (bytes == null || bytes.isEmpty) return null;
|
||||||
|
|
||||||
|
@ -331,7 +501,9 @@ final processedSignatureImageProvider = Provider<Uint8List?>((ref) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Parameters
|
// Parameters
|
||||||
// Rotation is not applied here (UI uses Transform; export applies once).
|
final double contrast = s.contrast; // [0..2], 1 = neutral
|
||||||
|
final double brightness = s.brightness; // [-1..1], 0 = neutral
|
||||||
|
final double rotationDeg = s.rotation; // degrees
|
||||||
const int thrLow = 220; // begin soft transparency from this avg luminance
|
const int thrLow = 220; // begin soft transparency from this avg luminance
|
||||||
const int thrHigh = 245; // fully transparent from this avg luminance
|
const int thrHigh = 245; // fully transparent from this avg luminance
|
||||||
|
|
||||||
|
@ -357,7 +529,7 @@ final processedSignatureImageProvider = Provider<Uint8List?>((ref) {
|
||||||
// Near-white background removal (compute average luminance)
|
// Near-white background removal (compute average luminance)
|
||||||
final int avg = ((r + g + b) / 3).round();
|
final int avg = ((r + g + b) / 3).round();
|
||||||
int remAlpha = 255; // 255 = fully opaque, 0 = transparent
|
int remAlpha = 255; // 255 = fully opaque, 0 = transparent
|
||||||
if (bgRemoval) {
|
if (s.bgRemoval) {
|
||||||
if (avg >= thrHigh) {
|
if (avg >= thrHigh) {
|
||||||
remAlpha = 0;
|
remAlpha = 0;
|
||||||
} else if (avg >= thrLow) {
|
} else if (avg >= thrLow) {
|
||||||
|
@ -376,9 +548,15 @@ final processedSignatureImageProvider = Provider<Uint8List?>((ref) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// NOTE: Do not rotate here to keep UI responsive while dragging the slider.
|
// Apply rotation if any (around center) using bilinear interpolation and keep size
|
||||||
// Rotation is applied in the UI using Transform.rotate for preview and
|
if (rotationDeg % 360 != 0) {
|
||||||
// performed once on confirm/export to avoid per-frame recomputation.
|
// The image package rotates counter-clockwise; positive degrees rotate CCW
|
||||||
|
out = img.copyRotate(
|
||||||
|
out,
|
||||||
|
angle: rotationDeg,
|
||||||
|
interpolation: img.Interpolation.linear,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
// Encode as PNG to preserve transparency
|
// Encode as PNG to preserve transparency
|
||||||
final png = img.encodePng(out, level: 6);
|
final png = img.encodePng(out, level: 6);
|
|
@ -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 '../../../../data/model/model.dart';
|
import '../../../../data/model/model.dart';
|
||||||
import '../view_model/signature_controller.dart';
|
import '../view_model/view_model.dart';
|
||||||
|
|
||||||
class AdjustmentsPanel extends ConsumerWidget {
|
class AdjustmentsPanel extends ConsumerWidget {
|
||||||
const AdjustmentsPanel({super.key, required this.sig});
|
const AdjustmentsPanel({super.key, required this.sig});
|
||||||
|
|
|
@ -2,9 +2,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 '../view_model/signature_controller.dart';
|
import '../view_model/view_model.dart';
|
||||||
import 'adjustments_panel.dart';
|
import 'adjustments_panel.dart';
|
||||||
import 'rotated_signature_image.dart';
|
|
||||||
|
|
||||||
class ImageEditorDialog extends ConsumerWidget {
|
class ImageEditorDialog extends ConsumerWidget {
|
||||||
const ImageEditorDialog({super.key});
|
const ImageEditorDialog({super.key});
|
||||||
|
@ -48,13 +47,7 @@ class ImageEditorDialog extends ConsumerWidget {
|
||||||
if (bytes == null) {
|
if (bytes == null) {
|
||||||
return Text(l.noSignatureLoaded);
|
return Text(l.noSignatureLoaded);
|
||||||
}
|
}
|
||||||
return RotatedSignatureImage(
|
return Image.memory(bytes, fit: BoxFit.contain);
|
||||||
bytes: bytes,
|
|
||||||
rotationDeg: sig.rotation,
|
|
||||||
enableAngleAwareScale: true,
|
|
||||||
fit: BoxFit.contain,
|
|
||||||
wrapInRepaintBoundary: true,
|
|
||||||
);
|
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
|
@ -4,8 +4,7 @@ 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 '../view_model/signature_controller.dart';
|
import '../view_model/view_model.dart';
|
||||||
import '../view_model/pdf_controller.dart';
|
|
||||||
import 'signature_drag_data.dart';
|
import '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,8 +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 '../view_model/signature_controller.dart';
|
import '../view_model/view_model.dart';
|
||||||
import '../view_model/pdf_controller.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.
|
||||||
|
|
|
@ -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 '../view_model/pdf_controller.dart';
|
import '../view_model/view_model.dart';
|
||||||
|
|
||||||
class PdfPagesOverview extends ConsumerWidget {
|
class PdfPagesOverview extends ConsumerWidget {
|
||||||
const PdfPagesOverview({super.key});
|
const PdfPagesOverview({super.key});
|
||||||
|
|
|
@ -9,10 +9,7 @@ import 'package:pdfrx/pdfrx.dart';
|
||||||
import 'package:multi_split_view/multi_split_view.dart';
|
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 '../view_model/view_model.dart';
|
||||||
import '../view_model/signature_controller.dart';
|
|
||||||
import '../view_model/pdf_controller.dart';
|
|
||||||
import '../view_model/signature_library.dart';
|
|
||||||
import 'draw_canvas.dart';
|
import 'draw_canvas.dart';
|
||||||
import 'pdf_toolbar.dart';
|
import 'pdf_toolbar.dart';
|
||||||
import 'pdf_page_area.dart';
|
import 'pdf_page_area.dart';
|
||||||
|
@ -140,39 +137,16 @@ class _PdfSignatureHomePageState extends ConsumerState<PdfSignatureHomePage> {
|
||||||
final useMock = ref.read(useMockViewerProvider);
|
final useMock = ref.read(useMockViewerProvider);
|
||||||
bool ok = false;
|
bool ok = false;
|
||||||
String? savedPath;
|
String? savedPath;
|
||||||
// Helper to apply rotation to bytes for export (single-signature path only)
|
|
||||||
Uint8List? _rotatedForExport(Uint8List? src, double deg) {
|
|
||||||
if (src == null || src.isEmpty) return src;
|
|
||||||
final r = deg % 360;
|
|
||||||
if (r == 0) return src;
|
|
||||||
try {
|
|
||||||
final decoded = img.decodeImage(src);
|
|
||||||
if (decoded == null) return src;
|
|
||||||
final out = img.copyRotate(
|
|
||||||
decoded,
|
|
||||||
angle: r,
|
|
||||||
interpolation: img.Interpolation.linear,
|
|
||||||
);
|
|
||||||
return Uint8List.fromList(img.encodePng(out, level: 6));
|
|
||||||
} catch (_) {
|
|
||||||
return src;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (kIsWeb) {
|
if (kIsWeb) {
|
||||||
Uint8List? src = pdf.pickedPdfBytes;
|
Uint8List? src = pdf.pickedPdfBytes;
|
||||||
if (src != null) {
|
if (src != null) {
|
||||||
final processed = ref.read(processedSignatureImageProvider);
|
final processed = ref.read(processedSignatureImageProvider);
|
||||||
final rotated = _rotatedForExport(
|
|
||||||
processed ?? sig.imageBytes,
|
|
||||||
sig.rotation,
|
|
||||||
);
|
|
||||||
final bytes = await exporter.exportSignedPdfFromBytes(
|
final bytes = await exporter.exportSignedPdfFromBytes(
|
||||||
srcBytes: src,
|
srcBytes: src,
|
||||||
signedPage: pdf.signedPage,
|
signedPage: pdf.signedPage,
|
||||||
signatureRectUi: sig.rect,
|
signatureRectUi: sig.rect,
|
||||||
uiPageSize: SignatureController.pageSize,
|
uiPageSize: SignatureController.pageSize,
|
||||||
signatureImageBytes: rotated,
|
signatureImageBytes: processed ?? sig.imageBytes,
|
||||||
placementsByPage: pdf.placementsByPage,
|
placementsByPage: pdf.placementsByPage,
|
||||||
placementImageByPage: pdf.placementImageByPage,
|
placementImageByPage: pdf.placementImageByPage,
|
||||||
libraryBytes: {
|
libraryBytes: {
|
||||||
|
@ -200,16 +174,12 @@ class _PdfSignatureHomePageState extends ConsumerState<PdfSignatureHomePage> {
|
||||||
savedPath = fullPath;
|
savedPath = fullPath;
|
||||||
if (pdf.pickedPdfBytes != null) {
|
if (pdf.pickedPdfBytes != null) {
|
||||||
final processed = ref.read(processedSignatureImageProvider);
|
final processed = ref.read(processedSignatureImageProvider);
|
||||||
final rotated = _rotatedForExport(
|
|
||||||
processed ?? sig.imageBytes,
|
|
||||||
sig.rotation,
|
|
||||||
);
|
|
||||||
final out = await exporter.exportSignedPdfFromBytes(
|
final out = await exporter.exportSignedPdfFromBytes(
|
||||||
srcBytes: pdf.pickedPdfBytes!,
|
srcBytes: pdf.pickedPdfBytes!,
|
||||||
signedPage: pdf.signedPage,
|
signedPage: pdf.signedPage,
|
||||||
signatureRectUi: sig.rect,
|
signatureRectUi: sig.rect,
|
||||||
uiPageSize: SignatureController.pageSize,
|
uiPageSize: SignatureController.pageSize,
|
||||||
signatureImageBytes: rotated,
|
signatureImageBytes: processed ?? sig.imageBytes,
|
||||||
placementsByPage: pdf.placementsByPage,
|
placementsByPage: pdf.placementsByPage,
|
||||||
placementImageByPage: pdf.placementImageByPage,
|
placementImageByPage: pdf.placementImageByPage,
|
||||||
libraryBytes: {
|
libraryBytes: {
|
||||||
|
@ -230,17 +200,13 @@ class _PdfSignatureHomePageState extends ConsumerState<PdfSignatureHomePage> {
|
||||||
ok = true;
|
ok = true;
|
||||||
} else {
|
} else {
|
||||||
final processed = ref.read(processedSignatureImageProvider);
|
final processed = ref.read(processedSignatureImageProvider);
|
||||||
final rotated = _rotatedForExport(
|
|
||||||
processed ?? sig.imageBytes,
|
|
||||||
sig.rotation,
|
|
||||||
);
|
|
||||||
ok = await exporter.exportSignedPdfFromFile(
|
ok = await exporter.exportSignedPdfFromFile(
|
||||||
inputPath: pdf.pickedPdfPath!,
|
inputPath: pdf.pickedPdfPath!,
|
||||||
outputPath: fullPath,
|
outputPath: fullPath,
|
||||||
signedPage: pdf.signedPage,
|
signedPage: pdf.signedPage,
|
||||||
signatureRectUi: sig.rect,
|
signatureRectUi: sig.rect,
|
||||||
uiPageSize: SignatureController.pageSize,
|
uiPageSize: SignatureController.pageSize,
|
||||||
signatureImageBytes: rotated,
|
signatureImageBytes: processed ?? sig.imageBytes,
|
||||||
placementsByPage: pdf.placementsByPage,
|
placementsByPage: pdf.placementsByPage,
|
||||||
placementImageByPage: pdf.placementImageByPage,
|
placementImageByPage: pdf.placementImageByPage,
|
||||||
libraryBytes: {
|
libraryBytes: {
|
||||||
|
|
|
@ -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 '../view_model/pdf_controller.dart';
|
import '../view_model/view_model.dart';
|
||||||
|
|
||||||
class PdfToolbar extends ConsumerStatefulWidget {
|
class PdfToolbar extends ConsumerStatefulWidget {
|
||||||
const PdfToolbar({
|
const PdfToolbar({
|
||||||
|
|
|
@ -1,132 +0,0 @@
|
||||||
import 'dart:math' as math;
|
|
||||||
import 'dart:typed_data';
|
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
|
|
||||||
/// A lightweight widget to render signature bytes with rotation and an
|
|
||||||
/// angle-aware scale-to-fit so the rotated image stays within its bounds.
|
|
||||||
class RotatedSignatureImage extends StatefulWidget {
|
|
||||||
const RotatedSignatureImage({
|
|
||||||
super.key,
|
|
||||||
required this.bytes,
|
|
||||||
this.rotationDeg = 0.0,
|
|
||||||
this.enableAngleAwareScale = true,
|
|
||||||
this.fit = BoxFit.contain,
|
|
||||||
this.gaplessPlayback = true,
|
|
||||||
this.filterQuality = FilterQuality.low,
|
|
||||||
this.wrapInRepaintBoundary = true,
|
|
||||||
this.alignment = Alignment.center,
|
|
||||||
this.semanticLabel,
|
|
||||||
this.intrinsicAspectRatio,
|
|
||||||
});
|
|
||||||
|
|
||||||
final Uint8List bytes;
|
|
||||||
final double rotationDeg;
|
|
||||||
final bool enableAngleAwareScale;
|
|
||||||
final BoxFit fit;
|
|
||||||
final bool gaplessPlayback;
|
|
||||||
final FilterQuality filterQuality;
|
|
||||||
final bool wrapInRepaintBoundary;
|
|
||||||
final AlignmentGeometry alignment;
|
|
||||||
final String? semanticLabel;
|
|
||||||
// Optional: intrinsic aspect ratio (width / height). If provided, we compute
|
|
||||||
// an angle-aware scale for non-square images to ensure the rotated rectangle
|
|
||||||
// (W,H) fits back into its (W,H) bounds. If null, we attempt to derive it
|
|
||||||
// from the image stream; only fall back to the square heuristic if unknown.
|
|
||||||
final double? intrinsicAspectRatio;
|
|
||||||
|
|
||||||
@override
|
|
||||||
State<RotatedSignatureImage> createState() => _RotatedSignatureImageState();
|
|
||||||
}
|
|
||||||
|
|
||||||
class _RotatedSignatureImageState extends State<RotatedSignatureImage> {
|
|
||||||
ImageStream? _stream;
|
|
||||||
ImageStreamListener? _listener;
|
|
||||||
double? _derivedAspectRatio; // width / height
|
|
||||||
|
|
||||||
MemoryImage get _provider => MemoryImage(widget.bytes);
|
|
||||||
|
|
||||||
@override
|
|
||||||
void didChangeDependencies() {
|
|
||||||
super.didChangeDependencies();
|
|
||||||
_resolveImage();
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
void didUpdateWidget(covariant RotatedSignatureImage oldWidget) {
|
|
||||||
super.didUpdateWidget(oldWidget);
|
|
||||||
if (!identical(oldWidget.bytes, widget.bytes)) {
|
|
||||||
_derivedAspectRatio = null;
|
|
||||||
_resolveImage();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void _resolveImage() {
|
|
||||||
_unlisten();
|
|
||||||
// Only derive AR if not provided
|
|
||||||
if (widget.intrinsicAspectRatio != null) return;
|
|
||||||
final stream = _provider.resolve(createLocalImageConfiguration(context));
|
|
||||||
_stream = stream;
|
|
||||||
_listener = ImageStreamListener((ImageInfo info, bool sync) {
|
|
||||||
final w = info.image.width;
|
|
||||||
final h = info.image.height;
|
|
||||||
if (w > 0 && h > 0) {
|
|
||||||
final ar = w / h;
|
|
||||||
if (mounted && _derivedAspectRatio != ar) {
|
|
||||||
setState(() => _derivedAspectRatio = ar);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
stream.addListener(_listener!);
|
|
||||||
}
|
|
||||||
|
|
||||||
void _unlisten() {
|
|
||||||
if (_stream != null && _listener != null) {
|
|
||||||
_stream!.removeListener(_listener!);
|
|
||||||
}
|
|
||||||
_stream = null;
|
|
||||||
_listener = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
void dispose() {
|
|
||||||
_unlisten();
|
|
||||||
super.dispose();
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
final angle = widget.rotationDeg * math.pi / 180.0;
|
|
||||||
Widget img = Image.memory(
|
|
||||||
widget.bytes,
|
|
||||||
fit: widget.fit,
|
|
||||||
gaplessPlayback: widget.gaplessPlayback,
|
|
||||||
filterQuality: widget.filterQuality,
|
|
||||||
alignment: widget.alignment,
|
|
||||||
semanticLabel: widget.semanticLabel,
|
|
||||||
);
|
|
||||||
|
|
||||||
if (angle != 0.0) {
|
|
||||||
if (widget.enableAngleAwareScale) {
|
|
||||||
final double c = math.cos(angle).abs();
|
|
||||||
final double s = math.sin(angle).abs();
|
|
||||||
final ar = widget.intrinsicAspectRatio ?? _derivedAspectRatio;
|
|
||||||
double scaleToFit;
|
|
||||||
if (ar != null && ar > 0) {
|
|
||||||
scaleToFit = math.min(ar / (ar * c + s), 1.0 / (ar * s + c));
|
|
||||||
} else {
|
|
||||||
// Fallback: square approximation
|
|
||||||
scaleToFit = 1.0 / (c + s).clamp(1.0, double.infinity);
|
|
||||||
}
|
|
||||||
img = Transform.scale(
|
|
||||||
scale: scaleToFit,
|
|
||||||
child: Transform.rotate(angle: angle, child: img),
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
img = Transform.rotate(angle: angle, child: img);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!widget.wrapInRepaintBoundary) return img;
|
|
||||||
return RepaintBoundary(child: img);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,8 +1,7 @@
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import '../view_model/signature_library.dart';
|
import '../view_model/view_model.dart';
|
||||||
import 'signature_drag_data.dart';
|
import 'signature_drag_data.dart';
|
||||||
import '../../../common/menu_labels.dart';
|
import '../../../common/menu_labels.dart';
|
||||||
import 'rotated_signature_image.dart';
|
|
||||||
|
|
||||||
class SignatureCard extends StatelessWidget {
|
class SignatureCard extends StatelessWidget {
|
||||||
const SignatureCard({
|
const SignatureCard({
|
||||||
|
@ -13,7 +12,6 @@ class SignatureCard extends StatelessWidget {
|
||||||
this.onTap,
|
this.onTap,
|
||||||
this.onAdjust,
|
this.onAdjust,
|
||||||
this.useCurrentBytesForDrag = false,
|
this.useCurrentBytesForDrag = false,
|
||||||
this.rotationDeg = 0.0,
|
|
||||||
});
|
});
|
||||||
final SignatureAsset asset;
|
final SignatureAsset asset;
|
||||||
final bool disabled;
|
final bool disabled;
|
||||||
|
@ -21,19 +19,10 @@ class SignatureCard extends StatelessWidget {
|
||||||
final VoidCallback? onTap;
|
final VoidCallback? onTap;
|
||||||
final VoidCallback? onAdjust;
|
final VoidCallback? onAdjust;
|
||||||
final bool useCurrentBytesForDrag;
|
final bool useCurrentBytesForDrag;
|
||||||
final double rotationDeg;
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
// Fit inside 96x64 with 6px padding using the shared rotated image widget
|
final img = Image.memory(asset.bytes, fit: BoxFit.contain);
|
||||||
const boxW = 96.0, boxH = 64.0, pad = 6.0;
|
|
||||||
Widget img = RotatedSignatureImage(
|
|
||||||
bytes: asset.bytes,
|
|
||||||
rotationDeg: rotationDeg,
|
|
||||||
enableAngleAwareScale: true,
|
|
||||||
fit: BoxFit.contain,
|
|
||||||
wrapInRepaintBoundary: true,
|
|
||||||
);
|
|
||||||
Widget base = SizedBox(
|
Widget base = SizedBox(
|
||||||
width: 96,
|
width: 96,
|
||||||
height: 64,
|
height: 64,
|
||||||
|
@ -47,14 +36,7 @@ class SignatureCard extends StatelessWidget {
|
||||||
border: Border.all(color: Theme.of(context).dividerColor),
|
border: Border.all(color: Theme.of(context).dividerColor),
|
||||||
borderRadius: BorderRadius.circular(8),
|
borderRadius: BorderRadius.circular(8),
|
||||||
),
|
),
|
||||||
child: Padding(
|
child: Padding(padding: const EdgeInsets.all(6), child: img),
|
||||||
padding: const EdgeInsets.all(pad),
|
|
||||||
child: SizedBox(
|
|
||||||
width: boxW - pad * 2,
|
|
||||||
height: boxH - pad * 2,
|
|
||||||
child: img,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
Positioned(
|
Positioned(
|
||||||
|
@ -160,13 +142,7 @@ class SignatureCard extends StatelessWidget {
|
||||||
),
|
),
|
||||||
child: Padding(
|
child: Padding(
|
||||||
padding: const EdgeInsets.all(6.0),
|
padding: const EdgeInsets.all(6.0),
|
||||||
child: RotatedSignatureImage(
|
child: Image.memory(asset.bytes, fit: BoxFit.contain),
|
||||||
bytes: asset.bytes,
|
|
||||||
rotationDeg: rotationDeg,
|
|
||||||
enableAngleAwareScale: true,
|
|
||||||
fit: BoxFit.contain,
|
|
||||||
wrapInRepaintBoundary: true,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
|
@ -4,8 +4,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 '../../../../data/services/export_providers.dart';
|
import '../../../../data/services/export_providers.dart';
|
||||||
import '../view_model/signature_controller.dart';
|
import '../view_model/view_model.dart';
|
||||||
import '../view_model/signature_library.dart';
|
|
||||||
import 'image_editor_dialog.dart';
|
import 'image_editor_dialog.dart';
|
||||||
import 'signature_card.dart';
|
import 'signature_card.dart';
|
||||||
|
|
||||||
|
@ -59,7 +58,6 @@ class _SignatureDrawerState extends ConsumerState<SignatureDrawer> {
|
||||||
name: a.name,
|
name: a.name,
|
||||||
)
|
)
|
||||||
: a,
|
: a,
|
||||||
rotationDeg: (sig.assetId == a.id) ? sig.rotation : 0.0,
|
|
||||||
disabled: disabled,
|
disabled: disabled,
|
||||||
onDelete:
|
onDelete:
|
||||||
() => ref
|
() => ref
|
||||||
|
@ -97,7 +95,6 @@ class _SignatureDrawerState extends ConsumerState<SignatureDrawer> {
|
||||||
? Text(l.noSignatureLoaded)
|
? Text(l.noSignatureLoaded)
|
||||||
: SignatureCard(
|
: SignatureCard(
|
||||||
asset: SignatureAsset(id: '', bytes: bytes, name: ''),
|
asset: SignatureAsset(id: '', bytes: bytes, name: ''),
|
||||||
rotationDeg: sig.rotation,
|
|
||||||
disabled: disabled,
|
disabled: disabled,
|
||||||
useCurrentBytesForDrag: true,
|
useCurrentBytesForDrag: true,
|
||||||
onDelete: () {
|
onDelete: () {
|
||||||
|
|
|
@ -5,12 +5,9 @@ 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/model/model.dart';
|
import '../../../../data/model/model.dart';
|
||||||
import '../view_model/signature_controller.dart';
|
import '../view_model/view_model.dart';
|
||||||
import '../view_model/pdf_controller.dart';
|
|
||||||
import '../view_model/signature_library.dart';
|
|
||||||
import 'image_editor_dialog.dart';
|
import 'image_editor_dialog.dart';
|
||||||
import '../../../common/menu_labels.dart';
|
import '../../../common/menu_labels.dart';
|
||||||
import 'rotated_signature_image.dart';
|
|
||||||
|
|
||||||
/// Renders a single signature overlay (either interactive or placed) on a page.
|
/// Renders a single signature overlay (either interactive or placed) on a page.
|
||||||
class SignatureOverlay extends ConsumerWidget {
|
class SignatureOverlay extends ConsumerWidget {
|
||||||
|
@ -93,7 +90,6 @@ class SignatureOverlay extends ConsumerWidget {
|
||||||
border: Border.all(color: borderColor, width: borderWidth),
|
border: Border.all(color: borderColor, width: borderWidth),
|
||||||
),
|
),
|
||||||
child: Stack(
|
child: Stack(
|
||||||
alignment: Alignment.center,
|
|
||||||
children: [
|
children: [
|
||||||
_SignatureImage(
|
_SignatureImage(
|
||||||
interactive: interactive,
|
interactive: interactive,
|
||||||
|
@ -119,7 +115,7 @@ class SignatureOverlay extends ConsumerWidget {
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
if (interactive) {
|
if (interactive && sig.editingEnabled) {
|
||||||
content = GestureDetector(
|
content = GestureDetector(
|
||||||
key: const Key('signature_overlay'),
|
key: const Key('signature_overlay'),
|
||||||
behavior: HitTestBehavior.opaque,
|
behavior: HitTestBehavior.opaque,
|
||||||
|
@ -281,12 +277,10 @@ class _SignatureImage extends ConsumerWidget {
|
||||||
return Center(child: Text(label));
|
return Center(child: Text(label));
|
||||||
}
|
}
|
||||||
|
|
||||||
return RotatedSignatureImage(
|
Widget im = Image.memory(bytes, fit: BoxFit.contain);
|
||||||
bytes: bytes,
|
if (sig.rotation % 360 != 0) {
|
||||||
rotationDeg: interactive ? sig.rotation : 0.0,
|
im = Transform.rotate(angle: sig.rotation * math.pi / 180.0, child: im);
|
||||||
enableAngleAwareScale: interactive,
|
}
|
||||||
fit: BoxFit.contain,
|
return im;
|
||||||
wrapInRepaintBoundary: true,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,8 +7,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 '../../pdf/view_model/signature_controller.dart';
|
import '../../pdf/view_model/view_model.dart';
|
||||||
import '../../pdf/view_model/pdf_controller.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,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/ui/features/pdf/view_model/signature_controller.dart';
|
import 'package:pdf_signature/ui/features/pdf/view_model/view_model.dart';
|
||||||
import '_world.dart';
|
import '_world.dart';
|
||||||
|
|
||||||
/// Usage: a drawn signature exists in the canvas
|
/// Usage: a drawn signature exists in 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/ui/features/pdf/view_model/pdf_controller.dart';
|
import 'package:pdf_signature/ui/features/pdf/view_model/view_model.dart';
|
||||||
import '_world.dart';
|
import '_world.dart';
|
||||||
|
|
||||||
/// Usage: a multi-page PDF is open
|
/// Usage: a multi-page PDF is open
|
||||||
|
|
|
@ -1,8 +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/ui/features/pdf/view_model/signature_controller.dart';
|
import 'package:pdf_signature/ui/features/pdf/view_model/view_model.dart';
|
||||||
import 'package:pdf_signature/ui/features/pdf/view_model/pdf_controller.dart';
|
|
||||||
import '_world.dart';
|
import '_world.dart';
|
||||||
|
|
||||||
/// Usage: a PDF is open and contains at least one placed signature
|
/// Usage: a PDF is open and contains at least one placed signature
|
||||||
|
|
|
@ -2,8 +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:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:pdf_signature/ui/features/pdf/view_model/signature_controller.dart';
|
import 'package:pdf_signature/ui/features/pdf/view_model/view_model.dart';
|
||||||
import 'package:pdf_signature/ui/features/pdf/view_model/pdf_controller.dart';
|
|
||||||
import '_world.dart';
|
import '_world.dart';
|
||||||
|
|
||||||
/// Usage: a PDF is open and contains multiple placed signatures across pages
|
/// Usage: a PDF is open and contains multiple placed signatures across pages
|
||||||
|
|
|
@ -1,7 +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/ui/features/pdf/view_model/signature_controller.dart';
|
import 'package:pdf_signature/ui/features/pdf/view_model/view_model.dart';
|
||||||
import 'package:pdf_signature/ui/features/pdf/view_model/pdf_controller.dart';
|
|
||||||
import '_world.dart';
|
import '_world.dart';
|
||||||
|
|
||||||
/// Usage: a PDF is open with no signatures placed
|
/// Usage: a PDF is open with no signatures 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/ui/features/pdf/view_model/pdf_controller.dart';
|
import 'package:pdf_signature/ui/features/pdf/view_model/view_model.dart';
|
||||||
import '_world.dart';
|
import '_world.dart';
|
||||||
|
|
||||||
/// Usage: a PDF page is selected for signing
|
/// Usage: a PDF 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/ui/features/pdf/view_model/pdf_controller.dart';
|
import 'package:pdf_signature/ui/features/pdf/view_model/view_model.dart';
|
||||||
import '_world.dart';
|
import '_world.dart';
|
||||||
|
|
||||||
/// Usage: a sample multi-page PDF (5 pages) is available
|
/// Usage: a sample multi-page PDF (5 pages) is available
|
||||||
|
|
|
@ -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/ui/features/pdf/view_model/signature_controller.dart';
|
import 'package:pdf_signature/ui/features/pdf/view_model/view_model.dart';
|
||||||
import '_world.dart';
|
import '_world.dart';
|
||||||
|
|
||||||
/// Usage: a signature image is created
|
/// Usage: a signature image is created
|
||||||
|
|
|
@ -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/ui/features/pdf/view_model/signature_controller.dart';
|
import 'package:pdf_signature/ui/features/pdf/view_model/view_model.dart';
|
||||||
import '_world.dart';
|
import '_world.dart';
|
||||||
|
|
||||||
/// Usage: a signature image is loaded or drawn
|
/// Usage: a signature image is loaded or drawn
|
||||||
|
|
|
@ -1,8 +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/ui/features/pdf/view_model/signature_controller.dart';
|
import 'package:pdf_signature/ui/features/pdf/view_model/view_model.dart';
|
||||||
import 'package:pdf_signature/ui/features/pdf/view_model/pdf_controller.dart';
|
|
||||||
import '_world.dart';
|
import '_world.dart';
|
||||||
|
|
||||||
/// Usage: a signature image is placed on the page
|
/// Usage: a signature image is placed on the page
|
||||||
|
|
|
@ -1,8 +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/ui/features/pdf/view_model/signature_controller.dart';
|
import 'package:pdf_signature/ui/features/pdf/view_model/view_model.dart';
|
||||||
import 'package:pdf_signature/ui/features/pdf/view_model/pdf_controller.dart';
|
|
||||||
import '_world.dart';
|
import '_world.dart';
|
||||||
|
|
||||||
/// Usage: a signature image is selected
|
/// Usage: a signature image is selected
|
||||||
|
|
|
@ -2,8 +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:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:pdf_signature/ui/features/pdf/view_model/signature_controller.dart';
|
import 'package:pdf_signature/ui/features/pdf/view_model/view_model.dart';
|
||||||
import 'package:pdf_signature/ui/features/pdf/view_model/pdf_controller.dart';
|
|
||||||
import '_world.dart';
|
import '_world.dart';
|
||||||
|
|
||||||
/// Usage: a signature is placed on page {2}
|
/// Usage: a signature is placed on page {2}
|
||||||
|
|
|
@ -2,8 +2,7 @@ import 'dart:typed_data';
|
||||||
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/ui/features/pdf/view_model/signature_controller.dart';
|
import 'package:pdf_signature/ui/features/pdf/view_model/view_model.dart';
|
||||||
import 'package:pdf_signature/ui/features/pdf/view_model/pdf_controller.dart';
|
|
||||||
import '_world.dart';
|
import '_world.dart';
|
||||||
|
|
||||||
/// Usage: a signature is placed with a position and size relative to the page
|
/// Usage: a signature is placed with a position and size relative to the 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/ui/features/pdf/view_model/pdf_controller.dart';
|
import 'package:pdf_signature/ui/features/pdf/view_model/view_model.dart';
|
||||||
import '_world.dart';
|
import '_world.dart';
|
||||||
|
|
||||||
/// Usage: adjusting one instance does not affect the others
|
/// Usage: adjusting one instance 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/ui/features/pdf/view_model/pdf_controller.dart';
|
import 'package:pdf_signature/ui/features/pdf/view_model/view_model.dart';
|
||||||
import '_world.dart';
|
import '_world.dart';
|
||||||
|
|
||||||
/// Usage: all placed signatures appear on their corresponding pages in the output
|
/// Usage: all placed signatures 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/ui/features/pdf/view_model/signature_controller.dart';
|
import 'package:pdf_signature/ui/features/pdf/view_model/view_model.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/ui/features/pdf/view_model/pdf_controller.dart';
|
import 'package:pdf_signature/ui/features/pdf/view_model/view_model.dart';
|
||||||
import '_world.dart';
|
import '_world.dart';
|
||||||
|
|
||||||
/// Usage: both signatures are shown on their respective pages
|
/// Usage: both signatures 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/ui/features/pdf/view_model/pdf_controller.dart';
|
import 'package:pdf_signature/ui/features/pdf/view_model/view_model.dart';
|
||||||
import '_world.dart';
|
import '_world.dart';
|
||||||
|
|
||||||
/// Usage: dragging or resizing one does not change the other
|
/// Usage: dragging or resizing one does not change the other
|
||||||
|
|
|
@ -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/ui/features/pdf/view_model/pdf_controller.dart';
|
import 'package:pdf_signature/ui/features/pdf/view_model/view_model.dart';
|
||||||
import '_world.dart';
|
import '_world.dart';
|
||||||
|
|
||||||
/// Usage: each signature can be dragged and resized independently
|
/// Usage: each signature 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/ui/features/pdf/view_model/pdf_controller.dart';
|
import 'package:pdf_signature/ui/features/pdf/view_model/view_model.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/ui/features/pdf/view_model/signature_controller.dart';
|
import 'package:pdf_signature/ui/features/pdf/view_model/view_model.dart';
|
||||||
import '_world.dart';
|
import '_world.dart';
|
||||||
|
|
||||||
/// Usage: it is placed on the selected page
|
/// Usage: it is placed 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/ui/features/pdf/view_model/signature_controller.dart';
|
import 'package:pdf_signature/ui/features/pdf/view_model/view_model.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/ui/features/pdf/view_model/signature_controller.dart';
|
import 'package:pdf_signature/ui/features/pdf/view_model/view_model.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/ui/features/pdf/view_model/pdf_controller.dart';
|
import 'package:pdf_signature/ui/features/pdf/view_model/view_model.dart';
|
||||||
import '_world.dart';
|
import '_world.dart';
|
||||||
|
|
||||||
/// Usage: only the selected signature is removed
|
/// Usage: only the selected signature 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/ui/features/pdf/view_model/pdf_controller.dart';
|
import 'package:pdf_signature/ui/features/pdf/view_model/view_model.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/ui/features/pdf/view_model/pdf_controller.dart';
|
import 'package:pdf_signature/ui/features/pdf/view_model/view_model.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/ui/features/pdf/view_model/signature_controller.dart';
|
import 'package:pdf_signature/ui/features/pdf/view_model/view_model.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/ui/features/pdf/view_model/pdf_controller.dart';
|
import 'package:pdf_signature/ui/features/pdf/view_model/view_model.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/ui/features/pdf/view_model/pdf_controller.dart';
|
import 'package:pdf_signature/ui/features/pdf/view_model/view_model.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/ui/features/pdf/view_model/pdf_controller.dart';
|
import 'package:pdf_signature/ui/features/pdf/view_model/view_model.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/ui/features/pdf/view_model/signature_controller.dart';
|
import 'package:pdf_signature/ui/features/pdf/view_model/view_model.dart';
|
||||||
import '_world.dart';
|
import '_world.dart';
|
||||||
|
|
||||||
/// Usage: the image is loaded and shown as a signature asset
|
/// Usage: the image is loaded and shown as a signature asset
|
||||||
|
|
|
@ -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/ui/features/pdf/view_model/signature_controller.dart';
|
import 'package:pdf_signature/ui/features/pdf/view_model/view_model.dart';
|
||||||
import '_world.dart';
|
import '_world.dart';
|
||||||
|
|
||||||
/// Usage: the image is not added to the document
|
/// Usage: the image is not added to 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/ui/features/pdf/view_model/signature_controller.dart';
|
import 'package:pdf_signature/ui/features/pdf/view_model/view_model.dart';
|
||||||
import '_world.dart';
|
import '_world.dart';
|
||||||
|
|
||||||
/// Usage: the image scales proportionally
|
/// Usage: the image scales proportionally
|
||||||
|
|
|
@ -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/ui/features/pdf/view_model/pdf_controller.dart';
|
import 'package:pdf_signature/ui/features/pdf/view_model/view_model.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/ui/features/pdf/view_model/signature_controller.dart';
|
import 'package:pdf_signature/ui/features/pdf/view_model/view_model.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/ui/features/pdf/view_model/pdf_controller.dart';
|
import 'package:pdf_signature/ui/features/pdf/view_model/view_model.dart';
|
||||||
import '_world.dart';
|
import '_world.dart';
|
||||||
|
|
||||||
/// Usage: the left pages overview highlights page {5}
|
/// Usage: the left pages overview highlights 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/ui/features/pdf/view_model/pdf_controller.dart';
|
import 'package:pdf_signature/ui/features/pdf/view_model/view_model.dart';
|
||||||
import '_world.dart';
|
import '_world.dart';
|
||||||
|
|
||||||
/// Usage: the other signatures remain unchanged
|
/// Usage: the other signatures 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/ui/features/pdf/view_model/pdf_controller.dart';
|
import 'package:pdf_signature/ui/features/pdf/view_model/view_model.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/ui/features/pdf/view_model/signature_controller.dart';
|
import 'package:pdf_signature/ui/features/pdf/view_model/view_model.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/ui/features/pdf/view_model/signature_controller.dart';
|
import 'package:pdf_signature/ui/features/pdf/view_model/view_model.dart';
|
||||||
import '_world.dart';
|
import '_world.dart';
|
||||||
|
|
||||||
/// Usage: the signature is stamped at the exact PDF page coordinates and size
|
/// Usage: the signature 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/ui/features/pdf/view_model/pdf_controller.dart';
|
import 'package:pdf_signature/ui/features/pdf/view_model/view_model.dart';
|
||||||
import '_world.dart';
|
import '_world.dart';
|
||||||
|
|
||||||
/// Usage: the signature on page {5} is shown on page {5}
|
/// Usage: the signature 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/ui/features/pdf/view_model/pdf_controller.dart';
|
import 'package:pdf_signature/ui/features/pdf/view_model/view_model.dart';
|
||||||
import '_world.dart';
|
import '_world.dart';
|
||||||
|
|
||||||
/// Usage: the signature on page {2} remains
|
/// Usage: the signature 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/ui/features/pdf/view_model/signature_controller.dart';
|
import 'package:pdf_signature/ui/features/pdf/view_model/view_model.dart';
|
||||||
import '_world.dart';
|
import '_world.dart';
|
||||||
|
|
||||||
/// Usage: the signature remains within the page area
|
/// Usage: the signature remains within the page area
|
||||||
|
|
|
@ -1,7 +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/ui/features/pdf/view_model/signature_controller.dart';
|
import 'package:pdf_signature/ui/features/pdf/view_model/view_model.dart';
|
||||||
import 'package:pdf_signature/ui/features/pdf/view_model/pdf_controller.dart';
|
|
||||||
import '_world.dart';
|
import '_world.dart';
|
||||||
|
|
||||||
/// Usage: the signatures appear on the corresponding page in the output
|
/// Usage: the signatures 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/ui/features/pdf/view_model/signature_controller.dart';
|
import 'package:pdf_signature/ui/features/pdf/view_model/view_model.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,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/ui/features/pdf/view_model/signature_controller.dart';
|
import 'package:pdf_signature/ui/features/pdf/view_model/view_model.dart';
|
||||||
import 'package:pdf_signature/ui/features/pdf/view_model/pdf_controller.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/ui/features/pdf/view_model/signature_controller.dart';
|
import 'package:pdf_signature/ui/features/pdf/view_model/view_model.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/ui/features/pdf/view_model/pdf_controller.dart';
|
import 'package:pdf_signature/ui/features/pdf/view_model/view_model.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/ui/features/pdf/view_model/signature_controller.dart';
|
import 'package:pdf_signature/ui/features/pdf/view_model/view_model.dart';
|
||||||
import '_world.dart';
|
import '_world.dart';
|
||||||
|
|
||||||
/// Usage: the user changes contrast and brightness controls
|
/// Usage: the user changes contrast and brightness controls
|
||||||
|
|
|
@ -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/ui/features/pdf/view_model/signature_controller.dart';
|
import 'package:pdf_signature/ui/features/pdf/view_model/view_model.dart';
|
||||||
import '_world.dart';
|
import '_world.dart';
|
||||||
|
|
||||||
/// Usage: the user chooses a signature image file
|
/// Usage: the user chooses a signature image 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/ui/features/pdf/view_model/signature_controller.dart';
|
import 'package:pdf_signature/ui/features/pdf/view_model/view_model.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/ui/features/pdf/view_model/signature_controller.dart';
|
import 'package:pdf_signature/ui/features/pdf/view_model/view_model.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/ui/features/pdf/view_model/pdf_controller.dart';
|
import 'package:pdf_signature/ui/features/pdf/view_model/view_model.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/ui/features/pdf/view_model/pdf_controller.dart';
|
import 'package:pdf_signature/ui/features/pdf/view_model/view_model.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/ui/features/pdf/view_model/pdf_controller.dart';
|
import 'package:pdf_signature/ui/features/pdf/view_model/view_model.dart';
|
||||||
import '_world.dart';
|
import '_world.dart';
|
||||||
|
|
||||||
/// Usage: the user deletes one selected signature
|
/// Usage: the user deletes one selected signature
|
||||||
|
|
|
@ -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/ui/features/pdf/view_model/signature_controller.dart';
|
import 'package:pdf_signature/ui/features/pdf/view_model/view_model.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
|
||||||
|
|
|
@ -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/ui/features/pdf/view_model/signature_controller.dart';
|
import 'package:pdf_signature/ui/features/pdf/view_model/view_model.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/ui/features/pdf/view_model/signature_controller.dart';
|
import 'package:pdf_signature/ui/features/pdf/view_model/view_model.dart';
|
||||||
import '_world.dart';
|
import '_world.dart';
|
||||||
|
|
||||||
/// Usage: the user enables aspect ratio lock and resizes
|
/// Usage: the user enables aspect ratio lock and resizes
|
||||||
|
|
|
@ -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/ui/features/pdf/view_model/signature_controller.dart';
|
import 'package:pdf_signature/ui/features/pdf/view_model/view_model.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/ui/features/pdf/view_model/pdf_controller.dart';
|
import 'package:pdf_signature/ui/features/pdf/view_model/view_model.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/ui/features/pdf/view_model/signature_controller.dart';
|
import 'package:pdf_signature/ui/features/pdf/view_model/view_model.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/ui/features/pdf/view_model/pdf_controller.dart';
|
import 'package:pdf_signature/ui/features/pdf/view_model/view_model.dart';
|
||||||
import '_world.dart';
|
import '_world.dart';
|
||||||
|
|
||||||
/// Usage: the user jumps to page {2}
|
/// Usage: the user jumps to page {2}
|
||||||
|
|
|
@ -2,8 +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:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:pdf_signature/ui/features/pdf/view_model/signature_controller.dart';
|
import 'package:pdf_signature/ui/features/pdf/view_model/view_model.dart';
|
||||||
import 'package:pdf_signature/ui/features/pdf/view_model/pdf_controller.dart';
|
|
||||||
import '_world.dart';
|
import '_world.dart';
|
||||||
|
|
||||||
/// Usage: the user navigates to page {3} and places another signature
|
/// Usage: the user navigates to page {3} and places another signature
|
||||||
|
|
|
@ -2,8 +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:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:pdf_signature/ui/features/pdf/view_model/signature_controller.dart';
|
import 'package:pdf_signature/ui/features/pdf/view_model/view_model.dart';
|
||||||
import 'package:pdf_signature/ui/features/pdf/view_model/pdf_controller.dart';
|
|
||||||
import '_world.dart';
|
import '_world.dart';
|
||||||
|
|
||||||
/// Usage: the user places a signature from picture <second_image> on page <second_page>
|
/// Usage: the user places a signature from picture <second_image> on page <second_page>
|
||||||
|
|
|
@ -2,8 +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:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:pdf_signature/ui/features/pdf/view_model/signature_controller.dart';
|
import 'package:pdf_signature/ui/features/pdf/view_model/view_model.dart';
|
||||||
import 'package:pdf_signature/ui/features/pdf/view_model/pdf_controller.dart';
|
|
||||||
import '_world.dart';
|
import '_world.dart';
|
||||||
|
|
||||||
/// Usage: the user places a signature on page {1}
|
/// Usage: the user places a signature on page {1}
|
||||||
|
|
|
@ -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/ui/features/pdf/view_model/pdf_controller.dart';
|
import 'package:pdf_signature/ui/features/pdf/view_model/view_model.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
|
||||||
|
|
|
@ -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/ui/features/pdf/view_model/signature_controller.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:pdf_signature/ui/features/pdf/view_model/pdf_controller.dart';
|
import 'package:pdf_signature/ui/features/pdf/view_model/view_model.dart';
|
||||||
import '_world.dart';
|
import '_world.dart';
|
||||||
|
|
||||||
/// Usage: the user places two signatures on the same page
|
/// Usage: the user places two signatures on the same page
|
||||||
|
|
|
@ -1,8 +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/ui/features/pdf/view_model/signature_controller.dart';
|
import 'package:pdf_signature/ui/features/pdf/view_model/view_model.dart';
|
||||||
import 'package:pdf_signature/ui/features/pdf/view_model/pdf_controller.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/ui/features/pdf/view_model/pdf_controller.dart';
|
import 'package:pdf_signature/ui/features/pdf/view_model/view_model.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/ui/features/pdf/view_model/pdf_controller.dart';
|
import 'package:pdf_signature/ui/features/pdf/view_model/view_model.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,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/ui/features/pdf/view_model/pdf_controller.dart';
|
import 'package:pdf_signature/ui/features/pdf/view_model/view_model.dart';
|
||||||
import '_world.dart';
|
import '_world.dart';
|
||||||
|
|
||||||
/// Usage: three signatures are placed on the current page
|
/// Usage: three signatures are placed on the current page
|
||||||
|
|
|
@ -0,0 +1,136 @@
|
||||||
|
import 'dart:ui' as ui;
|
||||||
|
import 'package:flutter/gestures.dart' show kSecondaryMouseButton;
|
||||||
|
import 'dart:typed_data';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
|
import 'package:image/image.dart' as img;
|
||||||
|
|
||||||
|
import 'package:pdf_signature/ui/features/pdf/widgets/pdf_screen.dart';
|
||||||
|
import 'package:pdf_signature/ui/features/pdf/view_model/view_model.dart';
|
||||||
|
import 'package:pdf_signature/data/services/export_providers.dart';
|
||||||
|
import 'package:pdf_signature/l10n/app_localizations.dart';
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
// Open the active overlay context menu robustly (mouse right-click, fallback to long-press)
|
||||||
|
Future<void> _openActiveMenuAndConfirm(WidgetTester tester) async {
|
||||||
|
final overlay = find.byKey(const Key('signature_overlay'));
|
||||||
|
expect(overlay, findsOneWidget);
|
||||||
|
// Ensure visible before interacting
|
||||||
|
await tester.ensureVisible(overlay);
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
|
||||||
|
// Try right-click first
|
||||||
|
final center = tester.getCenter(overlay);
|
||||||
|
final TestGesture mouse = await tester.createGesture(
|
||||||
|
kind: ui.PointerDeviceKind.mouse,
|
||||||
|
buttons: kSecondaryMouseButton,
|
||||||
|
);
|
||||||
|
await mouse.addPointer(location: center);
|
||||||
|
addTearDown(mouse.removePointer);
|
||||||
|
await tester.pump();
|
||||||
|
await mouse.down(center);
|
||||||
|
await tester.pump(const Duration(milliseconds: 30));
|
||||||
|
await mouse.up();
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
|
||||||
|
// If menu didn't appear, try long-press
|
||||||
|
if (find.byKey(const Key('ctx_active_confirm')).evaluate().isEmpty) {
|
||||||
|
await tester.longPress(overlay, warnIfMissed: false);
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
}
|
||||||
|
await tester.tap(find.byKey(const Key('ctx_active_confirm')));
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build a simple in-memory PNG as a signature image
|
||||||
|
Uint8List _makeSig() {
|
||||||
|
final canvas = img.Image(width: 80, height: 40);
|
||||||
|
img.fill(canvas, color: img.ColorUint8.rgb(255, 255, 255));
|
||||||
|
img.drawLine(
|
||||||
|
canvas,
|
||||||
|
x1: 6,
|
||||||
|
y1: 20,
|
||||||
|
x2: 74,
|
||||||
|
y2: 20,
|
||||||
|
color: img.ColorUint8.rgb(0, 0, 0),
|
||||||
|
);
|
||||||
|
return Uint8List.fromList(img.encodePng(canvas));
|
||||||
|
}
|
||||||
|
|
||||||
|
testWidgets('E2E: select, place default, and confirm signature', (
|
||||||
|
tester,
|
||||||
|
) async {
|
||||||
|
final sigBytes = _makeSig();
|
||||||
|
|
||||||
|
await tester.pumpWidget(
|
||||||
|
ProviderScope(
|
||||||
|
overrides: [
|
||||||
|
// Open a PDF
|
||||||
|
pdfProvider.overrideWith(
|
||||||
|
(ref) => PdfController()..openPicked(path: 'test.pdf'),
|
||||||
|
),
|
||||||
|
// Provide one signature asset in the library
|
||||||
|
signatureLibraryProvider.overrideWith((ref) {
|
||||||
|
final c = SignatureLibraryController();
|
||||||
|
c.add(sigBytes, name: 'image');
|
||||||
|
return c;
|
||||||
|
}),
|
||||||
|
// Use mock continuous viewer for deterministic layout in widget tests
|
||||||
|
useMockViewerProvider.overrideWithValue(true),
|
||||||
|
],
|
||||||
|
child: MaterialApp(
|
||||||
|
localizationsDelegates: AppLocalizations.localizationsDelegates,
|
||||||
|
supportedLocales: AppLocalizations.supportedLocales,
|
||||||
|
home: const PdfSignatureHomePage(),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
|
||||||
|
// Tap the signature card to set it as active overlay
|
||||||
|
final card = find.byKey(const Key('gd_signature_card_area')).first;
|
||||||
|
expect(card, findsOneWidget);
|
||||||
|
await tester.tap(card);
|
||||||
|
await tester.pump();
|
||||||
|
|
||||||
|
// Active overlay should appear
|
||||||
|
final active = find.byKey(const Key('signature_overlay'));
|
||||||
|
expect(active, findsOneWidget);
|
||||||
|
final sizeBefore = tester.getSize(active);
|
||||||
|
|
||||||
|
// Bring the overlay into the viewport (it's near the bottom of the page by default)
|
||||||
|
final listFinder = find.byKey(const Key('pdf_continuous_mock_list'));
|
||||||
|
if (listFinder.evaluate().isNotEmpty) {
|
||||||
|
// Ensure the active overlay is fully visible within the scrollable viewport
|
||||||
|
await tester.ensureVisible(active);
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Open context menu and confirm using a robust flow
|
||||||
|
await _openActiveMenuAndConfirm(tester);
|
||||||
|
|
||||||
|
// Verify active overlay gone and placed overlay shown
|
||||||
|
expect(find.byKey(const Key('signature_overlay')), findsNothing);
|
||||||
|
final placed = find.byKey(const Key('placed_signature_0'));
|
||||||
|
expect(placed, findsOneWidget);
|
||||||
|
final sizeAfter = tester.getSize(placed);
|
||||||
|
|
||||||
|
// Compare sizes: should be roughly equal (allowing small layout variance)
|
||||||
|
expect(
|
||||||
|
(sizeAfter.width - sizeBefore.width).abs() < sizeBefore.width * 0.15,
|
||||||
|
isTrue,
|
||||||
|
);
|
||||||
|
expect(
|
||||||
|
(sizeAfter.height - sizeBefore.height).abs() < sizeBefore.height * 0.15,
|
||||||
|
isTrue,
|
||||||
|
);
|
||||||
|
|
||||||
|
// Verify provider state reflects one placement on current page
|
||||||
|
final ctx = tester.element(find.byType(PdfSignatureHomePage));
|
||||||
|
final container = ProviderScope.containerOf(ctx);
|
||||||
|
final pdf = container.read(pdfProvider);
|
||||||
|
final list = pdf.placementsByPage[pdf.currentPage] ?? const [];
|
||||||
|
expect(list.length, 1);
|
||||||
|
});
|
||||||
|
}
|
|
@ -4,8 +4,7 @@ import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
|
|
||||||
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/ui/features/pdf/view_model/signature_controller.dart';
|
import 'package:pdf_signature/ui/features/pdf/view_model/view_model.dart';
|
||||||
import 'package:pdf_signature/ui/features/pdf/view_model/pdf_controller.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';
|
||||||
|
|
||||||
|
|
|
@ -5,8 +5,7 @@ import 'package:image/image.dart' as img;
|
||||||
import 'dart:typed_data';
|
import 'dart:typed_data';
|
||||||
|
|
||||||
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/ui/features/pdf/view_model/signature_controller.dart';
|
import 'package:pdf_signature/ui/features/pdf/view_model/view_model.dart';
|
||||||
import 'package:pdf_signature/ui/features/pdf/view_model/pdf_controller.dart';
|
|
||||||
import 'package:pdf_signature/data/services/export_providers.dart';
|
import 'package:pdf_signature/data/services/export_providers.dart';
|
||||||
import 'package:pdf_signature/l10n/app_localizations.dart';
|
import 'package:pdf_signature/l10n/app_localizations.dart';
|
||||||
// preferences_providers.dart no longer exports pageViewModeProvider
|
// preferences_providers.dart no longer exports pageViewModeProvider
|
||||||
|
|
|
@ -3,7 +3,7 @@ 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/ui/features/pdf/widgets/pdf_screen.dart';
|
import 'package:pdf_signature/ui/features/pdf/widgets/pdf_screen.dart';
|
||||||
import 'package:pdf_signature/ui/features/pdf/view_model/pdf_controller.dart';
|
import 'package:pdf_signature/ui/features/pdf/view_model/view_model.dart';
|
||||||
import 'package:pdf_signature/data/model/model.dart';
|
import 'package:pdf_signature/data/model/model.dart';
|
||||||
import 'package:pdf_signature/data/services/export_providers.dart';
|
import 'package:pdf_signature/data/services/export_providers.dart';
|
||||||
import 'package:pdf_signature/l10n/app_localizations.dart';
|
import 'package:pdf_signature/l10n/app_localizations.dart';
|
||||||
|
|
|
@ -3,7 +3,7 @@ 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/ui/features/pdf/widgets/pdf_page_area.dart';
|
import 'package:pdf_signature/ui/features/pdf/widgets/pdf_page_area.dart';
|
||||||
import 'package:pdf_signature/ui/features/pdf/view_model/pdf_controller.dart';
|
import 'package:pdf_signature/ui/features/pdf/view_model/view_model.dart';
|
||||||
import 'package:pdf_signature/data/services/export_providers.dart';
|
import 'package:pdf_signature/data/services/export_providers.dart';
|
||||||
import 'package:pdf_signature/l10n/app_localizations.dart';
|
import 'package:pdf_signature/l10n/app_localizations.dart';
|
||||||
import 'package:pdf_signature/data/model/model.dart';
|
import 'package:pdf_signature/data/model/model.dart';
|
||||||
|
|
|
@ -3,7 +3,7 @@ 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/ui/features/pdf/widgets/pdf_page_area.dart';
|
import 'package:pdf_signature/ui/features/pdf/widgets/pdf_page_area.dart';
|
||||||
import 'package:pdf_signature/ui/features/pdf/view_model/pdf_controller.dart';
|
import 'package:pdf_signature/ui/features/pdf/view_model/view_model.dart';
|
||||||
import 'package:pdf_signature/data/services/export_providers.dart';
|
import 'package:pdf_signature/data/services/export_providers.dart';
|
||||||
import 'package:pdf_signature/l10n/app_localizations.dart';
|
import 'package:pdf_signature/l10n/app_localizations.dart';
|
||||||
import 'package:pdf_signature/data/model/model.dart';
|
import 'package:pdf_signature/data/model/model.dart';
|
||||||
|
|
|
@ -3,7 +3,7 @@ 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/ui/features/pdf/widgets/pdf_page_area.dart';
|
import 'package:pdf_signature/ui/features/pdf/widgets/pdf_page_area.dart';
|
||||||
import 'package:pdf_signature/ui/features/pdf/view_model/pdf_controller.dart';
|
import 'package:pdf_signature/ui/features/pdf/view_model/view_model.dart';
|
||||||
import 'package:pdf_signature/data/services/export_providers.dart';
|
import 'package:pdf_signature/data/services/export_providers.dart';
|
||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
|
|
|
@ -1,9 +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/ui/features/pdf/view_model/signature_library.dart';
|
import 'package:pdf_signature/ui/features/pdf/view_model/view_model.dart';
|
||||||
import 'package:pdf_signature/ui/features/pdf/view_model/signature_controller.dart';
|
|
||||||
import 'package:pdf_signature/ui/features/pdf/view_model/pdf_controller.dart';
|
|
||||||
import 'package:pdf_signature/ui/features/pdf/widgets/pdf_screen.dart';
|
import 'package:pdf_signature/ui/features/pdf/widgets/pdf_screen.dart';
|
||||||
|
|
||||||
import 'helpers.dart';
|
import 'helpers.dart';
|
||||||
|
|
|
@ -6,8 +6,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 'package:pdf_signature/ui/features/welcome/widgets/welcome_screen.dart';
|
import 'package:pdf_signature/ui/features/welcome/widgets/welcome_screen.dart';
|
||||||
import 'package:pdf_signature/ui/features/pdf/view_model/signature_controller.dart';
|
import 'package:pdf_signature/ui/features/pdf/view_model/view_model.dart';
|
||||||
import 'package:pdf_signature/ui/features/pdf/view_model/pdf_controller.dart';
|
|
||||||
|
|
||||||
class _FakeDropReadable implements DropReadable {
|
class _FakeDropReadable implements DropReadable {
|
||||||
final String _name;
|
final String _name;
|
||||||
|
|
|
@ -1,73 +0,0 @@
|
||||||
import 'dart:typed_data';
|
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:flutter_test/flutter_test.dart';
|
|
||||||
import 'package:image/image.dart' as img;
|
|
||||||
|
|
||||||
import 'package:pdf_signature/ui/features/pdf/widgets/rotated_signature_image.dart';
|
|
||||||
|
|
||||||
/// Generates a simple solid-color PNG with given width/height.
|
|
||||||
Uint8List makePng({required int w, required int h}) {
|
|
||||||
final im = img.Image(width: w, height: h);
|
|
||||||
// Fill with opaque white
|
|
||||||
img.fill(im, color: img.ColorRgba8(255, 255, 255, 255));
|
|
||||||
return Uint8List.fromList(img.encodePng(im));
|
|
||||||
}
|
|
||||||
|
|
||||||
void main() {
|
|
||||||
testWidgets('4:3 image rotated -90 deg scales to 3/4', (tester) async {
|
|
||||||
// 4:3 aspect image -> width/height = 4/3
|
|
||||||
final bytes = makePng(w: 400, h: 300);
|
|
||||||
|
|
||||||
// Pump widget under a fixed-size parent so Transform.scale is applied
|
|
||||||
await tester.pumpWidget(
|
|
||||||
MaterialApp(
|
|
||||||
home: Scaffold(
|
|
||||||
body: Center(
|
|
||||||
child: SizedBox(
|
|
||||||
width: 200,
|
|
||||||
height: 150, // same aspect as image bounds (4:3)
|
|
||||||
child: RotatedSignatureImage(
|
|
||||||
bytes: bytes,
|
|
||||||
rotationDeg: -90,
|
|
||||||
enableAngleAwareScale: true,
|
|
||||||
intrinsicAspectRatio: 4 / 3,
|
|
||||||
fit: BoxFit.contain,
|
|
||||||
wrapInRepaintBoundary: false, // make Transform visible
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
// Find the Transform widget that applies the scale (the outer Transform.scale)
|
|
||||||
final transformFinder = find.byType(Transform);
|
|
||||||
expect(transformFinder, findsWidgets);
|
|
||||||
|
|
||||||
// Among the Transforms, we expect one to be a scale-only matrix.
|
|
||||||
// Grab the first Transform and assert the scale on x (m4x4 matrix) equals 0.75.
|
|
||||||
Transform? scaleTransform;
|
|
||||||
for (final e in tester.widgetList<Transform>(transformFinder)) {
|
|
||||||
final m = e.transform.storage;
|
|
||||||
// A scale-only matrix will have m[0] and m[5] as scale factors on x/y, with zeros elsewhere (except last row/column)
|
|
||||||
// Also rotation transform will have off-diagonal terms; we want the one with zeros in 1,4 and 4,1 positions approximately.
|
|
||||||
final isLikelyScale =
|
|
||||||
(m[1].abs() < 1e-6) &&
|
|
||||||
(m[4].abs() < 1e-6) &&
|
|
||||||
(m[12].abs() < 1e-6) &&
|
|
||||||
(m[13].abs() < 1e-6);
|
|
||||||
if (isLikelyScale) {
|
|
||||||
scaleTransform = e;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
expect(scaleTransform, isNotNull, reason: 'Scale Transform not found');
|
|
||||||
|
|
||||||
final scale = scaleTransform!.transform.storage[0];
|
|
||||||
expect(
|
|
||||||
(scale - 0.75).abs() < 1e-6,
|
|
||||||
isTrue,
|
|
||||||
reason: 'Expected scale 0.75 for 4:3 rotated -90°',
|
|
||||||
);
|
|
||||||
});
|
|
||||||
}
|
|
Loading…
Reference in New Issue