diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml
index 60bd3ee..005e3cf 100644
--- a/android/app/src/main/AndroidManifest.xml
+++ b/android/app/src/main/AndroidManifest.xml
@@ -32,4 +32,7 @@
android:name="flutterEmbedding"
android:value="2" />
+
+
+
diff --git a/ios/Podfile b/ios/Podfile
new file mode 100644
index 0000000..e9b0728
--- /dev/null
+++ b/ios/Podfile
@@ -0,0 +1,53 @@
+# Uncomment this line to define a global platform for your project
+# platform :ios, '11.0'
+
+# CocoaPods analytics sends network stats synchronously affecting flutter build latency.
+ENV['COCOAPODS_DISABLE_STATS'] = 'true'
+
+project 'Runner', {
+ 'Debug' => :debug,
+ 'Profile' => :release,
+ 'Release' => :release,
+}
+
+def flutter_root
+ generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'Generated.xcconfig'), __FILE__)
+ unless File.exist?(generated_xcode_build_settings_path)
+ raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure flutter pub get is executed first"
+ end
+
+ File.foreach(generated_xcode_build_settings_path) do |line|
+ matches = line.match(/FLUTTER_ROOT\=(.*)/)
+ return matches[1].strip if matches
+ end
+ raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Generated.xcconfig, then run flutter pub get"
+end
+
+require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root)
+
+flutter_ios_podfile_setup
+
+target 'Runner' do
+ use_frameworks!
+ use_modular_headers!
+
+ flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__))
+end
+
+post_install do |installer|
+ installer.pods_project.targets.each do |target|
+ flutter_additional_ios_build_settings(target)
+
+ # Start of the permission_handler configuration
+ target.build_configurations.each do |config|
+ # Preprocessor definitions can be found in: https://github.com/Baseflow/flutter-permission-handler/blob/master/permission_handler_apple/ios/Classes/PermissionHandlerEnums.h
+ config.build_settings['GCC_PREPROCESSOR_DEFINITIONS'] ||= [
+ '$(inherited)',
+ 'PERMISSION_CAMERA=1',
+
+ ## dart: PermissionGroup.sensors
+ # 'PERMISSION_SENSORS=1',
+ ]
+ end
+ end
+end
diff --git a/ios/Runner/Info.plist b/ios/Runner/Info.plist
index 5a40744..9b13e6e 100644
--- a/ios/Runner/Info.plist
+++ b/ios/Runner/Info.plist
@@ -1,49 +1,51 @@
-
- CADisableMinimumFrameDurationOnPhone
-
- CFBundleDevelopmentRegion
- $(DEVELOPMENT_LANGUAGE)
- CFBundleDisplayName
- Lightmeter
- CFBundleExecutable
- $(EXECUTABLE_NAME)
- CFBundleIdentifier
- $(PRODUCT_BUNDLE_IDENTIFIER)
- CFBundleInfoDictionaryVersion
- 6.0
- CFBundleName
- lightmeter
- CFBundlePackageType
- APPL
- CFBundleShortVersionString
- $(FLUTTER_BUILD_NAME)
- CFBundleSignature
- ????
- CFBundleVersion
- $(FLUTTER_BUILD_NUMBER)
- LSRequiresIPhoneOS
-
- UIApplicationSupportsIndirectInputEvents
-
- UILaunchStoryboardName
- LaunchScreen
- UIMainStoryboardFile
- Main
- UISupportedInterfaceOrientations
-
- UIInterfaceOrientationPortrait
-
- UISupportedInterfaceOrientations~ipad
-
- UIInterfaceOrientationPortrait
- UIInterfaceOrientationPortraitUpsideDown
- UIInterfaceOrientationLandscapeLeft
- UIInterfaceOrientationLandscapeRight
-
- UIViewControllerBasedStatusBarAppearance
-
-
-
+
+ CADisableMinimumFrameDurationOnPhone
+
+ CFBundleDevelopmentRegion
+ $(DEVELOPMENT_LANGUAGE)
+ CFBundleDisplayName
+ Lightmeter
+ CFBundleExecutable
+ $(EXECUTABLE_NAME)
+ CFBundleIdentifier
+ $(PRODUCT_BUNDLE_IDENTIFIER)
+ CFBundleInfoDictionaryVersion
+ 6.0
+ CFBundleName
+ lightmeter
+ CFBundlePackageType
+ APPL
+ CFBundleShortVersionString
+ $(FLUTTER_BUILD_NAME)
+ CFBundleSignature
+ ????
+ CFBundleVersion
+ $(FLUTTER_BUILD_NUMBER)
+ LSRequiresIPhoneOS
+
+ UIApplicationSupportsIndirectInputEvents
+
+ UILaunchStoryboardName
+ LaunchScreen
+ UIMainStoryboardFile
+ Main
+ UISupportedInterfaceOrientations
+
+ UIInterfaceOrientationPortrait
+
+ UISupportedInterfaceOrientations~ipad
+
+ UIInterfaceOrientationPortrait
+ UIInterfaceOrientationPortraitUpsideDown
+ UIInterfaceOrientationLandscapeLeft
+ UIInterfaceOrientationLandscapeRight
+
+ UIViewControllerBasedStatusBarAppearance
+
+ NSCameraUsageDescription
+ Provide camera permissions in order to make measurements
+
+
\ No newline at end of file
diff --git a/lib/data/permissions_service.dart b/lib/data/permissions_service.dart
new file mode 100644
index 0000000..fb04332
--- /dev/null
+++ b/lib/data/permissions_service.dart
@@ -0,0 +1,7 @@
+import 'package:permission_handler/permission_handler.dart';
+
+class PermissionsService {
+ Future checkCameraPermission() async => await Permission.camera.status;
+
+ Future requestCameraPermission() async => Permission.camera.request();
+}
diff --git a/lib/generated/intl/messages_en.dart b/lib/generated/intl/messages_en.dart
index f9d8167..434a72a 100644
--- a/lib/generated/intl/messages_en.dart
+++ b/lib/generated/intl/messages_en.dart
@@ -27,6 +27,11 @@ class MessageLookup extends MessageLookupByLibrary {
"haptics": MessageLookupByLibrary.simpleMessage("Haptics"),
"keepsScreenOn":
MessageLookupByLibrary.simpleMessage("Keeps screen on"),
+ "openSettings": MessageLookupByLibrary.simpleMessage("Open settings"),
+ "permissionNeeded":
+ MessageLookupByLibrary.simpleMessage("Permission needed"),
+ "permissionNeededMessage": MessageLookupByLibrary.simpleMessage(
+ "To use Lightmeter, turn on Camera permissions."),
"settings": MessageLookupByLibrary.simpleMessage("Settings"),
"slowestExposurePair": MessageLookupByLibrary.simpleMessage("Slowest")
};
diff --git a/lib/generated/l10n.dart b/lib/generated/l10n.dart
index 4fa1b88..8e6b760 100644
--- a/lib/generated/l10n.dart
+++ b/lib/generated/l10n.dart
@@ -50,6 +50,36 @@ class S {
return Localizations.of(context, S);
}
+ /// `Permission needed`
+ String get permissionNeeded {
+ return Intl.message(
+ 'Permission needed',
+ name: 'permissionNeeded',
+ desc: '',
+ args: [],
+ );
+ }
+
+ /// `To use Lightmeter, turn on Camera permissions.`
+ String get permissionNeededMessage {
+ return Intl.message(
+ 'To use Lightmeter, turn on Camera permissions.',
+ name: 'permissionNeededMessage',
+ desc: '',
+ args: [],
+ );
+ }
+
+ /// `Open settings`
+ String get openSettings {
+ return Intl.message(
+ 'Open settings',
+ name: 'openSettings',
+ desc: '',
+ args: [],
+ );
+ }
+
/// `Fastest`
String get fastestExposurePair {
return Intl.message(
diff --git a/lib/l10n/intl_en.arb b/lib/l10n/intl_en.arb
index 8c8f9e9..86b8586 100644
--- a/lib/l10n/intl_en.arb
+++ b/lib/l10n/intl_en.arb
@@ -1,5 +1,8 @@
{
"@@locale": "en",
+ "permissionNeeded": "Permission needed",
+ "permissionNeededMessage": "To use Lightmeter, turn on Camera permissions.",
+ "openSettings": "Open settings",
"fastestExposurePair": "Fastest",
"slowestExposurePair": "Slowest",
"settings": "Settings",
diff --git a/lib/main.dart b/lib/main.dart
index 12546a7..ed3afcd 100644
--- a/lib/main.dart
+++ b/lib/main.dart
@@ -2,14 +2,16 @@ import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_localizations/flutter_localizations.dart';
+import 'package:lightmeter/data/permissions_service.dart';
import 'package:lightmeter/screens/settings/settings_page_route_builder.dart';
+import 'package:provider/provider.dart';
import 'generated/l10n.dart';
import 'models/photography_value.dart';
import 'res/dimens.dart';
import 'res/theme.dart';
import 'screens/metering/metering_bloc.dart';
-import 'screens/metering/metering_screen.dart';
+import 'screens/permissions_check/flow_permissions_check.dart';
import 'utils/stop_type_provider.dart';
void main() {
@@ -57,23 +59,27 @@ class _ApplicationState extends State with TickerProviderStateMixin
@override
Widget build(BuildContext context) {
- return StopTypeProvider(
- child: BlocProvider(
- create: (context) => MeteringBloc(context.read()),
- child: MaterialApp(
- theme: ThemeData(
- useMaterial3: true,
- colorScheme: lightColorScheme,
+ return Provider(
+ create: (context) => PermissionsService(),
+ child: StopTypeProvider(
+ child: BlocProvider(
+ create: (context) => MeteringBloc(context.read()),
+ child: MaterialApp(
+ theme: ThemeData(
+ useMaterial3: true,
+ colorScheme: lightColorScheme,
+ ),
+ navigatorObservers: [_settingsRouteObserver],
+ localizationsDelegates: const [
+ S.delegate,
+ GlobalMaterialLocalizations.delegate,
+ GlobalWidgetsLocalizations.delegate,
+ GlobalCupertinoLocalizations.delegate,
+ ],
+ supportedLocales: S.delegate.supportedLocales,
+ home: const PermissionsCheckFlow(),
+ //home: MeteringScreen(animationController: _animationController),
),
- navigatorObservers: [_settingsRouteObserver],
- localizationsDelegates: const [
- S.delegate,
- GlobalMaterialLocalizations.delegate,
- GlobalWidgetsLocalizations.delegate,
- GlobalCupertinoLocalizations.delegate,
- ],
- supportedLocales: S.delegate.supportedLocales,
- home: MeteringScreen(animationController: _animationController),
),
),
);
diff --git a/lib/res/dimens.dart b/lib/res/dimens.dart
index 95f4396..34adfc3 100644
--- a/lib/res/dimens.dart
+++ b/lib/res/dimens.dart
@@ -8,8 +8,10 @@ class Dimens {
static const double grid8 = 8;
static const double grid16 = 16;
static const double grid24 = 24;
+ static const double grid168 = 168;
static const double paddingM = 16;
+ static const double paddingL = 24;
static const Duration durationS = Duration(milliseconds: 100);
static const Duration durationSM = Duration(milliseconds: 150);
diff --git a/lib/screens/permissions_check/bloc_permissions_check.dart b/lib/screens/permissions_check/bloc_permissions_check.dart
new file mode 100644
index 0000000..88efd42
--- /dev/null
+++ b/lib/screens/permissions_check/bloc_permissions_check.dart
@@ -0,0 +1,46 @@
+import 'package:flutter_bloc/flutter_bloc.dart';
+import 'package:lightmeter/data/permissions_service.dart';
+import 'package:permission_handler/permission_handler.dart';
+
+import 'event_permissions_check.dart';
+import 'state_permissions_check.dart';
+
+class PermissionsCheckBloc extends Bloc {
+ final PermissionsService _permissionsService;
+
+ PermissionsCheckBloc(this._permissionsService) : super(const LoadingState()) {
+ on((event, emit) => emit(const PermissionsGrantedState()));
+ on((event, emit) => emit(const PermissionsDeniedState()));
+ _checkAndRequestPermissions();
+ }
+
+ Future _checkAndRequestPermissions() async {
+ _permissionsService.checkCameraPermission().then((value) {
+ switch (value) {
+ case PermissionStatus.permanentlyDenied:
+ case PermissionStatus.restricted:
+ add(const PermissionsDeniedEvent());
+ break;
+ case PermissionStatus.denied:
+ _permissionsService.requestCameraPermission().then((value) {
+ switch (value) {
+ case PermissionStatus.permanentlyDenied:
+ case PermissionStatus.restricted:
+ case PermissionStatus.denied:
+ add(const PermissionsDeniedEvent());
+ break;
+ case PermissionStatus.limited:
+ case PermissionStatus.granted:
+ add(const PermissionsGrantedEvent());
+ break;
+ }
+ });
+ break;
+ case PermissionStatus.limited:
+ case PermissionStatus.granted:
+ add(const PermissionsGrantedEvent());
+ break;
+ }
+ });
+ }
+}
diff --git a/lib/screens/permissions_check/event_permissions_check.dart b/lib/screens/permissions_check/event_permissions_check.dart
new file mode 100644
index 0000000..2d93f1d
--- /dev/null
+++ b/lib/screens/permissions_check/event_permissions_check.dart
@@ -0,0 +1,11 @@
+abstract class PermissionsCheckEvent {
+ const PermissionsCheckEvent();
+}
+
+class PermissionsDeniedEvent extends PermissionsCheckEvent {
+ const PermissionsDeniedEvent();
+}
+
+class PermissionsGrantedEvent extends PermissionsCheckEvent {
+ const PermissionsGrantedEvent();
+}
diff --git a/lib/screens/permissions_check/flow_permissions_check.dart b/lib/screens/permissions_check/flow_permissions_check.dart
new file mode 100644
index 0000000..7fb650c
--- /dev/null
+++ b/lib/screens/permissions_check/flow_permissions_check.dart
@@ -0,0 +1,18 @@
+import 'package:flutter/material.dart';
+import 'package:flutter_bloc/flutter_bloc.dart';
+import 'package:lightmeter/data/permissions_service.dart';
+import 'package:lightmeter/screens/permissions_check/screen_permissions_check.dart';
+
+import 'bloc_permissions_check.dart';
+
+class PermissionsCheckFlow extends StatelessWidget {
+ const PermissionsCheckFlow({super.key});
+
+ @override
+ Widget build(BuildContext context) {
+ return BlocProvider(
+ create: (context) => PermissionsCheckBloc(context.read()),
+ child: const PermissionsCheckScreen(),
+ );
+ }
+}
diff --git a/lib/screens/permissions_check/screen_permissions_check.dart b/lib/screens/permissions_check/screen_permissions_check.dart
new file mode 100644
index 0000000..f3cff07
--- /dev/null
+++ b/lib/screens/permissions_check/screen_permissions_check.dart
@@ -0,0 +1,65 @@
+import 'package:flutter/material.dart';
+import 'package:flutter_bloc/flutter_bloc.dart';
+import 'package:lightmeter/generated/l10n.dart';
+import 'package:lightmeter/res/dimens.dart';
+import 'package:lightmeter/screens/settings/settings_screen.dart';
+
+import 'bloc_permissions_check.dart';
+import 'state_permissions_check.dart';
+
+class PermissionsCheckScreen extends StatelessWidget {
+ const PermissionsCheckScreen({super.key});
+
+ @override
+ Widget build(BuildContext context) {
+ return Scaffold(
+ backgroundColor: Theme.of(context).colorScheme.surface,
+ body: SafeArea(
+ child: Padding(
+ padding: const EdgeInsets.all(Dimens.paddingM * 2),
+ child: Center(
+ child: BlocConsumer(
+ listener: (context, state) {
+ if (state is PermissionsGrantedState) {
+ Navigator.of(context).pushReplacement(MaterialPageRoute(builder: (_) => SettingsScreen()));
+ }
+ },
+ builder: (context, state) {
+ return AnimatedSwitcher(
+ duration: Dimens.durationS,
+ child: state is LoadingState
+ ? const CircularProgressIndicator()
+ : Column(
+ mainAxisSize: MainAxisSize.min,
+ children: [
+ Text(
+ S.of(context).permissionNeeded,
+ style: Theme.of(context).textTheme.headlineLarge,
+ textAlign: TextAlign.center,
+ ),
+ const SizedBox(height: Dimens.grid16),
+ Text(
+ S.of(context).permissionNeededMessage,
+ style: Theme.of(context).textTheme.bodyLarge,
+ textAlign: TextAlign.center,
+ ),
+ const SizedBox(height: Dimens.grid24),
+ ElevatedButton(
+ style: ElevatedButton.styleFrom(
+ backgroundColor: Theme.of(context).colorScheme.primary,
+ foregroundColor: Theme.of(context).colorScheme.onPrimary,
+ ),
+ onPressed: () {},
+ child: Text(S.of(context).openSettings),
+ ),
+ ],
+ ),
+ );
+ },
+ ),
+ ),
+ ),
+ ),
+ );
+ }
+}
diff --git a/lib/screens/permissions_check/state_permissions_check.dart b/lib/screens/permissions_check/state_permissions_check.dart
new file mode 100644
index 0000000..f94ac6c
--- /dev/null
+++ b/lib/screens/permissions_check/state_permissions_check.dart
@@ -0,0 +1,15 @@
+abstract class PermissionsCheckState {
+ const PermissionsCheckState();
+}
+
+class LoadingState extends PermissionsCheckState {
+ const LoadingState();
+}
+
+class PermissionsGrantedState extends PermissionsCheckState {
+ const PermissionsGrantedState();
+}
+
+class PermissionsDeniedState extends PermissionsCheckState {
+ const PermissionsDeniedState();
+}
diff --git a/lib/screens/settings/settings_screen.dart b/lib/screens/settings/settings_screen.dart
index 85bbcd2..e4bd939 100644
--- a/lib/screens/settings/settings_screen.dart
+++ b/lib/screens/settings/settings_screen.dart
@@ -17,7 +17,7 @@ class SettingsScreen extends StatelessWidget {
SliverAppBar(
pinned: true,
automaticallyImplyLeading: false,
- expandedHeight: 160.0,
+ expandedHeight: Dimens.grid168,
flexibleSpace: FlexibleSpaceBar(
centerTitle: false,
titlePadding: const EdgeInsets.all(Dimens.paddingM),
diff --git a/pubspec.yaml b/pubspec.yaml
index 234a493..513c9f8 100644
--- a/pubspec.yaml
+++ b/pubspec.yaml
@@ -14,6 +14,7 @@ dependencies:
sdk: flutter
intl: ^0.17.0
material_color_utilities: ^0.2.0
+ permission_handler: 10.2.0
provider: ^6.0.4
dev_dependencies: