Skip to content

Commit

Permalink
Merge pull request #7 from Samoy/dev
Browse files Browse the repository at this point in the history
And unit tests
  • Loading branch information
Samoy authored Apr 23, 2024
2 parents 5eb5270 + e24fb7e commit 08132a7
Show file tree
Hide file tree
Showing 40 changed files with 1,635 additions and 609 deletions.
10 changes: 9 additions & 1 deletion .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,13 @@ jobs:
flutter-version: ${{ env.FLUTTER_VERSION }}
cache: true

- name: Install Tools
uses: awalsh128/cache-apt-pkgs-action@latest
with:
packages: clang cmake git ninja-build pkg-config libgtk-3-dev liblzma-dev libstdc++-12-dev
version: 1.0
if: runner.os == 'Linux'

- name: Install dependencies
id: install
run: flutter pub get
Expand All @@ -41,8 +48,9 @@ jobs:
run: dart analyze --fatal-infos
if: always() && steps.install.outcome == 'success'

- name: Run tests
- name: Run unit tests
run: flutter test --coverage --reporter github

- name: Upload coverage reports to Codecov
uses: codecov/[email protected]
with:
Expand Down
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -46,3 +46,6 @@ app.*.map.json

# Release
/release

# Test Coverage
coverage/
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# ExifHelper

