Skip to content

Commit 4539779

Browse files
author
Dimitri Dessus
authored
Merge pull request #145 from Apparence-io/feature/exif-gps-location
✨ Add Exif gps location
2 parents 246f5a4 + f0c3af1 commit 4539779

30 files changed

+1462
-984
lines changed

README.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ CamerAwesome include a lot of useful features like:
2929
- 🎮 Complete example.
3030
- 🎞 Taking a **picture** ( of course 😃 ).
3131
- 🎥 Video recording (iOS only for now).
32+
- 🛰 **GPS Location** saved in image (iOS only for now).
3233

3334
## 🧐  Live example
3435

@@ -54,6 +55,9 @@ CamerAwesome include a lot of useful features like:
5455

5556
<key>NSMicrophoneUsageDescription</key>
5657
<string>To enable microphone access when recording video</string>
58+
59+
<key>NSLocationWhenInUseUsageDescription</key>
60+
<string>To enable GPS location access for Exif data</string>
5761
```
5862

5963
- **Android**
@@ -155,6 +159,7 @@ _captureMode.value = CaptureModes.VIDEO;
155159
| captureMode | ```ValueNotifier<CaptureModes>``` | choose capture mode between **PHOTO** or **VIDEO** | |
156160
| fitted | ```bool``` | whether camera preview must be as big as it needs or cropped to fill with. false by default | |
157161
| imagesStreamBuilder | ```Function``` | returns an imageStream when camera has started preview | |
162+
| savedExifData | ```SavedExifData``` | set exif data when a picture was taken, GPS location can be saved to image file for ex. | |
158163

159164
</p>
160165
</details>

example/ios/Runner.xcodeproj/project.pbxproj

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -499,7 +499,7 @@
499499
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
500500
CLANG_ENABLE_MODULES = YES;
501501
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
502-
DEVELOPMENT_TEAM = "";
502+
DEVELOPMENT_TEAM = Q8N2T428YG;
503503
ENABLE_BITCODE = NO;
504504
FRAMEWORK_SEARCH_PATHS = (
505505
"$(inherited)",
@@ -806,7 +806,7 @@
806806
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
807807
CLANG_ENABLE_MODULES = YES;
808808
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
809-
DEVELOPMENT_TEAM = "";
809+
DEVELOPMENT_TEAM = Q8N2T428YG;
810810
ENABLE_BITCODE = NO;
811811
FRAMEWORK_SEARCH_PATHS = (
812812
"$(inherited)",
@@ -839,7 +839,7 @@
839839
CODE_SIGN_IDENTITY = "Apple Development";
840840
CODE_SIGN_STYLE = Automatic;
841841
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
842-
DEVELOPMENT_TEAM = "";
842+
DEVELOPMENT_TEAM = Q8N2T428YG;
843843
ENABLE_BITCODE = NO;
844844
FRAMEWORK_SEARCH_PATHS = (
845845
"$(inherited)",

example/ios/Runner/Info.plist

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
33
<plist version="1.0">
44
<dict>
5+
<key>CADisableMinimumFrameDurationOnPhone</key>
6+
<true/>
57
<key>CFBundleDevelopmentRegion</key>
68
<string>$(DEVELOPMENT_LANGUAGE)</string>
79
<key>CFBundleExecutable</key>
@@ -29,6 +31,8 @@
2931
</dict>
3032
<key>NSCameraUsageDescription</key>
3133
<string>To enable camera access</string>
34+
<key>NSLocationWhenInUseUsageDescription</key>
35+
<string>To enable location access for Exif data</string>
3236
<key>NSMicrophoneUsageDescription</key>
3337
<string>To enable microphone access</string>
3438
<key>UILaunchStoryboardName</key>
@@ -50,7 +54,5 @@
5054
</array>
5155
<key>UIViewControllerBasedStatusBarAppearance</key>
5256
<false/>
53-
<key>CADisableMinimumFrameDurationOnPhone</key>
54-
<true/>
5557
</dict>
5658
</plist>

example/lib/main.dart

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import 'package:camerawesome_example/widgets/bottom_bar.dart';
66
import 'package:camerawesome_example/widgets/camera_preview.dart';
77
import 'package:camerawesome_example/widgets/preview_card.dart';
88
import 'package:camerawesome_example/widgets/top_bar.dart';
9+
import 'package:exif/exif.dart';
910
import 'package:flutter/material.dart';
1011
import 'package:flutter/services.dart';
1112
import 'package:image/image.dart' as imgUtils;
@@ -57,6 +58,10 @@ class _MyAppState extends State<MyApp> with TickerProviderStateMixin {
5758
// StreamSubscription<Uint8List> previewStreamSub;
5859
Stream<Uint8List> previewStream;
5960

61+
ExifPreferences _exifPreferences = ExifPreferences(
62+
saveGPSLocation: false,
63+
);
64+
6065
@override
6166
void initState() {
6267
super.initState();
@@ -126,6 +131,11 @@ class _MyAppState extends State<MyApp> with TickerProviderStateMixin {
126131
switchFlash: _switchFlash,
127132
orientation: _orientation,
128133
rotationController: _iconsAnimationController,
134+
exifPreferences: _exifPreferences,
135+
onSetExifPreferences: (newExifData) {
136+
_pictureController.setExifPreferences(newExifData);
137+
setState(() {});
138+
},
129139
onFlashTap: () {
130140
switch (_switchFlash.value) {
131141
case CameraFlashes.NONE:
@@ -218,11 +228,17 @@ class _MyAppState extends State<MyApp> with TickerProviderStateMixin {
218228
_previewAnimationController.reset();
219229
}
220230
_previewAnimationController.forward();
231+
final bytes = file.readAsBytesSync();
221232
print("----------------------------------");
222233
print("TAKE PHOTO CALLED");
223234
print("==> hastakePhoto : ${file.exists()} | path : $filePath");
224-
final img = imgUtils.decodeImage(file.readAsBytesSync());
235+
final img = imgUtils.decodeImage(bytes);
225236
print("==> img.width : ${img.width} | img.height : ${img.height}");
237+
final exifData = await readExifFromBytes(bytes);
238+
for (var exif in exifData.entries) {
239+
print("==> exifData : ${exif.key} : ${exif.value}");
240+
}
241+
226242
print("----------------------------------");
227243
}
228244

@@ -360,6 +376,7 @@ class _MyAppState extends State<MyApp> with TickerProviderStateMixin {
360376
this._availableSizes = availableSizes;
361377
return availableSizes[0];
362378
},
379+
exifPreferences: _exifPreferences,
363380
captureMode: _captureMode,
364381
photoSize: _photoSize,
365382
sensor: _sensor,
@@ -407,6 +424,7 @@ class _MyAppState extends State<MyApp> with TickerProviderStateMixin {
407424
this._availableSizes = availableSizes;
408425
return availableSizes[0];
409426
},
427+
exifPreferences: _exifPreferences,
410428
captureMode: _captureMode,
411429
photoSize: _photoSize,
412430
sensor: _sensor,

example/lib/widgets/top_bar.dart

Lines changed: 29 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import 'package:camerawesome/camerawesome_plugin.dart';
12
import 'package:camerawesome/models/capture_modes.dart';
23
import 'package:camerawesome/models/flashmodes.dart';
34
import 'package:camerawesome/models/orientations.dart';
@@ -15,12 +16,14 @@ class TopBarWidget extends StatelessWidget {
1516
final ValueNotifier<bool> enableAudio;
1617
final ValueNotifier<CameraFlashes> switchFlash;
1718
final ValueNotifier<bool> enablePinchToZoom;
19+
final ExifPreferences exifPreferences;
1820
final Function onFullscreenTap;
1921
final Function onResolutionTap;
2022
final Function onChangeSensorTap;
2123
final Function onFlashTap;
2224
final Function onAudioChange;
2325
final Function onPinchToZoomChange;
26+
final Function onSetExifPreferences;
2427

2528
const TopBarWidget({
2629
Key key,
@@ -39,6 +42,8 @@ class TopBarWidget extends StatelessWidget {
3942
@required this.onChangeSensorTap,
4043
@required this.onResolutionTap,
4144
@required this.onPinchToZoomChange,
45+
@required this.exifPreferences,
46+
@required this.onSetExifPreferences,
4247
}) : super(key: key);
4348

4449
@override
@@ -115,24 +120,44 @@ class TopBarWidget extends StatelessWidget {
115120
children: [
116121
OptionButton(
117122
rotationController: rotationController,
118-
icon: enablePinchToZoom.value ? Icons.pinch_rounded : Icons.pinch_outlined,
123+
icon: enablePinchToZoom.value
124+
? Icons.pinch_rounded
125+
: Icons.pinch_outlined,
119126
orientation: orientation,
120127
onTapCallback: () => onPinchToZoomChange?.call(),
121128
),
122129
captureMode.value == CaptureModes.VIDEO
123130
? Padding(
124-
padding: const EdgeInsets.only(left: 20.0),
125-
child: OptionButton(
131+
padding: const EdgeInsets.only(left: 20.0),
132+
child: OptionButton(
126133
icon: enableAudio.value ? Icons.mic : Icons.mic_off,
127134
rotationController: rotationController,
128135
orientation: orientation,
129136
isEnabled: !isRecording,
130137
onTapCallback: () => onAudioChange?.call(),
131138
),
132-
)
139+
)
133140
: Container(),
134141
],
135142
),
143+
SizedBox(height: 20.0),
144+
Row(
145+
mainAxisAlignment: MainAxisAlignment.end,
146+
children: [
147+
OptionButton(
148+
rotationController: rotationController,
149+
icon: exifPreferences.saveGPSLocation
150+
? Icons.gps_fixed_rounded
151+
: Icons.gps_not_fixed_outlined,
152+
orientation: orientation,
153+
onTapCallback: () {
154+
exifPreferences.saveGPSLocation =
155+
!exifPreferences.saveGPSLocation;
156+
onSetExifPreferences?.call(exifPreferences);
157+
},
158+
),
159+
],
160+
),
136161
],
137162
),
138163
);

example/pubspec.lock

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,13 @@ packages:
88
url: "https://pub.dartlang.org"
99
source: hosted
1010
version: "3.3.0"
11+
args:
12+
dependency: transitive
13+
description:
14+
name: args
15+
url: "https://pub.dartlang.org"
16+
source: hosted
17+
version: "2.3.1"
1118
async:
1219
dependency: transitive
1320
description:
@@ -50,6 +57,13 @@ packages:
5057
url: "https://pub.dartlang.org"
5158
source: hosted
5259
version: "1.16.0"
60+
convert:
61+
dependency: transitive
62+
description:
63+
name: convert
64+
url: "https://pub.dartlang.org"
65+
source: hosted
66+
version: "3.0.2"
5367
crypto:
5468
dependency: transitive
5569
description:
@@ -71,6 +85,13 @@ packages:
7185
url: "https://pub.dartlang.org"
7286
source: hosted
7387
version: "1.0.5"
88+
exif:
89+
dependency: "direct main"
90+
description:
91+
name: exif
92+
url: "https://pub.dartlang.org"
93+
source: hosted
94+
version: "3.1.2"
7495
fake_async:
7596
dependency: transitive
7697
description:
@@ -143,6 +164,13 @@ packages:
143164
url: "https://pub.dartlang.org"
144165
source: hosted
145166
version: "0.6.4"
167+
json_annotation:
168+
dependency: transitive
169+
description:
170+
name: json_annotation
171+
url: "https://pub.dartlang.org"
172+
source: hosted
173+
version: "4.7.0"
146174
matcher:
147175
dependency: transitive
148176
description:
@@ -267,6 +295,13 @@ packages:
267295
url: "https://pub.dartlang.org"
268296
source: hosted
269297
version: "1.9.0"
298+
sprintf:
299+
dependency: transitive
300+
description:
301+
name: sprintf
302+
url: "https://pub.dartlang.org"
303+
source: hosted
304+
version: "6.0.2"
270305
stack_trace:
271306
dependency: transitive
272307
description:

example/pubspec.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ dependencies:
1818
path_provider: ^2.0.2
1919
rxdart: ^0.27.1
2020
video_player: ^2.1.6
21+
exif: ^3.1.2
2122

2223
dev_dependencies:
2324
integration_test:

ios/Classes/CameraPermissions.m

Lines changed: 16 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -10,22 +10,22 @@
1010
@implementation CameraPermissions
1111

1212
+ (BOOL)checkPermissions {
13-
NSString *mediaType = AVMediaTypeVideo;
14-
AVAuthorizationStatus authStatus = [AVCaptureDevice authorizationStatusForMediaType:mediaType];
15-
16-
__block BOOL permissionsGranted;
17-
if (authStatus == AVAuthorizationStatusNotDetermined) {
18-
dispatch_semaphore_t sem = dispatch_semaphore_create(0);
19-
[AVCaptureDevice requestAccessForMediaType:mediaType completionHandler:^(BOOL granted) {
20-
permissionsGranted = granted;
21-
dispatch_semaphore_signal(sem);
22-
}];
23-
dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER);
24-
} else {
25-
permissionsGranted = (authStatus == AVAuthorizationStatusAuthorized);
26-
}
27-
28-
return permissionsGranted;
13+
NSString *mediaType = AVMediaTypeVideo;
14+
AVAuthorizationStatus authStatus = [AVCaptureDevice authorizationStatusForMediaType:mediaType];
15+
16+
__block BOOL permissionsGranted;
17+
if (authStatus == AVAuthorizationStatusNotDetermined) {
18+
dispatch_semaphore_t sem = dispatch_semaphore_create(0);
19+
[AVCaptureDevice requestAccessForMediaType:mediaType completionHandler:^(BOOL granted) {
20+
permissionsGranted = granted;
21+
dispatch_semaphore_signal(sem);
22+
}];
23+
dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER);
24+
} else {
25+
permissionsGranted = (authStatus == AVAuthorizationStatusAuthorized);
26+
}
27+
28+
return permissionsGranted;
2929
}
3030

3131
@end

ios/Classes/CameraPreview/CameraPreview.h

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
#import <Foundation/Foundation.h>
1212

1313
#import "MotionController.h"
14+
#import "LocationController.h"
1415
#import "VideoController.h"
1516
#import "ImageStreamController.h"
1617
#import "CameraSensor.h"
@@ -40,14 +41,16 @@ AVCaptureAudioDataOutputSampleBufferDelegate>
4041
@property(readonly, nonatomic) CaptureModes captureMode;
4142
@property(readonly, nonatomic) FlutterResult result;
4243
@property(readonly, nonatomic) NSString *currentPresset;
44+
@property(readonly, nonatomic) bool saveGPSLocation;
4345
@property(readonly, nonatomic) NSObject<FlutterBinaryMessenger> *messenger;
4446
@property(readonly) CVPixelBufferRef volatile latestPixelBuffer;
4547
@property(readonly, nonatomic) CGSize currentPreviewSize;
4648
@property(readonly, nonatomic) ImageStreamController *imageStreamController;
4749
@property(readonly, nonatomic) MotionController *motionController;
50+
@property(readonly, nonatomic) LocationController *locationController;
4851
@property(readonly, nonatomic) VideoController *videoController;
4952
@property(nonatomic, copy) void (^onFrameAvailable)(void);
50-
53+
5154
- (instancetype)initWithCameraSensor:(CameraSensor)sensor
5255
streamImages:(BOOL)streamImages
5356
captureMode:(CaptureModes)captureMode
@@ -61,6 +64,7 @@ AVCaptureAudioDataOutputSampleBufferDelegate>
6164
- (void)setFlashMode:(CameraFlashMode)flashMode;
6265
- (void)setCaptureMode:(CaptureModes)captureMode;
6366
- (void)setRecordingAudioMode:(bool)enableAudio;
67+
- (void)setExifPreferencesGPSLocation:(bool)gpsLocation;
6468
- (void)refresh;
6569
- (void)start;
6670
- (void)stop;

0 commit comments

Comments
 (0)