fix: signature card repository wrong API

This commit is contained in:
insleker 2025-09-11 17:52:50 +08:00
parent 4d2cd09adf
commit 545d3ad688
11 changed files with 148 additions and 48 deletions

Binary file not shown.

View File

@ -42,10 +42,12 @@ void main() {
), ),
documentRepositoryProvider.overrideWith( documentRepositoryProvider.overrideWith(
(ref) => (ref) =>
DocumentStateNotifier() DocumentStateNotifier()..openPicked(
..openPicked(path: 'test.pdf', pageCount: 5), path: 'integration_test/data/sample-local-pdf.pdf',
pageCount: 5,
), ),
useMockViewerProvider.overrideWith((ref) => true), ),
useMockViewerProvider.overrideWith((ref) => false),
exportServiceProvider.overrideWith((_) => fake), exportServiceProvider.overrideWith((_) => fake),
savePathPickerProvider.overrideWith( savePathPickerProvider.overrideWith(
(_) => () async => 'C:/tmp/output.pdf', (_) => () async => 'C:/tmp/output.pdf',
@ -99,16 +101,17 @@ void main() {
), ),
documentRepositoryProvider.overrideWith( documentRepositoryProvider.overrideWith(
(ref) => (ref) =>
DocumentStateNotifier() DocumentStateNotifier()..openPicked(
..openPicked(path: 'test.pdf', pageCount: 5), path: 'integration_test/data/sample-local-pdf.pdf',
pageCount: 5,
),
), ),
signatureAssetRepositoryProvider.overrideWith((ref) { signatureAssetRepositoryProvider.overrideWith((ref) {
final c = SignatureAssetRepository(); final c = SignatureAssetRepository();
c.add(sigBytes, name: 'image'); c.add(sigBytes, name: 'image');
return c; return c;
}), }),
// Keep mock viewer for determinism on CI/desktop devices useMockViewerProvider.overrideWithValue(false),
useMockViewerProvider.overrideWithValue(true),
], ],
child: const MaterialApp( child: const MaterialApp(
localizationsDelegates: AppLocalizations.localizationsDelegates, localizationsDelegates: AppLocalizations.localizationsDelegates,

View File

@ -4,16 +4,20 @@ import '../../domain/models/model.dart';
class SignatureCardStateNotifier extends StateNotifier<List<SignatureCard>> { class SignatureCardStateNotifier extends StateNotifier<List<SignatureCard>> {
SignatureCardStateNotifier() : super(const []); SignatureCardStateNotifier() : super(const []);
add({required SignatureAsset asset, double rotationDeg = 0.0}) { void add(SignatureCard card) {
state = List.of(state)..add(card);
}
void addWithAsset(SignatureAsset asset, double rotationDeg) {
state = List.of(state) state = List.of(state)
..add(SignatureCard(asset: asset, rotationDeg: rotationDeg)); ..add(SignatureCard(asset: asset, rotationDeg: rotationDeg));
} }
void update({ void update(
required SignatureCard card, SignatureCard card,
double? rotationDeg, double? rotationDeg,
GraphicAdjust? graphicAdjust, GraphicAdjust? graphicAdjust,
}) { ) {
final list = List<SignatureCard>.of(state); final list = List<SignatureCard>.of(state);
for (var i = 0; i < list.length; i++) { for (var i = 0; i < list.length; i++) {
final c = list[i]; final c = list[i];

View File

@ -7,6 +7,8 @@ import 'pdf_page_overlays.dart';
import 'pdf_providers.dart'; import 'pdf_providers.dart';
import 'package:pdf_signature/data/repositories/signature_asset_repository.dart'; import 'package:pdf_signature/data/repositories/signature_asset_repository.dart';
// using only adjusted overlay, no direct model imports needed // using only adjusted overlay, no direct model imports needed
import '../../signature/widgets/signature_drag_data.dart';
import 'package:pdf_signature/data/repositories/document_repository.dart';
/// Mocked continuous viewer for tests or platforms without real viewer. /// Mocked continuous viewer for tests or platforms without real viewer.
@visibleForTesting @visibleForTesting
@ -81,8 +83,50 @@ class _PdfMockContinuousListState extends ConsumerState<PdfMockContinuousList> {
child: Stack( child: Stack(
key: ValueKey('page_stack_$pageNum'), key: ValueKey('page_stack_$pageNum'),
children: [ children: [
Container( DragTarget<SignatureDragData>(
color: Colors.grey.shade200, onAcceptWithDetails: (details) {
final dragData = details.data;
final offset = details.offset;
final renderBox =
context.findRenderObject() as RenderBox?;
if (renderBox != null) {
final localPosition = renderBox.globalToLocal(offset);
final normalizedX =
localPosition.dx / renderBox.size.width;
final normalizedY =
localPosition.dy / renderBox.size.height;
// Create a default rect for the signature (can be adjusted later)
final rect = Rect.fromLTWH(
(normalizedX - 0.1).clamp(
0.0,
0.8,
), // Center horizontally with some margin
(normalizedY - 0.05).clamp(
0.0,
0.9,
), // Center vertically with some margin
0.2, // Default width
0.1, // Default height
);
// Add placement to the document
ref
.read(documentRepositoryProvider.notifier)
.addPlacement(
page: pageNum,
rect: rect,
asset: dragData.card?.asset,
rotationDeg: dragData.card?.rotationDeg ?? 0.0,
);
}
},
builder: (context, candidateData, rejectedData) {
return Container(
color:
candidateData.isNotEmpty
? Colors.blue.withOpacity(0.3)
: Colors.grey.shade200,
child: Center( child: Center(
child: Builder( child: Builder(
builder: (ctx) { builder: (ctx) {
@ -104,6 +148,8 @@ class _PdfMockContinuousListState extends ConsumerState<PdfMockContinuousList> {
}, },
), ),
), ),
);
},
), ),
visible visible
? Stack( ? Stack(

View File

@ -6,6 +6,7 @@ import 'package:pdf_signature/l10n/app_localizations.dart';
import 'pdf_page_overlays.dart'; import 'pdf_page_overlays.dart';
import 'pdf_providers.dart'; import 'pdf_providers.dart';
import './pdf_mock_continuous_list.dart'; import './pdf_mock_continuous_list.dart';
import '../../signature/widgets/signature_drag_data.dart';
class PdfViewerWidget extends ConsumerStatefulWidget { class PdfViewerWidget extends ConsumerStatefulWidget {
const PdfViewerWidget({ const PdfViewerWidget({
@ -115,6 +116,41 @@ class _PdfViewerWidgetState extends ConsumerState<PdfViewerWidget> {
}, },
), ),
), ),
// Drag target for dropping signatures
Positioned.fill(
child: DragTarget<SignatureDragData>(
onAcceptWithDetails: (details) {
final dragData = details.data;
// For real PDF viewer, we need to calculate which page was dropped on
// This is a simplified implementation - in a real app you'd need to
// determine the exact page and position within that page
final currentPage =
ref.read(documentRepositoryProvider).currentPage;
// Create a default rect for the signature (can be adjusted later)
final rect = const Rect.fromLTWH(0.1, 0.1, 0.2, 0.1);
// Add placement to the document
ref
.read(documentRepositoryProvider.notifier)
.addPlacement(
page: currentPage,
rect: rect,
asset: dragData.card?.asset,
rotationDeg: dragData.card?.rotationDeg ?? 0.0,
);
},
builder: (context, candidateData, rejectedData) {
return Container(
color:
candidateData.isNotEmpty
? Colors.blue.withOpacity(0.1)
: Colors.transparent,
);
},
),
),
// Add signature overlays on top // Add signature overlays on top
Positioned.fill( Positioned.fill(
child: Consumer( child: Consumer(

View File

@ -1,5 +1,5 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:pdf_signature/domain/models/model.dart'; import 'package:pdf_signature/domain/models/model.dart' as domain;
import 'signature_drag_data.dart'; import 'signature_drag_data.dart';
import 'rotated_signature_image.dart'; import 'rotated_signature_image.dart';
import 'package:pdf_signature/l10n/app_localizations.dart'; import 'package:pdf_signature/l10n/app_localizations.dart';
@ -15,7 +15,7 @@ class SignatureCard extends StatelessWidget {
this.useCurrentBytesForDrag = false, this.useCurrentBytesForDrag = false,
this.rotationDeg = 0.0, this.rotationDeg = 0.0,
}); });
final SignatureAsset asset; final domain.SignatureAsset asset;
final bool disabled; final bool disabled;
final VoidCallback onDelete; final VoidCallback onDelete;
final VoidCallback? onTap; final VoidCallback? onTap;
@ -142,7 +142,12 @@ class SignatureCard extends StatelessWidget {
data: data:
useCurrentBytesForDrag useCurrentBytesForDrag
? const SignatureDragData() ? const SignatureDragData()
: SignatureDragData(asset: asset), : SignatureDragData(
card: domain.SignatureCard(
asset: asset,
rotationDeg: rotationDeg,
),
),
feedback: Opacity( feedback: Opacity(
opacity: 0.9, opacity: 0.9,
child: ConstrainedBox( child: ConstrainedBox(

View File

@ -1,6 +1,6 @@
import 'package:pdf_signature/domain/models/model.dart'; import 'package:pdf_signature/domain/models/model.dart';
class SignatureDragData { class SignatureDragData {
final SignatureAsset? asset; // null means use current processed signature final SignatureCard? card; // null means use current processed signature
const SignatureDragData({this.asset}); const SignatureDragData({this.card});
} }

View File

@ -4,6 +4,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/data/repositories/document_repository.dart'; import 'package:pdf_signature/data/repositories/document_repository.dart';
import 'package:pdf_signature/data/repositories/signature_asset_repository.dart'; import 'package:pdf_signature/data/repositories/signature_asset_repository.dart';
import 'package:pdf_signature/data/repositories/signature_card_repository.dart';
import 'package:pdf_signature/domain/models/model.dart'; import 'package:pdf_signature/domain/models/model.dart';
import '_world.dart'; import '_world.dart';
@ -37,6 +38,14 @@ theUserDragsThisSignatureCardOnThePageOfTheDocumentToPlaceASignaturePlacement(
.firstWhere((a) => a.name == 'placement.png'); .firstWhere((a) => a.name == 'placement.png');
} }
// create a signature card
final temp_card = SignatureCard(asset: asset, rotationDeg: 0);
container
.read(signatureCardRepositoryProvider.notifier)
.addWithAsset(temp_card.asset, temp_card.rotationDeg);
// drag and drop (DragTarget<SignatureCard>, `onAccept`) it on document page
final drop_card = temp_card;
// Place it on the current page // Place it on the current page
final pdf = container.read(documentRepositoryProvider); final pdf = container.read(documentRepositoryProvider);
container container
@ -44,6 +53,7 @@ theUserDragsThisSignatureCardOnThePageOfTheDocumentToPlaceASignaturePlacement(
.addPlacement( .addPlacement(
page: pdf.currentPage, page: pdf.currentPage,
rect: Rect.fromLTWH(100, 100, 100, 50), rect: Rect.fromLTWH(100, 100, 100, 50),
asset: asset, asset: drop_card.asset,
rotationDeg: drop_card.rotationDeg,
); );
} }

View File

@ -6,7 +6,7 @@ import 'package:pdf_signature/data/repositories/document_repository.dart';
import 'package:pdf_signature/data/repositories/signature_asset_repository.dart'; import 'package:pdf_signature/data/repositories/signature_asset_repository.dart';
import '_world.dart'; import '_world.dart';
/// Usage: the user places a signature placement from asset <second_asset> on page <second_page> /// Usage: the user places a signature placement from asset <secondAsset> on page <secondPage>
Future<void> theUserPlacesASignaturePlacementFromAssetOnPage( Future<void> theUserPlacesASignaturePlacementFromAssetOnPage(
WidgetTester tester, WidgetTester tester,
String assetName, String assetName,

View File

@ -2,15 +2,11 @@ Feature: support multiple signature assets
Scenario: Place signature placements on different pages with different assets Scenario: Place signature placements on different pages with different assets
Given a multi-page document is open Given a multi-page document is open
When the user places a signature placement from asset <first_asset> on page <first_page> When the user places a signature placement from asset <firstAsset> on page <firstPage>.
And the user places a signature placement from asset <second_asset> on page <second_page> And the user places a signature placement from asset <secondAsset> on page <secondPage>.
Then both signature placements are shown on their respective pages Then both signature placements are shown on their respective pages
Examples: Examples:
# Same page, same asset | firstAsset | firstPage | secondAsset | secondPage |
# Same page, different assets
# Different pages, same asset
# Different pages, different assets
| first_asset | first_page | second_asset | second_page |
| 'alice.png' | 1 | 'alice.png' | 1 | | 'alice.png' | 1 | 'alice.png' | 1 |
| 'alice.png' | 1 | 'bob.png' | 1 | | 'alice.png' | 1 | 'bob.png' | 1 |
| 'alice.png' | 1 | 'bob.png' | 3 | | 'alice.png' | 1 | 'bob.png' | 3 |