[![CI](https://github.com/Samoy/exif_helper/actions/workflows/ci.yml/badge.svg)](https://github.com/Samoy/exif_helper/actions/workflows/ci.yml)
[![codecov](https://codecov.io/github/Samoy/exif_helper/graph/badge.svg?token=SCJGI01J89)](https://codecov.io/github/Samoy/exif_helper)
Read or write image exif without internet

## Features
### 💻 Cross Platform
Support Windows, Linux, Macos, Android and iOS
Expand Down
4 changes: 4 additions & 0 deletions lib/common/constant.dart
Original file line number Diff line number Diff line change
Expand Up @@ -33,3 +33,7 @@ const double normalButtonHeight = 40;
const String keyDismissWarning = "dismissWarning";

/*--------------------Storage End--------------------*/

/*--------------------List Start--------------------*/
const List<String> allowedExtensions = ["jpg", "tif", "jpeg", "tiff"];
/*--------------------List End--------------------*/
16 changes: 16 additions & 0 deletions lib/common/utils.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import 'package:flutter/material.dart';

class SnackBarUtils {
static ScaffoldFeatureController<SnackBar, SnackBarClosedReason> showSnackBar(
BuildContext context,
String message, {
Duration duration = const Duration(seconds: 2),
}) {
return ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(message),
duration: duration,
),
);
}
}
2 changes: 1 addition & 1 deletion lib/main.dart
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import 'package:exif_helper/models/system.dart';

import 'extensions/platform_extension.dart';

void main() async {
main() async {
WidgetsFlutterBinding.ensureInitialized();
if (PlatformExtension.isDesktop) {
await initDesktop();
Expand Down
117 changes: 62 additions & 55 deletions lib/models/exif.dart → lib/models/image_exif.dart
Original file line number Diff line number Diff line change
@@ -1,87 +1,87 @@
import 'dart:async';
import 'dart:collection';

import 'package:flutter/foundation.dart';
import 'package:image/image.dart';
import 'package:flutter/material.dart';
import 'package:image/image.dart' as img;

class ImageExifModel extends ChangeNotifier {
ImageExifModel({this.path = ""});

class ExifModel extends ChangeNotifier {
String? _path;
Future<Image?>? _image;
Image? _imageData;
final String path;

final GlobalKey<FormState> _formKey = GlobalKey<FormState>();
img.Image? _imageData;
bool _loading = false;
img.Image? _image;
List<ExifItem> _exifItems = [];

String get path => _path ?? "";
bool get loading => _loading;

img.Image? get image => _image;

Future<Image?>? get image => _image;
img.Image? get imageData => _imageData;

Image? get imageData => _imageData;
GlobalKey<FormState> get formKey => _formKey;

UnmodifiableListView<ExifItem> get exifItems =>
UnmodifiableListView(_exifItems);

void setImagePath(String path) {
_path = path;
_image = _fetchExifData(_path!);
fetchImageExifInfo() async {
if (path.isEmpty) return;
_loading = true;
_image = await compute((message) => img.decodeImageFile(message), path);
_setImageData(_image?.clone());
_setExifItems(_imageData);
_loading = false;
notifyListeners();
}

void setImageData(Image? image) {
void _setImageData(img.Image? image) {
if (image == null) return;
_imageData = image;
}

void clearImage() {
_path = null;
_image = null;
_exifItems.clear();
notifyListeners();
}

Future<Image?> _fetchExifData(String path) {
return compute((path) {
return path.isEmpty ? null : decodeImageFile(path).then((value) => value);
}, path);
}

void setExifItems(Image? image) {
void _setExifItems(img.Image? image) {
if (image == null) {
return;
}
final List<ExifItem> items = [];
final directories = image.exif;
final getTagName = directories.getTagName;
List<ExifItem> items = [];
for (final name in directories.keys) {
List<Map<String, IfdValue?>> info = [];
List<Map<String, img.IfdValue?>> info = [];
final directory = directories[name];
for (final tag in directory.keys) {
final value = directory[tag];
final tagName = getTagName(tag);
if (tagName != "<unknown>" && value?.type != IfdValueType.undefined) {
if (tagName != "<unknown>" &&
value?.type != img.IfdValueType.undefined) {
info.add({tagName: value});
}
}
items.add(ExifItem(name, info));
for (final subName in directory.sub.keys) {
List<Map<String, IfdValue?>> subInfo = [];
List<Map<String, img.IfdValue?>> subInfo = [];
final subDirectory = directory.sub[subName];
for (final tag in subDirectory.keys) {
final value = subDirectory[tag];
final subTagName = _getSubTagName(subName, tag);
if (subTagName != "<unknown>" &&
value?.type != IfdValueType.undefined) {
value?.type != img.IfdValueType.undefined) {
subInfo.add({subTagName: value});
}
}
items.add(ExifItem(subName, subInfo));
}
}
_exifItems = items;
notifyListeners();
}

void changeExifValue(
Map<String, IfdValue?> info, String tag, String key, String value) {
IfdValue? ifdValue = info[key]?.clone();
void changeExifValue(Map<String, img.IfdValue?> info, ExifItem exifItem,
String key, String value) {
img.IfdValue? ifdValue = info[key]?.clone();
String tag = exifItem.tag;
if (ifdValue != null && value.isNotEmpty) {
try {
_setIfdValue(ifdValue, key, value);
Expand All @@ -104,7 +104,14 @@ class ExifModel extends ChangeNotifier {
}
}

void _setIfdValue(IfdValue ifdValue, String key, String value) {
void resetExif() {
_formKey.currentState?.reset();
_setImageData(_image?.clone());
_setExifItems(_imageData);
notifyListeners();
}

void _setIfdValue(img.IfdValue ifdValue, String key, String value) {
if (value.startsWith("[") && value.endsWith("]")) {
List<String> valueArray = value.substring(1, value.length - 1).split(",");
for (int i = 0; i < valueArray.length; i++) {
Expand All @@ -126,33 +133,33 @@ class ExifModel extends ChangeNotifier {
}

void _setSinglesValue(
{required IfdValue ifdValue, required String value, index = 0}) {
{required img.IfdValue ifdValue, required String value, index = 0}) {
Type type = ifdValue.runtimeType;
switch (type) {
case const (IfdByteValue):
case const (IfdValueShort):
case const (IfdValueLong):
case const (IfdValueSByte):
case const (IfdValueSShort):
case const (IfdValueSLong):
case const (img.IfdByteValue):
case const (img.IfdValueShort):
case const (img.IfdValueLong):
case const (img.IfdValueSByte):
case const (img.IfdValueSShort):
case const (img.IfdValueSLong):
{
int? result = int.tryParse(value);
if (result != null) {
ifdValue.setInt(result, index);
}
}
break;
case const (IfdValueSingle):
case const (IfdValueDouble):
case const (img.IfdValueSingle):
case const (img.IfdValueDouble):
{
double? result = double.tryParse(value);
if (result != null) {
ifdValue.setDouble(result, index);
}
}
break;
case const (IfdValueRational):
case const (IfdValueSRational):
case const (img.IfdValueRational):
case const (img.IfdValueSRational):
{
final array = value.split("/");
if (array.length != 2) {
Expand All @@ -168,7 +175,7 @@ class ExifModel extends ChangeNotifier {
}
}
break;
case const (IfdValueAscii):
case const (img.IfdValueAscii):
ifdValue.setString(value);
break;
default:
Expand All @@ -180,24 +187,24 @@ class ExifModel extends ChangeNotifier {
switch (subName) {
case "gps":
{
if (!exifGpsTags.containsKey(tag)) {
if (!img.exifGpsTags.containsKey(tag)) {
return "<unknown>";
}
return exifGpsTags[tag]!.name;
return img.exifGpsTags[tag]!.name;
}
case "interop":
{
if (!exifInteropTags.containsKey(tag)) {
if (!img.exifInteropTags.containsKey(tag)) {
return "<unknown>";
}
return exifInteropTags[tag]!.name;
return img.exifInteropTags[tag]!.name;
}
default:
{
if (!exifImageTags.containsKey(tag)) {
if (!img.exifImageTags.containsKey(tag)) {
return "<unknown>";
}
return exifImageTags[tag]!.name;
return img.exifImageTags[tag]!.name;
}
}
}
Expand All @@ -207,5 +214,5 @@ class ExifItem {
ExifItem(this.tag, this.info);

String tag;
List<Map<String, IfdValue?>> info;
List<Map<String, img.IfdValue?>> info;
}
17 changes: 17 additions & 0 deletions lib/models/image_path.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import 'package:flutter/material.dart';

class ImagePathModel extends ChangeNotifier {
String _imagePath = "";

String get imagePath => _imagePath;

set imagePath(String value) {
_imagePath = value;
notifyListeners();
}

void clearImage() {
_imagePath = "";
notifyListeners();
}
}
Loading

0 comments on commit 08132a7

Please sign in to comment.