mirror of
https://github.com/vodemn/m3_lightmeter.git
synced 2024-11-22 15:30:59 +00:00
Merge branch 'main' of https://github.com/vodemn/m3_lightmeter into feature/ML-104
This commit is contained in:
commit
5a62326037
16 changed files with 218 additions and 125 deletions
3
.gitignore
vendored
3
.gitignore
vendored
|
@ -59,4 +59,5 @@ ios/firebase_app_id_file.json
|
||||||
ios/Runner/GoogleService-Info.plist
|
ios/Runner/GoogleService-Info.plist
|
||||||
/lib/firebase_options.dart
|
/lib/firebase_options.dart
|
||||||
|
|
||||||
coverage/
|
coverage/
|
||||||
|
screenshots/
|
BIN
assets/camera_stub_image.jpg
Normal file
BIN
assets/camera_stub_image.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 899 KiB |
|
@ -6,8 +6,26 @@ enum IAPProductStatus {
|
||||||
|
|
||||||
enum IAPProductType { paidFeatures }
|
enum IAPProductType { paidFeatures }
|
||||||
|
|
||||||
abstract class IAPProduct {
|
class IAPProduct {
|
||||||
const IAPProduct._();
|
final String storeId;
|
||||||
|
final IAPProductStatus status;
|
||||||
|
|
||||||
IAPProductStatus get status => IAPProductStatus.purchasable;
|
const IAPProduct({
|
||||||
|
required this.storeId,
|
||||||
|
this.status = IAPProductStatus.purchasable,
|
||||||
|
});
|
||||||
|
|
||||||
|
IAPProduct copyWith({IAPProductStatus? status}) => IAPProduct(
|
||||||
|
storeId: storeId,
|
||||||
|
status: status ?? this.status,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
extension IAPProductTypeExtension on IAPProductType {
|
||||||
|
String get storeId {
|
||||||
|
switch (this) {
|
||||||
|
case IAPProductType.paidFeatures:
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,7 +18,12 @@ class IAPProductsProviderState extends State<IAPProductsProvider> {
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return IAPProducts(
|
return IAPProducts(
|
||||||
products: const [],
|
products: [
|
||||||
|
IAPProduct(
|
||||||
|
storeId: IAPProductType.paidFeatures.storeId,
|
||||||
|
status: IAPProductStatus.purchased,
|
||||||
|
)
|
||||||
|
],
|
||||||
child: widget.child,
|
child: widget.child,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -35,13 +40,28 @@ class IAPProducts extends InheritedModel<IAPProductType> {
|
||||||
super.key,
|
super.key,
|
||||||
});
|
});
|
||||||
|
|
||||||
static IAPProduct? productOf(BuildContext context, IAPProductType type) => null;
|
static IAPProduct? productOf(BuildContext context, IAPProductType type) {
|
||||||
|
final IAPProducts? result = InheritedModel.inheritFrom<IAPProducts>(context, aspect: type);
|
||||||
|
return result!._findProduct(type);
|
||||||
|
}
|
||||||
|
|
||||||
static bool isPurchased(BuildContext context, IAPProductType type) => false;
|
static bool isPurchased(BuildContext context, IAPProductType type) {
|
||||||
|
final IAPProducts? result = InheritedModel.inheritFrom<IAPProducts>(context, aspect: type);
|
||||||
|
return result!._findProduct(type)?.status == IAPProductStatus.purchased;
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
bool updateShouldNotify(IAPProducts oldWidget) => false;
|
bool updateShouldNotify(IAPProducts oldWidget) => false;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
bool updateShouldNotifyDependent(covariant IAPProducts oldWidget, Set<IAPProductType> dependencies) => false;
|
bool updateShouldNotifyDependent(IAPProducts oldWidget, Set<IAPProductType> dependencies) =>
|
||||||
|
false;
|
||||||
|
|
||||||
|
IAPProduct? _findProduct(IAPProductType type) {
|
||||||
|
try {
|
||||||
|
return products.firstWhere((element) => element.storeId == type.storeId);
|
||||||
|
} catch (_) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
10
integration_test/generate_screenshots.sh
Normal file
10
integration_test/generate_screenshots.sh
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
flutter drive \
|
||||||
|
--dart-define="cameraPreviewAspectRatio=240/320" \
|
||||||
|
--dart-define="cameraStubImage=assets/camera_stub_image.jpg" \
|
||||||
|
--driver=test_driver/screenshot_driver.dart \
|
||||||
|
--target=integration_test/generate_screenshots.dart \
|
||||||
|
--profile \
|
||||||
|
--flavor=dev \
|
||||||
|
--no-dds \
|
||||||
|
--endless-trace-buffer \
|
||||||
|
--purge-persistent-cache
|
|
@ -1,97 +1,48 @@
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
import 'package:flutter_localizations/flutter_localizations.dart';
|
import 'package:flutter_localizations/flutter_localizations.dart';
|
||||||
import 'package:lightmeter/data/caffeine_service.dart';
|
|
||||||
import 'package:lightmeter/data/haptics_service.dart';
|
|
||||||
import 'package:lightmeter/data/light_sensor_service.dart';
|
|
||||||
import 'package:lightmeter/data/models/supported_locale.dart';
|
import 'package:lightmeter/data/models/supported_locale.dart';
|
||||||
import 'package:lightmeter/data/permissions_service.dart';
|
|
||||||
import 'package:lightmeter/data/shared_prefs_service.dart';
|
|
||||||
import 'package:lightmeter/data/volume_events_service.dart';
|
|
||||||
import 'package:lightmeter/environment.dart';
|
|
||||||
import 'package:lightmeter/generated/l10n.dart';
|
import 'package:lightmeter/generated/l10n.dart';
|
||||||
import 'package:lightmeter/providers/services_provider.dart';
|
|
||||||
import 'package:lightmeter/providers/user_preferences_provider.dart';
|
import 'package:lightmeter/providers/user_preferences_provider.dart';
|
||||||
import 'package:lightmeter/screens/metering/flow_metering.dart';
|
import 'package:lightmeter/screens/metering/flow_metering.dart';
|
||||||
import 'package:lightmeter/screens/settings/flow_settings.dart';
|
import 'package:lightmeter/screens/settings/flow_settings.dart';
|
||||||
import 'package:m3_lightmeter_iap/m3_lightmeter_iap.dart';
|
|
||||||
import 'package:platform/platform.dart';
|
|
||||||
import 'package:shared_preferences/shared_preferences.dart';
|
|
||||||
|
|
||||||
class Application extends StatelessWidget {
|
class Application extends StatelessWidget {
|
||||||
final Environment env;
|
const Application({super.key});
|
||||||
|
|
||||||
const Application(this.env, {super.key});
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return FutureBuilder(
|
final theme = UserPreferencesProvider.themeOf(context);
|
||||||
future: Future.wait([
|
final systemIconsBrightness = ThemeData.estimateBrightnessForColor(theme.colorScheme.onSurface);
|
||||||
SharedPreferences.getInstance(),
|
return AnnotatedRegion(
|
||||||
const LightSensorService(LocalPlatform()).hasSensor(),
|
value: SystemUiOverlayStyle(
|
||||||
]),
|
statusBarColor: Colors.transparent,
|
||||||
builder: (_, snapshot) {
|
statusBarBrightness:
|
||||||
if (snapshot.data != null) {
|
systemIconsBrightness == Brightness.light ? Brightness.dark : Brightness.light,
|
||||||
return IAPProviders(
|
statusBarIconBrightness: systemIconsBrightness,
|
||||||
sharedPreferences: snapshot.data![0] as SharedPreferences,
|
systemNavigationBarColor: Colors.transparent,
|
||||||
child: ServicesProvider(
|
systemNavigationBarIconBrightness: systemIconsBrightness,
|
||||||
caffeineService: const CaffeineService(),
|
),
|
||||||
environment: env.copyWith(hasLightSensor: snapshot.data![1] as bool),
|
child: MaterialApp(
|
||||||
hapticsService: const HapticsService(),
|
theme: theme,
|
||||||
lightSensorService: const LightSensorService(LocalPlatform()),
|
locale: Locale(UserPreferencesProvider.localeOf(context).intlName),
|
||||||
permissionsService: const PermissionsService(),
|
localizationsDelegates: const [
|
||||||
userPreferencesService:
|
S.delegate,
|
||||||
UserPreferencesService(snapshot.data![0] as SharedPreferences),
|
GlobalMaterialLocalizations.delegate,
|
||||||
volumeEventsService: const VolumeEventsService(LocalPlatform()),
|
GlobalWidgetsLocalizations.delegate,
|
||||||
child: UserPreferencesProvider(
|
GlobalCupertinoLocalizations.delegate,
|
||||||
child: Builder(
|
],
|
||||||
builder: (context) {
|
supportedLocales: S.delegate.supportedLocales,
|
||||||
final theme = UserPreferencesProvider.themeOf(context);
|
builder: (context, child) => MediaQuery(
|
||||||
final systemIconsBrightness =
|
data: MediaQuery.of(context).copyWith(textScaleFactor: 1.0),
|
||||||
ThemeData.estimateBrightnessForColor(theme.colorScheme.onSurface);
|
child: child!,
|
||||||
return AnnotatedRegion(
|
),
|
||||||
value: SystemUiOverlayStyle(
|
initialRoute: "metering",
|
||||||
statusBarColor: Colors.transparent,
|
routes: {
|
||||||
statusBarBrightness: systemIconsBrightness == Brightness.light
|
"metering": (context) => const MeteringFlow(),
|
||||||
? Brightness.dark
|
"settings": (context) => const SettingsFlow(),
|
||||||
: Brightness.light,
|
},
|
||||||
statusBarIconBrightness: systemIconsBrightness,
|
),
|
||||||
systemNavigationBarColor: Colors.transparent,
|
|
||||||
systemNavigationBarIconBrightness: systemIconsBrightness,
|
|
||||||
),
|
|
||||||
child: MaterialApp(
|
|
||||||
theme: theme,
|
|
||||||
locale: Locale(UserPreferencesProvider.localeOf(context).intlName),
|
|
||||||
localizationsDelegates: const [
|
|
||||||
S.delegate,
|
|
||||||
GlobalMaterialLocalizations.delegate,
|
|
||||||
GlobalWidgetsLocalizations.delegate,
|
|
||||||
GlobalCupertinoLocalizations.delegate,
|
|
||||||
],
|
|
||||||
supportedLocales: S.delegate.supportedLocales,
|
|
||||||
builder: (context, child) => MediaQuery(
|
|
||||||
data: MediaQuery.of(context).copyWith(textScaleFactor: 1.0),
|
|
||||||
child: child!,
|
|
||||||
),
|
|
||||||
initialRoute: "metering",
|
|
||||||
routes: {
|
|
||||||
"metering": (context) => const MeteringFlow(),
|
|
||||||
"settings": (context) => const SettingsFlow(),
|
|
||||||
},
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
} else if (snapshot.error != null) {
|
|
||||||
return Center(child: Text(snapshot.error!.toString()));
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO(@vodemn): maybe user splashscreen instead
|
|
||||||
return const SizedBox();
|
|
||||||
},
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
55
lib/application_wrapper.dart
Normal file
55
lib/application_wrapper.dart
Normal file
|
@ -0,0 +1,55 @@
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:lightmeter/data/caffeine_service.dart';
|
||||||
|
import 'package:lightmeter/data/haptics_service.dart';
|
||||||
|
import 'package:lightmeter/data/light_sensor_service.dart';
|
||||||
|
import 'package:lightmeter/data/permissions_service.dart';
|
||||||
|
import 'package:lightmeter/data/shared_prefs_service.dart';
|
||||||
|
import 'package:lightmeter/data/volume_events_service.dart';
|
||||||
|
import 'package:lightmeter/environment.dart';
|
||||||
|
import 'package:lightmeter/providers/services_provider.dart';
|
||||||
|
import 'package:lightmeter/providers/user_preferences_provider.dart';
|
||||||
|
import 'package:m3_lightmeter_iap/m3_lightmeter_iap.dart';
|
||||||
|
import 'package:platform/platform.dart';
|
||||||
|
import 'package:shared_preferences/shared_preferences.dart';
|
||||||
|
|
||||||
|
class ApplicationWrapper extends StatelessWidget {
|
||||||
|
final Environment env;
|
||||||
|
final Widget child;
|
||||||
|
|
||||||
|
const ApplicationWrapper(this.env, {required this.child, super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return FutureBuilder(
|
||||||
|
future: Future.wait([
|
||||||
|
SharedPreferences.getInstance(),
|
||||||
|
const LightSensorService(LocalPlatform()).hasSensor(),
|
||||||
|
]),
|
||||||
|
builder: (_, snapshot) {
|
||||||
|
if (snapshot.data != null) {
|
||||||
|
return IAPProviders(
|
||||||
|
sharedPreferences: snapshot.data![0] as SharedPreferences,
|
||||||
|
child: ServicesProvider(
|
||||||
|
caffeineService: const CaffeineService(),
|
||||||
|
environment: env.copyWith(hasLightSensor: snapshot.data![1] as bool),
|
||||||
|
hapticsService: const HapticsService(),
|
||||||
|
lightSensorService: const LightSensorService(LocalPlatform()),
|
||||||
|
permissionsService: const PermissionsService(),
|
||||||
|
userPreferencesService:
|
||||||
|
UserPreferencesService(snapshot.data![0] as SharedPreferences),
|
||||||
|
volumeEventsService: const VolumeEventsService(LocalPlatform()),
|
||||||
|
child: UserPreferencesProvider(
|
||||||
|
child: child,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
} else if (snapshot.error != null) {
|
||||||
|
return Center(child: Text(snapshot.error!.toString()));
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO(@vodemn): maybe user splashscreen instead
|
||||||
|
return const SizedBox();
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -43,7 +43,7 @@
|
||||||
"film": "胶片",
|
"film": "胶片",
|
||||||
"filmPush": "胶片 (push)",
|
"filmPush": "胶片 (push)",
|
||||||
"filmPull": "胶片 (pull)",
|
"filmPull": "胶片 (pull)",
|
||||||
"filmReciprocityHint": "Applies correction for shutter speeds grater than 1 second",
|
"filmReciprocityHint": "对快门速度超过 1 秒的情况进行修正",
|
||||||
"equipmentProfileName": "设备配置名称",
|
"equipmentProfileName": "设备配置名称",
|
||||||
"equipmentProfileNameHint": "Praktica MTL5B",
|
"equipmentProfileNameHint": "Praktica MTL5B",
|
||||||
"equipmentProfileAllValues": "全部",
|
"equipmentProfileAllValues": "全部",
|
||||||
|
@ -54,12 +54,12 @@
|
||||||
"shutterSpeedValues": "快门速度",
|
"shutterSpeedValues": "快门速度",
|
||||||
"shutterSpeedValuesFilterDescription": "选择要显示的快门速度范围。这通常由您使用的相机机身决定。",
|
"shutterSpeedValuesFilterDescription": "选择要显示的快门速度范围。这通常由您使用的相机机身决定。",
|
||||||
"isoValues": "ISO",
|
"isoValues": "ISO",
|
||||||
"isoValuesFilterDescription": "选择要显示的 ISO。这些值可能是您最常用的值,也可能是相机支持的值。",
|
"isoValuesFilterDescription": "选择要显示的 ISO 。这些值可能是您最常用的值,也可能是相机支持的值。",
|
||||||
"equipmentProfile": "设备配置",
|
"equipmentProfile": "设备配置",
|
||||||
"equipmentProfiles": "设备配置",
|
"equipmentProfiles": "设备配置",
|
||||||
"tapToAdd": "點擊添加",
|
"tapToAdd": "點擊添加",
|
||||||
"filmsInUse": "Films in use",
|
"filmsInUse": "使用的胶片",
|
||||||
"filmsInUseDescription": "Select films which you use.",
|
"filmsInUseDescription": "选择你使用的胶片",
|
||||||
"general": "通用",
|
"general": "通用",
|
||||||
"keepScreenOn": "保持屏幕常亮",
|
"keepScreenOn": "保持屏幕常亮",
|
||||||
"haptics": "震动",
|
"haptics": "震动",
|
||||||
|
@ -92,20 +92,20 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"buyLightmeterPro": "Buy Lightmeter Pro",
|
"buyLightmeterPro": "购买 Lightmeter Pro",
|
||||||
"lightmeterPro": "Lightmeter Pro",
|
"lightmeterPro": "Lightmeter Pro",
|
||||||
"lightmeterProDescription": "Unlocks extra features, such as equipment profiles containing filters for aperture, shutter speed, and more; and a list of films with compensation for what's known as reciprocity failure.\n\nThe source code of Lightmeter is available on GitHub. You are welcome to compile it yourself. However, if you want to support the development and receive new features and updates, consider purchasing Lightmeter Pro.",
|
"lightmeterProDescription": "购买以解锁额外功能。例如包含光圈、快门速度等参数的配置文件;以及一个胶卷预设列表来提供倒易率失效时的曝光补偿。\n\n您可以在 GitHub 上获取 Lightmeter 的源代码,欢迎自行编译。不过,如果您想支持开发并获得新功能和更新,请考虑购买 Lightmeter Pro。",
|
||||||
"buy": "Buy",
|
"buy": "购买",
|
||||||
"tooltipAdd": "Add",
|
"tooltipAdd": "添加",
|
||||||
"tooltipClose": "Close",
|
"tooltipClose": "关闭",
|
||||||
"tooltipExpand": "Expand",
|
"tooltipExpand": "展开",
|
||||||
"tooltipCollapse": "Collapse",
|
"tooltipCollapse": "崩溃",
|
||||||
"tooltipCopy": "Copy",
|
"tooltipCopy": "复制",
|
||||||
"tooltipDelete": "Delete",
|
"tooltipDelete": "删除",
|
||||||
"tooltipSelectAll": "Select all",
|
"tooltipSelectAll": "全选",
|
||||||
"tooltipDesecelectAll": "Deselect all",
|
"tooltipDesecelectAll": "取消全选",
|
||||||
"resetToZero": "Reset to zero",
|
"resetToZero": "重置为零",
|
||||||
"tooltipUseLightSensor": "Use lightsensor",
|
"tooltipUseLightSensor": "使用光线传感器",
|
||||||
"tooltipUseCamera": "Use camera",
|
"tooltipUseCamera": "使用摄像头",
|
||||||
"tooltipOpenSettings": "Open settings"
|
"tooltipOpenSettings": "打开设置"
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/rendering.dart';
|
import 'package:flutter/rendering.dart';
|
||||||
import 'package:lightmeter/application.dart';
|
import 'package:lightmeter/application.dart';
|
||||||
|
import 'package:lightmeter/application_wrapper.dart';
|
||||||
import 'package:lightmeter/environment.dart';
|
import 'package:lightmeter/environment.dart';
|
||||||
|
|
||||||
Future<void> main() async {
|
Future<void> main() async {
|
||||||
WidgetsFlutterBinding.ensureInitialized();
|
WidgetsFlutterBinding.ensureInitialized();
|
||||||
//debugRepaintRainbowEnabled = true;
|
runApp(const ApplicationWrapper(Environment.dev(), child: Application()));
|
||||||
runApp(const Application(Environment.dev()));
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,10 +1,11 @@
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:lightmeter/application.dart';
|
import 'package:lightmeter/application.dart';
|
||||||
|
import 'package:lightmeter/application_wrapper.dart';
|
||||||
import 'package:lightmeter/environment.dart';
|
import 'package:lightmeter/environment.dart';
|
||||||
import 'package:lightmeter/firebase.dart';
|
import 'package:lightmeter/firebase.dart';
|
||||||
|
|
||||||
Future<void> main() async {
|
Future<void> main() async {
|
||||||
WidgetsFlutterBinding.ensureInitialized();
|
WidgetsFlutterBinding.ensureInitialized();
|
||||||
await initializeFirebase(handleErrors: true);
|
await initializeFirebase(handleErrors: true);
|
||||||
runApp(const Application(Environment.prod()));
|
runApp(const ApplicationWrapper(Environment.prod(), child: Application()));
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,10 +1,11 @@
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:lightmeter/application.dart';
|
import 'package:lightmeter/application.dart';
|
||||||
|
import 'package:lightmeter/application_wrapper.dart';
|
||||||
import 'package:lightmeter/environment.dart';
|
import 'package:lightmeter/environment.dart';
|
||||||
import 'package:lightmeter/firebase.dart';
|
import 'package:lightmeter/firebase.dart';
|
||||||
|
|
||||||
Future<void> main() async {
|
Future<void> main() async {
|
||||||
WidgetsFlutterBinding.ensureInitialized();
|
WidgetsFlutterBinding.ensureInitialized();
|
||||||
await initializeFirebase(handleErrors: false);
|
await initializeFirebase(handleErrors: false);
|
||||||
runApp(const Application(Environment.prod()));
|
runApp(const ApplicationWrapper(Environment.prod(), child: Application()));
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,4 +5,6 @@ class PlatformConfig {
|
||||||
final rational = const String.fromEnvironment('cameraPreviewAspectRatio').split('/');
|
final rational = const String.fromEnvironment('cameraPreviewAspectRatio').split('/');
|
||||||
return int.parse(rational[0]) / int.parse(rational[1]);
|
return int.parse(rational[0]) / int.parse(rational[1]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static String get cameraStubImage => const String.fromEnvironment('cameraStubImage');
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,13 +2,14 @@ import 'dart:async';
|
||||||
import 'dart:developer';
|
import 'dart:developer';
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
import 'dart:math' as math;
|
import 'dart:math' as math;
|
||||||
import 'dart:typed_data';
|
|
||||||
|
|
||||||
import 'package:camera/camera.dart';
|
import 'package:camera/camera.dart';
|
||||||
import 'package:exif/exif.dart';
|
import 'package:exif/exif.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter/services.dart';
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
import 'package:lightmeter/interactors/metering_interactor.dart';
|
import 'package:lightmeter/interactors/metering_interactor.dart';
|
||||||
|
import 'package:lightmeter/platform_config.dart';
|
||||||
import 'package:lightmeter/screens/metering/communication/bloc_communication_metering.dart';
|
import 'package:lightmeter/screens/metering/communication/bloc_communication_metering.dart';
|
||||||
import 'package:lightmeter/screens/metering/communication/event_communication_metering.dart'
|
import 'package:lightmeter/screens/metering/communication/event_communication_metering.dart'
|
||||||
as communication_event;
|
as communication_event;
|
||||||
|
@ -32,7 +33,7 @@ class CameraContainerBloc extends EvSourceBlocBase<CameraContainerEvent, CameraC
|
||||||
|
|
||||||
static const _exposureMaxRange = RangeValues(-4, 4);
|
static const _exposureMaxRange = RangeValues(-4, 4);
|
||||||
RangeValues? _exposureOffsetRange;
|
RangeValues? _exposureOffsetRange;
|
||||||
double _exposureStep = 0.0;
|
double _exposureStep = 0.1;
|
||||||
double _currentExposureOffset = 0.0;
|
double _currentExposureOffset = 0.0;
|
||||||
|
|
||||||
double? _ev100 = 0.0;
|
double? _ev100 = 0.0;
|
||||||
|
@ -199,21 +200,28 @@ class CameraContainerBloc extends EvSourceBlocBase<CameraContainerEvent, CameraC
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool get _canTakePhoto => !(_cameraController == null ||
|
bool get _canTakePhoto =>
|
||||||
!_cameraController!.value.isInitialized ||
|
PlatformConfig.cameraStubImage.isNotEmpty ||
|
||||||
_cameraController!.value.isTakingPicture);
|
!(_cameraController == null ||
|
||||||
|
!_cameraController!.value.isInitialized ||
|
||||||
|
_cameraController!.value.isTakingPicture);
|
||||||
|
|
||||||
Future<double?> _takePhoto() async {
|
Future<double?> _takePhoto() async {
|
||||||
try {
|
try {
|
||||||
// https://github.com/flutter/flutter/issues/84957#issuecomment-1661155095
|
// https://github.com/flutter/flutter/issues/84957#issuecomment-1661155095
|
||||||
await _cameraController!.setFocusMode(FocusMode.locked);
|
|
||||||
await _cameraController!.setExposureMode(ExposureMode.locked);
|
|
||||||
final file = await _cameraController!.takePicture();
|
|
||||||
await _cameraController!.setFocusMode(FocusMode.auto);
|
|
||||||
await _cameraController!.setExposureMode(ExposureMode.auto);
|
|
||||||
|
|
||||||
final Uint8List bytes = await file.readAsBytes();
|
late final Uint8List bytes;
|
||||||
Directory(file.path).deleteSync(recursive: true);
|
if (PlatformConfig.cameraStubImage.isNotEmpty) {
|
||||||
|
bytes = (await rootBundle.load(PlatformConfig.cameraStubImage)).buffer.asUint8List();
|
||||||
|
} else {
|
||||||
|
await _cameraController!.setFocusMode(FocusMode.locked);
|
||||||
|
await _cameraController!.setExposureMode(ExposureMode.locked);
|
||||||
|
final file = await _cameraController!.takePicture();
|
||||||
|
await _cameraController!.setFocusMode(FocusMode.auto);
|
||||||
|
await _cameraController!.setExposureMode(ExposureMode.auto);
|
||||||
|
bytes = await file.readAsBytes();
|
||||||
|
Directory(file.path).deleteSync(recursive: true);
|
||||||
|
}
|
||||||
|
|
||||||
final tags = await readExifFromBytes(bytes);
|
final tags = await readExifFromBytes(bytes);
|
||||||
final iso = double.tryParse("${tags["EXIF ISOSpeedRatings"]}");
|
final iso = double.tryParse("${tags["EXIF ISOSpeedRatings"]}");
|
||||||
|
|
|
@ -69,6 +69,9 @@ class _CameraPreviewBuilderState extends State<_CameraPreviewBuilder> {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
if (PlatformConfig.cameraStubImage.isNotEmpty) {
|
||||||
|
return Image.asset(PlatformConfig.cameraStubImage);
|
||||||
|
}
|
||||||
return ValueListenableBuilder<bool>(
|
return ValueListenableBuilder<bool>(
|
||||||
valueListenable: _initializedNotifier,
|
valueListenable: _initializedNotifier,
|
||||||
builder: (context, value, child) => value
|
builder: (context, value, child) => value
|
||||||
|
|
|
@ -34,6 +34,26 @@ class _DialogFilterState<T> extends State<DialogFilter<T>> {
|
||||||
bool get _hasAnySelected => checkboxValues.contains(true);
|
bool get _hasAnySelected => checkboxValues.contains(true);
|
||||||
bool get _hasAnyUnselected => checkboxValues.contains(false);
|
bool get _hasAnyUnselected => checkboxValues.contains(false);
|
||||||
|
|
||||||
|
late final ScrollController _scrollController;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
int i = 0;
|
||||||
|
for (; i < checkboxValues.length; i++) {
|
||||||
|
if (checkboxValues[i]) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_scrollController = ScrollController(initialScrollOffset: Dimens.grid56 * i);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
_scrollController.dispose();
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return AlertDialog(
|
return AlertDialog(
|
||||||
|
@ -50,6 +70,7 @@ class _DialogFilterState<T> extends State<DialogFilter<T>> {
|
||||||
const Divider(),
|
const Divider(),
|
||||||
Expanded(
|
Expanded(
|
||||||
child: SingleChildScrollView(
|
child: SingleChildScrollView(
|
||||||
|
controller: _scrollController,
|
||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
|
|
@ -58,6 +58,8 @@ dev_dependencies:
|
||||||
|
|
||||||
flutter:
|
flutter:
|
||||||
uses-material-design: true
|
uses-material-design: true
|
||||||
|
assets:
|
||||||
|
- assets/camera_stub_image.jpg
|
||||||
|
|
||||||
flutter_intl:
|
flutter_intl:
|
||||||
enabled: true
|
enabled: true
|
||||||
|
|
Loading…
Reference in a new issue