mirror of
https://github.com/vodemn/m3_lightmeter.git
synced 2024-11-23 07:50:42 +00:00
ML-12 Prepare repo to be public (#13)
* added source code list tile * added settings sections * wip * moved theme tiles to separate folders * added env * added contact email * widget folders * dynamic colors -> dynamic color * fixed `SettingsSection` clipBehavior * version bump * typo * updated flutter to 3.7 * added style guide * typo * Update style_guide.md * Update README.md * Update README.md * Update README.md
This commit is contained in:
parent
ab0b0cefab
commit
31ef42c4c0
30 changed files with 446 additions and 161 deletions
20
.vscode/launch.json
vendored
20
.vscode/launch.json
vendored
|
@ -14,7 +14,7 @@
|
|||
"--dart-define",
|
||||
"cameraPreviewAspectRatio=2/3",
|
||||
],
|
||||
"program": "${workspaceFolder}/lib/main.dart",
|
||||
"program": "${workspaceFolder}/lib/main_dev.dart",
|
||||
},
|
||||
{
|
||||
"name": "dev (ios)",
|
||||
|
@ -26,19 +26,7 @@
|
|||
"--dart-define",
|
||||
"cameraPreviewAspectRatio=3/4",
|
||||
],
|
||||
"program": "${workspaceFolder}/lib/main.dart",
|
||||
},
|
||||
{
|
||||
"name": "dev (mock)",
|
||||
"request": "launch",
|
||||
"type": "dart",
|
||||
"args": [
|
||||
"--flavor",
|
||||
"dev",
|
||||
"--dart-define",
|
||||
"cameraPreviewAspectRatio=3/4",
|
||||
],
|
||||
"program": "${workspaceFolder}/lib/main_mock.dart",
|
||||
"program": "${workspaceFolder}/lib/main_dev.dart",
|
||||
},
|
||||
{
|
||||
"name": "prod (android)",
|
||||
|
@ -50,7 +38,7 @@
|
|||
"--dart-define",
|
||||
"cameraPreviewAspectRatio=2/3",
|
||||
],
|
||||
"program": "${workspaceFolder}/lib/main.dart",
|
||||
"program": "${workspaceFolder}/lib/main_prod.dart",
|
||||
},
|
||||
{
|
||||
"name": "prod (ios)",
|
||||
|
@ -62,7 +50,7 @@
|
|||
"--dart-define",
|
||||
"cameraPreviewAspectRatio=3/4",
|
||||
],
|
||||
"program": "${workspaceFolder}/lib/main.dart",
|
||||
"program": "${workspaceFolder}/lib/main_prod.dart",
|
||||
},
|
||||
],
|
||||
}
|
25
README.md
25
README.md
|
@ -5,7 +5,14 @@
|
|||
<b>Material Lightmeter</b>
|
||||
</p>
|
||||
|
||||
## Backstory
|
||||
# Table of contents
|
||||
|
||||
- [Backstory](#backstory)
|
||||
- [Legacy features](#legacy-features)
|
||||
- [Build](#build)
|
||||
- [Contribution](#contribution)
|
||||
|
||||
# Backstory
|
||||
|
||||
Some time ago I've started developing the [Material Lightmeter](https://play.google.com/store/apps/details?id=com.vodemn.lightmeter&hl=en&gl=US) app. Unfortunately, the last update of this app was almost a year prior to creation of this repo. So after reading some positive review on Google Play saying that "this is an excellent app, too bad it is no longer updated", I've decided to make an update and also make this app open source. Maybe someone sometime will decide to contribute to this project.
|
||||
|
||||
|
@ -13,13 +20,7 @@ But as the existing repo contained some sensitive data, that I've pushed due to
|
|||
|
||||
Without further delay behold my new Lightmeter app inspired by Material You (a.k.a. M3)
|
||||
|
||||
## Table of contents
|
||||
|
||||
- [Backstory](#backstory)
|
||||
- [Legacy features](#legacy-features)
|
||||
- [Build](#build)
|
||||
|
||||
## Legacy features
|
||||
# Legacy features
|
||||
|
||||
The list of features that the old lightmeter app has and that have to be implemeneted in the M3 lightmeter.
|
||||
|
||||
|
@ -48,5 +49,11 @@ The list of features that the old lightmeter app has and that have to be impleme
|
|||
## Build
|
||||
|
||||
```
|
||||
flutter build apk --flavor dev --dart-define cameraPreviewAspectRatio=2/3
|
||||
flutter build apk --flavor dev --dart-define cameraPreviewAspectRatio=2/3 -t lib/main_dev.dart
|
||||
```
|
||||
|
||||
## Contribution
|
||||
|
||||
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).
|
||||
|
|
140
doc/style_guide.md
Normal file
140
doc/style_guide.md
Normal file
|
@ -0,0 +1,140 @@
|
|||
# M3 Lightmeter repo style guide
|
||||
|
||||
This repo uses [Effective Dart Style](https://dart.dev/guides/language/effective-dart/style) and [Style guide for Flutter repo](https://github.com/flutter/flutter/wiki/Style-guide-for-Flutter-repo#formatting) with some alterations.
|
||||
|
||||
## Table of contents
|
||||
|
||||
- [Table of contents](#table-of-contents)
|
||||
- [Folder structure guidelines](#folder-structure-guidelines)
|
||||
- [Inverse file naming](#inverse-file-naming)
|
||||
- [Always use a functional prefix in widgets file names](#always-use-a-functional-prefix-in-widgets-file-names)
|
||||
- [All files must be grouped according to their function](#all-files-must-be-grouped-according-to-their-function)
|
||||
- [Place elements used by one screen in the folder of this screen](#place-elements-used-by-one-screen-in-the-folder-of-this-screen)
|
||||
- [Place elements used within one logical group in the _shared_ folder inside this group folder](#place-elements-used-within-one-logical-group-in-the-shared-folder-inside-this-group-folder)
|
||||
- [Always place component in its own folder](#always-place-component-in-its-own-folder)
|
||||
- [Formatting](#formatting)
|
||||
- [Omit trailing comma after single parameter](#omit-trailing-comma-after-single-parameter)
|
||||
|
||||
## Folder structure guidelines
|
||||
|
||||
### Inverse file naming
|
||||
|
||||
We use inverse names for files, but a regular one for folders.
|
||||
```
|
||||
.
|
||||
└── settigns/
|
||||
├── fractional_stops/
|
||||
│ └── widget_list_tile_fractional_stops.dart
|
||||
├── ...
|
||||
└── screen_settings.dart
|
||||
```
|
||||
|
||||
```dart
|
||||
/// widget_list_tile_fractional_stops.dart
|
||||
|
||||
class FractionalStopsListTile extends StatelessWidget {...}
|
||||
```
|
||||
|
||||
```dart
|
||||
/// screen_settings.dart
|
||||
|
||||
class SettingsScreen extends StatelessWidget {...}
|
||||
```
|
||||
|
||||
### Always use a functional prefix in widgets file names
|
||||
|
||||
Basically this rule comes from the previous one but covers specifically widgets.
|
||||
It is pretty obvious that for example `FooIcon` and `BarButton` are widgets and respective file names *icon_foo.dart* and *button_bar.dart* reflect it. But we still add *widget_* prefix to all widgets to maintain consistency while omitting `Widget` in the class names (i.e. `FooIconWidget`).
|
||||
|
||||
### All files must be grouped according to their function
|
||||
|
||||
That basically means, that all files should be placed in the folders according to their functional prefix even if there is only one file.
|
||||
|
||||
### Place elements used by one screen in the folder of this screen
|
||||
|
||||
Place all widgets, utils, etc. used by a single screen in corresponding folders on the same level with other screen files.
|
||||
|
||||
```
|
||||
.
|
||||
└── screens/
|
||||
└── metering/
|
||||
├── components/
|
||||
│ ├── bottom_controls/
|
||||
│ │ └── ...
|
||||
│ └── topbar/
|
||||
│ └── ...
|
||||
└── screen_metering.dart
|
||||
```
|
||||
|
||||
### Place elements used within one logical group in the _shared_ folder inside this group folder
|
||||
|
||||
Components used by multiple screens or by other components within one logical group should be placed in the _shared_ folder on the same level with the corresponding screens/components.
|
||||
|
||||
In the example below the `DialogPicker` is used by `FractionalStopsListTile` and `ThemeTypeListTile`.
|
||||
|
||||
```
|
||||
.
|
||||
└── settigns/
|
||||
├── fractional_stops/
|
||||
│ └── widget_list_tile_fractional_stops.dart
|
||||
├── shared/
|
||||
│ └── dialog_picker/
|
||||
│ └── widget_dialog_picker.dart
|
||||
└── theme_type/
|
||||
└── widget_list_tile_theme_type.dart
|
||||
```
|
||||
|
||||
### Always place component in its own folder
|
||||
|
||||
Folder structure for the most basic widget looks like this:
|
||||
- *<widget_name>*
|
||||
- bloc_*<widget_name>*.dart
|
||||
- provider_*<widget_name>*.dart
|
||||
- widget_*<widget_name>*.dart
|
||||
|
||||
But sometimes widgets don't need a bloc and provider and therefore there is only one file left - the widget itself.
|
||||
Even in this case a single file has to be placed in its own folder:
|
||||
- *<widget2_name>*
|
||||
- widget_*<widget2_name>*.dart
|
||||
|
||||
```
|
||||
/// BAD
|
||||
components/
|
||||
├── haptics/
|
||||
│ ├── bloc_list_tile_haptics.dart
|
||||
│ ├── provider_list_tile_haptics.dart
|
||||
│ └── widget_list_tile_haptics.dart
|
||||
└── widget_list_tile_theme_type.dart
|
||||
|
||||
/// GOOD
|
||||
components/
|
||||
├── haptics/
|
||||
│ ├── bloc_list_tile_haptics.dart
|
||||
│ ├── provider_list_tile_haptics.dart
|
||||
│ └── widget_list_tile_haptics.dart
|
||||
└── theme_type/
|
||||
└── widget_list_tile_theme_type.dart
|
||||
```
|
||||
|
||||
## Formatting
|
||||
|
||||
### Omit trailing comma after single parameter
|
||||
|
||||
```dart
|
||||
/// BAD
|
||||
const SizedBox(
|
||||
width: 16.0,
|
||||
)
|
||||
|
||||
/// ALSO BAD
|
||||
const SizedBox(width: 16.0, height: 16.0)
|
||||
|
||||
/// GOOD
|
||||
const SizedBox(width: 16.0)
|
||||
|
||||
/// ALSO GOOD
|
||||
const SizedBox(
|
||||
width: 16.0,
|
||||
height: 16.0,
|
||||
)
|
||||
```
|
|
@ -2,12 +2,13 @@ import 'package:flutter/material.dart';
|
|||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_localizations/flutter_localizations.dart';
|
||||
import 'package:lightmeter/data/haptics_service.dart';
|
||||
import 'package:lightmeter/data/models/ev_source_type.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
|
||||
import 'data/models/ev_source_type.dart';
|
||||
import 'data/permissions_service.dart';
|
||||
import 'data/shared_prefs_service.dart';
|
||||
import 'environment.dart';
|
||||
import 'generated/l10n.dart';
|
||||
import 'res/theme.dart';
|
||||
import 'screens/metering/flow_metering.dart';
|
||||
|
@ -17,9 +18,9 @@ import 'utils/stop_type_provider.dart';
|
|||
final RouteObserver<PageRoute> routeObserver = RouteObserver<PageRoute>();
|
||||
|
||||
class Application extends StatelessWidget {
|
||||
final EvSourceType evSource;
|
||||
final Environment env;
|
||||
|
||||
const Application(this.evSource, {super.key});
|
||||
const Application(this.env, {super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
|
@ -29,10 +30,11 @@ class Application extends StatelessWidget {
|
|||
if (snapshot.data != null) {
|
||||
return MultiProvider(
|
||||
providers: [
|
||||
Provider.value(value: env),
|
||||
Provider.value(value: EvSourceType.camera),
|
||||
Provider(create: (_) => UserPreferencesService(snapshot.data!)),
|
||||
Provider(create: (_) => const HapticsService()),
|
||||
Provider(create: (_) => PermissionsService()),
|
||||
Provider.value(value: evSource),
|
||||
],
|
||||
child: StopTypeProvider(
|
||||
child: ThemeProvider(
|
||||
|
|
|
@ -1 +1 @@
|
|||
enum DynamicColorsState { unavailable, enabled, disabled }
|
||||
enum DynamicColorState { unavailable, enabled, disabled }
|
||||
|
|
|
@ -1 +1 @@
|
|||
enum EvSourceType { camera, mock }
|
||||
enum EvSourceType { camera, sensor }
|
||||
|
|
|
@ -10,7 +10,7 @@ class UserPreferencesService {
|
|||
|
||||
static const _hapticsKey = "haptics";
|
||||
static const _themeTypeKey = "themeType";
|
||||
static const _dynamicColorsKey = "dynamicColors";
|
||||
static const _dynamicColorKey = "dynamicColor";
|
||||
|
||||
final SharedPreferences _sharedPreferences;
|
||||
|
||||
|
@ -28,6 +28,6 @@ class UserPreferencesService {
|
|||
ThemeType get themeType => ThemeType.values[_sharedPreferences.getInt(_themeTypeKey) ?? 0];
|
||||
set themeType(ThemeType value) => _sharedPreferences.setInt(_themeTypeKey, value.index);
|
||||
|
||||
bool get dynamicColors => _sharedPreferences.getBool(_dynamicColorsKey) ?? false;
|
||||
set dynamicColors(bool value) => _sharedPreferences.setBool(_dynamicColorsKey, value);
|
||||
bool get dynamicColor => _sharedPreferences.getBool(_dynamicColorKey) ?? false;
|
||||
set dynamicColor(bool value) => _sharedPreferences.setBool(_dynamicColorKey, value);
|
||||
}
|
||||
|
|
21
lib/environment.dart
Normal file
21
lib/environment.dart
Normal file
|
@ -0,0 +1,21 @@
|
|||
class Environment {
|
||||
final String sourceCodeUrl;
|
||||
final String issuesReportUrl;
|
||||
final String contactEmail;
|
||||
|
||||
const Environment({
|
||||
required this.sourceCodeUrl,
|
||||
required this.issuesReportUrl,
|
||||
required this.contactEmail,
|
||||
});
|
||||
|
||||
const Environment.dev()
|
||||
: sourceCodeUrl = 'https://github.com/vodemn/m3_lightmeter',
|
||||
issuesReportUrl = 'https://github.com/vodemn/m3_lightmeter/issues',
|
||||
contactEmail = 'contact.vodemn@gmail.com';
|
||||
|
||||
const Environment.prod()
|
||||
: sourceCodeUrl = 'https://github.com/vodemn/m3_lightmeter',
|
||||
issuesReportUrl = 'https://github.com/vodemn/m3_lightmeter/issues',
|
||||
contactEmail = 'contact.vodemn@gmail.com';
|
||||
}
|
|
@ -13,19 +13,26 @@
|
|||
"cancel": "Cancel",
|
||||
"select": "Select",
|
||||
"settings": "Settings",
|
||||
"metering": "Metering",
|
||||
"fractionalStops": "Fractional stops",
|
||||
"showFractionalStops": "Show fractional stops",
|
||||
"halfStops": "1/2",
|
||||
"thirdStops": "1/3",
|
||||
"general": "General",
|
||||
"haptics": "Haptics",
|
||||
"theme": "Theme",
|
||||
"chooseTheme": "Choose theme",
|
||||
"dynamicColors": "Dynamic colors",
|
||||
"dynamicColor": "Dynamic color",
|
||||
"themeLight": "Light",
|
||||
"themeDark": "Dark",
|
||||
"themeSystemDefault": "System default",
|
||||
"version": "Version: {version} ({buildNumber})",
|
||||
"@version": {
|
||||
"about": "About",
|
||||
"sourceCode": "Source code",
|
||||
"reportIssue": "Report an issue",
|
||||
"writeEmail": "Write an email",
|
||||
"version": "Version",
|
||||
"versionNumber": "{version} ({buildNumber})",
|
||||
"@versionNumber": {
|
||||
"placeholders": {
|
||||
"version": {
|
||||
"type": "String"
|
||||
|
|
9
lib/launch_app.dart
Normal file
9
lib/launch_app.dart
Normal file
|
@ -0,0 +1,9 @@
|
|||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'application.dart';
|
||||
import 'environment.dart';
|
||||
|
||||
void launchApp(Environment env) {
|
||||
WidgetsFlutterBinding.ensureInitialized();
|
||||
runApp(Application(env));
|
||||
}
|
|
@ -1,9 +0,0 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:lightmeter/data/models/ev_source_type.dart';
|
||||
|
||||
import 'application.dart';
|
||||
|
||||
void main() {
|
||||
WidgetsFlutterBinding.ensureInitialized();
|
||||
runApp(const Application(EvSourceType.camera));
|
||||
}
|
5
lib/main_dev.dart
Normal file
5
lib/main_dev.dart
Normal file
|
@ -0,0 +1,5 @@
|
|||
import 'package:lightmeter/environment.dart';
|
||||
|
||||
import 'launch_app.dart';
|
||||
|
||||
void main() => launchApp(const Environment.dev());
|
|
@ -1,9 +0,0 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:lightmeter/data/models/ev_source_type.dart';
|
||||
|
||||
import 'application.dart';
|
||||
|
||||
void main() {
|
||||
WidgetsFlutterBinding.ensureInitialized();
|
||||
runApp(const Application(EvSourceType.mock));
|
||||
}
|
5
lib/main_prod.dart
Normal file
5
lib/main_prod.dart
Normal file
|
@ -0,0 +1,5 @@
|
|||
import 'package:lightmeter/environment.dart';
|
||||
|
||||
import 'launch_app.dart';
|
||||
|
||||
void main() => launchApp(const Environment.prod());
|
|
@ -26,13 +26,13 @@ class ThemeProvider extends StatefulWidget {
|
|||
|
||||
class ThemeProviderState extends State<ThemeProvider> {
|
||||
late final _themeTypeNotifier = ValueNotifier<ThemeType>(context.read<UserPreferencesService>().themeType);
|
||||
late final _dynamicColorsNotifier = ValueNotifier<bool>(context.read<UserPreferencesService>().dynamicColors);
|
||||
late final _dynamicColorNotifier = ValueNotifier<bool>(context.read<UserPreferencesService>().dynamicColor);
|
||||
late final _primaryColorNotifier = ValueNotifier<Color>(const Color(0xFF2196f3));
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_themeTypeNotifier.dispose();
|
||||
_dynamicColorsNotifier.dispose();
|
||||
_dynamicColorNotifier.dispose();
|
||||
_primaryColorNotifier.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
@ -44,9 +44,9 @@ class ThemeProviderState extends State<ThemeProvider> {
|
|||
builder: (_, themeType, __) => Provider.value(
|
||||
value: themeType,
|
||||
child: ValueListenableBuilder(
|
||||
valueListenable: _dynamicColorsNotifier,
|
||||
builder: (_, useDynamicColors, __) => _DynamicColorsProvider(
|
||||
useDynamicColors: useDynamicColors,
|
||||
valueListenable: _dynamicColorNotifier,
|
||||
builder: (_, useDynamicColor, __) => _DynamicColorProvider(
|
||||
useDynamicColor: useDynamicColor,
|
||||
themeBrightness: _themeBrightness,
|
||||
builder: (_, dynamicPrimaryColor) => ValueListenableBuilder(
|
||||
valueListenable: _primaryColorNotifier,
|
||||
|
@ -78,19 +78,19 @@ class ThemeProviderState extends State<ThemeProvider> {
|
|||
}
|
||||
}
|
||||
|
||||
void enableDynamicColors(bool enable) {
|
||||
_dynamicColorsNotifier.value = enable;
|
||||
context.read<UserPreferencesService>().dynamicColors = enable;
|
||||
void enableDynamicColor(bool enable) {
|
||||
_dynamicColorNotifier.value = enable;
|
||||
context.read<UserPreferencesService>().dynamicColor = enable;
|
||||
}
|
||||
}
|
||||
|
||||
class _DynamicColorsProvider extends StatelessWidget {
|
||||
final bool useDynamicColors;
|
||||
class _DynamicColorProvider extends StatelessWidget {
|
||||
final bool useDynamicColor;
|
||||
final Brightness themeBrightness;
|
||||
final Widget Function(BuildContext context, Color? primaryColor) builder;
|
||||
|
||||
const _DynamicColorsProvider({
|
||||
required this.useDynamicColors,
|
||||
const _DynamicColorProvider({
|
||||
required this.useDynamicColor,
|
||||
required this.themeBrightness,
|
||||
required this.builder,
|
||||
});
|
||||
|
@ -99,19 +99,19 @@ class _DynamicColorsProvider extends StatelessWidget {
|
|||
Widget build(BuildContext context) {
|
||||
return DynamicColorBuilder(
|
||||
builder: (lightDynamic, darkDynamic) {
|
||||
late final DynamicColorsState state;
|
||||
late final DynamicColorState state;
|
||||
late final Color? dynamicPrimaryColor;
|
||||
if (lightDynamic != null && darkDynamic != null) {
|
||||
if (useDynamicColors) {
|
||||
if (useDynamicColor) {
|
||||
dynamicPrimaryColor = (themeBrightness == Brightness.light ? lightDynamic : darkDynamic).primary;
|
||||
state = DynamicColorsState.enabled;
|
||||
state = DynamicColorState.enabled;
|
||||
} else {
|
||||
dynamicPrimaryColor = null;
|
||||
state = DynamicColorsState.disabled;
|
||||
state = DynamicColorState.disabled;
|
||||
}
|
||||
} else {
|
||||
dynamicPrimaryColor = null;
|
||||
state = DynamicColorsState.unavailable;
|
||||
state = DynamicColorState.unavailable;
|
||||
}
|
||||
return Provider.value(
|
||||
value: state,
|
||||
|
@ -144,13 +144,11 @@ class _ThemeDataProvider extends StatelessWidget {
|
|||
ThemeData _themeFromColorScheme(ColorScheme scheme) {
|
||||
return ThemeData(
|
||||
useMaterial3: true,
|
||||
bottomAppBarColor: scheme.surface,
|
||||
brightness: scheme.brightness,
|
||||
colorScheme: scheme,
|
||||
dialogBackgroundColor: scheme.surface,
|
||||
dialogTheme: DialogTheme(backgroundColor: scheme.surface),
|
||||
scaffoldBackgroundColor: scheme.surface,
|
||||
toggleableActiveColor: scheme.primary,
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -40,7 +40,7 @@ class MeteringFlow extends StatelessWidget {
|
|||
context.read<HapticsInteractor>(),
|
||||
),
|
||||
),
|
||||
if (context.read<EvSourceType>() == EvSourceType.mock)
|
||||
if (context.read<EvSourceType>() == EvSourceType.sensor)
|
||||
BlocProvider(
|
||||
lazy: false,
|
||||
create: (context) => RandomEvBloc(context.read<MeteringCommunicationBloc>()),
|
||||
|
|
|
@ -0,0 +1,33 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:lightmeter/data/models/dynamic_colors_state.dart';
|
||||
import 'package:lightmeter/generated/l10n.dart';
|
||||
import 'package:lightmeter/res/theme.dart';
|
||||
|
||||
class DynamicColorListTile extends StatelessWidget {
|
||||
const DynamicColorListTile({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
if (context.read<DynamicColorState>() == DynamicColorState.unavailable) {
|
||||
return Opacity(
|
||||
opacity: 0.5,
|
||||
child: IgnorePointer(
|
||||
child: SwitchListTile(
|
||||
secondary: const Icon(Icons.colorize),
|
||||
title: Text(S.of(context).dynamicColor),
|
||||
value: false,
|
||||
enableFeedback: false,
|
||||
onChanged: (value) {},
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
return SwitchListTile(
|
||||
secondary: const Icon(Icons.colorize),
|
||||
title: Text(S.of(context).dynamicColor),
|
||||
value: context.watch<DynamicColorState>() == DynamicColorState.enabled,
|
||||
onChanged: ThemeProvider.of(context).enableDynamicColor,
|
||||
);
|
||||
}
|
||||
}
|
|
@ -1,11 +1,10 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:lightmeter/data/models/photography_values/photography_value.dart';
|
||||
import 'package:lightmeter/generated/l10n.dart';
|
||||
import 'package:lightmeter/screens/settings/components/shared/dialog_picker.dart/widget_dialog_picker.dart';
|
||||
import 'package:lightmeter/utils/stop_type_provider.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
import 'shared/widget_dialog_picker.dart';
|
||||
|
||||
class StopTypeListTile extends StatelessWidget {
|
||||
const StopTypeListTile({super.key});
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:lightmeter/environment.dart';
|
||||
import 'package:lightmeter/generated/l10n.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:url_launcher/url_launcher.dart';
|
||||
|
||||
class ReportIssueListTile extends StatelessWidget {
|
||||
const ReportIssueListTile({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return ListTile(
|
||||
leading: const Icon(Icons.bug_report),
|
||||
title: Text(S.of(context).reportIssue),
|
||||
onTap: () {
|
||||
launchUrl(Uri.parse(context.read<Environment>().issuesReportUrl));
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,47 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:lightmeter/res/dimens.dart';
|
||||
|
||||
class SettingsSection extends StatelessWidget {
|
||||
final String title;
|
||||
final List<Widget> children;
|
||||
|
||||
const SettingsSection({
|
||||
required this.title,
|
||||
required this.children,
|
||||
super.key,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.fromLTRB(
|
||||
Dimens.paddingM,
|
||||
0,
|
||||
Dimens.paddingM,
|
||||
Dimens.paddingM,
|
||||
),
|
||||
child: Material(
|
||||
clipBehavior: Clip.antiAlias,
|
||||
borderRadius: BorderRadius.circular(Dimens.borderRadiusL),
|
||||
color: Theme.of(context).colorScheme.primaryContainer,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: Dimens.paddingM),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(left: Dimens.paddingM),
|
||||
child: Text(
|
||||
title,
|
||||
style: Theme.of(context).textTheme.labelLarge,
|
||||
),
|
||||
),
|
||||
...children,
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,20 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:lightmeter/environment.dart';
|
||||
import 'package:lightmeter/generated/l10n.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:url_launcher/url_launcher.dart';
|
||||
|
||||
class SourceCodeListTile extends StatelessWidget {
|
||||
const SourceCodeListTile({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return ListTile(
|
||||
leading: const Icon(Icons.code),
|
||||
title: Text(S.of(context).sourceCode),
|
||||
onTap: () {
|
||||
launchUrl(Uri.parse(context.read<Environment>().sourceCodeUrl));
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
|
@ -1,19 +0,0 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:lightmeter/data/models/dynamic_colors_state.dart';
|
||||
import 'package:lightmeter/generated/l10n.dart';
|
||||
import 'package:lightmeter/res/theme.dart';
|
||||
|
||||
class DynamicColorsListTile extends StatelessWidget {
|
||||
const DynamicColorsListTile({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return SwitchListTile(
|
||||
secondary: const Icon(Icons.colorize),
|
||||
title: Text(S.of(context).dynamicColors),
|
||||
value: context.watch<DynamicColorsState>() == DynamicColorsState.enabled,
|
||||
onChanged: ThemeProvider.of(context).enableDynamicColors,
|
||||
);
|
||||
}
|
||||
}
|
|
@ -1,20 +0,0 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:lightmeter/data/models/dynamic_colors_state.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
import 'components/widget_list_tile_dynamic_colors.dart';
|
||||
import 'components/widget_list_tile_theme_type.dart';
|
||||
|
||||
class ThemeSettings extends StatelessWidget {
|
||||
const ThemeSettings({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Column(
|
||||
children: [
|
||||
const ThemeTypeListTile(),
|
||||
if (context.read<DynamicColorsState>() != DynamicColorsState.unavailable) const DynamicColorsListTile(),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
|
@ -2,7 +2,7 @@ import 'package:flutter/material.dart';
|
|||
import 'package:lightmeter/data/models/theme_type.dart';
|
||||
import 'package:lightmeter/generated/l10n.dart';
|
||||
import 'package:lightmeter/res/theme.dart';
|
||||
import 'package:lightmeter/screens/settings/components/shared/widget_dialog_picker.dart';
|
||||
import 'package:lightmeter/screens/settings/components/shared/dialog_picker.dart/widget_dialog_picker.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
class ThemeTypeListTile extends StatelessWidget {
|
|
@ -0,0 +1,24 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:lightmeter/generated/l10n.dart';
|
||||
import 'package:package_info_plus/package_info_plus.dart';
|
||||
|
||||
class VersionListTile extends StatelessWidget {
|
||||
const VersionListTile({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return ListTile(
|
||||
leading: const Icon(Icons.info_outline),
|
||||
title: Text(S.of(context).version),
|
||||
trailing: FutureBuilder<PackageInfo>(
|
||||
future: PackageInfo.fromPlatform(),
|
||||
builder: (context, snapshot) {
|
||||
if (snapshot.data != null) {
|
||||
return Text(S.of(context).versionNumber(snapshot.data!.version, snapshot.data!.buildNumber));
|
||||
}
|
||||
return const SizedBox.shrink();
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
|
@ -1,27 +0,0 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:lightmeter/generated/l10n.dart';
|
||||
import 'package:package_info_plus/package_info_plus.dart';
|
||||
|
||||
class VersionLabel extends StatelessWidget {
|
||||
const VersionLabel({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return FutureBuilder<PackageInfo>(
|
||||
future: PackageInfo.fromPlatform(),
|
||||
builder: (context, snapshot) {
|
||||
if (snapshot.data != null) {
|
||||
final version = snapshot.data!.version;
|
||||
final buildNumber = snapshot.data!.buildNumber;
|
||||
return Center(
|
||||
child: Text(
|
||||
S.of(context).version(version, buildNumber),
|
||||
style: Theme.of(context).textTheme.bodySmall,
|
||||
),
|
||||
);
|
||||
}
|
||||
return const SizedBox.shrink();
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,20 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:lightmeter/environment.dart';
|
||||
import 'package:lightmeter/generated/l10n.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:url_launcher/url_launcher.dart';
|
||||
|
||||
class WriteEmailListTile extends StatelessWidget {
|
||||
const WriteEmailListTile({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return ListTile(
|
||||
leading: const Icon(Icons.email),
|
||||
title: Text(S.of(context).writeEmail),
|
||||
onTap: () {
|
||||
launchUrl(Uri.parse('mailto:${context.read<Environment>().contactEmail}?subject=M3 Lightmeter'));
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
|
@ -3,9 +3,14 @@ import 'package:lightmeter/generated/l10n.dart';
|
|||
import 'package:lightmeter/res/dimens.dart';
|
||||
|
||||
import 'components/haptics/provider_list_tile_haptics.dart';
|
||||
import 'components/widget_list_tile_fractional_stops.dart';
|
||||
import 'components/theme/widget_settings_theme.dart';
|
||||
import 'components/widget_label_version.dart';
|
||||
import 'components/report_issue/widget_list_tile_report_issue.dart';
|
||||
import 'components/shared/settings_section/widget_settings_section.dart';
|
||||
import 'components/source_code/widget_list_tile_source_code.dart';
|
||||
import 'components/dynamic_color/widget_list_tile_dynamic_color.dart';
|
||||
import 'components/theme_type/widget_list_tile_theme_type.dart';
|
||||
import 'components/version/widget_list_tile_version.dart';
|
||||
import 'components/fractional_stops/widget_list_tile_fractional_stops.dart';
|
||||
import 'components/write_email/widget_list_tile_write_email.dart';
|
||||
|
||||
class SettingsScreen extends StatelessWidget {
|
||||
const SettingsScreen({super.key});
|
||||
|
@ -38,20 +43,38 @@ class SettingsScreen extends StatelessWidget {
|
|||
),
|
||||
SliverList(
|
||||
delegate: SliverChildListDelegate(
|
||||
[
|
||||
const StopTypeListTile(),
|
||||
const HapticsListTileProvider(),
|
||||
const ThemeSettings(),
|
||||
<SettingsSection>[
|
||||
SettingsSection(
|
||||
title: S.of(context).metering,
|
||||
children: const [
|
||||
StopTypeListTile(),
|
||||
],
|
||||
),
|
||||
SettingsSection(
|
||||
title: S.of(context).general,
|
||||
children: const [
|
||||
HapticsListTileProvider(),
|
||||
],
|
||||
),
|
||||
SettingsSection(
|
||||
title: S.of(context).theme,
|
||||
children: const [
|
||||
ThemeTypeListTile(),
|
||||
DynamicColorListTile(),
|
||||
],
|
||||
),
|
||||
SettingsSection(
|
||||
title: S.of(context).about,
|
||||
children: const [
|
||||
SourceCodeListTile(),
|
||||
ReportIssueListTile(),
|
||||
WriteEmailListTile(),
|
||||
VersionListTile(),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
SliverFillRemaining(
|
||||
hasScrollBody: false,
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.end,
|
||||
children: const [VersionLabel()],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
name: lightmeter
|
||||
description: A new Flutter project.
|
||||
publish_to: "none"
|
||||
version: 0.3.0+5
|
||||
version: 0.4.0+6
|
||||
|
||||
environment:
|
||||
sdk: ">=2.18.0 <3.0.0"
|
||||
|
@ -17,11 +17,12 @@ dependencies:
|
|||
sdk: flutter
|
||||
intl: 0.17.0
|
||||
intl_utils: 2.8.1
|
||||
material_color_utilities: 0.1.5
|
||||
material_color_utilities: 0.2.0
|
||||
package_info_plus: 3.0.2
|
||||
permission_handler: 10.2.0
|
||||
provider: 6.0.4
|
||||
shared_preferences: 2.0.15
|
||||
url_launcher: 6.1.8
|
||||
vibration: 1.7.6
|
||||
|
||||
dev_dependencies:
|
||||
|
@ -32,7 +33,7 @@ dev_dependencies:
|
|||
test: 1.22.2
|
||||
|
||||
dependency_overrides:
|
||||
test_api: 0.4.12
|
||||
test_api: 0.4.16
|
||||
|
||||
flutter:
|
||||
uses-material-design: true
|
||||
|
|
Loading…
Reference in a new issue