11 Commits
Author SHA1 Message Date
Alhan 8a80cba116 chore: point README at Gitea releases; build-windows.ps1 mirrors pubspec version
Build & Release / build-linux (push) Canceled after 0s
Build & Release / build-windows (push) Canceled after 0s
Build & Release / create-release (push) Canceled after 0s
2026-10-31 00:14:47 +03:00
Alhan 3b1fad18fa fix: right-button double click no longer wipes rotation mid-drag (v1.7.2)
Build & Release / build-linux (push) Canceled after 0s
Build & Release / build-windows (push) Canceled after 0s
Build & Release / create-release (push) Canceled after 0s
Two rotate drags started quickly at nearly the same spot were treated as a
double click: the reset was detected on button down against the previous down
time/position, so it wiped all effects and ignored the second drag, leaving
Ctrl+S to save an unrotated crop.

- Double click is now only counted for actual clicks (movement <= 4px) and is
  decided on button up; a drag breaks the chain.
- Pointer up is matched by pointer id, so a left-drag pan never counts as a click.
- dist: 1.7.2 .deb replaces the tracked 1.7.1 package.
2026-10-25 20:48:59 +03:00
Alhan 32fb12c210 feat: zoom percentage badge at bottom center (v1.7.1)
Build & Release / build-linux (push) Canceled after 0s
Build & Release / build-windows (push) Canceled after 0s
Build & Release / create-release (push) Canceled after 0s
- Percentage shown between the prev/next buttons, together with the hover
  controls, on a translucent background.
- Value is the on-screen scale relative to the image's real pixel size
  (100% = 1:1); updated on wheel zoom, fill/fit toggle, image change and
  window resize.
- Pixel-size header parsing moved to lib/services/image_header.dart, shared
  with the settings file-properties panel.
- dist: 1.7.1 .deb replaces the tracked 1.7.0 package.
2026-10-25 18:53:59 +03:00
Alhan abbde04e50 chore: rebuild 1.7.0 .deb with F11 + file properties
Build & Release / build-linux (push) Has been cancelled
Build & Release / build-windows (push) Has been cancelled
Build & Release / create-release (push) Has been cancelled
2026-10-21 02:22:24 +03:00
Alhan e6808aaf8e feat: file properties in settings (resolution, size, format, modified, path)
Build & Release / build-linux (push) Has been cancelled
Build & Release / build-windows (push) Has been cancelled
Build & Release / create-release (push) Has been cancelled
2026-10-21 02:10:08 +03:00
Alhan 805c7eedf2 chore: regenerate l10n base class (F11 doc line)
Build & Release / build-linux (push) Has been cancelled
Build & Release / build-windows (push) Has been cancelled
Build & Release / create-release (push) Has been cancelled
2026-10-21 01:45:36 +03:00
Alhan 2ae32e351d feat: F11 fullscreen toggle (Esc still exits)
Build & Release / build-linux (push) Has been cancelled
Build & Release / build-windows (push) Has been cancelled
Build & Release / create-release (push) Has been cancelled
2026-10-21 01:45:21 +03:00
Alhan ee456773d4 chore: ship 1.7.0 .deb as latest in dist/ + changelog (v1.7.0)
Build & Release / build-linux (push) Has been cancelled
Build & Release / build-windows (push) Has been cancelled
Build & Release / create-release (push) Has been cancelled
2026-10-20 23:14:49 +03:00
Alhan 57fa3d92dd feat: settings version display, complete shortcut list, Ctrl fine zoom (v1.7.0)
Build & Release / build-linux (push) Has been cancelled
Build & Release / build-windows (push) Has been cancelled
Build & Release / create-release (push) Has been cancelled
2026-10-20 23:09:13 +03:00
Alhan 5f4d4ffe1a fix: window resize crash by building with Flutter 3.47.7 (v1.6.3)
Build & Release / build-linux (push) Has been cancelled
Build & Release / build-windows (push) Has been cancelled
Build & Release / create-release (push) Has been cancelled
The GTK embedder's OpenGL compositor uploaded the frame with the window's new
dimensions while its pixel buffer still held the previous size, so memcpy read
past the end of the buffer and the process died with SIGSEGV
(fl_compositor_opengl.cc). Upstream fix: flutter#187626, present in Flutter
3.47.7 but not in 3.44.7.

Linux builds now use /home/alhan/development/flutter-3.47.7; pubspec.lock was
re-resolved for Dart 3.13.5 and the new tool added its build/platform excludes
to analysis_options.yaml. dist/ carries the 1.6.3 package instead of 1.6.2.
2026-10-20 22:22:17 +03:00
Alhan a2d2c0d151 chore: crop save JPEG quality 93 -> 95 (v1.6.2)
Build & Release / build-linux (push) Has been cancelled
Build & Release / build-windows (push) Has been cancelled
Build & Release / create-release (push) Has been cancelled
2026-10-14 04:04:54 +03:00
18 changed files with 453 additions and 56 deletions

No files matched your search

