Giter Club home page Giter Club logo

m3_lightmeter's Introduction

Table of contents

Backstory

Some time ago I've started developing the Material Lightmeter 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.

But as the existing repo contained some sensitive data, that I've pushed due to lack of experience, I had to make a new one. And if creating a new repo, why not rewrite the app from scratch?

Without further delay behold my new Lightmeter app inspired by Material You (a.k.a. M3)

Screenshots

Development

1. Install Flutter

To build this app you need to install Flutter 3.13.9 stable. How to install.

2. Project setup

Restore constants.dart file

Create a file lib/constants.dart and paste the following content:

const String contactEmail = '';
const String iapServerUrl = '';
const String issuesReportUrl = '';
const String sourceCodeUrl = '';

Stub IAP package

As part of the app's functionallity is in the private repo, you have to replace these lines in pubspec.yaml:

m3_lightmeter_iap:
  git:
    url: "https://github.com/vodemn/m3_lightmeter_iap"
    ref: main

with these:

m3_lightmeter_iap:
  path: iap

You can do it simply by running the script:

sh .github/scripts/stub_iap.sh

If you are using VSCode, you can open the workspace like so: File -> Open Workspace from File -> m3_lightmeter.code-workspace. Otherwise you have to run flutter pub get command from the iap folder.

Then you can fetch all the neccessary dependencies and generate translation files by running the following commands:

flutter pub get
flutter pub run intl_utils:generate

