mirror of
https://github.com/vodemn/m3_lightmeter.git
synced 2024-11-25 08:50:40 +00:00
Compare commits
15 commits
ab76271387
...
354cc07e6e
Author | SHA1 | Date | |
---|---|---|---|
|
354cc07e6e | ||
|
5dc2f9d18d | ||
|
2e0811a357 | ||
|
40c670ad30 | ||
|
119e079554 | ||
|
b02b50bac3 | ||
|
dd5f551fd2 | ||
|
bb9b023fa7 | ||
|
dbf1f09eb6 | ||
|
99eebff9a4 | ||
|
88ec733596 | ||
|
154fd9c56d | ||
|
f5135d00eb | ||
|
8595aae00f | ||
|
cb675e43e1 |
30 changed files with 554 additions and 196 deletions
1
.github/workflows/cd_dev.yml
vendored
1
.github/workflows/cd_dev.yml
vendored
|
@ -68,6 +68,7 @@ jobs:
|
||||||
uses: subosito/flutter-action@v2
|
uses: subosito/flutter-action@v2
|
||||||
with:
|
with:
|
||||||
channel: "stable"
|
channel: "stable"
|
||||||
|
flutter-version: '3.10.0'
|
||||||
|
|
||||||
- name: Prepare flutter project
|
- name: Prepare flutter project
|
||||||
run: |
|
run: |
|
||||||
|
|
1
.github/workflows/cd_prod.yml
vendored
1
.github/workflows/cd_prod.yml
vendored
|
@ -70,6 +70,7 @@ jobs:
|
||||||
uses: subosito/flutter-action@v2
|
uses: subosito/flutter-action@v2
|
||||||
with:
|
with:
|
||||||
channel: "stable"
|
channel: "stable"
|
||||||
|
flutter-version: '3.10.0'
|
||||||
|
|
||||||
- name: Prepare flutter project
|
- name: Prepare flutter project
|
||||||
run: |
|
run: |
|
||||||
|
|
30
.github/workflows/ci.yml
vendored
30
.github/workflows/ci.yml
vendored
|
@ -12,14 +12,18 @@ on:
|
||||||
branches: ["main"]
|
branches: ["main"]
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
analyze_and_test:
|
||||||
|
name: Analyze & test
|
||||||
runs-on: macos-11
|
runs-on: macos-11
|
||||||
timeout-minutes: 10
|
timeout-minutes: 10
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: webfactory/ssh-agent@v0.8.0
|
- uses: shaunco/ssh-agent@git-repo-mapping
|
||||||
with:
|
with:
|
||||||
ssh-private-key: ${{ secrets.M3_LIGHTMETER_IAP_KEY }}
|
ssh-private-key: |
|
||||||
|
${{ secrets.M3_LIGHTMETER_IAP_KEY }}
|
||||||
|
repo-mappings: |
|
||||||
|
github.com/vodemn/m3_lightmeter_iap
|
||||||
|
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v3
|
||||||
with:
|
with:
|
||||||
|
@ -28,23 +32,13 @@ jobs:
|
||||||
- uses: subosito/flutter-action@v2
|
- uses: subosito/flutter-action@v2
|
||||||
with:
|
with:
|
||||||
channel: "stable"
|
channel: "stable"
|
||||||
|
flutter-version: '3.10.0'
|
||||||
|
|
||||||
- name: Check flutter version
|
- name: Prepare flutter project
|
||||||
run: flutter --version
|
|
||||||
|
|
||||||
- name: Install dependencies
|
|
||||||
run: flutter pub get
|
|
||||||
|
|
||||||
- name: Generate intl
|
|
||||||
run: flutter pub run intl_utils:generate
|
|
||||||
|
|
||||||
- name: Restore firebase_options.dart
|
|
||||||
env:
|
|
||||||
FIREBASE_OPTIONS: ${{ secrets.FIREBASE_OPTIONS }}
|
|
||||||
run: |
|
run: |
|
||||||
FIREBASE_OPTIONS_PATH=$RUNNER_TEMP/firebase_options.dart
|
flutter --version
|
||||||
echo -n "$FIREBASE_OPTIONS" | base64 --decode --output $FIREBASE_OPTIONS_PATH
|
flutter pub get
|
||||||
cp $FIREBASE_OPTIONS_PATH ./lib
|
flutter pub run intl_utils:generate
|
||||||
|
|
||||||
- name: Analyze project source
|
- name: Analyze project source
|
||||||
run: flutter analyze lib --fatal-infos
|
run: flutter analyze lib --fatal-infos
|
||||||
|
|
47
README.md
47
README.md
|
@ -5,7 +5,7 @@
|
||||||
- [Table of contents](#table-of-contents)
|
- [Table of contents](#table-of-contents)
|
||||||
- [Backstory](#backstory)
|
- [Backstory](#backstory)
|
||||||
- [Screenshots](#screenshots)
|
- [Screenshots](#screenshots)
|
||||||
- [Build](#build)
|
- [Development](#development)
|
||||||
- [Contribution](#contribution)
|
- [Contribution](#contribution)
|
||||||
- [iOS Limitations](#ios-limitations)
|
- [iOS Limitations](#ios-limitations)
|
||||||
|
|
||||||
|
@ -27,22 +27,61 @@ Without further delay behold my new Lightmeter app inspired by Material You (a.k
|
||||||
<img src="https://lh3.googleusercontent.com/15g_SPV8knDLFbz1_-wGNJFsJeyVWZ_y--TGHpk75MaaIdMDyTXY2_TL-Aw8bpOhpw" width="18.8%" />
|
<img src="https://lh3.googleusercontent.com/15g_SPV8knDLFbz1_-wGNJFsJeyVWZ_y--TGHpk75MaaIdMDyTXY2_TL-Aw8bpOhpw" width="18.8%" />
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
# Build
|
# Development
|
||||||
|
|
||||||
As part of this project is private, you will be able to run this app from the _main_dev.dart_ file (i.e. --flavor dev). Also to avoid fatal errors the _main_prod.dart_ file is excluded from analysis.
|
### 1. Install Flutter
|
||||||
|
|
||||||
|
To build this app you need to install Flutter 3.10.0 stable. [How to install](https://docs.flutter.dev/get-started/install).
|
||||||
|
|
||||||
|
### 2. (Optional) Install Firebase
|
||||||
|
|
||||||
|
Out of the box Firebase Crashlytics won't work. If you want to add Crashlytics to your local build please follow [this guide](https://firebase.google.com/docs/flutter/setup).
|
||||||
|
|
||||||
|
### 3. Get packages
|
||||||
|
|
||||||
|
As part of the app's functionallity is in the private repo, you have to replace these lines in _pubspec.yaml_:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
m3_lightmeter_iap:
|
||||||
|
git:
|
||||||
|
url: "https://github.com/vodemn/m3_lightmeter_iap"
|
||||||
|
ref: main
|
||||||
|
```
|
||||||
|
with these:
|
||||||
|
```yaml
|
||||||
|
m3_lightmeter_iap:
|
||||||
|
path: iap
|
||||||
|
```
|
||||||
|
and run `flutter pub get` from the _iap/_ folder.
|
||||||
|
|
||||||
|
Then you can fetch all the neccessary dependencies and generate translation files by running the following commands:
|
||||||
|
```console
|
||||||
|
flutter pub get
|
||||||
|
flutter pub run intl_utils:generate
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4. Build
|
||||||
|
|
||||||
|
You can build an apk by running the following command from the root of the repository:
|
||||||
|
```console
|
||||||
|
flutter build apk --release --flavor $FLAVOR --dart-define cameraPreviewAspectRatio=2/3 -t lib/main_$FLAVOR.dart
|
||||||
|
```
|
||||||
|
Just replace `$FLAVOR` with `dev` or `prod`.
|
||||||
|
|
||||||
# Contribution
|
# Contribution
|
||||||
|
|
||||||
To report a bug or suggest a new feature open a new [issue](https://github.com/vodemn/m3_lightmeter/issues).
|
To report a bug or suggest a new feature open a new [issue](https://github.com/vodemn/m3_lightmeter/issues).
|
||||||
|
|
||||||
In case you want to help develop this project you need to follow this [style guide](doc/style_guide.md).
|
In case you want to help develop this project feel free to open a Pull Request, but you need to follow this [style guide](doc/style_guide.md).
|
||||||
|
|
||||||
# iOS Limitations
|
# iOS Limitations
|
||||||
|
|
||||||
A list of features, that Android version of the app has and that iOS does not.
|
A list of features, that Android version of the app has and that iOS does not.
|
||||||
|
|
||||||
## Incident light metering
|
## Incident light metering
|
||||||
|
|
||||||
Apple does not provide API for reading Lux stream form the ambient light sensor. Lux can be calculated based on front camera image stream, but this would be a reflected light. So there is no way incident light metering can be implemented on iOS.
|
Apple does not provide API for reading Lux stream form the ambient light sensor. Lux can be calculated based on front camera image stream, but this would be a reflected light. So there is no way incident light metering can be implemented on iOS.
|
||||||
|
|
||||||
## Volume buttons action
|
## Volume buttons action
|
||||||
|
|
||||||
This can be [implemented](https://stackoverflow.com/questions/70161271/ios-override-hardware-volume-buttons-same-as-zello) but the app will be rejected due to [2.5.9](https://developer.apple.com/app-store/review/guidelines/#software-requirements)
|
This can be [implemented](https://stackoverflow.com/questions/70161271/ios-override-hardware-volume-buttons-same-as-zello) but the app will be rejected due to [2.5.9](https://developer.apple.com/app-store/review/guidelines/#software-requirements)
|
36
iap/.gitignore
vendored
Normal file
36
iap/.gitignore
vendored
Normal file
|
@ -0,0 +1,36 @@
|
||||||
|
# Miscellaneous
|
||||||
|
*.class
|
||||||
|
*.log
|
||||||
|
*.pyc
|
||||||
|
*.swp
|
||||||
|
.DS_Store
|
||||||
|
.atom/
|
||||||
|
.buildlog/
|
||||||
|
.history
|
||||||
|
.svn/
|
||||||
|
migrate_working_dir/
|
||||||
|
|
||||||
|
# IntelliJ related
|
||||||
|
*.iml
|
||||||
|
*.ipr
|
||||||
|
*.iws
|
||||||
|
.idea/
|
||||||
|
|
||||||
|
# The .vscode folder contains launch configuration and tasks you configure in
|
||||||
|
# VS Code which you may wish to be included in version control, so this line
|
||||||
|
# is commented out by default.
|
||||||
|
#.vscode/
|
||||||
|
|
||||||
|
# Flutter/Dart/Pub related
|
||||||
|
# Libraries should not include pubspec.lock, per https://dart.dev/guides/libraries/private-files#pubspeclock.
|
||||||
|
/pubspec.lock
|
||||||
|
**/doc/api/
|
||||||
|
.dart_tool/
|
||||||
|
.packages
|
||||||
|
build/
|
||||||
|
|
||||||
|
.fvm/
|
||||||
|
*.properties
|
||||||
|
ios/Flutter/
|
||||||
|
.flutter-plugins
|
||||||
|
.flutter-plugins-dependencies
|
10
iap/.metadata
Normal file
10
iap/.metadata
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
# This file tracks properties of this Flutter project.
|
||||||
|
# Used by Flutter tool to assess capabilities and perform upgrades etc.
|
||||||
|
#
|
||||||
|
# This file should be version controlled and should not be manually edited.
|
||||||
|
|
||||||
|
version:
|
||||||
|
revision: 9944297138845a94256f1cf37beb88ff9a8e811a
|
||||||
|
channel: stable
|
||||||
|
|
||||||
|
project_type: package
|
1
iap/LICENSE
Normal file
1
iap/LICENSE
Normal file
|
@ -0,0 +1 @@
|
||||||
|
TODO: Add your license here.
|
4
iap/analysis_options.yaml
Normal file
4
iap/analysis_options.yaml
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
include: package:flutter_lints/flutter.yaml
|
||||||
|
|
||||||
|
# Additional information about this file can be found at
|
||||||
|
# https://dart.dev/guides/language/analysis-options
|
30
iap/lib/m3_lightmeter_iap.dart
Normal file
30
iap/lib/m3_lightmeter_iap.dart
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
library m3_lightmeter_iap;
|
||||||
|
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:m3_lightmeter_iap/src/providers/equipment_profile_provider.dart';
|
||||||
|
import 'package:m3_lightmeter_iap/src/providers/iap_products_provider.dart';
|
||||||
|
|
||||||
|
export 'src/data/models/iap_product.dart';
|
||||||
|
|
||||||
|
export 'src/providers/equipment_profile_provider.dart' hide EquipmentProfilesAspect;
|
||||||
|
export 'src/providers/iap_products_provider.dart';
|
||||||
|
|
||||||
|
class IAPProviders extends StatelessWidget {
|
||||||
|
final Object sharedPreferences;
|
||||||
|
final Widget child;
|
||||||
|
|
||||||
|
const IAPProviders({
|
||||||
|
required this.sharedPreferences,
|
||||||
|
required this.child,
|
||||||
|
super.key,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return IAPProductsProvider(
|
||||||
|
child: EquipmentProfileProvider(
|
||||||
|
child: child,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
5
iap/lib/src/data/models/iap_product.dart
Normal file
5
iap/lib/src/data/models/iap_product.dart
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
enum IAPProductType { paidFeatures }
|
||||||
|
|
||||||
|
class IAPProduct {
|
||||||
|
IAPProduct();
|
||||||
|
}
|
79
iap/lib/src/providers/equipment_profile_provider.dart
Normal file
79
iap/lib/src/providers/equipment_profile_provider.dart
Normal file
|
@ -0,0 +1,79 @@
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:m3_lightmeter_resources/m3_lightmeter_resources.dart';
|
||||||
|
|
||||||
|
class EquipmentProfileProvider extends StatefulWidget {
|
||||||
|
final Widget child;
|
||||||
|
|
||||||
|
const EquipmentProfileProvider({required this.child, super.key});
|
||||||
|
|
||||||
|
static EquipmentProfileProviderState of(BuildContext context) {
|
||||||
|
return context.findAncestorStateOfType<EquipmentProfileProviderState>()!;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<EquipmentProfileProvider> createState() => EquipmentProfileProviderState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class EquipmentProfileProviderState extends State<EquipmentProfileProvider> {
|
||||||
|
static const EquipmentProfile _defaultProfile = EquipmentProfile(
|
||||||
|
id: '',
|
||||||
|
name: '',
|
||||||
|
apertureValues: ApertureValue.values,
|
||||||
|
ndValues: NdValue.values,
|
||||||
|
shutterSpeedValues: ShutterSpeedValue.values,
|
||||||
|
isoValues: IsoValue.values,
|
||||||
|
);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return EquipmentProfiles(
|
||||||
|
profiles: const [_defaultProfile],
|
||||||
|
selected: _defaultProfile,
|
||||||
|
child: widget.child,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
void setProfile(EquipmentProfile data) {}
|
||||||
|
|
||||||
|
void addProfile(String name) {}
|
||||||
|
|
||||||
|
void updateProdile(EquipmentProfile data) {}
|
||||||
|
|
||||||
|
void deleteProfile(EquipmentProfile data) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
enum EquipmentProfilesAspect { list, selected }
|
||||||
|
|
||||||
|
class EquipmentProfiles extends InheritedModel<EquipmentProfilesAspect> {
|
||||||
|
const EquipmentProfiles({
|
||||||
|
super.key,
|
||||||
|
required this.profiles,
|
||||||
|
required this.selected,
|
||||||
|
required super.child,
|
||||||
|
});
|
||||||
|
|
||||||
|
final List<EquipmentProfile> profiles;
|
||||||
|
final EquipmentProfile selected;
|
||||||
|
|
||||||
|
static List<EquipmentProfile> of(BuildContext context) {
|
||||||
|
return InheritedModel.inheritFrom<EquipmentProfiles>(
|
||||||
|
context,
|
||||||
|
aspect: EquipmentProfilesAspect.list,
|
||||||
|
)!
|
||||||
|
.profiles;
|
||||||
|
}
|
||||||
|
|
||||||
|
static EquipmentProfile selectedOf(BuildContext context) {
|
||||||
|
return InheritedModel.inheritFrom<EquipmentProfiles>(
|
||||||
|
context,
|
||||||
|
aspect: EquipmentProfilesAspect.selected,
|
||||||
|
)!
|
||||||
|
.selected;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool updateShouldNotify(EquipmentProfiles oldWidget) => false;
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool updateShouldNotifyDependent(EquipmentProfiles oldWidget, Set<EquipmentProfilesAspect> dependencies) => false;
|
||||||
|
}
|
47
iap/lib/src/providers/iap_products_provider.dart
Normal file
47
iap/lib/src/providers/iap_products_provider.dart
Normal file
|
@ -0,0 +1,47 @@
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:m3_lightmeter_iap/src/data/models/iap_product.dart';
|
||||||
|
|
||||||
|
class IAPProductsProvider extends StatefulWidget {
|
||||||
|
final Widget child;
|
||||||
|
|
||||||
|
const IAPProductsProvider({required this.child, super.key});
|
||||||
|
|
||||||
|
static IAPProductsProviderState of(BuildContext context) {
|
||||||
|
return context.findAncestorStateOfType<IAPProductsProviderState>()!;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<IAPProductsProvider> createState() => IAPProductsProviderState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class IAPProductsProviderState extends State<IAPProductsProvider> {
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return IAPProducts(
|
||||||
|
products: const [],
|
||||||
|
child: widget.child,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> buy(IAPProductType type) async {}
|
||||||
|
}
|
||||||
|
|
||||||
|
class IAPProducts extends InheritedModel<IAPProductType> {
|
||||||
|
final List<IAPProduct> products;
|
||||||
|
|
||||||
|
const IAPProducts({
|
||||||
|
required this.products,
|
||||||
|
required super.child,
|
||||||
|
super.key,
|
||||||
|
});
|
||||||
|
|
||||||
|
static IAPProduct? of(BuildContext context, IAPProductType type) => null;
|
||||||
|
|
||||||
|
static bool isPurchased(BuildContext context, IAPProductType type) => false;
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool updateShouldNotify(IAPProducts oldWidget) => false;
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool updateShouldNotifyDependent(covariant IAPProducts oldWidget, Set<IAPProductType> dependencies) => false;
|
||||||
|
}
|
25
iap/pubspec.yaml
Normal file
25
iap/pubspec.yaml
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
name: m3_lightmeter_iap
|
||||||
|
description: IAP stubs for the M3 Lightmeter app.
|
||||||
|
version: 0.2.0
|
||||||
|
publish_to: 'none'
|
||||||
|
|
||||||
|
environment:
|
||||||
|
sdk: '>=2.19.2 <3.0.0'
|
||||||
|
flutter: ">=1.17.0"
|
||||||
|
|
||||||
|
dependencies:
|
||||||
|
flutter:
|
||||||
|
sdk: flutter
|
||||||
|
m3_lightmeter_resources:
|
||||||
|
git:
|
||||||
|
url: "https://github.com/vodemn/m3_lightmeter_resources"
|
||||||
|
ref: main
|
||||||
|
shared_preferences: 2.2.0
|
||||||
|
|
||||||
|
dev_dependencies:
|
||||||
|
flutter_test:
|
||||||
|
sdk: flutter
|
||||||
|
flutter_lints: ^2.0.0
|
||||||
|
|
||||||
|
flutter:
|
||||||
|
uses-material-design: true
|
|
@ -1,4 +1,4 @@
|
||||||
enum SupportedLocale { en, fr, ru }
|
enum SupportedLocale { en, fr, ru, zh }
|
||||||
|
|
||||||
extension SupportedLocaleExtension on SupportedLocale {
|
extension SupportedLocaleExtension on SupportedLocale {
|
||||||
String get intlName => toString().replaceAll("SupportedLocale.", "");
|
String get intlName => toString().replaceAll("SupportedLocale.", "");
|
||||||
|
@ -11,6 +11,8 @@ extension SupportedLocaleExtension on SupportedLocale {
|
||||||
return 'Français';
|
return 'Français';
|
||||||
case SupportedLocale.ru:
|
case SupportedLocale.ru:
|
||||||
return 'Русский';
|
return 'Русский';
|
||||||
|
case SupportedLocale.zh:
|
||||||
|
return '简体中文';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,7 +7,6 @@ import 'package:lightmeter/data/models/metering_screen_layout_config.dart';
|
||||||
import 'package:lightmeter/data/models/supported_locale.dart';
|
import 'package:lightmeter/data/models/supported_locale.dart';
|
||||||
import 'package:lightmeter/data/models/theme_type.dart';
|
import 'package:lightmeter/data/models/theme_type.dart';
|
||||||
import 'package:lightmeter/data/models/volume_action.dart';
|
import 'package:lightmeter/data/models/volume_action.dart';
|
||||||
import 'package:m3_lightmeter_iap/m3_lightmeter_iap.dart';
|
|
||||||
import 'package:m3_lightmeter_resources/m3_lightmeter_resources.dart';
|
import 'package:m3_lightmeter_resources/m3_lightmeter_resources.dart';
|
||||||
import 'package:shared_preferences/shared_preferences.dart';
|
import 'package:shared_preferences/shared_preferences.dart';
|
||||||
|
|
||||||
|
@ -148,14 +147,4 @@ class UserPreferencesService {
|
||||||
orElse: () => Film.values.first,
|
orElse: () => Film.values.first,
|
||||||
);
|
);
|
||||||
set film(Film value) => _sharedPreferences.setString(filmKey, value.name);
|
set film(Film value) => _sharedPreferences.setString(filmKey, value.name);
|
||||||
|
|
||||||
String get selectedEquipmentProfileId => _sharedPreferences.selectedEquipmentProfileId;
|
|
||||||
set selectedEquipmentProfileId(String id) {
|
|
||||||
_sharedPreferences.selectedEquipmentProfileId = id;
|
|
||||||
}
|
|
||||||
|
|
||||||
List<EquipmentProfileData> get equipmentProfiles => _sharedPreferences.equipmentProfiles;
|
|
||||||
set equipmentProfiles(List<EquipmentProfileData> profiles) {
|
|
||||||
_sharedPreferences.equipmentProfiles = profiles;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
68
lib/firebase_options.dart
Normal file
68
lib/firebase_options.dart
Normal file
|
@ -0,0 +1,68 @@
|
||||||
|
// File generated by FlutterFire CLI.
|
||||||
|
// ignore_for_file: lines_longer_than_80_chars, avoid_classes_with_only_static_members
|
||||||
|
import 'package:firebase_core/firebase_core.dart' show FirebaseOptions;
|
||||||
|
import 'package:flutter/foundation.dart' show defaultTargetPlatform, kIsWeb, TargetPlatform;
|
||||||
|
|
||||||
|
/// Default [FirebaseOptions] for use with your Firebase apps.
|
||||||
|
///
|
||||||
|
/// Example:
|
||||||
|
/// ```dart
|
||||||
|
/// import 'firebase_options.dart';
|
||||||
|
/// // ...
|
||||||
|
/// await Firebase.initializeApp(
|
||||||
|
/// options: DefaultFirebaseOptions.currentPlatform,
|
||||||
|
/// );
|
||||||
|
/// ```
|
||||||
|
class DefaultFirebaseOptions {
|
||||||
|
static FirebaseOptions get currentPlatform {
|
||||||
|
if (kIsWeb) {
|
||||||
|
throw UnsupportedError(
|
||||||
|
'DefaultFirebaseOptions have not been configured for web - '
|
||||||
|
'you can reconfigure this by running the FlutterFire CLI again.',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
switch (defaultTargetPlatform) {
|
||||||
|
case TargetPlatform.android:
|
||||||
|
return android;
|
||||||
|
case TargetPlatform.iOS:
|
||||||
|
return ios;
|
||||||
|
case TargetPlatform.macOS:
|
||||||
|
throw UnsupportedError(
|
||||||
|
'DefaultFirebaseOptions have not been configured for macos - '
|
||||||
|
'you can reconfigure this by running the FlutterFire CLI again.',
|
||||||
|
);
|
||||||
|
case TargetPlatform.windows:
|
||||||
|
throw UnsupportedError(
|
||||||
|
'DefaultFirebaseOptions have not been configured for windows - '
|
||||||
|
'you can reconfigure this by running the FlutterFire CLI again.',
|
||||||
|
);
|
||||||
|
case TargetPlatform.linux:
|
||||||
|
throw UnsupportedError(
|
||||||
|
'DefaultFirebaseOptions have not been configured for linux - '
|
||||||
|
'you can reconfigure this by running the FlutterFire CLI again.',
|
||||||
|
);
|
||||||
|
default:
|
||||||
|
throw UnsupportedError(
|
||||||
|
'DefaultFirebaseOptions are not supported for this platform.',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static const FirebaseOptions android = FirebaseOptions(
|
||||||
|
apiKey: '',
|
||||||
|
appId: '',
|
||||||
|
messagingSenderId: '',
|
||||||
|
projectId: '',
|
||||||
|
storageBucket: '',
|
||||||
|
);
|
||||||
|
|
||||||
|
static const FirebaseOptions ios = FirebaseOptions(
|
||||||
|
apiKey: '',
|
||||||
|
appId: '',
|
||||||
|
messagingSenderId: '',
|
||||||
|
projectId: '',
|
||||||
|
storageBucket: '',
|
||||||
|
iosClientId: '',
|
||||||
|
iosBundleId: '',
|
||||||
|
);
|
||||||
|
}
|
88
lib/l10n/intl_zh.arb
Normal file
88
lib/l10n/intl_zh.arb
Normal file
|
@ -0,0 +1,88 @@
|
||||||
|
{
|
||||||
|
"@@locale": "zh",
|
||||||
|
"fastestExposurePair": "最快曝光组合",
|
||||||
|
"slowestExposurePair": "最慢曝光组合",
|
||||||
|
"ev": "EV",
|
||||||
|
"evValue": "{value} EV",
|
||||||
|
"@evValue": {
|
||||||
|
"placeholders": {
|
||||||
|
"value": {
|
||||||
|
"type": "String"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"iso": "ISO",
|
||||||
|
"filmSpeed": "胶片感光度",
|
||||||
|
"nd": "ND",
|
||||||
|
"ndFilterFactor": "ND 滤镜系数",
|
||||||
|
"noExposurePairs": "所选设置没有曝光配对",
|
||||||
|
"noCamerasDetected": "您的设备似乎没有连接到任何摄像头",
|
||||||
|
"noCameraPermission": "未获得摄像头权限",
|
||||||
|
"otherCameraError": "连接摄像头时发生错误",
|
||||||
|
"none": "无",
|
||||||
|
"cancel": "取消",
|
||||||
|
"select": "选择",
|
||||||
|
"save": "保存",
|
||||||
|
"settings": "设置",
|
||||||
|
"metering": "测量",
|
||||||
|
"fractionalStops": "EV 步进值",
|
||||||
|
"showFractionalStops": "显示 EV 步进值",
|
||||||
|
"halfStops": "1/2",
|
||||||
|
"thirdStops": "1/3",
|
||||||
|
"calibration": "校准",
|
||||||
|
"calibrationMessage": "此应用测量读数的准确性完全取决于设备的硬件。因此,请考虑测试此应用并手动设置 EV 校准,以获得准确的测量结果。",
|
||||||
|
"calibrationMessageCameraOnly": "此应用程序测量读数的准确性完全取决于设备的后置摄像头。因此,请考虑测试此应用并手动设置 EV 校准,以获得准确的测量结果。",
|
||||||
|
"camera": "摄像头",
|
||||||
|
"lightSensor": "光传感器",
|
||||||
|
"meteringScreenLayout": "布局",
|
||||||
|
"meteringScreenLayoutHint": "隐藏不需要的元素,以免浪费曝光列表空间",
|
||||||
|
"meteringScreenFeatureExtremeExposurePairs": "最快 & 最慢曝光组合",
|
||||||
|
"meteringScreenFeatureFilmPicker": "胶片选择",
|
||||||
|
"film": "胶片",
|
||||||
|
"equipment": "设备",
|
||||||
|
"equipmentProfileName": "设备配置名称",
|
||||||
|
"equipmentProfileNameHint": "Praktica MTL5B",
|
||||||
|
"equipmentProfileAllValues": "全部",
|
||||||
|
"apertureValues": "光圈值",
|
||||||
|
"apertureValuesFilterDescription": "选择要显示的光圈值范围。这通常由您使用的镜头决定。",
|
||||||
|
"ndFilters": "ND 滤镜",
|
||||||
|
"ndFiltersFilterDescription": "选择要显示的 ND 滤镜系数。这些可能是您最常用的 ND 滤镜,也可能是适合您镜头的滤光镜。",
|
||||||
|
"shutterSpeedValues": "快门速度",
|
||||||
|
"shutterSpeedValuesFilterDescription": "选择要显示的快门速度范围。这通常由您使用的相机机身决定。",
|
||||||
|
"isoValues": "ISO",
|
||||||
|
"isoValuesFilterDescription": "选择要显示的 ISO。这些值可能是您最常用的值,也可能是相机支持的值。",
|
||||||
|
"equipmentProfile": "设备配置",
|
||||||
|
"equipmentProfiles": "设备配置",
|
||||||
|
"general": "通用",
|
||||||
|
"keepScreenOn": "保持屏幕常亮",
|
||||||
|
"haptics": "震动",
|
||||||
|
"volumeKeysAction": "音量键快门",
|
||||||
|
"language": "语言",
|
||||||
|
"chooseLanguage": "选择语言",
|
||||||
|
"theme": "主题",
|
||||||
|
"chooseTheme": "选择主题",
|
||||||
|
"themeLight": "亮色",
|
||||||
|
"themeDark": "暗色",
|
||||||
|
"themeSystemDefault": "跟随系统",
|
||||||
|
"dynamicColor": "动态颜色",
|
||||||
|
"primaryColor": "主题颜色",
|
||||||
|
"choosePrimaryColor": "选择主题颜色",
|
||||||
|
"about": "关于",
|
||||||
|
"sourceCode": "源代码",
|
||||||
|
"reportIssue": "报告问题",
|
||||||
|
"writeEmail": "Email",
|
||||||
|
"youDontHaveMailApp": "您没有安装任何邮件App。",
|
||||||
|
"copyEmail": "复制电子邮件",
|
||||||
|
"version": "Version",
|
||||||
|
"versionNumber": "{version} ({buildNumber})",
|
||||||
|
"@versionNumber": {
|
||||||
|
"placeholders": {
|
||||||
|
"version": {
|
||||||
|
"type": "String"
|
||||||
|
},
|
||||||
|
"buildNumber": {
|
||||||
|
"type": "String"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,3 +1,5 @@
|
||||||
|
import 'dart:developer';
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:lightmeter/application.dart';
|
import 'package:lightmeter/application.dart';
|
||||||
import 'package:lightmeter/environment.dart';
|
import 'package:lightmeter/environment.dart';
|
||||||
|
@ -5,6 +7,10 @@ import 'package:lightmeter/firebase.dart';
|
||||||
|
|
||||||
Future<void> main() async {
|
Future<void> main() async {
|
||||||
WidgetsFlutterBinding.ensureInitialized();
|
WidgetsFlutterBinding.ensureInitialized();
|
||||||
|
try {
|
||||||
await initializeFirebase();
|
await initializeFirebase();
|
||||||
|
} catch (e) {
|
||||||
|
log(e.toString());
|
||||||
|
}
|
||||||
runApp(const Application(Environment.prod()));
|
runApp(const Application(Environment.prod()));
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,7 +6,7 @@ import 'package:lightmeter/data/permissions_service.dart';
|
||||||
import 'package:lightmeter/data/shared_prefs_service.dart';
|
import 'package:lightmeter/data/shared_prefs_service.dart';
|
||||||
import 'package:lightmeter/data/volume_events_service.dart';
|
import 'package:lightmeter/data/volume_events_service.dart';
|
||||||
import 'package:lightmeter/environment.dart';
|
import 'package:lightmeter/environment.dart';
|
||||||
import 'package:lightmeter/providers/equipment_profile_provider.dart';
|
|
||||||
import 'package:lightmeter/providers/ev_source_type_provider.dart';
|
import 'package:lightmeter/providers/ev_source_type_provider.dart';
|
||||||
import 'package:lightmeter/providers/metering_screen_layout_provider.dart';
|
import 'package:lightmeter/providers/metering_screen_layout_provider.dart';
|
||||||
import 'package:lightmeter/providers/stop_type_provider.dart';
|
import 'package:lightmeter/providers/stop_type_provider.dart';
|
||||||
|
@ -32,11 +32,13 @@ class LightmeterProviders extends StatelessWidget {
|
||||||
]),
|
]),
|
||||||
builder: (_, snapshot) {
|
builder: (_, snapshot) {
|
||||||
if (snapshot.data != null) {
|
if (snapshot.data != null) {
|
||||||
return IAPProductsProvider(
|
final sharedPrefs = snapshot.data![0] as SharedPreferences;
|
||||||
|
return IAPProviders(
|
||||||
|
sharedPreferences: sharedPrefs,
|
||||||
child: InheritedWidgetBase<Environment>(
|
child: InheritedWidgetBase<Environment>(
|
||||||
data: env.copyWith(hasLightSensor: snapshot.data![1] as bool),
|
data: env.copyWith(hasLightSensor: snapshot.data![1] as bool),
|
||||||
child: InheritedWidgetBase<UserPreferencesService>(
|
child: InheritedWidgetBase<UserPreferencesService>(
|
||||||
data: UserPreferencesService(snapshot.data![0] as SharedPreferences),
|
data: UserPreferencesService(sharedPrefs),
|
||||||
child: InheritedWidgetBase<LightSensorService>(
|
child: InheritedWidgetBase<LightSensorService>(
|
||||||
data: const LightSensorService(LocalPlatform()),
|
data: const LightSensorService(LocalPlatform()),
|
||||||
child: InheritedWidgetBase<CaffeineService>(
|
child: InheritedWidgetBase<CaffeineService>(
|
||||||
|
@ -49,7 +51,6 @@ class LightmeterProviders extends StatelessWidget {
|
||||||
data: const PermissionsService(),
|
data: const PermissionsService(),
|
||||||
child: MeteringScreenLayoutProvider(
|
child: MeteringScreenLayoutProvider(
|
||||||
child: StopTypeProvider(
|
child: StopTypeProvider(
|
||||||
child: EquipmentProfileProvider(
|
|
||||||
child: EvSourceTypeProvider(
|
child: EvSourceTypeProvider(
|
||||||
child: SupportedLocaleProvider(
|
child: SupportedLocaleProvider(
|
||||||
child: ThemeProvider(
|
child: ThemeProvider(
|
||||||
|
@ -68,7 +69,6 @@ class LightmeterProviders extends StatelessWidget {
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
|
||||||
);
|
);
|
||||||
} else if (snapshot.error != null) {
|
} else if (snapshot.error != null) {
|
||||||
return Center(child: Text(snapshot.error!.toString()));
|
return Center(child: Text(snapshot.error!.toString()));
|
||||||
|
|
|
@ -1,101 +0,0 @@
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:lightmeter/data/shared_prefs_service.dart';
|
|
||||||
import 'package:lightmeter/utils/inherited_generics.dart';
|
|
||||||
import 'package:m3_lightmeter_resources/m3_lightmeter_resources.dart';
|
|
||||||
import 'package:uuid/uuid.dart';
|
|
||||||
|
|
||||||
typedef EquipmentProfiles = List<EquipmentProfileData>;
|
|
||||||
typedef EquipmentProfile = EquipmentProfileData;
|
|
||||||
|
|
||||||
class EquipmentProfileProvider extends StatefulWidget {
|
|
||||||
final Widget child;
|
|
||||||
|
|
||||||
const EquipmentProfileProvider({required this.child, super.key});
|
|
||||||
|
|
||||||
static EquipmentProfileProviderState of(BuildContext context) {
|
|
||||||
return context.findAncestorStateOfType<EquipmentProfileProviderState>()!;
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
State<EquipmentProfileProvider> createState() => EquipmentProfileProviderState();
|
|
||||||
}
|
|
||||||
|
|
||||||
class EquipmentProfileProviderState extends State<EquipmentProfileProvider> {
|
|
||||||
static const EquipmentProfileData _defaultProfile = EquipmentProfileData(
|
|
||||||
id: '',
|
|
||||||
name: '',
|
|
||||||
apertureValues: ApertureValue.values,
|
|
||||||
ndValues: NdValue.values,
|
|
||||||
shutterSpeedValues: ShutterSpeedValue.values,
|
|
||||||
isoValues: IsoValue.values,
|
|
||||||
);
|
|
||||||
|
|
||||||
List<EquipmentProfileData> _customProfiles = [];
|
|
||||||
String _selectedId = '';
|
|
||||||
|
|
||||||
EquipmentProfileData get _selectedProfile => _customProfiles.firstWhere(
|
|
||||||
(e) => e.id == _selectedId,
|
|
||||||
orElse: () {
|
|
||||||
context.get<UserPreferencesService>().selectedEquipmentProfileId = _defaultProfile.id;
|
|
||||||
return _defaultProfile;
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
@override
|
|
||||||
void initState() {
|
|
||||||
super.initState();
|
|
||||||
_selectedId = context.get<UserPreferencesService>().selectedEquipmentProfileId;
|
|
||||||
_customProfiles = context.get<UserPreferencesService>().equipmentProfiles;
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return InheritedWidgetBase<List<EquipmentProfileData>>(
|
|
||||||
data: [_defaultProfile] + _customProfiles,
|
|
||||||
child: InheritedWidgetBase<EquipmentProfileData>(
|
|
||||||
data: _selectedProfile,
|
|
||||||
child: widget.child,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
void setProfile(EquipmentProfileData data) {
|
|
||||||
setState(() {
|
|
||||||
_selectedId = data.id;
|
|
||||||
});
|
|
||||||
context.get<UserPreferencesService>().selectedEquipmentProfileId = _selectedProfile.id;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Creates a default equipment profile
|
|
||||||
void addProfile(String name) {
|
|
||||||
_customProfiles.add(
|
|
||||||
EquipmentProfileData(
|
|
||||||
id: const Uuid().v1(),
|
|
||||||
name: name,
|
|
||||||
apertureValues: ApertureValue.values,
|
|
||||||
ndValues: NdValue.values,
|
|
||||||
shutterSpeedValues: ShutterSpeedValue.values,
|
|
||||||
isoValues: IsoValue.values,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
_refreshSavedProfiles();
|
|
||||||
}
|
|
||||||
|
|
||||||
void updateProdile(EquipmentProfileData data) {
|
|
||||||
final indexToUpdate = _customProfiles.indexWhere((element) => element.id == data.id);
|
|
||||||
if (indexToUpdate >= 0) {
|
|
||||||
_customProfiles[indexToUpdate] = data;
|
|
||||||
_refreshSavedProfiles();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void deleteProfile(EquipmentProfileData data) {
|
|
||||||
_customProfiles.remove(data);
|
|
||||||
_refreshSavedProfiles();
|
|
||||||
}
|
|
||||||
|
|
||||||
void _refreshSavedProfiles() {
|
|
||||||
context.get<UserPreferencesService>().equipmentProfiles = _customProfiles;
|
|
||||||
setState(() {});
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -3,12 +3,12 @@ import 'package:lightmeter/data/models/exposure_pair.dart';
|
||||||
import 'package:lightmeter/data/models/film.dart';
|
import 'package:lightmeter/data/models/film.dart';
|
||||||
import 'package:lightmeter/data/models/metering_screen_layout_config.dart';
|
import 'package:lightmeter/data/models/metering_screen_layout_config.dart';
|
||||||
import 'package:lightmeter/generated/l10n.dart';
|
import 'package:lightmeter/generated/l10n.dart';
|
||||||
import 'package:lightmeter/providers/equipment_profile_provider.dart';
|
|
||||||
import 'package:lightmeter/providers/metering_screen_layout_provider.dart';
|
import 'package:lightmeter/providers/metering_screen_layout_provider.dart';
|
||||||
import 'package:lightmeter/res/dimens.dart';
|
import 'package:lightmeter/res/dimens.dart';
|
||||||
import 'package:lightmeter/screens/metering/components/shared/readings_container/components/animated_dialog_picker/widget_picker_dialog_animated.dart';
|
import 'package:lightmeter/screens/metering/components/shared/readings_container/components/animated_dialog_picker/widget_picker_dialog_animated.dart';
|
||||||
import 'package:lightmeter/screens/metering/components/shared/readings_container/components/reading_value_container/widget_container_reading_value.dart';
|
import 'package:lightmeter/screens/metering/components/shared/readings_container/components/reading_value_container/widget_container_reading_value.dart';
|
||||||
import 'package:lightmeter/utils/inherited_generics.dart';
|
import 'package:m3_lightmeter_iap/m3_lightmeter_iap.dart';
|
||||||
import 'package:m3_lightmeter_resources/m3_lightmeter_resources.dart';
|
import 'package:m3_lightmeter_resources/m3_lightmeter_resources.dart';
|
||||||
|
|
||||||
class ReadingsContainer extends StatelessWidget {
|
class ReadingsContainer extends StatelessWidget {
|
||||||
|
@ -79,7 +79,7 @@ class ReadingsContainer extends StatelessWidget {
|
||||||
Expanded(
|
Expanded(
|
||||||
child: _IsoValuePicker(
|
child: _IsoValuePicker(
|
||||||
selectedValue: iso,
|
selectedValue: iso,
|
||||||
values: context.listen<EquipmentProfile>().isoValues,
|
values: EquipmentProfiles.selectedOf(context).isoValues,
|
||||||
onChanged: onIsoChanged,
|
onChanged: onIsoChanged,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
@ -87,7 +87,7 @@ class ReadingsContainer extends StatelessWidget {
|
||||||
Expanded(
|
Expanded(
|
||||||
child: _NdValuePicker(
|
child: _NdValuePicker(
|
||||||
selectedValue: nd,
|
selectedValue: nd,
|
||||||
values: context.listen<EquipmentProfile>().ndValues,
|
values: EquipmentProfiles.selectedOf(context).ndValues,
|
||||||
onChanged: onNdChanged,
|
onChanged: onNdChanged,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
@ -107,19 +107,19 @@ class _EquipmentProfilePicker extends StatelessWidget {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return AnimatedDialogPicker<EquipmentProfileData>(
|
return AnimatedDialogPicker<EquipmentProfile>(
|
||||||
icon: Icons.camera,
|
icon: Icons.camera,
|
||||||
title: S.of(context).equipmentProfile,
|
title: S.of(context).equipmentProfile,
|
||||||
selectedValue: context.listen<EquipmentProfile>(),
|
selectedValue: EquipmentProfiles.selectedOf(context),
|
||||||
values: context.listen<EquipmentProfiles>(),
|
values: EquipmentProfiles.of(context),
|
||||||
itemTitleBuilder: (_, value) => Text(value.id.isEmpty ? S.of(context).none : value.name),
|
itemTitleBuilder: (_, value) => Text(value.id.isEmpty ? S.of(context).none : value.name),
|
||||||
onChanged: EquipmentProfileProvider.of(context).setProfile,
|
onChanged: EquipmentProfileProvider.of(context).setProfile,
|
||||||
closedChild: ReadingValueContainer.singleValue(
|
closedChild: ReadingValueContainer.singleValue(
|
||||||
value: ReadingValue(
|
value: ReadingValue(
|
||||||
label: S.of(context).equipmentProfile,
|
label: S.of(context).equipmentProfile,
|
||||||
value: context.listen<EquipmentProfile>().id.isEmpty
|
value: EquipmentProfiles.selectedOf(context).id.isEmpty
|
||||||
? S.of(context).none
|
? S.of(context).none
|
||||||
: context.listen<EquipmentProfile>().name,
|
: EquipmentProfiles.selectedOf(context).name,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
|
@ -6,7 +6,7 @@ sealed class MeteringEvent {
|
||||||
}
|
}
|
||||||
|
|
||||||
class EquipmentProfileChangedEvent extends MeteringEvent {
|
class EquipmentProfileChangedEvent extends MeteringEvent {
|
||||||
final EquipmentProfileData equipmentProfileData;
|
final EquipmentProfile equipmentProfileData;
|
||||||
|
|
||||||
const EquipmentProfileChangedEvent(this.equipmentProfileData);
|
const EquipmentProfileChangedEvent(this.equipmentProfileData);
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,7 +7,6 @@ import 'package:lightmeter/data/models/exposure_pair.dart';
|
||||||
import 'package:lightmeter/data/models/film.dart';
|
import 'package:lightmeter/data/models/film.dart';
|
||||||
import 'package:lightmeter/data/models/metering_screen_layout_config.dart';
|
import 'package:lightmeter/data/models/metering_screen_layout_config.dart';
|
||||||
import 'package:lightmeter/environment.dart';
|
import 'package:lightmeter/environment.dart';
|
||||||
import 'package:lightmeter/providers/equipment_profile_provider.dart';
|
|
||||||
import 'package:lightmeter/providers/ev_source_type_provider.dart';
|
import 'package:lightmeter/providers/ev_source_type_provider.dart';
|
||||||
import 'package:lightmeter/screens/metering/bloc_metering.dart';
|
import 'package:lightmeter/screens/metering/bloc_metering.dart';
|
||||||
import 'package:lightmeter/screens/metering/components/bottom_controls/provider_bottom_controls.dart';
|
import 'package:lightmeter/screens/metering/components/bottom_controls/provider_bottom_controls.dart';
|
||||||
|
@ -15,7 +14,9 @@ import 'package:lightmeter/screens/metering/components/camera_container/provider
|
||||||
import 'package:lightmeter/screens/metering/components/light_sensor_container/provider_container_light_sensor.dart';
|
import 'package:lightmeter/screens/metering/components/light_sensor_container/provider_container_light_sensor.dart';
|
||||||
import 'package:lightmeter/screens/metering/event_metering.dart';
|
import 'package:lightmeter/screens/metering/event_metering.dart';
|
||||||
import 'package:lightmeter/screens/metering/state_metering.dart';
|
import 'package:lightmeter/screens/metering/state_metering.dart';
|
||||||
|
import 'package:lightmeter/screens/metering/utils/equipment_profile_listener.dart';
|
||||||
import 'package:lightmeter/utils/inherited_generics.dart';
|
import 'package:lightmeter/utils/inherited_generics.dart';
|
||||||
|
import 'package:m3_lightmeter_iap/m3_lightmeter_iap.dart';
|
||||||
import 'package:m3_lightmeter_resources/m3_lightmeter_resources.dart';
|
import 'package:m3_lightmeter_resources/m3_lightmeter_resources.dart';
|
||||||
|
|
||||||
class MeteringScreen extends StatelessWidget {
|
class MeteringScreen extends StatelessWidget {
|
||||||
|
@ -72,7 +73,7 @@ class _InheritedListeners extends StatelessWidget {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return InheritedWidgetListener<EquipmentProfile>(
|
return EquipmentProfileListener(
|
||||||
onDidChangeDependencies: (value) {
|
onDidChangeDependencies: (value) {
|
||||||
context.read<MeteringBloc>().add(EquipmentProfileChangedEvent(value));
|
context.read<MeteringBloc>().add(EquipmentProfileChangedEvent(value));
|
||||||
},
|
},
|
||||||
|
@ -87,8 +88,7 @@ class _InheritedListeners extends StatelessWidget {
|
||||||
aspect: MeteringScreenLayoutFeature.equipmentProfiles,
|
aspect: MeteringScreenLayoutFeature.equipmentProfiles,
|
||||||
onDidChangeDependencies: (value) {
|
onDidChangeDependencies: (value) {
|
||||||
if (!value) {
|
if (!value) {
|
||||||
EquipmentProfileProvider.of(context)
|
EquipmentProfileProvider.of(context).setProfile(EquipmentProfiles.of(context).first);
|
||||||
.setProfile(context.get<EquipmentProfiles>().first);
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
child: child,
|
child: child,
|
||||||
|
@ -123,7 +123,7 @@ class MeteringContainerBuidler extends StatelessWidget {
|
||||||
? buildExposureValues(
|
? buildExposureValues(
|
||||||
ev!,
|
ev!,
|
||||||
context.listen<StopType>(),
|
context.listen<StopType>(),
|
||||||
context.listen<EquipmentProfile>(),
|
EquipmentProfiles.selectedOf(context),
|
||||||
film,
|
film,
|
||||||
)
|
)
|
||||||
: <ExposurePair>[];
|
: <ExposurePair>[];
|
||||||
|
|
30
lib/screens/metering/utils/equipment_profile_listener.dart
Normal file
30
lib/screens/metering/utils/equipment_profile_listener.dart
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:m3_lightmeter_iap/m3_lightmeter_iap.dart';
|
||||||
|
import 'package:m3_lightmeter_resources/m3_lightmeter_resources.dart';
|
||||||
|
|
||||||
|
class EquipmentProfileListener extends StatefulWidget {
|
||||||
|
final ValueChanged<EquipmentProfile> onDidChangeDependencies;
|
||||||
|
final Widget child;
|
||||||
|
|
||||||
|
const EquipmentProfileListener({
|
||||||
|
required this.onDidChangeDependencies,
|
||||||
|
required this.child,
|
||||||
|
super.key,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<EquipmentProfileListener> createState() => _EquipmentProfileListenerState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _EquipmentProfileListenerState extends State<EquipmentProfileListener> {
|
||||||
|
@override
|
||||||
|
void didChangeDependencies() {
|
||||||
|
super.didChangeDependencies();
|
||||||
|
widget.onDidChangeDependencies(EquipmentProfiles.selectedOf(context));
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return widget.child;
|
||||||
|
}
|
||||||
|
}
|
|
@ -8,8 +8,8 @@ import 'package:lightmeter/screens/settings/components/metering/components/equip
|
||||||
import 'package:m3_lightmeter_resources/m3_lightmeter_resources.dart';
|
import 'package:m3_lightmeter_resources/m3_lightmeter_resources.dart';
|
||||||
|
|
||||||
class EquipmentProfileContainer extends StatefulWidget {
|
class EquipmentProfileContainer extends StatefulWidget {
|
||||||
final EquipmentProfileData data;
|
final EquipmentProfile data;
|
||||||
final ValueChanged<EquipmentProfileData> onUpdate;
|
final ValueChanged<EquipmentProfile> onUpdate;
|
||||||
final VoidCallback onDelete;
|
final VoidCallback onDelete;
|
||||||
final VoidCallback onExpand;
|
final VoidCallback onExpand;
|
||||||
|
|
||||||
|
@ -27,7 +27,7 @@ class EquipmentProfileContainer extends StatefulWidget {
|
||||||
|
|
||||||
class EquipmentProfileContainerState extends State<EquipmentProfileContainer>
|
class EquipmentProfileContainerState extends State<EquipmentProfileContainer>
|
||||||
with TickerProviderStateMixin {
|
with TickerProviderStateMixin {
|
||||||
late EquipmentProfileData _equipmentData = EquipmentProfileData(
|
late EquipmentProfile _equipmentData = EquipmentProfile(
|
||||||
id: widget.data.id,
|
id: widget.data.id,
|
||||||
name: widget.data.name,
|
name: widget.data.name,
|
||||||
apertureValues: widget.data.apertureValues,
|
apertureValues: widget.data.apertureValues,
|
||||||
|
@ -45,7 +45,7 @@ class EquipmentProfileContainerState extends State<EquipmentProfileContainer>
|
||||||
@override
|
@override
|
||||||
void didUpdateWidget(EquipmentProfileContainer oldWidget) {
|
void didUpdateWidget(EquipmentProfileContainer oldWidget) {
|
||||||
super.didUpdateWidget(oldWidget);
|
super.didUpdateWidget(oldWidget);
|
||||||
_equipmentData = EquipmentProfileData(
|
_equipmentData = EquipmentProfile(
|
||||||
id: widget.data.id,
|
id: widget.data.id,
|
||||||
name: widget.data.name,
|
name: widget.data.name,
|
||||||
apertureValues: widget.data.apertureValues,
|
apertureValues: widget.data.apertureValues,
|
||||||
|
@ -196,7 +196,7 @@ class _AnimatedArrowButton extends AnimatedWidget {
|
||||||
}
|
}
|
||||||
|
|
||||||
class _AnimatedEquipmentListTiles extends AnimatedWidget {
|
class _AnimatedEquipmentListTiles extends AnimatedWidget {
|
||||||
final EquipmentProfileData equipmentData;
|
final EquipmentProfile equipmentData;
|
||||||
final ValueChanged<List<ApertureValue>> onApertureValuesSelected;
|
final ValueChanged<List<ApertureValue>> onApertureValuesSelected;
|
||||||
final ValueChanged<List<IsoValue>> onIsoValuesSelecred;
|
final ValueChanged<List<IsoValue>> onIsoValuesSelecred;
|
||||||
final ValueChanged<List<NdValue>> onNdValuesSelected;
|
final ValueChanged<List<NdValue>> onNdValuesSelected;
|
||||||
|
|
|
@ -1,11 +1,11 @@
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:lightmeter/generated/l10n.dart';
|
import 'package:lightmeter/generated/l10n.dart';
|
||||||
import 'package:lightmeter/providers/equipment_profile_provider.dart';
|
|
||||||
import 'package:lightmeter/res/dimens.dart';
|
import 'package:lightmeter/res/dimens.dart';
|
||||||
import 'package:lightmeter/screens/settings/components/metering/components/equipment_profiles/components/equipment_profile_screen/components/equipment_profile_container/widget_container_equipment_profile.dart';
|
import 'package:lightmeter/screens/settings/components/metering/components/equipment_profiles/components/equipment_profile_screen/components/equipment_profile_container/widget_container_equipment_profile.dart';
|
||||||
import 'package:lightmeter/screens/settings/components/metering/components/equipment_profiles/components/equipment_profile_screen/components/equipment_profile_name_dialog/widget_dialog_equipment_profile_name.dart';
|
import 'package:lightmeter/screens/settings/components/metering/components/equipment_profiles/components/equipment_profile_screen/components/equipment_profile_name_dialog/widget_dialog_equipment_profile_name.dart';
|
||||||
import 'package:lightmeter/screens/shared/sliver_screen/screen_sliver.dart';
|
import 'package:lightmeter/screens/shared/sliver_screen/screen_sliver.dart';
|
||||||
import 'package:lightmeter/utils/inherited_generics.dart';
|
import 'package:m3_lightmeter_iap/m3_lightmeter_iap.dart';
|
||||||
import 'package:m3_lightmeter_resources/m3_lightmeter_resources.dart';
|
import 'package:m3_lightmeter_resources/m3_lightmeter_resources.dart';
|
||||||
|
|
||||||
class EquipmentProfilesScreen extends StatefulWidget {
|
class EquipmentProfilesScreen extends StatefulWidget {
|
||||||
|
@ -19,13 +19,12 @@ class _EquipmentProfilesScreenState extends State<EquipmentProfilesScreen> {
|
||||||
static const maxProfiles = 5 + 1; // replace with a constant from iap
|
static const maxProfiles = 5 + 1; // replace with a constant from iap
|
||||||
|
|
||||||
late List<GlobalKey<EquipmentProfileContainerState>> profileContainersKeys = [];
|
late List<GlobalKey<EquipmentProfileContainerState>> profileContainersKeys = [];
|
||||||
int get profilesCount => context.listen<EquipmentProfiles>().length;
|
int get profilesCount => EquipmentProfiles.of(context).length;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void didChangeDependencies() {
|
||||||
super.initState();
|
super.didChangeDependencies();
|
||||||
profileContainersKeys = context
|
profileContainersKeys = EquipmentProfiles.of(context)
|
||||||
.get<EquipmentProfiles>()
|
|
||||||
.map((e) => GlobalKey<EquipmentProfileContainerState>(debugLabel: e.id))
|
.map((e) => GlobalKey<EquipmentProfileContainerState>(debugLabel: e.id))
|
||||||
.toList();
|
.toList();
|
||||||
}
|
}
|
||||||
|
@ -58,14 +57,14 @@ class _EquipmentProfilesScreenState extends State<EquipmentProfilesScreen> {
|
||||||
),
|
),
|
||||||
child: EquipmentProfileContainer(
|
child: EquipmentProfileContainer(
|
||||||
key: profileContainersKeys[index],
|
key: profileContainersKeys[index],
|
||||||
data: context.listen<EquipmentProfiles>()[index],
|
data: EquipmentProfiles.of(context)[index],
|
||||||
onExpand: () => _keepExpandedAt(index),
|
onExpand: () => _keepExpandedAt(index),
|
||||||
onUpdate: (profileData) => _updateProfileAt(profileData, index),
|
onUpdate: (profileData) => _updateProfileAt(profileData, index),
|
||||||
onDelete: () => _removeProfileAt(index),
|
onDelete: () => _removeProfileAt(index),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
: const SizedBox.shrink(),
|
: const SizedBox.shrink(),
|
||||||
childCount: profileContainersKeys.length,
|
childCount: profilesCount,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
@ -79,18 +78,16 @@ class _EquipmentProfilesScreenState extends State<EquipmentProfilesScreen> {
|
||||||
).then((value) {
|
).then((value) {
|
||||||
if (value != null) {
|
if (value != null) {
|
||||||
EquipmentProfileProvider.of(context).addProfile(value);
|
EquipmentProfileProvider.of(context).addProfile(value);
|
||||||
profileContainersKeys.add(GlobalKey<EquipmentProfileContainerState>());
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
void _updateProfileAt(EquipmentProfileData data, int index) {
|
void _updateProfileAt(EquipmentProfile data, int index) {
|
||||||
EquipmentProfileProvider.of(context).updateProdile(data);
|
EquipmentProfileProvider.of(context).updateProdile(data);
|
||||||
}
|
}
|
||||||
|
|
||||||
void _removeProfileAt(int index) {
|
void _removeProfileAt(int index) {
|
||||||
EquipmentProfileProvider.of(context).deleteProfile(context.listen<EquipmentProfiles>()[index]);
|
EquipmentProfileProvider.of(context).deleteProfile(EquipmentProfiles.of(context)[index]);
|
||||||
profileContainersKeys.removeAt(index);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void _keepExpandedAt(int index) {
|
void _keepExpandedAt(int index) {
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
|
import 'dart:developer';
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:lightmeter/environment.dart';
|
|
||||||
import 'package:lightmeter/generated/l10n.dart';
|
import 'package:lightmeter/generated/l10n.dart';
|
||||||
import 'package:lightmeter/screens/settings/components/metering/components/equipment_profiles/components/equipment_profile_screen/screen_equipment_profile.dart';
|
import 'package:lightmeter/screens/settings/components/metering/components/equipment_profiles/components/equipment_profile_screen/screen_equipment_profile.dart';
|
||||||
import 'package:lightmeter/utils/inherited_generics.dart';
|
|
||||||
import 'package:m3_lightmeter_iap/m3_lightmeter_iap.dart';
|
import 'package:m3_lightmeter_iap/m3_lightmeter_iap.dart';
|
||||||
import 'package:m3_lightmeter_resources/m3_lightmeter_resources.dart';
|
import 'package:m3_lightmeter_resources/m3_lightmeter_resources.dart';
|
||||||
|
|
||||||
|
@ -11,18 +11,25 @@ class EquipmentProfilesListTile extends StatelessWidget {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
final paidStatus = IAPProducts.productOf(context, IAPProductType.paidFeatures)?.status;
|
||||||
|
log(paidStatus.toString());
|
||||||
return ListTile(
|
return ListTile(
|
||||||
leading: const Icon(Icons.camera),
|
leading: const Icon(Icons.camera),
|
||||||
title: Text(S.of(context).equipmentProfiles),
|
title: Text(S.of(context).equipmentProfiles),
|
||||||
onTap: () {
|
onTap: switch (paidStatus) {
|
||||||
if (context.get<Environment>().buildType == BuildType.dev ||
|
IAPProductStatus.purchased => () {
|
||||||
IAPProducts.isPurchased(context, IAPProductType.paidFeatures)) {
|
Navigator.of(context).push<EquipmentProfile>(
|
||||||
Navigator.of(context).push<EquipmentProfileData>(
|
|
||||||
MaterialPageRoute(builder: (_) => const EquipmentProfilesScreen()),
|
MaterialPageRoute(builder: (_) => const EquipmentProfilesScreen()),
|
||||||
);
|
);
|
||||||
} else {
|
},
|
||||||
|
IAPProductStatus.purchasable => () {
|
||||||
IAPProductsProvider.of(context).buy(IAPProductType.paidFeatures);
|
IAPProductsProvider.of(context).buy(IAPProductType.paidFeatures);
|
||||||
}
|
},
|
||||||
|
_ => null,
|
||||||
|
},
|
||||||
|
trailing: switch (paidStatus) {
|
||||||
|
IAPProductStatus.purchasable => const Icon(Icons.lock),
|
||||||
|
_ => null,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
name: lightmeter
|
name: lightmeter
|
||||||
description: Lightmeter app inspired by Material 3 design system.
|
description: Lightmeter app inspired by Material 3 design system.
|
||||||
publish_to: "none"
|
publish_to: "none"
|
||||||
version: 0.12.0+31
|
version: 0.12.2+33
|
||||||
|
|
||||||
environment:
|
environment:
|
||||||
sdk: ">=3.0.0 <4.0.0"
|
sdk: ">=3.0.0 <4.0.0"
|
||||||
|
|
|
@ -495,7 +495,7 @@ void main() {
|
||||||
group(
|
group(
|
||||||
'`EquipmentProfileChangedEvent`',
|
'`EquipmentProfileChangedEvent`',
|
||||||
() {
|
() {
|
||||||
final reducedProfile = EquipmentProfileData(
|
final reducedProfile = EquipmentProfile(
|
||||||
id: '0',
|
id: '0',
|
||||||
name: 'Reduced',
|
name: 'Reduced',
|
||||||
apertureValues: ApertureValue.values,
|
apertureValues: ApertureValue.values,
|
||||||
|
|
|
@ -5,7 +5,7 @@ import 'package:lightmeter/screens/metering/screen_metering.dart';
|
||||||
import 'package:m3_lightmeter_resources/m3_lightmeter_resources.dart';
|
import 'package:m3_lightmeter_resources/m3_lightmeter_resources.dart';
|
||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
const defaultEquipmentProfile = EquipmentProfileData(
|
const defaultEquipmentProfile = EquipmentProfile(
|
||||||
id: "",
|
id: "",
|
||||||
name: 'Default',
|
name: 'Default',
|
||||||
apertureValues: ApertureValue.values,
|
apertureValues: ApertureValue.values,
|
||||||
|
@ -334,7 +334,7 @@ void main() {
|
||||||
});
|
});
|
||||||
|
|
||||||
group('Reduced equipment profile', () {
|
group('Reduced equipment profile', () {
|
||||||
final equipmentProfile = EquipmentProfileData(
|
final equipmentProfile = EquipmentProfile(
|
||||||
id: "1",
|
id: "1",
|
||||||
name: 'Test1',
|
name: 'Test1',
|
||||||
apertureValues: ApertureValue.values.sublist(4),
|
apertureValues: ApertureValue.values.sublist(4),
|
||||||
|
|
Loading…
Reference in a new issue