Compare commits
No commits in common. "a354efb0b1a8b93928da654ed8543c7d3db9a9dc" and "a4890b6ea0575ab87ba750e0dffcadf7937b864c" have entirely different histories.
a354efb0b1
...
a4890b6ea0
|
@ -1,46 +0,0 @@
|
||||||
# Prevent leaking host-specific caches/paths into the image
|
|
||||||
.dockerignore
|
|
||||||
.git
|
|
||||||
.gitignore
|
|
||||||
.idea
|
|
||||||
.vscode
|
|
||||||
**/.DS_Store
|
|
||||||
build/
|
|
||||||
.dart_tool/
|
|
||||||
.flutter-plugins
|
|
||||||
.flutter-plugins-dependencies
|
|
||||||
.packages
|
|
||||||
pubspec.lock # keep if you want reproducible, comment out to include
|
|
||||||
# Flutter/Platform build outputs
|
|
||||||
android/
|
|
||||||
ios/
|
|
||||||
linux/
|
|
||||||
macos/
|
|
||||||
windows/
|
|
||||||
web/.dart_tool/
|
|
||||||
# Tests and dev artifacts (optional, not needed in image build stage)
|
|
||||||
test/
|
|
||||||
integration_test/
|
|
||||||
coverage/
|
|
||||||
custom_lint.log
|
|
||||||
test_cache/
|
|
||||||
unit_test_assets/
|
|
||||||
|
|
||||||
# Docs and repo meta to avoid cache busting
|
|
||||||
docs/
|
|
||||||
**/*.md
|
|
||||||
wireframe.assets/
|
|
||||||
AGENTS.md
|
|
||||||
README.md
|
|
||||||
LICENSE
|
|
||||||
|
|
||||||
# Packaging artifacts not needed for web image
|
|
||||||
AppDir/
|
|
||||||
AppRun
|
|
||||||
pdf_signature.desktop
|
|
||||||
tool/
|
|
||||||
*.iml
|
|
||||||
*.ipr
|
|
||||||
*.iws
|
|
||||||
.github/
|
|
||||||
.husky/
|
|
|
@ -1,93 +0,0 @@
|
||||||
name: Publish Docker image
|
|
||||||
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
branches: [ "main" ]
|
|
||||||
tags:
|
|
||||||
- "v*"
|
|
||||||
workflow_dispatch:
|
|
||||||
|
|
||||||
permissions:
|
|
||||||
contents: read
|
|
||||||
packages: write
|
|
||||||
|
|
||||||
env:
|
|
||||||
REGISTRY: docker.io
|
|
||||||
IMAGE_NAME: ${{ github.repository }}
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
build-and-push:
|
|
||||||
name: Build and push image
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
concurrency:
|
|
||||||
group: docker-${{ github.ref }}
|
|
||||||
cancel-in-progress: false
|
|
||||||
steps:
|
|
||||||
- name: Checkout
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
|
|
||||||
- name: Set up QEMU (for multi-arch builds)
|
|
||||||
uses: docker/setup-qemu-action@v3
|
|
||||||
|
|
||||||
- name: Set up Docker Buildx
|
|
||||||
uses: docker/setup-buildx-action@v3
|
|
||||||
|
|
||||||
- name: Cache Docker layers
|
|
||||||
uses: actions/cache@v4
|
|
||||||
with:
|
|
||||||
path: /tmp/.buildx-cache
|
|
||||||
key: ${{ runner.os }}-buildx-${{ hashFiles('Dockerfile', 'pubspec.lock') }}
|
|
||||||
restore-keys: |
|
|
||||||
${{ runner.os }}-buildx-
|
|
||||||
|
|
||||||
# Docker Hub login (active)
|
|
||||||
- name: Log in to Docker Hub
|
|
||||||
uses: docker/login-action@v3
|
|
||||||
with:
|
|
||||||
registry: docker.io
|
|
||||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
|
||||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
|
||||||
|
|
||||||
# GHCR login (commented out for future usage)
|
|
||||||
# - name: Log in to GitHub Container Registry
|
|
||||||
# uses: docker/login-action@v3
|
|
||||||
# with:
|
|
||||||
# registry: ghcr.io
|
|
||||||
# username: ${{ github.actor }}
|
|
||||||
# password: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
|
|
||||||
- name: Extract Docker metadata (tags, labels)
|
|
||||||
id: meta
|
|
||||||
uses: docker/metadata-action@v5
|
|
||||||
with:
|
|
||||||
images: |
|
|
||||||
${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
|
|
||||||
tags: |
|
|
||||||
type=ref,event=branch
|
|
||||||
type=semver,pattern={{version}}
|
|
||||||
type=semver,pattern={{major}}.{{minor}}
|
|
||||||
flavor: |
|
|
||||||
latest=${{ github.ref == format('refs/heads/{0}', github.event.repository.default_branch) }}
|
|
||||||
token: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
|
|
||||||
- name: Build and push
|
|
||||||
uses: docker/build-push-action@v6
|
|
||||||
with:
|
|
||||||
context: .
|
|
||||||
file: ./Dockerfile
|
|
||||||
platforms: linux/amd64
|
|
||||||
push: ${{ github.event_name != 'pull_request' }}
|
|
||||||
tags: ${{ steps.meta.outputs.tags }}
|
|
||||||
labels: ${{ steps.meta.outputs.labels }}
|
|
||||||
cache-from: type=local,src=/tmp/.buildx-cache
|
|
||||||
cache-to: type=local,dest=/tmp/.buildx-cache-new,mode=max
|
|
||||||
|
|
||||||
- name: Move cache
|
|
||||||
if: always()
|
|
||||||
run: |
|
|
||||||
if [ -d /tmp/.buildx-cache-new ]; then
|
|
||||||
rm -rf /tmp/.buildx-cache
|
|
||||||
mv /tmp/.buildx-cache-new /tmp/.buildx-cache
|
|
||||||
else
|
|
||||||
echo "No new cache to move"
|
|
||||||
fi
|
|
|
@ -125,7 +125,6 @@ devtools_options.yaml
|
||||||
test/features/*_test.dart
|
test/features/*_test.dart
|
||||||
**/app_localizations*.dart
|
**/app_localizations*.dart
|
||||||
.env
|
.env
|
||||||
.secrets
|
|
||||||
docs/wireframe.assets/*.excalidraw.svg
|
docs/wireframe.assets/*.excalidraw.svg
|
||||||
docs/wireframe.assets/*.svg
|
docs/wireframe.assets/*.svg
|
||||||
docs/wireframe.assets/*.png
|
docs/wireframe.assets/*.png
|
||||||
|
|
54
Dockerfile
54
Dockerfile
|
@ -1,54 +0,0 @@
|
||||||
# syntax=docker/dockerfile:1.7-labs
|
|
||||||
## Two-stage build for minimal Flutter web static server (Caddy runtime)
|
|
||||||
# Stage 1: Build the Flutter web app
|
|
||||||
FROM ghcr.io/cirruslabs/flutter:latest AS build
|
|
||||||
|
|
||||||
WORKDIR /app
|
|
||||||
|
|
||||||
# Copy pubspec first for better layer caching
|
|
||||||
COPY pubspec.* ./
|
|
||||||
# Use BuildKit cache for Dart pub cache
|
|
||||||
RUN --mount=type=cache,target=/root/.pub-cache \
|
|
||||||
flutter pub get
|
|
||||||
|
|
||||||
# Copy the rest of the project
|
|
||||||
COPY . .
|
|
||||||
|
|
||||||
# Ensure no host caches leak into the container; use BuildKit caches for pub and Flutter
|
|
||||||
RUN --mount=type=cache,target=/root/.pub-cache \
|
|
||||||
--mount=type=cache,target=/sdks/flutter/bin/cache \
|
|
||||||
rm -rf .dart_tool build && \
|
|
||||||
flutter pub get && \
|
|
||||||
flutter gen-l10n && \
|
|
||||||
flutter build web --release -O4 --wasm
|
|
||||||
|
|
||||||
# Stage 2: Caddy (Alpine) to serve static files with SPA fallback
|
|
||||||
FROM caddy:2-alpine AS runtime
|
|
||||||
WORKDIR /usr/share/caddy
|
|
||||||
# Copy built web assets
|
|
||||||
COPY --from=build /app/build/web/ /usr/share/caddy/
|
|
||||||
# Write Caddyfile inline (listens on :8080 and SPA fallback)
|
|
||||||
ENV PORT=8080
|
|
||||||
RUN cat > /etc/caddy/Caddyfile <<'CADDY'
|
|
||||||
{
|
|
||||||
admin off
|
|
||||||
}
|
|
||||||
|
|
||||||
:{$PORT} {
|
|
||||||
root * /usr/share/caddy
|
|
||||||
encode zstd gzip
|
|
||||||
# SPA fallback: serve index.html if file not found
|
|
||||||
try_files {path} /index.html
|
|
||||||
file_server
|
|
||||||
}
|
|
||||||
CADDY
|
|
||||||
# Some platforms (e.g., gVisor/Firecracker like Render) forbid file capabilities; strip and copy to a clean path
|
|
||||||
USER root
|
|
||||||
RUN apk add --no-cache libcap && \
|
|
||||||
(setcap -r /usr/bin/caddy || true) && \
|
|
||||||
install -m 0755 /usr/bin/caddy /caddy && \
|
|
||||||
apk del libcap
|
|
||||||
# Use numeric UID/GID for caddy to avoid passwd lookup issues across platforms
|
|
||||||
USER 65532:65532
|
|
||||||
EXPOSE 8080
|
|
||||||
ENTRYPOINT ["/caddy", "run", "--config", "/etc/caddy/Caddyfile", "--adapter", "caddyfile"]
|
|
26
README.md
26
README.md
|
@ -21,13 +21,13 @@ flutter analyze
|
||||||
# > run unit tests and widget tests
|
# > run unit tests and widget tests
|
||||||
flutter test
|
flutter test
|
||||||
# > run integration tests
|
# > run integration tests
|
||||||
flutter test integration_test/ -d <device_id>
|
flutter test integration_test/ -d linux
|
||||||
|
|
||||||
# dart run tool/gen_view_wireframe_md.dart
|
# dart run tool/gen_view_wireframe_md.dart
|
||||||
# flutter pub run dead_code_analyzer
|
# flutter pub run dead_code_analyzer
|
||||||
|
|
||||||
# run the app
|
# run the app
|
||||||
flutter run -d <device_id>
|
flutter run
|
||||||
```
|
```
|
||||||
|
|
||||||
### build
|
### build
|
||||||
|
@ -39,33 +39,13 @@ flutter build windows
|
||||||
flutter pub run msix:create
|
flutter pub run msix:create
|
||||||
```
|
```
|
||||||
|
|
||||||
#### web
|
For web
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
flutter build web
|
flutter build web
|
||||||
# flutter build web --release -O4 --wasm
|
|
||||||
```
|
```
|
||||||
Open the `index.html` file in the `build/web` directory. Remove the `<base href="/">` to ensure proper routing on GitHub Pages.
|
Open the `index.html` file in the `build/web` directory. Remove the `<base href="/">` to ensure proper routing on GitHub Pages.
|
||||||
|
|
||||||
##### Docker
|
|
||||||
|
|
||||||
To build and run a minimal Docker image serving static Flutter web files:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Build the Docker image
|
|
||||||
docker build -t pdf_signature .
|
|
||||||
|
|
||||||
# Run the container (serves static files on port 8080)
|
|
||||||
docker run --rm -p 8080:8080 pdf_signature
|
|
||||||
|
|
||||||
# act push -P ubuntu-latest=catthehacker/ubuntu:act-latest --container-options "--privileged" --env-file .env --secret-file .secrets
|
|
||||||
```
|
|
||||||
Access your app at [http://localhost:8080](http://localhost:8080)
|
|
||||||
|
|
||||||
#### Linux
|
|
||||||
|
|
||||||
For Linux
|
For Linux
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
flutter build linux
|
flutter build linux
|
||||||
cp -r build/linux/x64/release/bundle/ AppDir
|
cp -r build/linux/x64/release/bundle/ AppDir
|
||||||
|
|
|
@ -45,7 +45,6 @@ void main() {
|
||||||
child: const MaterialApp(
|
child: const MaterialApp(
|
||||||
localizationsDelegates: AppLocalizations.localizationsDelegates,
|
localizationsDelegates: AppLocalizations.localizationsDelegates,
|
||||||
supportedLocales: AppLocalizations.supportedLocales,
|
supportedLocales: AppLocalizations.supportedLocales,
|
||||||
locale: Locale('en'),
|
|
||||||
home: PdfSignatureHomePage(),
|
home: PdfSignatureHomePage(),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
@ -97,7 +96,6 @@ void main() {
|
||||||
child: const MaterialApp(
|
child: const MaterialApp(
|
||||||
localizationsDelegates: AppLocalizations.localizationsDelegates,
|
localizationsDelegates: AppLocalizations.localizationsDelegates,
|
||||||
supportedLocales: AppLocalizations.supportedLocales,
|
supportedLocales: AppLocalizations.supportedLocales,
|
||||||
locale: Locale('en'),
|
|
||||||
home: PdfSignatureHomePage(),
|
home: PdfSignatureHomePage(),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
|
@ -2,7 +2,6 @@ import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
import 'package:path_provider/path_provider.dart' as pp;
|
import 'package:path_provider/path_provider.dart' as pp;
|
||||||
import 'package:file_selector/file_selector.dart' as fs;
|
import 'package:file_selector/file_selector.dart' as fs;
|
||||||
import 'package:pdf_signature/data/services/export_service.dart';
|
import 'package:pdf_signature/data/services/export_service.dart';
|
||||||
import 'package:pdf_signature/data/services/preferences_providers.dart';
|
|
||||||
|
|
||||||
// Feature-scoped DI and configuration providers
|
// Feature-scoped DI and configuration providers
|
||||||
|
|
||||||
|
@ -12,19 +11,8 @@ final useMockViewerProvider = Provider<bool>((_) => false);
|
||||||
// Export service injection for testability
|
// Export service injection for testability
|
||||||
final exportServiceProvider = Provider<ExportService>((_) => ExportService());
|
final exportServiceProvider = Provider<ExportService>((_) => ExportService());
|
||||||
|
|
||||||
// Export DPI setting (points per inch mapping). Reads from SharedPreferences when available,
|
// Export DPI setting (points per inch mapping), default 144 DPI
|
||||||
// otherwise falls back to 144.0 to keep tests deterministic without bootstrapping prefs.
|
final exportDpiProvider = StateProvider<double>((_) => 144.0);
|
||||||
final exportDpiProvider = Provider<double>((ref) {
|
|
||||||
final sp = ref.watch(sharedPreferencesProvider);
|
|
||||||
return sp.maybeWhen(
|
|
||||||
data: (prefs) {
|
|
||||||
const allowed = [96.0, 144.0, 200.0, 300.0];
|
|
||||||
final v = prefs.getDouble('export_dpi');
|
|
||||||
return (v != null && allowed.contains(v)) ? v : 144.0;
|
|
||||||
},
|
|
||||||
orElse: () => 144.0,
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
// Controls whether signature overlay is visible (used to hide on non-stamped pages during export)
|
// Controls whether signature overlay is visible (used to hide on non-stamped pages during export)
|
||||||
final signatureVisibilityProvider = StateProvider<bool>((_) => true);
|
final signatureVisibilityProvider = StateProvider<bool>((_) => true);
|
||||||
|
|
|
@ -29,7 +29,6 @@ Set<String> _supportedTags() {
|
||||||
const _kTheme = 'theme'; // 'light'|'dark'|'system'
|
const _kTheme = 'theme'; // 'light'|'dark'|'system'
|
||||||
const _kLanguage = 'language'; // BCP-47 tag like 'en', 'zh-TW', 'es'
|
const _kLanguage = 'language'; // BCP-47 tag like 'en', 'zh-TW', 'es'
|
||||||
const _kPageView = 'page_view'; // now only 'continuous'
|
const _kPageView = 'page_view'; // now only 'continuous'
|
||||||
const _kExportDpi = 'export_dpi'; // double, allowed: 96,144,200,300
|
|
||||||
|
|
||||||
String _normalizeLanguageTag(String tag) {
|
String _normalizeLanguageTag(String tag) {
|
||||||
final tags = _supportedTags();
|
final tags = _supportedTags();
|
||||||
|
@ -67,24 +66,20 @@ class PreferencesState {
|
||||||
final String theme; // 'light' | 'dark' | 'system'
|
final String theme; // 'light' | 'dark' | 'system'
|
||||||
final String language; // 'en' | 'zh-TW' | 'es'
|
final String language; // 'en' | 'zh-TW' | 'es'
|
||||||
final String pageView; // only 'continuous'
|
final String pageView; // only 'continuous'
|
||||||
final double exportDpi; // 96.0 | 144.0 | 200.0 | 300.0
|
|
||||||
const PreferencesState({
|
const PreferencesState({
|
||||||
required this.theme,
|
required this.theme,
|
||||||
required this.language,
|
required this.language,
|
||||||
required this.pageView,
|
required this.pageView,
|
||||||
required this.exportDpi,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
PreferencesState copyWith({
|
PreferencesState copyWith({
|
||||||
String? theme,
|
String? theme,
|
||||||
String? language,
|
String? language,
|
||||||
String? pageView,
|
String? pageView,
|
||||||
double? exportDpi,
|
|
||||||
}) => PreferencesState(
|
}) => PreferencesState(
|
||||||
theme: theme ?? this.theme,
|
theme: theme ?? this.theme,
|
||||||
language: language ?? this.language,
|
language: language ?? this.language,
|
||||||
pageView: pageView ?? this.pageView,
|
pageView: pageView ?? this.pageView,
|
||||||
exportDpi: exportDpi ?? this.exportDpi,
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -100,20 +95,12 @@ class PreferencesNotifier extends StateNotifier<PreferencesState> {
|
||||||
.toLanguageTag(),
|
.toLanguageTag(),
|
||||||
),
|
),
|
||||||
pageView: prefs.getString(_kPageView) ?? 'continuous',
|
pageView: prefs.getString(_kPageView) ?? 'continuous',
|
||||||
exportDpi: _readDpi(prefs),
|
|
||||||
),
|
),
|
||||||
) {
|
) {
|
||||||
// normalize language to supported/fallback
|
// normalize language to supported/fallback
|
||||||
_ensureValid();
|
_ensureValid();
|
||||||
}
|
}
|
||||||
|
|
||||||
static double _readDpi(SharedPreferences prefs) {
|
|
||||||
final d = prefs.getDouble(_kExportDpi);
|
|
||||||
if (d == null) return 144.0;
|
|
||||||
const allowed = [96.0, 144.0, 200.0, 300.0];
|
|
||||||
return allowed.contains(d) ? d : 144.0;
|
|
||||||
}
|
|
||||||
|
|
||||||
void _ensureValid() {
|
void _ensureValid() {
|
||||||
final themeValid = {'light', 'dark', 'system'};
|
final themeValid = {'light', 'dark', 'system'};
|
||||||
if (!themeValid.contains(state.theme)) {
|
if (!themeValid.contains(state.theme)) {
|
||||||
|
@ -130,12 +117,6 @@ class PreferencesNotifier extends StateNotifier<PreferencesState> {
|
||||||
state = state.copyWith(pageView: 'continuous');
|
state = state.copyWith(pageView: 'continuous');
|
||||||
prefs.setString(_kPageView, 'continuous');
|
prefs.setString(_kPageView, 'continuous');
|
||||||
}
|
}
|
||||||
// Ensure DPI is one of allowed values
|
|
||||||
const allowed = [96.0, 144.0, 200.0, 300.0];
|
|
||||||
if (!allowed.contains(state.exportDpi)) {
|
|
||||||
state = state.copyWith(exportDpi: 144.0);
|
|
||||||
prefs.setDouble(_kExportDpi, 144.0);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> setTheme(String theme) async {
|
Future<void> setTheme(String theme) async {
|
||||||
|
@ -159,12 +140,10 @@ class PreferencesNotifier extends StateNotifier<PreferencesState> {
|
||||||
theme: 'system',
|
theme: 'system',
|
||||||
language: normalized,
|
language: normalized,
|
||||||
pageView: 'continuous',
|
pageView: 'continuous',
|
||||||
exportDpi: 144.0,
|
|
||||||
);
|
);
|
||||||
await prefs.setString(_kTheme, 'system');
|
await prefs.setString(_kTheme, 'system');
|
||||||
await prefs.setString(_kLanguage, normalized);
|
await prefs.setString(_kLanguage, normalized);
|
||||||
await prefs.setString(_kPageView, 'continuous');
|
await prefs.setString(_kPageView, 'continuous');
|
||||||
await prefs.setDouble(_kExportDpi, 144.0);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> setPageView(String pageView) async {
|
Future<void> setPageView(String pageView) async {
|
||||||
|
@ -173,13 +152,6 @@ class PreferencesNotifier extends StateNotifier<PreferencesState> {
|
||||||
state = state.copyWith(pageView: pageView);
|
state = state.copyWith(pageView: pageView);
|
||||||
await prefs.setString(_kPageView, pageView);
|
await prefs.setString(_kPageView, pageView);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> setExportDpi(double dpi) async {
|
|
||||||
const allowed = [96.0, 144.0, 200.0, 300.0];
|
|
||||||
if (!allowed.contains(dpi)) return;
|
|
||||||
state = state.copyWith(exportDpi: dpi);
|
|
||||||
await prefs.setDouble(_kExportDpi, dpi);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
final sharedPreferencesProvider = FutureProvider<SharedPreferences>((
|
final sharedPreferencesProvider = FutureProvider<SharedPreferences>((
|
||||||
|
|
|
@ -11,7 +11,7 @@
|
||||||
"delete": "Löschen",
|
"delete": "Löschen",
|
||||||
"display": "Anzeige",
|
"display": "Anzeige",
|
||||||
"downloadStarted": "Download gestartet",
|
"downloadStarted": "Download gestartet",
|
||||||
"dpi": "DPI",
|
"dpi": "DPI:",
|
||||||
"drawSignature": "Signatur zeichnen",
|
"drawSignature": "Signatur zeichnen",
|
||||||
"errorWithMessage": "Fehler: {message}",
|
"errorWithMessage": "Fehler: {message}",
|
||||||
"exportingPleaseWait": "Exportiere… Bitte warten",
|
"exportingPleaseWait": "Exportiere… Bitte warten",
|
||||||
|
@ -19,7 +19,6 @@
|
||||||
"failedToSavePdf": "PDF konnte nicht gespeichert werden",
|
"failedToSavePdf": "PDF konnte nicht gespeichert werden",
|
||||||
"general": "Allgemein",
|
"general": "Allgemein",
|
||||||
"goTo": "Gehe zu:",
|
"goTo": "Gehe zu:",
|
||||||
"image": "Bild",
|
|
||||||
"invalidOrUnsupportedFile": "Ungültige oder nicht unterstützte Datei",
|
"invalidOrUnsupportedFile": "Ungültige oder nicht unterstützte Datei",
|
||||||
"language": "Sprache",
|
"language": "Sprache",
|
||||||
"loadSignatureFromFile": "Signatur aus Datei laden",
|
"loadSignatureFromFile": "Signatur aus Datei laden",
|
||||||
|
@ -35,7 +34,6 @@
|
||||||
"pageViewContinuous": "Kontinuierlich",
|
"pageViewContinuous": "Kontinuierlich",
|
||||||
"prev": "Vorherige",
|
"prev": "Vorherige",
|
||||||
"resetToDefaults": "Auf Standardwerte zurücksetzen",
|
"resetToDefaults": "Auf Standardwerte zurücksetzen",
|
||||||
"rotate": "Drehen",
|
|
||||||
"save": "Speichern",
|
"save": "Speichern",
|
||||||
"savedWithPath": "Gespeichert: {path}",
|
"savedWithPath": "Gespeichert: {path}",
|
||||||
"saveSignedPdf": "Signiertes PDF speichern",
|
"saveSignedPdf": "Signiertes PDF speichern",
|
||||||
|
|
|
@ -24,7 +24,7 @@
|
||||||
"@display": {},
|
"@display": {},
|
||||||
"downloadStarted": "Download started",
|
"downloadStarted": "Download started",
|
||||||
"@downloadStarted": {},
|
"@downloadStarted": {},
|
||||||
"dpi": "DPI",
|
"dpi": "DPI:",
|
||||||
"@dpi": {},
|
"@dpi": {},
|
||||||
"drawSignature": "Draw Signature",
|
"drawSignature": "Draw Signature",
|
||||||
"@drawSignature": {},
|
"@drawSignature": {},
|
||||||
|
@ -47,8 +47,6 @@
|
||||||
"@general": {},
|
"@general": {},
|
||||||
"goTo": "Go to:",
|
"goTo": "Go to:",
|
||||||
"@goTo": {},
|
"@goTo": {},
|
||||||
"image": "Image",
|
|
||||||
"@image": {},
|
|
||||||
"invalidOrUnsupportedFile": "Invalid or unsupported file",
|
"invalidOrUnsupportedFile": "Invalid or unsupported file",
|
||||||
"@invalidOrUnsupportedFile": {},
|
"@invalidOrUnsupportedFile": {},
|
||||||
"language": "Language",
|
"language": "Language",
|
||||||
|
@ -89,8 +87,6 @@
|
||||||
"@prev": {},
|
"@prev": {},
|
||||||
"resetToDefaults": "Reset to defaults",
|
"resetToDefaults": "Reset to defaults",
|
||||||
"@resetToDefaults": {},
|
"@resetToDefaults": {},
|
||||||
"rotate": "Rotate",
|
|
||||||
"@rotate": {},
|
|
||||||
"save": "Save",
|
"save": "Save",
|
||||||
"@save": {},
|
"@save": {},
|
||||||
"savedWithPath": "Saved: {path}",
|
"savedWithPath": "Saved: {path}",
|
||||||
|
|
|
@ -11,7 +11,7 @@
|
||||||
"delete": "Eliminar",
|
"delete": "Eliminar",
|
||||||
"display": "Pantalla",
|
"display": "Pantalla",
|
||||||
"downloadStarted": "Descarga iniciada",
|
"downloadStarted": "Descarga iniciada",
|
||||||
"dpi": "DPI",
|
"dpi": "DPI:",
|
||||||
"drawSignature": "Dibujar firma",
|
"drawSignature": "Dibujar firma",
|
||||||
"errorWithMessage": "Error: {message}",
|
"errorWithMessage": "Error: {message}",
|
||||||
"exportingPleaseWait": "Exportando... Por favor, espere",
|
"exportingPleaseWait": "Exportando... Por favor, espere",
|
||||||
|
@ -19,7 +19,6 @@
|
||||||
"failedToSavePdf": "No se pudo guardar el PDF",
|
"failedToSavePdf": "No se pudo guardar el PDF",
|
||||||
"general": "General",
|
"general": "General",
|
||||||
"goTo": "Ir a:",
|
"goTo": "Ir a:",
|
||||||
"image": "Imagen",
|
|
||||||
"invalidOrUnsupportedFile": "Archivo inválido o no compatible",
|
"invalidOrUnsupportedFile": "Archivo inválido o no compatible",
|
||||||
"language": "Idioma",
|
"language": "Idioma",
|
||||||
"loadSignatureFromFile": "Cargar firma desde archivo",
|
"loadSignatureFromFile": "Cargar firma desde archivo",
|
||||||
|
@ -35,7 +34,6 @@
|
||||||
"pageViewContinuous": "Continuo",
|
"pageViewContinuous": "Continuo",
|
||||||
"prev": "Anterior",
|
"prev": "Anterior",
|
||||||
"resetToDefaults": "Restablecer valores predeterminados",
|
"resetToDefaults": "Restablecer valores predeterminados",
|
||||||
"rotate": "Rotar",
|
|
||||||
"save": "Guardar",
|
"save": "Guardar",
|
||||||
"savedWithPath": "Guardado: {path}",
|
"savedWithPath": "Guardado: {path}",
|
||||||
"saveSignedPdf": "Guardar PDF firmado",
|
"saveSignedPdf": "Guardar PDF firmado",
|
||||||
|
|
|
@ -19,7 +19,6 @@
|
||||||
"failedToSavePdf": "Échec de l'enregistrement du PDF",
|
"failedToSavePdf": "Échec de l'enregistrement du PDF",
|
||||||
"general": "Général",
|
"general": "Général",
|
||||||
"goTo": "Aller à :",
|
"goTo": "Aller à :",
|
||||||
"image": "Image",
|
|
||||||
"invalidOrUnsupportedFile": "Fichier invalide ou non pris en charge",
|
"invalidOrUnsupportedFile": "Fichier invalide ou non pris en charge",
|
||||||
"language": "Langue",
|
"language": "Langue",
|
||||||
"loadSignatureFromFile": "Charger une signature depuis un fichier",
|
"loadSignatureFromFile": "Charger une signature depuis un fichier",
|
||||||
|
@ -35,7 +34,6 @@
|
||||||
"pageViewContinuous": "Continu",
|
"pageViewContinuous": "Continu",
|
||||||
"prev": "Précédent",
|
"prev": "Précédent",
|
||||||
"resetToDefaults": "Rétablir les valeurs par défaut",
|
"resetToDefaults": "Rétablir les valeurs par défaut",
|
||||||
"rotate": "Rotation",
|
|
||||||
"save": "Enregistrer",
|
"save": "Enregistrer",
|
||||||
"savedWithPath": "Enregistré : {path}",
|
"savedWithPath": "Enregistré : {path}",
|
||||||
"saveSignedPdf": "Enregistrer le PDF signé",
|
"saveSignedPdf": "Enregistrer le PDF signé",
|
||||||
|
|
|
@ -11,7 +11,7 @@
|
||||||
"delete": "削除",
|
"delete": "削除",
|
||||||
"display": "表示",
|
"display": "表示",
|
||||||
"downloadStarted": "ダウンロード開始",
|
"downloadStarted": "ダウンロード開始",
|
||||||
"dpi": "DPI",
|
"dpi": "DPI:",
|
||||||
"drawSignature": "署名をかく",
|
"drawSignature": "署名をかく",
|
||||||
"errorWithMessage": "エラー:{message}",
|
"errorWithMessage": "エラー:{message}",
|
||||||
"exportingPleaseWait": "エクスポート中…お待ちください",
|
"exportingPleaseWait": "エクスポート中…お待ちください",
|
||||||
|
@ -19,7 +19,6 @@
|
||||||
"failedToSavePdf": "PDFの保存に失敗しました",
|
"failedToSavePdf": "PDFの保存に失敗しました",
|
||||||
"general": "一般",
|
"general": "一般",
|
||||||
"goTo": "移動:",
|
"goTo": "移動:",
|
||||||
"image": "画像",
|
|
||||||
"invalidOrUnsupportedFile": "無効なファイルまたはサポートされていないファイル",
|
"invalidOrUnsupportedFile": "無効なファイルまたはサポートされていないファイル",
|
||||||
"language": "言語",
|
"language": "言語",
|
||||||
"loadSignatureFromFile": "ファイルから署名を読み込む",
|
"loadSignatureFromFile": "ファイルから署名を読み込む",
|
||||||
|
@ -35,7 +34,6 @@
|
||||||
"pageViewContinuous": "連続",
|
"pageViewContinuous": "連続",
|
||||||
"prev": "前へ",
|
"prev": "前へ",
|
||||||
"resetToDefaults": "デフォルトに戻す",
|
"resetToDefaults": "デフォルトに戻す",
|
||||||
"rotate": "回転",
|
|
||||||
"save": "保存",
|
"save": "保存",
|
||||||
"savedWithPath": "保存しました:{path}",
|
"savedWithPath": "保存しました:{path}",
|
||||||
"saveSignedPdf": "署名済みPDFを保存",
|
"saveSignedPdf": "署名済みPDFを保存",
|
||||||
|
|
|
@ -11,7 +11,7 @@
|
||||||
"delete": "삭제",
|
"delete": "삭제",
|
||||||
"display": "표시",
|
"display": "표시",
|
||||||
"downloadStarted": "다운로드 시작됨",
|
"downloadStarted": "다운로드 시작됨",
|
||||||
"dpi": "DPI",
|
"dpi": "DPI:",
|
||||||
"drawSignature": "서명 그리기",
|
"drawSignature": "서명 그리기",
|
||||||
"errorWithMessage": "오류: {message}",
|
"errorWithMessage": "오류: {message}",
|
||||||
"exportingPleaseWait": "내보내는 중... 잠시 기다려주세요",
|
"exportingPleaseWait": "내보내는 중... 잠시 기다려주세요",
|
||||||
|
@ -19,7 +19,6 @@
|
||||||
"failedToSavePdf": "PDF 저장 실패",
|
"failedToSavePdf": "PDF 저장 실패",
|
||||||
"general": "일반",
|
"general": "일반",
|
||||||
"goTo": "이동:",
|
"goTo": "이동:",
|
||||||
"image": "이미지",
|
|
||||||
"invalidOrUnsupportedFile": "잘못된 파일이거나 지원되지 않는 파일입니다.",
|
"invalidOrUnsupportedFile": "잘못된 파일이거나 지원되지 않는 파일입니다.",
|
||||||
"language": "언어",
|
"language": "언어",
|
||||||
"loadSignatureFromFile": "파일에서 서명 불러오기",
|
"loadSignatureFromFile": "파일에서 서명 불러오기",
|
||||||
|
@ -35,7 +34,6 @@
|
||||||
"pageViewContinuous": "연속",
|
"pageViewContinuous": "연속",
|
||||||
"prev": "이전",
|
"prev": "이전",
|
||||||
"resetToDefaults": "기본값으로 재설정",
|
"resetToDefaults": "기본값으로 재설정",
|
||||||
"rotate": "회전",
|
|
||||||
"save": "저장",
|
"save": "저장",
|
||||||
"savedWithPath": "{path}에 저장됨",
|
"savedWithPath": "{path}에 저장됨",
|
||||||
"saveSignedPdf": "서명된 PDF 저장",
|
"saveSignedPdf": "서명된 PDF 저장",
|
||||||
|
|
|
@ -11,7 +11,7 @@
|
||||||
"delete": "Видалити",
|
"delete": "Видалити",
|
||||||
"display": "Відображення",
|
"display": "Відображення",
|
||||||
"downloadStarted": "Завантаження розпочато",
|
"downloadStarted": "Завантаження розпочато",
|
||||||
"dpi": "DPI",
|
"dpi": "DPI:",
|
||||||
"drawSignature": "Намалювати підпис",
|
"drawSignature": "Намалювати підпис",
|
||||||
"errorWithMessage": "Помилка: {message}",
|
"errorWithMessage": "Помилка: {message}",
|
||||||
"exportingPleaseWait": "Експортування... Зачекайте",
|
"exportingPleaseWait": "Експортування... Зачекайте",
|
||||||
|
@ -19,7 +19,6 @@
|
||||||
"failedToSavePdf": "Не вдалося зберегти PDF",
|
"failedToSavePdf": "Не вдалося зберегти PDF",
|
||||||
"general": "Загальні",
|
"general": "Загальні",
|
||||||
"goTo": "Перейти до:",
|
"goTo": "Перейти до:",
|
||||||
"image": "Зображення",
|
|
||||||
"invalidOrUnsupportedFile": "Недійсний або непідтримуваний файл",
|
"invalidOrUnsupportedFile": "Недійсний або непідтримуваний файл",
|
||||||
"language": "Мова",
|
"language": "Мова",
|
||||||
"loadSignatureFromFile": "Завантажити підпис з файлу",
|
"loadSignatureFromFile": "Завантажити підпис з файлу",
|
||||||
|
@ -35,7 +34,6 @@
|
||||||
"pageViewContinuous": "Безперервний",
|
"pageViewContinuous": "Безперервний",
|
||||||
"prev": "Попередня",
|
"prev": "Попередня",
|
||||||
"resetToDefaults": "Скинути до значень за замовчуванням",
|
"resetToDefaults": "Скинути до значень за замовчуванням",
|
||||||
"rotate": "Повернути",
|
|
||||||
"save": "Зберегти",
|
"save": "Зберегти",
|
||||||
"savedWithPath": "Збережено: {path}",
|
"savedWithPath": "Збережено: {path}",
|
||||||
"saveSignedPdf": "Зберегти підписаний PDF",
|
"saveSignedPdf": "Зберегти підписаний PDF",
|
||||||
|
|
|
@ -12,7 +12,7 @@
|
||||||
"delete": "刪除",
|
"delete": "刪除",
|
||||||
"display": "顯示",
|
"display": "顯示",
|
||||||
"downloadStarted": "已開始下載",
|
"downloadStarted": "已開始下載",
|
||||||
"dpi": "DPI",
|
"dpi": "DPI:",
|
||||||
"drawSignature": "手寫簽名",
|
"drawSignature": "手寫簽名",
|
||||||
"errorWithMessage": "錯誤:{message}",
|
"errorWithMessage": "錯誤:{message}",
|
||||||
"exportingPleaseWait": "匯出中…請稍候",
|
"exportingPleaseWait": "匯出中…請稍候",
|
||||||
|
@ -20,7 +20,6 @@
|
||||||
"failedToSavePdf": "儲存 PDF 失敗",
|
"failedToSavePdf": "儲存 PDF 失敗",
|
||||||
"general": "一般",
|
"general": "一般",
|
||||||
"goTo": "前往:",
|
"goTo": "前往:",
|
||||||
"image": "圖片",
|
|
||||||
"invalidOrUnsupportedFile": "無效或不支援的檔案",
|
"invalidOrUnsupportedFile": "無效或不支援的檔案",
|
||||||
"language": "語言",
|
"language": "語言",
|
||||||
"loadSignatureFromFile": "從檔案載入簽名",
|
"loadSignatureFromFile": "從檔案載入簽名",
|
||||||
|
@ -28,7 +27,7 @@
|
||||||
"longPressOrRightClickTheSignatureToConfirmOrDelete": "長按或右鍵點擊簽名以確認或刪除。",
|
"longPressOrRightClickTheSignatureToConfirmOrDelete": "長按或右鍵點擊簽名以確認或刪除。",
|
||||||
"next": "下一頁",
|
"next": "下一頁",
|
||||||
"noPdfLoaded": "尚未載入 PDF",
|
"noPdfLoaded": "尚未載入 PDF",
|
||||||
"noSignatureLoaded": "沒有加載簽名",
|
"noSignatureLoaded": "没有加载签名",
|
||||||
"nothingToSaveYet": "尚無可儲存的內容",
|
"nothingToSaveYet": "尚無可儲存的內容",
|
||||||
"openPdf": "開啟 PDF…",
|
"openPdf": "開啟 PDF…",
|
||||||
"pageInfo": "第 {current}/{total} 頁",
|
"pageInfo": "第 {current}/{total} 頁",
|
||||||
|
@ -36,7 +35,6 @@
|
||||||
"pageViewContinuous": "連續",
|
"pageViewContinuous": "連續",
|
||||||
"prev": "上一頁",
|
"prev": "上一頁",
|
||||||
"resetToDefaults": "重設為預設值",
|
"resetToDefaults": "重設為預設值",
|
||||||
"rotate": "旋轉",
|
|
||||||
"save": "儲存",
|
"save": "儲存",
|
||||||
"savedWithPath": "已儲存:{path}",
|
"savedWithPath": "已儲存:{path}",
|
||||||
"saveSignedPdf": "儲存已簽名 PDF",
|
"saveSignedPdf": "儲存已簽名 PDF",
|
||||||
|
|
|
@ -11,7 +11,7 @@
|
||||||
"delete": "删除",
|
"delete": "删除",
|
||||||
"display": "显示",
|
"display": "显示",
|
||||||
"downloadStarted": "下载已开始",
|
"downloadStarted": "下载已开始",
|
||||||
"dpi": "DPI",
|
"dpi": "DPI:",
|
||||||
"drawSignature": "绘制签名",
|
"drawSignature": "绘制签名",
|
||||||
"errorWithMessage": "错误:{message}",
|
"errorWithMessage": "错误:{message}",
|
||||||
"exportingPleaseWait": "正在导出... 请稍候",
|
"exportingPleaseWait": "正在导出... 请稍候",
|
||||||
|
@ -19,7 +19,6 @@
|
||||||
"failedToSavePdf": "PDF 保存失败",
|
"failedToSavePdf": "PDF 保存失败",
|
||||||
"general": "常规",
|
"general": "常规",
|
||||||
"goTo": "跳转到:",
|
"goTo": "跳转到:",
|
||||||
"image": "图片",
|
|
||||||
"invalidOrUnsupportedFile": "无效或不支持的文件",
|
"invalidOrUnsupportedFile": "无效或不支持的文件",
|
||||||
"language": "语言",
|
"language": "语言",
|
||||||
"loadSignatureFromFile": "从文件加载签名",
|
"loadSignatureFromFile": "从文件加载签名",
|
||||||
|
@ -35,7 +34,6 @@
|
||||||
"pageViewContinuous": "连续",
|
"pageViewContinuous": "连续",
|
||||||
"prev": "上一页",
|
"prev": "上一页",
|
||||||
"resetToDefaults": "恢复默认值",
|
"resetToDefaults": "恢复默认值",
|
||||||
"rotate": "旋转",
|
|
||||||
"save": "保存",
|
"save": "保存",
|
||||||
"savedWithPath": "已保存:{path}",
|
"savedWithPath": "已保存:{path}",
|
||||||
"saveSignedPdf": "保存已签名的 PDF",
|
"saveSignedPdf": "保存已签名的 PDF",
|
||||||
|
|
|
@ -12,7 +12,7 @@
|
||||||
"delete": "刪除",
|
"delete": "刪除",
|
||||||
"display": "顯示",
|
"display": "顯示",
|
||||||
"downloadStarted": "已開始下載",
|
"downloadStarted": "已開始下載",
|
||||||
"dpi": "DPI",
|
"dpi": "DPI:",
|
||||||
"drawSignature": "手寫簽名",
|
"drawSignature": "手寫簽名",
|
||||||
"errorWithMessage": "錯誤:{message}",
|
"errorWithMessage": "錯誤:{message}",
|
||||||
"exportingPleaseWait": "匯出中…請稍候",
|
"exportingPleaseWait": "匯出中…請稍候",
|
||||||
|
@ -20,7 +20,6 @@
|
||||||
"failedToSavePdf": "儲存 PDF 失敗",
|
"failedToSavePdf": "儲存 PDF 失敗",
|
||||||
"general": "一般",
|
"general": "一般",
|
||||||
"goTo": "前往:",
|
"goTo": "前往:",
|
||||||
"image": "圖片",
|
|
||||||
"invalidOrUnsupportedFile": "無效或不支援的檔案",
|
"invalidOrUnsupportedFile": "無效或不支援的檔案",
|
||||||
"language": "語言",
|
"language": "語言",
|
||||||
"loadSignatureFromFile": "從檔案載入簽名",
|
"loadSignatureFromFile": "從檔案載入簽名",
|
||||||
|
@ -36,7 +35,6 @@
|
||||||
"pageViewContinuous": "連續",
|
"pageViewContinuous": "連續",
|
||||||
"prev": "上一頁",
|
"prev": "上一頁",
|
||||||
"resetToDefaults": "重設為預設值",
|
"resetToDefaults": "重設為預設值",
|
||||||
"rotate": "旋轉",
|
|
||||||
"save": "儲存",
|
"save": "儲存",
|
||||||
"savedWithPath": "已儲存:{path}",
|
"savedWithPath": "已儲存:{path}",
|
||||||
"saveSignedPdf": "儲存已簽名 PDF",
|
"saveSignedPdf": "儲存已簽名 PDF",
|
||||||
|
|
|
@ -4,7 +4,6 @@ import 'dart:typed_data';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
import 'package:image/image.dart' as img;
|
import 'package:image/image.dart' as img;
|
||||||
import 'package:pdf_signature/l10n/app_localizations.dart';
|
|
||||||
|
|
||||||
import '../../../../data/model/model.dart';
|
import '../../../../data/model/model.dart';
|
||||||
|
|
||||||
|
@ -252,14 +251,7 @@ class SignatureController extends StateNotifier<SignatureState> {
|
||||||
void setInvalidSelected(BuildContext context) {
|
void setInvalidSelected(BuildContext context) {
|
||||||
// Fallback message without localization to keep core logic testable
|
// Fallback message without localization to keep core logic testable
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
SnackBar(
|
const SnackBar(content: Text('Invalid or unsupported file')),
|
||||||
content: Text(
|
|
||||||
Localizations.of<AppLocalizations>(
|
|
||||||
context,
|
|
||||||
AppLocalizations,
|
|
||||||
)!.invalidOrUnsupportedFile,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -10,8 +10,6 @@ class ImageEditorDialog extends ConsumerWidget {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context, WidgetRef ref) {
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
final l10n = Localizations.of<AppLocalizations>(context, AppLocalizations)!;
|
|
||||||
|
|
||||||
final l = AppLocalizations.of(context);
|
final l = AppLocalizations.of(context);
|
||||||
final sig = ref.watch(signatureProvider);
|
final sig = ref.watch(signatureProvider);
|
||||||
return Dialog(
|
return Dialog(
|
||||||
|
@ -59,7 +57,7 @@ class ImageEditorDialog extends ConsumerWidget {
|
||||||
const SizedBox(height: 8),
|
const SizedBox(height: 8),
|
||||||
Row(
|
Row(
|
||||||
children: [
|
children: [
|
||||||
Text(l10n.rotate),
|
Text('Rotate'),
|
||||||
Expanded(
|
Expanded(
|
||||||
child: Slider(
|
child: Slider(
|
||||||
key: const Key('sld_rotation'),
|
key: const Key('sld_rotation'),
|
||||||
|
|
|
@ -68,9 +68,8 @@ class _PdfSignatureHomePageState extends ConsumerState<PdfSignatureHomePage> {
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<Uint8List?> _loadSignatureFromFile() async {
|
Future<Uint8List?> _loadSignatureFromFile() async {
|
||||||
final typeGroup = fs.XTypeGroup(
|
final typeGroup = const fs.XTypeGroup(
|
||||||
label:
|
label: 'Image',
|
||||||
Localizations.of<AppLocalizations>(context, AppLocalizations)?.image,
|
|
||||||
extensions: ['png', 'jpg', 'jpeg', 'webp'],
|
extensions: ['png', 'jpg', 'jpeg', 'webp'],
|
||||||
);
|
);
|
||||||
final file = await fs.openFile(acceptedTypeGroups: [typeGroup]);
|
final file = await fs.openFile(acceptedTypeGroups: [typeGroup]);
|
||||||
|
|
|
@ -3,6 +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 '../../../../data/services/export_providers.dart';
|
||||||
import '../view_model/view_model.dart';
|
import '../view_model/view_model.dart';
|
||||||
|
|
||||||
class PdfToolbar extends ConsumerStatefulWidget {
|
class PdfToolbar extends ConsumerStatefulWidget {
|
||||||
|
@ -56,6 +57,7 @@ class _PdfToolbarState extends ConsumerState<PdfToolbar> {
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final pdf = ref.watch(pdfProvider);
|
final pdf = ref.watch(pdfProvider);
|
||||||
|
final dpi = ref.watch(exportDpiProvider);
|
||||||
final l = AppLocalizations.of(context);
|
final l = AppLocalizations.of(context);
|
||||||
final pageInfo = l.pageInfo(pdf.currentPage, pdf.pageCount);
|
final pageInfo = l.pageInfo(pdf.currentPage, pdf.pageCount);
|
||||||
|
|
||||||
|
@ -94,17 +96,13 @@ class _PdfToolbarState extends ConsumerState<PdfToolbar> {
|
||||||
if (pdf.loaded) ...[
|
if (pdf.loaded) ...[
|
||||||
Wrap(
|
Wrap(
|
||||||
spacing: 8,
|
spacing: 8,
|
||||||
children: [
|
|
||||||
Wrap(
|
|
||||||
crossAxisAlignment: WrapCrossAlignment.center,
|
|
||||||
children: [
|
children: [
|
||||||
IconButton(
|
IconButton(
|
||||||
key: const Key('btn_prev'),
|
key: const Key('btn_prev'),
|
||||||
onPressed:
|
onPressed:
|
||||||
widget.disabled
|
widget.disabled
|
||||||
? null
|
? null
|
||||||
: () =>
|
: () => widget.onJumpToPage(pdf.currentPage - 1),
|
||||||
widget.onJumpToPage(pdf.currentPage - 1),
|
|
||||||
icon: const Icon(Icons.chevron_left),
|
icon: const Icon(Icons.chevron_left),
|
||||||
tooltip: l.prev,
|
tooltip: l.prev,
|
||||||
),
|
),
|
||||||
|
@ -115,13 +113,10 @@ class _PdfToolbarState extends ConsumerState<PdfToolbar> {
|
||||||
onPressed:
|
onPressed:
|
||||||
widget.disabled
|
widget.disabled
|
||||||
? null
|
? null
|
||||||
: () =>
|
: () => widget.onJumpToPage(pdf.currentPage + 1),
|
||||||
widget.onJumpToPage(pdf.currentPage + 1),
|
|
||||||
icon: const Icon(Icons.chevron_right),
|
icon: const Icon(Icons.chevron_right),
|
||||||
tooltip: l.next,
|
tooltip: l.next,
|
||||||
),
|
),
|
||||||
],
|
|
||||||
),
|
|
||||||
Wrap(
|
Wrap(
|
||||||
spacing: 6,
|
spacing: 6,
|
||||||
runSpacing: 4,
|
runSpacing: 4,
|
||||||
|
@ -155,9 +150,6 @@ class _PdfToolbarState extends ConsumerState<PdfToolbar> {
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
const SizedBox(width: 8),
|
const SizedBox(width: 8),
|
||||||
Wrap(
|
|
||||||
crossAxisAlignment: WrapCrossAlignment.center,
|
|
||||||
children: [
|
|
||||||
IconButton(
|
IconButton(
|
||||||
key: const Key('btn_zoom_out'),
|
key: const Key('btn_zoom_out'),
|
||||||
tooltip: 'Zoom out',
|
tooltip: 'Zoom out',
|
||||||
|
@ -175,9 +167,31 @@ class _PdfToolbarState extends ConsumerState<PdfToolbar> {
|
||||||
onPressed: widget.disabled ? null : widget.onZoomIn,
|
onPressed: widget.disabled ? null : widget.onZoomIn,
|
||||||
icon: const Icon(Icons.zoom_in),
|
icon: const Icon(Icons.zoom_in),
|
||||||
),
|
),
|
||||||
],
|
|
||||||
),
|
|
||||||
SizedBox(width: 6),
|
SizedBox(width: 6),
|
||||||
|
// show zoom ratio
|
||||||
|
Text(l.dpi),
|
||||||
|
const SizedBox(width: 8),
|
||||||
|
DropdownButton<double>(
|
||||||
|
key: const Key('ddl_export_dpi'),
|
||||||
|
value: dpi,
|
||||||
|
items:
|
||||||
|
const [96.0, 144.0, 200.0, 300.0]
|
||||||
|
.map(
|
||||||
|
(v) => DropdownMenuItem(
|
||||||
|
value: v,
|
||||||
|
child: Text(v.toStringAsFixed(0)),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.toList(),
|
||||||
|
onChanged:
|
||||||
|
widget.disabled
|
||||||
|
? null
|
||||||
|
: (v) {
|
||||||
|
if (v != null) {
|
||||||
|
ref.read(exportDpiProvider.notifier).state = v;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
|
|
@ -14,7 +14,6 @@ class _SettingsDialogState extends ConsumerState<SettingsDialog> {
|
||||||
String? _theme;
|
String? _theme;
|
||||||
String? _language;
|
String? _language;
|
||||||
// Page view removed; continuous-only
|
// Page view removed; continuous-only
|
||||||
double? _exportDpi;
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
|
@ -22,7 +21,6 @@ class _SettingsDialogState extends ConsumerState<SettingsDialog> {
|
||||||
final prefs = ref.read(preferencesProvider);
|
final prefs = ref.read(preferencesProvider);
|
||||||
_theme = prefs.theme;
|
_theme = prefs.theme;
|
||||||
_language = prefs.language;
|
_language = prefs.language;
|
||||||
_exportDpi = prefs.exportDpi;
|
|
||||||
// pageView no longer configurable (continuous-only)
|
// pageView no longer configurable (continuous-only)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -120,29 +118,6 @@ class _SettingsDialogState extends ConsumerState<SettingsDialog> {
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
Row(
|
|
||||||
children: [
|
|
||||||
SizedBox(width: 140, child: Text('${l.dpi}:')),
|
|
||||||
const SizedBox(width: 8),
|
|
||||||
Expanded(
|
|
||||||
child: DropdownButton<double>(
|
|
||||||
key: const Key('ddl_export_dpi'),
|
|
||||||
isExpanded: true,
|
|
||||||
value: _exportDpi,
|
|
||||||
items:
|
|
||||||
const [96.0, 144.0, 200.0, 300.0]
|
|
||||||
.map(
|
|
||||||
(v) => DropdownMenuItem<double>(
|
|
||||||
value: v,
|
|
||||||
child: Text(v.toStringAsFixed(0)),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
.toList(),
|
|
||||||
onChanged: (v) => setState(() => _exportDpi = v),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
const SizedBox(height: 16),
|
const SizedBox(height: 16),
|
||||||
Text(l.display, style: Theme.of(context).textTheme.titleMedium),
|
Text(l.display, style: Theme.of(context).textTheme.titleMedium),
|
||||||
const SizedBox(height: 8),
|
const SizedBox(height: 8),
|
||||||
|
@ -174,7 +149,7 @@ class _SettingsDialogState extends ConsumerState<SettingsDialog> {
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
// Page view setting removed (continuous-only)
|
||||||
const SizedBox(height: 24),
|
const SizedBox(height: 24),
|
||||||
Row(
|
Row(
|
||||||
mainAxisAlignment: MainAxisAlignment.end,
|
mainAxisAlignment: MainAxisAlignment.end,
|
||||||
|
@ -189,7 +164,6 @@ class _SettingsDialogState extends ConsumerState<SettingsDialog> {
|
||||||
final n = ref.read(preferencesProvider.notifier);
|
final n = ref.read(preferencesProvider.notifier);
|
||||||
if (_theme != null) await n.setTheme(_theme!);
|
if (_theme != null) await n.setTheme(_theme!);
|
||||||
if (_language != null) await n.setLanguage(_language!);
|
if (_language != null) await n.setLanguage(_language!);
|
||||||
if (_exportDpi != null) await n.setExportDpi(_exportDpi!);
|
|
||||||
// pageView not configurable anymore
|
// pageView not configurable anymore
|
||||||
if (mounted) Navigator.of(context).pop(true);
|
if (mounted) Navigator.of(context).pop(true);
|
||||||
},
|
},
|
||||||
|
|
Loading…
Reference in New Issue