3. (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.

4. Build

Support

To report a bug or suggest a new feature open a new issue. To contribute to the project feel free to open a Pull Request, but you need to follow this style guide.

In case you have any other questions please contact me via email.

iOS Limitations

A list of features, that Android version of the app has and that iOS does not.

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.

Volume buttons action

This can be implemented but the app will be rejected due to 2.5.9

m3_lightmeter's People

Contributors

github-actions[bot] avatar musoke avatar scaredcube avatar vodemn avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar

m3_lightmeter's Issues

Live histogram

Is your feature request related to a problem? Please describe.
Currently it can be tricky in the field in some conditions to see what the levels are actually doing without a histogram

Describe the solution you'd like
Would be awesome to have a live histogram overlay below the live preview. It could react to the +/- ev slider

Describe alternatives you've considered
The live view is great, but hard to tell in the field when highlights are clipping for example.

Additional context
It could be a simple rgb histogram or just a histogram

Hide providers from the widget tree

Problem

Nested InheritedWidgetBase<...> widgets at the top of the widget tree do not provide any helpful information about the layout and actually are adding visual noise.

Screenshot 2023-08-13 at 14 40 31

Proposed solution

  • Service providers can be combined into one InheritedWidget thus replacing several providers with only 1 widget
  • Values that change over time can be combined into one InheritedModel allowing descendants to depend on the specific value
  • Remove Inherited Generics

Films filter

Is your feature request related to a problem? Please describe.
Allow users to deselect unused films in the settings and thus hide them from the picker on the main screen.

Describe the solution you'd like

  • Add a standalone film picker (the same as for ISO/ND values)
  • By default no films are selected
  • Decouple film and iso pickers. The film should only affect the shutter speeds displayed. If the selected ISO does not match the selected film, show the push/pull label in the left corner of the film picker tile.

Optional

  • Add a disclaimer to the Metering screen layout dialog, that hiding Film or Equipment profiles picker discards the respective selection to Other/None.

Metering UX improvements

  • While making a measurement with camera user can encounter a parsing error:
Fatal Exception: io.flutter.plugins.firebase.crashlytics.FlutterError: FormatException: Invalid double
null
       at double.parse(dart:core)
       at CameraContainerBloc._takePhoto(bloc_container_camera.dart:186)

It will be good to indicate, that recent measurement ended with error and displayed EV can be incorrect.

  • User may want to change the ISO, ND or other settings while metering is in progress. This results in animated dialog's inability to close:
Fatal Exception: io.flutter.plugins.firebase.crashlytics.FlutterError: Null check operator used on a null value
       at AnimationController.stop(animation_controller.dart:785)
       at AnimationController._animateToInternal(animation_controller.dart:605)
       at AnimationController.reverse(animation_controller.dart:501)
       at AnimatedDialogState._animateReverse(widget_dialog_animated.dart:195)
       at AnimatedDialogState.close(widget_dialog_animated.dart:201)
  • Add vibration on ISO, ND and film changes as it changes the resulting EV value that affect displayed exposure pairs. Quick vibration indicates start of measurement and long vibration indicates change of EV and therefore change of exposure pairs.

Improve app performance

  • Eliminate redundant widgets rebuilds
  • Add RepaintBoundary where needed
  • Eliminate janks
    • MeteringTopBar
    • DialogPicker
  • Implement performance tests to fix the result of the previous steps

Implement migration mechanism for material_lightmeter users

Users of the old material_lightmeter app can have their own settings (ISO, ND, etc.) And as M3 Lightmeter has different storage keys for the same values, it is necessary to map old keys to the new ones on the first startup of the M3 Lightmeter app. In the second M3 release this mechanism should be removed.

Exposure offset doesn't apply on app reopen

Steps to reproduce:

  • Set a nonzero exposure offset
  • Observe lightened/darkened camera preview
  • Exit app, so it is in the recent apps
  • Open the app again

Expected result
The camera review is lightened/darkened. Exposure offset slider position is the same as it was before exit.

Actual result
The camera preview has zero exposure offset. But the corresponding slider shows a nonzero offset.

Suggested solution
Reset exposure offset slider position, when reconnected to the camera.

Add ability to copy equipment profile

Is your feature request related to a problem? Please describe.
As a photographer, I can have 1 camera body with different lenses. To create equipment profiles for different combinations of this body and lenses it would be convenient to:

  1. Create the first profile from scratch
  2. Copy the created profile
  3. Adjust apertures supported by the other lens
  4. Adjust ND if needed

Describe the solution you'd like

  • Add copy button to EP container
  • Add tooltips to EP container icon buttons
  • Display range values instead of values count for list tiles with range picker dialog

`CameraController._throwIfNotInitialized`

Issues

  • buildPreview (#97)
  • getExposureOffsetStepSize
  • stopImageStream (#100)
  • setExposureOffset

Logs

buildPreview()
Fatal Exception: io.flutter.plugins.firebase.crashlytics.FlutterError: CameraException(Disposed CameraController, buildPreview() was called on a disposed CameraController.). Error thrown building Widget.
       at CameraController._throwIfNotInitialized(camera_controller.dart:858)
       at CameraController.buildPreview(camera_controller.dart:611)
       at CameraView.build.<fn>(widget_camera_view.dart:19)
       at _ValueListenableBuilderState.build(value_listenable_builder.dart:186)
       at StatefulElement.build(framework.dart:5198)
       at ComponentElement.performRebuild(framework.dart:5086)
       at StatefulElement.performRebuild(framework.dart:5251)
       at Element.rebuild(framework.dart:4805)
       at BuildOwner.buildScope(framework.dart:2780)
       at WidgetsBinding.drawFrame(binding.dart:903)
       at RendererBinding._handlePersistentFrameCallback(binding.dart:358)
       at SchedulerBinding._invokeFrameCallback(binding.dart:1284)
       at SchedulerBinding.handleDrawFrame(binding.dart:1214)
       at SchedulerBinding._handleDrawFrame(binding.dart:1072)
getExposureOffsetStepSize()
Fatal Exception: io.flutter.plugins.firebase.crashlytics.FlutterError: CameraException(Disposed CameraController, getExposureOffsetStepSize() was called on a disposed CameraController.)
     at CameraController._throwIfNotInitialized(camera_controller.dart:858)
     at CameraController.getExposureOffsetStepSize(camera_controller.dart:723)
     at CameraController.setExposureOffset(camera_controller.dart:755)
stopImageStream()
Fatal Exception: io.flutter.plugins.firebase.crashlytics.FlutterError: CameraException(Disposed CameraController, stopImageStream() was called on a disposed CameraController.)
     at CameraController._throwIfNotInitialized(camera_controller.dart:858)
     at CameraController.stopImageStream(camera_controller.dart:487)
     at _CameraHistogramState.dispose(widget_histogram.dart:31)
     at StatefulElement.unmount(framework.dart:5297)
     at _InactiveElements._unmount(framework.dart:1953)
     at _InactiveElements._unmount.<fn>(framework.dart:1951)
     at ComponentElement.visitChildren(framework.dart:5138)
     at _InactiveElements._unmount(framework.dart:1949)
     at _InactiveElements._unmount.<fn>(framework.dart:1951)
     at MultiChildRenderObjectElement.visitChildren(framework.dart:6533)
     at _InactiveElements._unmount(framework.dart:1949)
     at _InactiveElements._unmount.<fn>(framework.dart:1951)
     at ComponentElement.visitChildren(framework.dart:5138)
     at _InactiveElements._unmount(framework.dart:1949)
     at ListIterable.forEach(dart:_internal)
     at _InactiveElements._unmountAll(framework.dart:1962)
     at BuildOwner.lockState(framework.dart:2640)
     at BuildOwner.finalizeTree(framework.dart:3050)
     at WidgetsBinding.drawFrame(binding.dart:906)
     at RendererBinding._handlePersistentFrameCallback(binding.dart:358)
     at SchedulerBinding._invokeFrameCallback(binding.dart:1284)
     at SchedulerBinding.handleDrawFrame(binding.dart:1214)
     at SchedulerBinding._handleDrawFrame(binding.dart:1072)
setExposureOffset()
Fatal Exception: io.flutter.plugins.firebase.crashlytics.FlutterError: CameraException(Disposed CameraController, setExposureOffset() was called on a disposed CameraController.)
     at CameraController._throwIfNotInitialized(camera_controller.dart:858)
     at CameraController.setExposureOffset(camera_controller.dart:743)
     at CameraContainerBloc._onExposureOffsetChanged(bloc_container_camera.dart:179)
     at Bloc.on.<fn>.handleEvent(bloc.dart:229)
     at Bloc.on.<fn>(bloc.dart:238)

`ScaffoldMessenger` Null check operator used on a null value

https://console.firebase.google.com/u/0/project/lightmeter-6c745/crashlytics/app/android:com.vodemn.lightmeter/issues/e49c3bc684e3447a8a47e18554d7bd15?time=last-seven-days&versions=0.10.0%20(70022)&sessionEventKey=6459FDBA034700011E9F19F9C4C98BDE_1809536077403461632

Fatal Exception: io.flutter.plugins.firebase.crashlytics.FlutterError: Null check operator used on a null value
       at ScaffoldMessenger.of(scaffold.dart:150)
       at WriteEmailListTile.build.<fn>.<fn>.<fn>.<fn>(widget_list_tile_write_email.dart:34)

Test coverage

As the app is in release it must have at least unit tests (at least to avoid issues like #60)

  • lib/data/models/ (#80)
  • Services tests (#82)
  • lib/providers/ (#131)
  • lib/interactors/ (#87)
  • Bloc tests (#78)
  • lib/utils/ (#133)

Some methods provided by packages (like Vibration or AppSettings) are available through static access and thus cannot be stubbed with mocktail
- [ ] Static classes tests

Equipment profiles issues

  • Equipment profile section is closed after editing one of the fields
  • Aperture speed range dialog lacks f/3.5 and has f/2.4 twice

Device:

  • Device: Pixel 6
  • OS: A13

App version
0.14.0

Apeture filter

Thwere should be a possibility to filter out Apeture manually:
For example my camera only has f/16 /8 /5,6 and /3,5 so the app should just show me these

Metering top bar cutout doesn't pass through taps

Describe the bug
Exposure reset button is not clickable.

To Reproduce
Steps to reproduce the behavior:

  1. Use camera as a source
  2. Make sure that exposure reset button is covered by transparent part of the cutout
  3. Try reseting exposure

Expected behavior
Exposure reset button should be clickable.

Screenshot

sketch-1690148430774

Unsaved fractional stops

Hello,

The value chosen for the fractional stops option is not saved and defaults to 1/3, which means you have to reset it each time you launch the application.

Thanks for your work πŸ‘Œ

Translation/UI problems

App ver: 0.12.1,Chinese

Device info:POCO F4,Android 13 in MIUI14.0.4

Problems:
1.EV value becomes infinity in ND page
Screenshot_2023-07-24-16-32-47-978_com vodemn lightmeter
2.Chinese translation in setting page,"εˆ†ζ•°δ½"should be "EVζ­₯θΏ›ε€Ό"
Screenshot_2023-07-24-16-32-51-246_com vodemn lightmeter-edit

Redundant vibrations

1. Vibration on non-measurement user input

When user changes one of these settings:

  • Language
  • Theme type
  • Primary color
  • Dynamic color

due to this code:

  @override
  void didChangeDependencies() {
    super.didChangeDependencies();
    _bloc.add(EquipmentProfileChangedEvent(EquipmentProfile.of(context)));
    _bloc.add(StopTypeChangedEvent(context.watch<StopType>()));
    if (!MeteringScreenLayout.featureStatusOf(context, MeteringScreenLayoutFeature.filmPicker)) {
      _bloc.add(const FilmChangedEvent(Film.other()));
    }
  }

response vibration is called.

In order to fix it custom dependency listener can be implemented.

2. Vibration when resulting EV is not changed

Response vibration should be called only when EV and corresponding exposure pairs list are changed.

This can be achieve simply by checking if, for example, new ND values is equal to the current one. If true - nothing happens.

Try to automate GP & Github releases via Github Actions

Currently to create a new release on GP and Github I need to manually run a Build prod .aab & .apk action, download attached artefacts and create 2 separate releases with the same release notes. Probably it can be at least partially automated.

Common

  • Find a way to pass release notes to the Github Actions workflow (#101)

Github release

  • Update version in pubspec from GitHub actions (#84)
  • Create Github release from Github actions (#84)
  • Delete apk artefact after Github release has been created (#96)

Google Play release

  • Try using buildTypes.release.ndk.debugSymbolLevel 'FULL' (#79)
  • Zip merged_native_libs only for arm64-v8a & armeabi-v7a (#96)
  • Create Google Play release from Github actions (#101)
  • Delete aab artefact after Google Play release has been created (#96)

App Store release

TBD

Unsupported operation: Infinity or NaN toInt

Link to issue

Fatal Exception: io.flutter.plugins.firebase.crashlytics.FlutterError: Unsupported operation: Infinity or NaN toInt
       at MeteringBloc._buildExposureValues(bloc_metering.dart)
       at MeteringBloc._emitMeasuredState(bloc_metering.dart:157)
       at MeteringBloc._onFilmChanged(bloc_metering.dart:117)
       at Bloc.on.<fn>.handleEvent(bloc.dart:226)
       at Bloc.on.<fn>(bloc.dart:235)

ND filter value is not applied on measure

When user changes ND filter current EV value is adjusted by ND filter reduction value: ev = ev - nd. But when making a measurement with non-zero ND filter selected filter value is applied on the current EV value: ev = ev.

First exposure metering seems to be wrong

Describe the bug
When I first start the app, maybe I'm being too quick, but I'll get a reading of a scene like this:

F22
2"
ISO 200

However I hit the button again, without moving the camera, and I'll get the right reading like this

F22
1/30
ISO 200

To Reproduce
I can get it to reproduce when first opening the app
OR unlocking the screen and trying it straight away.

Expected behavior
The second metering is always correct.

Device:

  • Pixel 6
  • Android 13 (GraphenOS but that shouldn't make a difference)

App version
Latest as of 26.8.23

Save zoom level for equipment profile

The problem
The user's device and camera can have different focus lengths, which results in different magnifications and a need to always adjust the zoom level in the app to meter the portion of the scene captured by the camera.

Describe the solution you'd like

  • Each equipment profile should have a nullable field lensZoom
  • [Metering] When selecting another equipment profile and the current zoom level is in the new range - set the new lensZoom
  • [Metering] When selecting another equipment profile and the current zoom level is not in the new range - zoom to the closet level available
  • [Metering] Show zoom level in the zoom slider
  • [Settings] Zoom range can be set via dialog with a slider

Additional context
Switching between different lenses should be handled by the system as in the pre-installed camera app.
No handling for metering screen is required when user edits zoom range in the settings. At this point metering screen camera is disposed and should be initialised with the new range.

Implement volume buttons actions

As in Google Camera the Lightmeter app should have these actions executed by volume buttons:

  • Measure
  • Zoom
  • Volume (Off)

Things to do:

  • Users should be able to trigger measuring by pressing hardware volume keys.
  • While the settings screen is opened, the system should handle volume keys so it won't trigger unnecessary measurements.
  • When the settings screen is opened, the camera should be initialized.
  • When the settings screen is opened, lightmeter metering should be canceled.
  • (Optional) Add Developer settings section and allow to stop metering when settings screen is opened.

Measure button needs loading state

Light measurement usually takes some feasible time. For better user experience it will be good to add loading state to measure button. For example, button's outer ring can be transformed to circular progress indicator of the same thickness.

Dialog pickers content jumps when selected item is out of view

Steps to reproduce:

  1. Open any dialog picker
  2. Selected the last item
  3. Close dialog
  4. Open dialog again

Expected result:
Dialog content is scrolled to the end, the last item is visible.

Actual result:
Dialog content is scrolled to the last item's offset and then bounces back to the biggest offset possible.

[Android] Exception on `mailto` intent

https://console.firebase.google.com/u/0/project/lightmeter-6c745/crashlytics/app/android:com.vodemn.lightmeter/issues/e7aeb743a846c783173a8575f6ad391e?time=last-seven-days&sessionEventKey=64557A2503B800011E0B9EE74AE8744F_1808265554593103851

Fatal Exception: io.flutter.plugins.firebase.crashlytics.FlutterError: PlatformException(ACTIVITY_NOT_FOUND, No Activity found to handle intent { mailto:[email protected]?subject=M3%20Lightmeter }, null, null)
       at StandardMethodCodec.decodeEnvelope(message_codecs.dart:653)
       at MethodChannel._invokeMethod(platform_channel.dart:315)

Metering container issues

Describe the bug

  1. After changing the metering screen layout dialogs retain their positions, resulting in animations starting from the wrong place.
  2. If the "step" between two columns of the metering top bar is too small to accommodate a doubled corner radius, it is shifted to either side.

To Reproduce
Steps to reproduce the behavior:

  1. Go to the Settings screen
  2. Open the MeteringScreenLayoutFeaturesDialog
  3. Toggle any feature
  4. Back to the Metering screen
  5. Open & close any picker

Expected behavior

  • After changing the metering screen layout dialogs open starting from the new positions
  • "step" between two columns of the metering top bar is always centered

Screenshots

Video
Screen.Recording.2023-09-08.at.20.24.39.mov
Screenshot Screenshot 2023-09-08 at 20 32 40

App version
0.14.0

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    πŸ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. πŸ“ŠπŸ“ˆπŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❀️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.