+1 -1
View File
@@ -49,7 +49,7 @@ app.*.map.json
*.deb *.deb
*.tar.gz *.tar.gz
*.exe *.exe
!dist/imajviewer_1.6.1_amd64.deb !dist/imajviewer_1.7.2_amd64.deb
!dist/imajviewer_setup_1.1.0.exe !dist/imajviewer_setup_1.1.0.exe
deb/usr/lib/imajviewer/ deb/usr/lib/imajviewer/
release/ release/
+49
View File
@@ -4,6 +4,55 @@ All notable changes to imajViewer are documented in this file.
## [Unreleased] ## [Unreleased]
## [1.7.2] - 2026-10-25
### Fixed
- Rotating sometimes vanished before saving: the right-button double-click reset was
detected on button *down* against the previous down time and position, so two rotate
drags started quickly at nearly the same spot were treated as a double click — all
effects (rotation included) were wiped and the second drag was ignored, so `Ctrl+S`
saved an unrotated crop. A double click is now only counted for actual clicks
(movement ≤ 4 px, decided on button up), a drag breaks the chain, and
`pointer up` is matched by pointer id so a left-drag pan never counts as a click.
## [1.7.1] - 2026-10-25
### Added
- Zoom percentage badge at the bottom center, between the previous/next buttons: shown
together with the hover controls, translucent background, live while wheeling
(100% = one image pixel per screen pixel), recomputed on fill/fit toggle, image
change and window resize.
### Changed
- Image header parsing (pixel size without decoding) moved from the viewer screen into
`lib/services/image_header.dart`; the settings file-properties panel and the zoom badge
use the same code.
## [1.7.0] - 2026-10-20
### Added
- Settings dialog shows the app version next to the name.
- Fine zoom: `Ctrl`+wheel zooms 1% per step (plain wheel stays 10% per step).
- Settings shortcut list completed: `Ctrl+O` (open), `Esc` (exit fullscreen), scroll/zoom,
`Ctrl`+scroll fine zoom, left-drag pan, double right click (reset effects).
### Changed
- README: zoom feature line and shortcut table updated to match.
## [1.6.3] - 2026-10-20
### Fixed
- `SIGSEGV` while resizing the window. The GTK embedder's OpenGL compositor uploaded the frame using
the window's new dimensions while its pixel buffer still held the previous size, so `memcpy` read
past the end of the buffer (`engine/.../linux/fl_compositor_opengl.cc`). Fixed upstream in
flutter#187626, which is not part of Flutter 3.44.7 — the Linux build now uses Flutter 3.47.7,
where the compositor derives the texture size from the framebuffer itself.
## [1.6.2] - 2026-10-14
### Changed
- Crop save JPEG quality raised from 93 to 95.
## [1.6.1] - 2026-10-14 ## [1.6.1] - 2026-10-14
### Fixed ### Fixed
+19 -12
View File
@@ -1,8 +1,8 @@
# imajViewer # imajViewer
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE) [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)
[![Build](https://github.com/alhan/imajviewer/actions/workflows/build.yml/badge.svg)](https://github.com/alhan/imajviewer/actions/workflows/build.yml)
[![Release](https://img.shields.io/github/v/release/alhan/imajviewer)](https://github.com/alhan/imajviewer/releases/latest) [Releases](https://git.softmediadesign.com/git_alhan/imajviewer/releases)
Ultra-lightweight image viewer written in Flutter — Linux & Windows. Ultra-lightweight image viewer written in Flutter — Linux & Windows.
@@ -12,7 +12,8 @@ Ultra-lightweight image viewer written in Flutter — Linux & Windows.
- **Formats:** PNG, JPEG, JFIF, WebP, BMP, GIF, WBMP - **Formats:** PNG, JPEG, JFIF, WebP, BMP, GIF, WBMP
- **Pan:** Left drag - **Pan:** Left drag
- **Zoom:** Mouse wheel (cursor-centered; viewport-centered in rotate mode) - **Zoom:** Mouse wheel — 10% per step, cursor-centered (viewport-centered in rotate mode);
hold `Ctrl` for fine zoom (1% per step)
- **Fill/Fit toggle:** Double left click - **Fill/Fit toggle:** Double left click
- **Contrast:** Right drag + vertical move - **Contrast:** Right drag + vertical move
- **Saturation:** Ctrl + Right drag + vertical move - **Saturation:** Ctrl + Right drag + vertical move
@@ -26,7 +27,7 @@ Ultra-lightweight image viewer written in Flutter — Linux & Windows.
- **Settings (gear):** sort by time/name, print, shortcut info - **Settings (gear):** sort by time/name, print, shortcut info
- **Print:** send the image to a printer (A4, fit) - **Print:** send the image to a printer (A4, fit)
- **Save visible area:** title bar save icon or `Ctrl+S` — the on-screen region is written at - **Save visible area:** title bar save icon or `Ctrl+S` — the on-screen region is written at
original resolution as JPEG (quality 93) next to the source file (`photo.jpg` → original resolution as JPEG (quality 95) next to the source file (`photo.jpg` →
`photo_143025.jpg`); active effects (contrast/saturation/brightness), rotation and horizontal `photo_143025.jpg`); active effects (contrast/saturation/brightness), rotation and horizontal
flip are baked into the output; if that name already exists, nothing is written and a warning is shown flip are baked into the output; if that name already exists, nothing is written and a warning is shown
- Drag & drop to open images - Drag & drop to open images
@@ -40,7 +41,7 @@ Ultra-lightweight image viewer written in Flutter — Linux & Windows.
### Linux (.deb, recommended) ### Linux (.deb, recommended)
No Flutter SDK needed — grab the latest `.deb` from the No Flutter SDK needed — grab the latest `.deb` from the
[Releases page](https://github.com/alhan/imajviewer/releases/latest): [Releases page](https://git.softmediadesign.com/git_alhan/imajviewer/releases):
```bash ```bash
sudo dpkg -i imajviewer_*_amd64.deb sudo dpkg -i imajviewer_*_amd64.deb
@@ -52,7 +53,7 @@ After install, open an image with `xdg-open <file>` or double-click it.
### Windows (installer) ### Windows (installer)
Download `imajviewer_setup_*.exe` from the Download `imajviewer_setup_*.exe` from the
[Releases page](https://github.com/alhan/imajviewer/releases/latest) and run it. [Releases page](https://git.softmediadesign.com/git_alhan/imajviewer/releases) and run it.
The installer: The installer:
- Installs to Program Files - Installs to Program Files
@@ -83,7 +84,8 @@ folder including `imajviewer.exe` and all DLLs).
| Interaction | Action | | Interaction | Action |
|---|---| |---|---|
| Left drag | Pan | | Left drag | Pan |
| Mouse wheel | Zoom (cursor-centered) | | Mouse wheel | Zoom (cursor-centered, 10% per step) |
| Ctrl + Mouse wheel | Fine zoom (1% per step) |
| Double left click | Fill / Fit toggle | | Double left click | Fill / Fit toggle |
| Right drag + vertical | Contrast (0.0 – 2.0) | | Right drag + vertical | Contrast (0.0 – 2.0) |
| Ctrl + Right drag + vertical | Saturation (0.0 – 2.0) | | Ctrl + Right drag + vertical | Saturation (0.0 – 2.0) |
@@ -92,15 +94,20 @@ folder including `imajviewer.exe` and all DLLs).
| Ctrl + R | Flip horizontally (clears effects; press again to undo) | | Ctrl + R | Flip horizontally (clears effects; press again to undo) |
| Double right click | Reset (all effects) | | Double right click | Reset (all effects) |
| Left / Right arrow | Previous / next image | | Left / Right arrow | Previous / next image |
| Ctrl + S | Save visible area (JPEG 93, original resolution) | | Ctrl + O | Open file |
| Ctrl + S | Save visible area (JPEG 95, original resolution) |
| Esc | Exit fullscreen |
| Ctrl + Q | Close window | | Ctrl + Q | Close window |
| Ctrl + Shift + Q | Close all windows | | Ctrl + Shift + Q | Close all windows |
## CI / Releases ## Releases
GitHub Actions builds Linux `.deb` and Windows installer on every push to Installers are published on the
`main`. Pushing a `v*` tag creates a GitHub Release with both installers as [Gitea Releases page](https://git.softmediadesign.com/git_alhan/imajviewer/releases):
assets.
- Linux `.deb` — `flutter build linux --release` + `bash dist/rebuild-deb.sh`
- Windows installer — on a Windows machine: `.\build-windows.ps1`
(Flutter + VS 2022 Build Tools + Inno Setup 6)
## Development ## Development
+5
View File
@@ -7,6 +7,11 @@
# The following line activates a set of recommended lints for Flutter apps, # The following line activates a set of recommended lints for Flutter apps,
# packages, and plugins designed to encourage good coding practices. # packages, and plugins designed to encourage good coding practices.
analyzer:
exclude:
- build/**
- windows/**
- linux/**
include: package:flutter_lints/flutter.yaml include: package:flutter_lints/flutter.yaml
linter: linter:
+5
View File
@@ -136,6 +136,11 @@ if (-not $SkipInno) {
Write-Err "Installer scripti bulunamadi: $installerScript" Write-Err "Installer scripti bulunamadi: $installerScript"
} }
# Installer surumunu pubspec.yaml'dan aynala (CI'daki adimla ayni)
$pubVersion = (Select-String -Path (Join-Path $ProjectRoot 'pubspec.yaml') -Pattern '^version:\s*([0-9]+\.[0-9]+\.[0-9]+)').Matches[0].Groups[1].Value
(Get-Content $installerScript -Raw) -replace '#define MyAppVersion "[^"]+"', "#define MyAppVersion `"$pubVersion`"" | Set-Content $installerScript
Write-Ok "Inno Setup surumu pubspec'ten: $pubVersion"
$distDir = Join-Path $ProjectRoot "dist" $distDir = Join-Path $ProjectRoot "dist"
New-Item -ItemType Directory -Force -Path $distDir | Out-Null New-Item -ItemType Directory -Force -Path $distDir | Out-Null
+8 -2
View File
@@ -6,10 +6,16 @@
"sortByName": "By name", "sortByName": "By name",
"appDescription": "Ultra-lightweight and fast image viewer. Open images and use arrows or keyboard keys to navigate.", "appDescription": "Ultra-lightweight and fast image viewer. Open images and use arrows or keyboard keys to navigate.",
"shortcuts": "Shortcuts", "shortcuts": "Shortcuts",
"shortcutsList": "• Left/Right arrow — previous/next image\n• Ctrl+S — save visible area (JPEG 93)\n• Ctrl+R — flip horizontally (clears effects, press again to undo)\n• Ctrl+Q — close window\n• Ctrl+Shift+Q — close all windows\n• Double click — toggle fill/fit\n• Right-drag — contrast\n• Shift+right-drag — rotate\n• Ctrl+right-drag — saturation\n• Ctrl+Shift+right-drag — brightness", "shortcutsList": "• Left/Right arrow — previous/next image\n• Ctrl+O — open file\n• Ctrl+S — save visible area (JPEG 95)\n• Ctrl+R — flip horizontally (clears effects, press again to undo)\n• Ctrl+Q — close window\n• Ctrl+Shift+Q — close all windows\n• F11 — toggle fullscreen\n• Esc — exit fullscreen\n• Scroll — zoom (10% per step)\n• Ctrl+scroll — fine zoom (1% per step)\n• Left-drag — pan\n• Double click — toggle fill/fit\n• Right double click — reset effects\n• Right-drag — contrast\n• Shift+right-drag — rotate\n• Ctrl+right-drag — saturation\n• Ctrl+Shift+right-drag — brightness",
"print": "Print", "print": "Print",
"fileProperties": "File properties",
"propResolution": "Resolution",
"propFileSize": "File size",
"propFormat": "Format",
"propModified": "Modified",
"propPath": "Path",
"close": "Close", "close": "Close",
"saveCrop": "Save visible area (JPEG 93)", "saveCrop": "Save visible area (JPEG 95)",
"cropSaved": "Saved: {path}", "cropSaved": "Saved: {path}",
"@cropSaved": { "@cropSaved": {
"placeholders": { "placeholders": {
+38 -2
View File
@@ -137,7 +137,7 @@ abstract class AppLocalizations {
/// No description provided for @shortcutsList. /// No description provided for @shortcutsList.
/// ///
/// In en, this message translates to: /// In en, this message translates to:
/// **'• Left/Right arrow — previous/next image\n• Ctrl+S — save visible area (JPEG 93)\n• Ctrl+R — flip horizontally (clears effects, press again to undo)\n• Ctrl+Q — close window\n• Ctrl+Shift+Q — close all windows\n• Double click — toggle fill/fit\n• Right-drag — contrast\n• Shift+right-drag — rotate\n• Ctrl+right-drag — saturation\n• Ctrl+Shift+right-drag — brightness'** /// **'• Left/Right arrow — previous/next image\n• Ctrl+O — open file\n• Ctrl+S — save visible area (JPEG 95)\n• Ctrl+R — flip horizontally (clears effects, press again to undo)\n• Ctrl+Q — close window\n• Ctrl+Shift+Q — close all windows\n• F11 — toggle fullscreen\n• Esc — exit fullscreen\n• Scroll — zoom (10% per step)\n• Ctrl+scroll — fine zoom (1% per step)\n• Left-drag — pan\n• Double click — toggle fill/fit\n• Right double click — reset effects\n• Right-drag — contrast\n• Shift+right-drag — rotate\n• Ctrl+right-drag — saturation\n• Ctrl+Shift+right-drag — brightness'**
String get shortcutsList; String get shortcutsList;
/// No description provided for @print. /// No description provided for @print.
@@ -146,6 +146,42 @@ abstract class AppLocalizations {
/// **'Print'** /// **'Print'**
String get print; String get print;
/// No description provided for @fileProperties.
///
/// In en, this message translates to:
/// **'File properties'**
String get fileProperties;
/// No description provided for @propResolution.
///
/// In en, this message translates to:
/// **'Resolution'**
String get propResolution;
/// No description provided for @propFileSize.
///
/// In en, this message translates to:
/// **'File size'**
String get propFileSize;
/// No description provided for @propFormat.
///
/// In en, this message translates to:
/// **'Format'**
String get propFormat;
/// No description provided for @propModified.
///
/// In en, this message translates to:
/// **'Modified'**
String get propModified;
/// No description provided for @propPath.
///
/// In en, this message translates to:
/// **'Path'**
String get propPath;
/// No description provided for @close. /// No description provided for @close.
/// ///
/// In en, this message translates to: /// In en, this message translates to:
@@ -155,7 +191,7 @@ abstract class AppLocalizations {
/// No description provided for @saveCrop. /// No description provided for @saveCrop.
/// ///
/// In en, this message translates to: /// In en, this message translates to:
/// **'Save visible area (JPEG 93)'** /// **'Save visible area (JPEG 95)'**
String get saveCrop; String get saveCrop;
/// No description provided for @cropSaved. /// No description provided for @cropSaved.
+20 -2
View File
@@ -29,16 +29,34 @@ class AppLocalizationsEn extends AppLocalizations {
@override @override
String get shortcutsList => String get shortcutsList =>
'• Left/Right arrow — previous/next image\n• Ctrl+S — save visible area (JPEG 93)\n• Ctrl+R — flip horizontally (clears effects, press again to undo)\n• Ctrl+Q — close window\n• Ctrl+Shift+Q — close all windows\n• Double click — toggle fill/fit\n• Right-drag — contrast\n• Shift+right-drag — rotate\n• Ctrl+right-drag — saturation\n• Ctrl+Shift+right-drag — brightness'; '• Left/Right arrow — previous/next image\n• Ctrl+O — open file\n• Ctrl+S — save visible area (JPEG 95)\n• Ctrl+R — flip horizontally (clears effects, press again to undo)\n• Ctrl+Q — close window\n• Ctrl+Shift+Q — close all windows\n• F11 — toggle fullscreen\n• Esc — exit fullscreen\n• Scroll — zoom (10% per step)\n• Ctrl+scroll — fine zoom (1% per step)\n• Left-drag — pan\n• Double click — toggle fill/fit\n• Right double click — reset effects\n• Right-drag — contrast\n• Shift+right-drag — rotate\n• Ctrl+right-drag — saturation\n• Ctrl+Shift+right-drag — brightness';
@override @override
String get print => 'Print'; String get print => 'Print';
@override
String get fileProperties => 'File properties';
@override
String get propResolution => 'Resolution';
@override
String get propFileSize => 'File size';
@override
String get propFormat => 'Format';
@override
String get propModified => 'Modified';
@override
String get propPath => 'Path';
@override @override
String get close => 'Close'; String get close => 'Close';
@override @override
String get saveCrop => 'Save visible area (JPEG 93)'; String get saveCrop => 'Save visible area (JPEG 95)';
@override @override
String cropSaved(String path) { String cropSaved(String path) {
+20 -2
View File
@@ -29,16 +29,34 @@ class AppLocalizationsTr extends AppLocalizations {
@override @override
String get shortcutsList => String get shortcutsList =>
'• Sol/Sağ ok — önceki/sonraki resim\n• Ctrl+S — görünen alanı kaydet (JPEG 93)\n• Ctrl+R — yatay aynala (efektleri sıfırlar, tekrar basınca geri alır)\n• Ctrl+Q — pencereyi kapat\n• Ctrl+Shift+Q — tüm pencereleri kapat\n• Çift tık — doldur/sığdır geçişi\n• Sağ tık sürükle — kontrast\n• Shift+sağ tık — döndür\n• Ctrl+sağ tık — doygunluk\n• Ctrl+Shift+sağ tık — parlaklık'; '• Sol/Sağ ok — önceki/sonraki resim\n• Ctrl+O — dosya aç\n• Ctrl+S — görünen alanı kaydet (JPEG 95)\n• Ctrl+R — yatay aynala (efektleri sıfırlar, tekrar basınca geri alır)\n• Ctrl+Q — pencereyi kapat\n• Ctrl+Shift+Q — tüm pencereleri kapat\n• F11 — tam ekran / normal geçişi\n• Esc — tam ekrandan çık\n• Kaydırma — yakınlaştır (tık başına %10)\n• Ctrl+kaydırma — ince yakınlaştırma (tık başına %1)\n• Sol tık sürükle — kaydır\n• Çift tık — doldur/sığdır geçişi\n• Sağ çift tık — efektleri sıfırla\n• Sağ tık sürükle — kontrast\n• Shift+sağ tık — döndür\n• Ctrl+sağ tık — doygunluk\n• Ctrl+Shift+sağ tık — parlaklık';
@override @override
String get print => 'Yazdır'; String get print => 'Yazdır';
@override
String get fileProperties => 'Dosya özellikleri';
@override
String get propResolution => 'Çözünürlük';
@override
String get propFileSize => 'Dosya boyutu';
@override
String get propFormat => 'Biçim';
@override
String get propModified => 'Değiştirilme';
@override
String get propPath => 'Yol';
@override @override
String get close => 'Kapat'; String get close => 'Kapat';
@override @override
String get saveCrop => 'Görünen alanı kaydet (JPEG 93)'; String get saveCrop => 'Görünen alanı kaydet (JPEG 95)';
@override @override
String cropSaved(String path) { String cropSaved(String path) {
+8 -2
View File
@@ -6,10 +6,16 @@
"sortByName": "İsme göre", "sortByName": "İsme göre",
"appDescription": "Ultra hafif ve hızlı resim görüntüleyici. Görselleri açın, gezinti için okları veya klavye tuşlarını kullanın.", "appDescription": "Ultra hafif ve hızlı resim görüntüleyici. Görselleri açın, gezinti için okları veya klavye tuşlarını kullanın.",
"shortcuts": "Kısayollar", "shortcuts": "Kısayollar",
"shortcutsList": "• Sol/Sağ ok — önceki/sonraki resim\n• Ctrl+S — görünen alanı kaydet (JPEG 93)\n• Ctrl+R — yatay aynala (efektleri sıfırlar, tekrar basınca geri alır)\n• Ctrl+Q — pencereyi kapat\n• Ctrl+Shift+Q — tüm pencereleri kapat\n• Çift tık — doldur/sığdır geçişi\n• Sağ tık sürükle — kontrast\n• Shift+sağ tık — döndür\n• Ctrl+sağ tık — doygunluk\n• Ctrl+Shift+sağ tık — parlaklık", "shortcutsList": "• Sol/Sağ ok — önceki/sonraki resim\n• Ctrl+O — dosya aç\n• Ctrl+S — görünen alanı kaydet (JPEG 95)\n• Ctrl+R — yatay aynala (efektleri sıfırlar, tekrar basınca geri alır)\n• Ctrl+Q — pencereyi kapat\n• Ctrl+Shift+Q — tüm pencereleri kapat\n• F11 — tam ekran / normal geçişi\n• Esc — tam ekrandan çık\n• Kaydırma — yakınlaştır (tık başına %10)\n• Ctrl+kaydırma — ince yakınlaştırma (tık başına %1)\n• Sol tık sürükle — kaydır\n• Çift tık — doldur/sığdır geçişi\n• Sağ çift tık — efektleri sıfırla\n• Sağ tık sürükle — kontrast\n• Shift+sağ tık — döndür\n• Ctrl+sağ tık — doygunluk\n• Ctrl+Shift+sağ tık — parlaklık",
"print": "Yazdır", "print": "Yazdır",
"fileProperties": "Dosya özellikleri",
"propResolution": "Çözünürlük",
"propFileSize": "Dosya boyutu",
"propFormat": "Biçim",
"propModified": "Değiştirilme",
"propPath": "Yol",
"close": "Kapat", "close": "Kapat",
"saveCrop": "Görünen alanı kaydet (JPEG 93)", "saveCrop": "Görünen alanı kaydet (JPEG 95)",
"cropSaved": "Kaydedildi: {path}", "cropSaved": "Kaydedildi: {path}",
"@cropSaved": { "@cropSaved": {
"placeholders": { "placeholders": {
+99 -1
View File
@@ -6,6 +6,7 @@ import 'package:desktop_drop/desktop_drop.dart';
import 'package:pdf/pdf.dart'; import 'package:pdf/pdf.dart';
import 'package:pdf/widgets.dart' as pw; import 'package:pdf/widgets.dart' as pw;
import 'package:printing/printing.dart'; import 'package:printing/printing.dart';
import '../services/image_header.dart';
import '../services/image_manager.dart'; import '../services/image_manager.dart';
import '../services/file_handler.dart'; import '../services/file_handler.dart';
import '../services/crop_save_service.dart'; import '../services/crop_save_service.dart';
@@ -16,6 +17,8 @@ import '../shortcuts/app_shortcuts.dart';
import '../services/window_persistence.dart'; import '../services/window_persistence.dart';
import '../l10n/app_localizations.dart'; import '../l10n/app_localizations.dart';
const appVersion = '1.7.2';
class ViewerScreen extends StatefulWidget { class ViewerScreen extends StatefulWidget {
final List<String> initialFiles; final List<String> initialFiles;
const ViewerScreen({super.key, this.initialFiles = const []}); const ViewerScreen({super.key, this.initialFiles = const []});
@@ -29,6 +32,7 @@ class _ViewerScreenState extends State<ViewerScreen> with WindowListener {
bool _controlsVisible = false; bool _controlsVisible = false;
Timer? _saveDebounce; Timer? _saveDebounce;
Rect _lastBounds = Rect.zero; Rect _lastBounds = Rect.zero;
final ValueNotifier<double> _zoomPercent = ValueNotifier<double>(100);
final GlobalKey<ImageCanvasState> _canvasKey = GlobalKey<ImageCanvasState>(); final GlobalKey<ImageCanvasState> _canvasKey = GlobalKey<ImageCanvasState>();
@override @override
@@ -49,6 +53,7 @@ class _ViewerScreenState extends State<ViewerScreen> with WindowListener {
windowManager.removeListener(this); windowManager.removeListener(this);
ImageManager.instance.removeListener(_onImageManagerChanged); ImageManager.instance.removeListener(_onImageManagerChanged);
_saveDebounce?.cancel(); _saveDebounce?.cancel();
_zoomPercent.dispose();
super.dispose(); super.dispose();
} }
@@ -132,7 +137,31 @@ class _ViewerScreenState extends State<ViewerScreen> with WindowListener {
}, },
), ),
const Divider(color: Colors.white24, height: 24), const Divider(color: Colors.white24, height: 24),
const Text('imajViewer', style: TextStyle(color: Colors.white, fontSize: 15)), if (_hasImages) ...[
Text(AppLocalizations.of(context)!.fileProperties,
style: const TextStyle(color: Colors.white70, fontSize: 13)),
const SizedBox(height: 4),
FutureBuilder<List<(String, String)>>(
future: _fileProperties(ImageManager.instance.currentImagePath),
builder: (context, snap) {
if (!snap.hasData) return const SizedBox(height: 20);
return Column(
children: snap.data!.map((r) => Padding(
padding: const EdgeInsets.only(bottom: 2),
child: Text.rich(
TextSpan(children: [
TextSpan(text: '${r.$1}: ', style: const TextStyle(color: Colors.white70, fontSize: 13)),
TextSpan(text: r.$2, style: const TextStyle(color: Colors.white, fontSize: 13)),
]),
),
)).toList(),
);
},
),
const SizedBox(height: 12),
],
Text('imajViewer $appVersion',
style: const TextStyle(color: Colors.white, fontSize: 15)),
const SizedBox(height: 4), const SizedBox(height: 4),
Text( Text(
AppLocalizations.of(context)!.appDescription, AppLocalizations.of(context)!.appDescription,
@@ -190,6 +219,44 @@ class _ViewerScreenState extends State<ViewerScreen> with WindowListener {
); );
} }
Future<List<(String, String)>> _fileProperties(String path) async {
final l10n = AppLocalizations.of(context)!;
final file = File(path);
final rows = <(String, String)>[];
final Size? size = pixelSizeOfFile(path);
rows.add((l10n.propResolution, size == null ? '—' : '${size.width.toInt()} × ${size.height.toInt()}'));
try {
rows.add((l10n.propFileSize, _fmtSize(file.statSync().size)));
} catch (_) {
rows.add((l10n.propFileSize, '—'));
}
rows.add((l10n.propFormat, path.toLowerCase().split('.').last.toUpperCase()));
try {
rows.add((l10n.propModified, _fmtDate(file.statSync().modified)));
} catch (_) {
rows.add((l10n.propModified, '—'));
}
rows.add((l10n.propPath, path));
return rows;
}
String _fmtSize(int bytes) {
if (bytes < 1024) return '$bytes B';
const units = ['KB', 'MB', 'GB', 'TB'];
double v = bytes / 1024;
var i = 0;
while (v >= 1024 && i < units.length - 1) {
v /= 1024;
i++;
}
return '${v.toStringAsFixed(v >= 100 ? 0 : 1)} ${units[i]}';
}
String _fmtDate(DateTime t) {
String two(int n) => n.toString().padLeft(2, '0');
return '${t.year}-${two(t.month)}-${two(t.day)} ${two(t.hour)}:${two(t.minute)}';
}
void _flipHorizontal() => _canvasKey.currentState?.toggleFlipH(); void _flipHorizontal() => _canvasKey.currentState?.toggleFlipH();
Future<void> _cropSave() async { Future<void> _cropSave() async {
@@ -274,6 +341,7 @@ class _ViewerScreenState extends State<ViewerScreen> with WindowListener {
? ImageCanvas( ? ImageCanvas(
key: _canvasKey, key: _canvasKey,
filePath: ImageManager.instance.currentImagePath, filePath: ImageManager.instance.currentImagePath,
zoomPercent: _zoomPercent,
) )
: Center( : Center(
child: Column( child: Column(
@@ -312,6 +380,36 @@ class _ViewerScreenState extends State<ViewerScreen> with WindowListener {
onTap: () => ImageManager.instance.nextImage(), onTap: () => ImageManager.instance.nextImage(),
), ),
), ),
Positioned(
left: 0,
right: 0,
bottom: 12,
child: IgnorePointer(
child: Center(
child: ValueListenableBuilder<double>(
valueListenable: _zoomPercent,
builder: (context, zoom, _) {
final pct = zoom.round();
return Container(
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 4),
decoration: BoxDecoration(
color: Colors.black.withValues(alpha: 0.35),
borderRadius: BorderRadius.circular(13),
),
child: Text(
pct < 1 ? '<1%' : '$pct%',
style: const TextStyle(
color: Colors.white,
fontSize: 12,
fontWeight: FontWeight.w500,
),
),
);
},
),
),
),
),
], ],
if (_isDragging) if (_isDragging)
Positioned.fill( Positioned.fill(
+1 -1
View File
@@ -67,7 +67,7 @@ class CropSaveService {
required img.Image source, required img.Image source,
required CropViewParams vp, required CropViewParams vp,
List<double>? matrix, List<double>? matrix,
int jpegQuality = 93, int jpegQuality = 95,
}) async { }) async {
final outPath = buildOutputPath(srcPath); final outPath = buildOutputPath(srcPath);
if (outPath == null) throw const CropSaveError('file_exists'); if (outPath == null) throw const CropSaveError('file_exists');
+80
View File
@@ -0,0 +1,80 @@
import 'dart:io';
import 'dart:typed_data';
import 'package:flutter/widgets.dart' show Size;
import 'package:image/image.dart' as img;
/// Görüntü dosyasının gerçek piksel boyutu — tam decode yapmadan.
///
/// PNG/GIF/BMP/WebP: boyut ilk 33 baytta → elle parse.
/// JPEG: SOF büyük APP/EXIF segmentlerinin ardından gelebilir → package:image startDecode.
Size? pixelSizeOfFile(String path) {
try {
final file = File(path);
final stat = file.statSync();
const maxHead = 512 * 1024;
final raf = file.openSync();
final header = raf.readSync(stat.size < maxHead ? stat.size : maxHead);
raf.closeSync();
return _headerPixelSize(header);
} catch (_) {}
return null;
}
Size? _headerPixelSize(Uint8List b) {
try {
if (b.length >= 24 && b[0] == 0x89 && b[1] == 0x50) {
// PNG
final w = (b[16] << 24) | (b[17] << 16) | (b[18] << 8) | b[19];
final h = (b[20] << 24) | (b[21] << 16) | (b[22] << 8) | b[23];
return Size(w.toDouble(), h.toDouble());
}
if (b.length >= 10 && b[0] == 0x47 && b[1] == 0x49 && b[2] == 0x46) {
// GIF (WBMP da GIF tabanlı olabilir)
final w = b[6] | (b[7] << 8);
final h = b[8] | (b[9] << 8);
return Size(w.toDouble(), h.toDouble());
}
if (b.length >= 26 && b[0] == 0x42 && b[1] == 0x4D) {
// BMP
final w = b[18] | (b[19] << 8) | (b[20] << 16) | (b[21] << 24);
final h = b[22] | (b[23] << 8) | (b[24] << 16) | (b[25] << 24);
return Size(w.toDouble(), h.abs().toDouble());
}
if (b.length >= 30 &&
b[0] == 0x52 &&
b[1] == 0x49 &&
b[2] == 0x46 &&
b[3] == 0x46 &&
b[8] == 0x57 &&
b[9] == 0x45 &&
b[10] == 0x42 &&
b[11] == 0x50) {
// WebP
if (b[12] == 0x56 && b[13] == 0x50 && b[15] == 0x58) {
// VP8X
final w = b[24] | (b[25] << 8) | (b[26] << 16);
final h = b[27] | (b[28] << 8) | (b[29] << 16);
return Size((w + 1).toDouble(), (h + 1).toDouble());
}
if (b.length >= 25 && b[12] == 0x56 && b[13] == 0x50 && b[15] == 0x4C && b[20] == 0x2F) {
// VP8L
final w = (b[21] & 0x3F) | ((b[22] & 0x0F) << 6);
final h = (b[23] & 0x3F) | ((b[24] & 0x0F) << 6);
return Size((w + 1).toDouble(), (h + 1).toDouble());
}
if (b[12] == 0x56 && b[13] == 0x50 && b[15] == 0x20) {
// VP8 (lossy)
final w = (b[26] | (b[27] << 8)) & 0x3FFF;
final h = (b[28] | (b[29] << 8)) & 0x3FFF;
return Size(w.toDouble(), h.toDouble());
}
}
} catch (_) {}
try {
// JPEG
final info = img.JpegDecoder().startDecode(b);
if (info != null) return Size(info.width.toDouble(), info.height.toDouble());
} catch (_) {}
return null;
}
+2 -1
View File
@@ -68,9 +68,10 @@ class AppShortcuts extends StatelessWidget {
case LogicalKeyboardKey.arrowRight: case LogicalKeyboardKey.arrowRight:
ImageManager.instance.nextImage(); ImageManager.instance.nextImage();
return KeyEventResult.handled; return KeyEventResult.handled;
case LogicalKeyboardKey.f11:
case LogicalKeyboardKey.escape: case LogicalKeyboardKey.escape:
windowManager.isFullScreen().then((isFullscreen) { windowManager.isFullScreen().then((isFullscreen) {
if (isFullscreen) windowManager.setFullScreen(false); windowManager.setFullScreen(event.logicalKey == LogicalKeyboardKey.f11 ? !isFullscreen : false);
}); });
return KeyEventResult.handled; return KeyEventResult.handled;
} }
+87 -19
View File
@@ -6,11 +6,15 @@ import 'package:flutter/scheduler.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import '../l10n/app_localizations.dart'; import '../l10n/app_localizations.dart';
import '../services/crop_save_service.dart'; import '../services/crop_save_service.dart';
import '../services/image_header.dart';
import '../services/window_persistence.dart'; import '../services/window_persistence.dart';
class ImageCanvas extends StatefulWidget { class ImageCanvas extends StatefulWidget {
final String filePath; final String filePath;
const ImageCanvas({super.key, required this.filePath});
/// Görüntünün gerçek piksel boyutuna göre ölçek yüzdesi (100% = 1:1).
final ValueNotifier<double>? zoomPercent;
const ImageCanvas({super.key, required this.filePath, this.zoomPercent});
@override @override
State<ImageCanvas> createState() => ImageCanvasState(); State<ImageCanvas> createState() => ImageCanvasState();
} }
@@ -37,6 +41,11 @@ class ImageCanvasState extends State<ImageCanvas> {
double _startBrightness = 0; double _startBrightness = 0;
DateTime? _lastRightTapTime; DateTime? _lastRightTapTime;
Offset _lastRightTapPos = Offset.zero; Offset _lastRightTapPos = Offset.zero;
/// Sağ tuş basılıyken anlamlı hareket oldu mu? (tıklama mı sürükleme mi)
bool _rightDragMoved = false;
int? _rightPointerId;
/// Sürükleme sayılma eşiği (px) — rotate/contrast ince ayarında el titremesi.
static const double _dragClickSlop = 4;
// ── Klavye modifier tracking ── // ── Klavye modifier tracking ──
bool _ctrlPressed = false; bool _ctrlPressed = false;
@@ -47,6 +56,44 @@ class ImageCanvasState extends State<ImageCanvas> {
Size _imageSize = Size.zero; Size _imageSize = Size.zero;
static const int _downsampleTarget = 2048; static const int _downsampleTarget = 2048;
// ── Gerçek piksel boyutu + zoom yüzdesi ──
Size? _origSize;
bool _zoomSyncScheduled = false;
/// Yüzdeyi bir sonraki kare sonunda yayınla (build sırasında setState tetiklememek için).
void _syncZoom() {
if (_zoomSyncScheduled) return;
_zoomSyncScheduled = true;
WidgetsBinding.instance.addPostFrameCallback((_) {
_zoomSyncScheduled = false;
if (!mounted) return;
final n = widget.zoomPercent;
if (n == null) return;
final v = _zoomPercentValue();
if (n.value != v) n.value = v;
});
}
/// Ekrandaki ölçeğin görüntünün gerçek piksel boyutuna oranı (100% = 1:1).
double _zoomPercentValue() {
final o = _origSize;
if (o == null || o.isEmpty || _vpSize.isEmpty) return _sc * 100;
final double ratio;
if (_isFilled) {
// cover: kutu viewport, boya ölçeği max oran
ratio = math.max(_vpSize.width / o.width, _vpSize.height / o.height);
} else {
// contain: yerleşmiş görüntü kutusu (downsample sınırı dahil) → gerçek ölçek
final box = _imageKey.currentContext?.findRenderObject() as RenderBox?;
if (box != null && box.hasSize && box.size.width > 0) {
ratio = box.size.width / o.width;
} else {
ratio = math.min(_vpSize.width / o.width, _vpSize.height / o.height);
}
}
return _sc * ratio * 100;
}
// ── Kare hızına eşitlenmiş render tick ── // ── Kare hızına eşitlenmiş render tick ──
final ValueNotifier<int> _renderTick = ValueNotifier(0); final ValueNotifier<int> _renderTick = ValueNotifier(0);
bool _frameScheduled = false; bool _frameScheduled = false;
@@ -128,8 +175,10 @@ class ImageCanvasState extends State<ImageCanvas> {
@override @override
void initState() { void initState() {
super.initState(); super.initState();
_origSize = pixelSizeOfFile(widget.filePath);
HardwareKeyboard.instance.addHandler(_onHardwareKey); HardwareKeyboard.instance.addHandler(_onHardwareKey);
_startWarmup(); _startWarmup();
_syncZoom();
} }
/// İlk kullanım shader derlemesini açılışa çek: gizli katmanda sırayla /// İlk kullanım shader derlemesini açılışa çek: gizli katmanda sırayla
@@ -189,6 +238,7 @@ class ImageCanvasState extends State<ImageCanvas> {
void didUpdateWidget(ImageCanvas old) { void didUpdateWidget(ImageCanvas old) {
super.didUpdateWidget(old); super.didUpdateWidget(old);
if (old.filePath != widget.filePath) { if (old.filePath != widget.filePath) {
_origSize = pixelSizeOfFile(widget.filePath);
_cachedFileImage?.evict(); _cachedFileImage?.evict();
_cachedProvider?.evict(); _cachedProvider?.evict();
_cachedFileImage = null; _cachedFileImage = null;
@@ -207,6 +257,7 @@ class ImageCanvasState extends State<ImageCanvas> {
_usingFullRes = false; _usingFullRes = false;
_imageSize = Size.zero; _imageSize = Size.zero;
_markRenderDirty(); _markRenderDirty();
_syncZoom();
} }
void _toggleFillFit() { void _toggleFillFit() {
@@ -215,6 +266,7 @@ class ImageCanvasState extends State<ImageCanvas> {
_flipH = false; _flipH = false;
WindowPersistence.saveFilled(_isFilled); WindowPersistence.saveFilled(_isFilled);
_markRenderDirty(); _markRenderDirty();
_syncZoom();
} }
/// Ctrl+R: sağ çift tık gibi efektleri sıfırlar ve yatay aynalar; tekrar basınca geri alır. /// Ctrl+R: sağ çift tık gibi efektleri sıfırlar ve yatay aynalar; tekrar basınca geri alır.
@@ -233,7 +285,7 @@ class ImageCanvasState extends State<ImageCanvas> {
final double vy = _angle == 0 ? e.localPosition.dy : _vpSize.height / 2; final double vy = _angle == 0 ? e.localPosition.dy : _vpSize.height / 2;
final double cx = (vx - _tx) / _sc; final double cx = (vx - _tx) / _sc;
final double cy = (vy - _ty) / _sc; final double cy = (vy - _ty) / _sc;
const double factor = 1.1; final double factor = _ctrlPressed ? 1.01 : 1.1;
final double rawSc = _sc * (e.scrollDelta.dy < 0 ? factor : 1.0 / factor); final double rawSc = _sc * (e.scrollDelta.dy < 0 ? factor : 1.0 / factor);
final double newSc = rawSc.clamp(1.0, 20.0); final double newSc = rawSc.clamp(1.0, 20.0);
if (_angle == 0) { if (_angle == 0) {
@@ -250,6 +302,7 @@ class ImageCanvasState extends State<ImageCanvas> {
_checkFullResSwap(); _checkFullResSwap();
_clamp(); _clamp();
_markRenderDirty(); _markRenderDirty();
_syncZoom();
} }
void _onPanUpdate(DragUpdateDetails d) { void _onPanUpdate(DragUpdateDetails d) {
@@ -263,23 +316,8 @@ class ImageCanvasState extends State<ImageCanvas> {
if (e.buttons != kSecondaryMouseButton) return; if (e.buttons != kSecondaryMouseButton) return;
_pointerDownPos = e.localPosition; _pointerDownPos = e.localPosition;
_rightDragMoved = false;
final now = DateTime.now(); _rightPointerId = e.pointer;
if (_lastRightTapTime != null &&
now.difference(_lastRightTapTime!) < const Duration(milliseconds: 400) &&
(e.localPosition - _lastRightTapPos).distance < 20) {
_angle = 0;
_contrast = 1.0;
_saturation = 1.0;
_brightness = 0;
_flipH = false;
_interactionMode = 'none';
_markRenderDirty();
_lastRightTapTime = null;
return;
}
_lastRightTapTime = now;
_lastRightTapPos = e.localPosition;
if (_ctrlPressed && _shiftPressed) { if (_ctrlPressed && _shiftPressed) {
_interactionMode = 'brightness'; _interactionMode = 'brightness';
@@ -298,6 +336,10 @@ class ImageCanvasState extends State<ImageCanvas> {
void _onPointerMove(PointerMoveEvent e) { void _onPointerMove(PointerMoveEvent e) {
if (e.buttons != kSecondaryMouseButton) return; if (e.buttons != kSecondaryMouseButton) return;
if (!_rightDragMoved &&
(e.localPosition - _pointerDownPos).distance > _dragClickSlop) {
_rightDragMoved = true;
}
final dy = e.localPosition.dy - _pointerDownPos.dy; final dy = e.localPosition.dy - _pointerDownPos.dy;
@@ -319,7 +361,30 @@ class ImageCanvasState extends State<ImageCanvas> {
} }
void _onPointerUp(PointerUpEvent e) { void _onPointerUp(PointerUpEvent e) {
if (e.pointer != _rightPointerId) return;
_rightPointerId = null;
_interactionMode = 'none'; _interactionMode = 'none';
// Sürükleme ile biten etkileşim sağ çift tık zincirini keser; yalnızca
// gerçek tıklamalar (hareketsiz bas-bırak) çift tık sayılır.
if (_rightDragMoved) {
_lastRightTapTime = null;
_rightDragMoved = false;
return;
}
final now = DateTime.now();
final isDouble = _lastRightTapTime != null &&
now.difference(_lastRightTapTime!) < const Duration(milliseconds: 400) &&
(e.localPosition - _lastRightTapPos).distance < 20;
_lastRightTapTime = isDouble ? null : now;
_lastRightTapPos = e.localPosition;
if (isDouble) {
_angle = 0;
_contrast = 1.0;
_saturation = 1.0;
_brightness = 0;
_flipH = false;
_markRenderDirty();
}
} }
// ── Combined color filter (brightness × saturation × contrast) ── // ── Combined color filter (brightness × saturation × contrast) ──
@@ -502,7 +567,10 @@ class ImageCanvasState extends State<ImageCanvas> {
Widget build(BuildContext context) { Widget build(BuildContext context) {
return LayoutBuilder( return LayoutBuilder(
builder: (context, constraints) { builder: (context, constraints) {
if (constraints.biggest != _vpSize) {
_vpSize = constraints.biggest; _vpSize = constraints.biggest;
_syncZoom();
}
_clamp(); _clamp();
return RepaintBoundary( return RepaintBoundary(
child: ValueListenableBuilder<int>( child: ValueListenableBuilder<int>(
+10 -10
View File
@@ -209,10 +209,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: intl name: intl
sha256: "3df61194eb431efc39c4ceba583b95633a403f46c9fd341e550ce0bfa50e9aa5" sha256: "1ca20c894b1717686a2319b8548763d812bc0aabdac580420a44c5178c57a867"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.20.2" version: "0.20.3"
json_annotation: json_annotation:
dependency: transitive dependency: transitive
description: description:
@@ -257,10 +257,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: matcher name: matcher
sha256: dc0b7dc7651697ea4ff3e69ef44b0407ea32c487a39fff6a4004fa585e901861 sha256: "31bd099b47c10cd1aeb55146a2d46ce0277630ecef3f7dae54ad7873f36696cd"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.12.19" version: "0.12.20"
material_color_utilities: material_color_utilities:
dependency: transitive dependency: transitive
description: description:
@@ -273,10 +273,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: meta name: meta
sha256: "1741988757a65eb6b36abe716829688cf01910bbf91c34354ff7ec1c3de2b349" sha256: "307249ce4ff29d58a18e97f6345f539382eb9c9c29ecda628900f31de0443dd9"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.18.0" version: "1.19.0"
msix: msix:
dependency: "direct main" dependency: "direct main"
description: description:
@@ -462,10 +462,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: test_api name: test_api
sha256: "949a932224383300f01be9221c39180316445ecb8e7547f70a41a35bf421fb9e" sha256: "2a122cbe059f8b610d3a5415f42e255b6c17b1f21eee1d960f31080237fb4f11"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.7.11" version: "0.7.12"
typed_data: typed_data:
dependency: transitive dependency: transitive
description: description:
@@ -486,10 +486,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: vector_math name: vector_math
sha256: d530bd74fea330e6e364cda7a85019c434070188383e1cd8d9777ee586914c5b sha256: "92b9910f66ed1057fd4da7b040ae7c74cafacf885bdc81be496928d5049b032d"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.2.0" version: "2.4.3"
vm_service: vm_service:
dependency: transitive dependency: transitive
description: description:
+1 -1
View File
@@ -1,7 +1,7 @@
name: imajviewer name: imajviewer
description: "Ultra-lightweight image viewer for Linux and Windows." description: "Ultra-lightweight image viewer for Linux and Windows."
publish_to: 'none' publish_to: 'none'
version: 1.6.1+13 version: 1.7.2+18
msix_config: msix_config:
display_name: ImajViewer display_name: ImajViewer